0002-misc-replace-DeletedUser-model-by-attribute-deleted-.patch
src/authentic2/admin.py | ||
---|---|---|
84 | 84 |
admin.site.register(models.UserExternalId, UserExternalIdAdmin) |
85 | 85 | |
86 | 86 | |
87 |
class DeletedUserAdmin(admin.ModelAdmin): |
|
88 |
list_display = ('user', 'creation') |
|
89 |
date_hierarchy = 'creation' |
|
90 |
admin.site.register(models.DeletedUser, DeletedUserAdmin) |
|
91 | ||
92 | 87 |
DB_SESSION_ENGINES = ( |
93 | 88 |
'django.contrib.sessions.backends.db', |
94 | 89 |
'django.contrib.sessions.backends.cached_db', |
src/authentic2/custom_user/managers.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from django.db import models |
|
17 |
import datetime |
|
18 |
import logging |
|
19 | ||
20 |
from django.db import models, transaction |
|
18 | 21 |
from django.utils import six |
19 | 22 |
from django.utils import timezone |
20 | 23 |
from django.contrib.auth.models import BaseUserManager |
... | ... | |
81 | 84 | |
82 | 85 |
def get_by_natural_key(self, uuid): |
83 | 86 |
return self.get(uuid=uuid) |
87 | ||
88 |
@transaction.atomic |
|
89 |
def cleanup(self, threshold=600, timestamp=None): |
|
90 |
'''Delete all deleted users for more than 10 minutes.''' |
|
91 |
not_after = (timestamp or timezone.now()) - datetime.timedelta(seconds=threshold) |
|
92 |
qs = self.filter(deleted__lt=not_after) |
|
93 | ||
94 |
loaded = list(qs) |
|
95 | ||
96 |
def log(): |
|
97 |
logger = logging.getLogger('authentic2') |
|
98 |
for user in loaded: |
|
99 |
logger.info(u'deleted account %s', user) |
|
100 |
transaction.on_commit(log) |
|
101 |
qs.delete() |
src/authentic2/custom_user/migrations/0019_user_deleted.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.29 on 2020-04-21 13:38 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 |
import django.contrib.postgres.fields.jsonb |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('custom_user', '0018_user_last_account_deletion_alert'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='user', |
|
18 |
name='deleted', |
|
19 |
field=models.DateTimeField(blank=True, null=True, verbose_name='Deletion date'), |
|
20 |
), |
|
21 |
migrations.CreateModel( |
|
22 |
name='DeletedUser', |
|
23 |
fields=[ |
|
24 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
25 |
('deleted', models.DateTimeField(verbose_name='Deletion date')), |
|
26 |
('old_uuid', models.TextField(blank=True, null=True, verbose_name='Old UUID')), |
|
27 |
('old_user_id', models.PositiveIntegerField(blank=True, null=True, verbose_name='Old user id')), |
|
28 |
('old_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Old email adress')), |
|
29 |
('old_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='Old data')), |
|
30 |
], |
|
31 |
options={ |
|
32 |
'verbose_name': 'deleted user', |
|
33 |
'verbose_name_plural': 'deleted users', |
|
34 |
'ordering': ('deleted', 'id'), |
|
35 |
}, |
|
36 |
), |
|
37 |
] |
src/authentic2/custom_user/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import random |
|
18 | ||
17 | 19 |
from django.db import models, transaction |
18 | 20 |
from django.utils import timezone |
19 | 21 |
from django.core.mail import send_mail |
... | ... | |
155 | 157 |
default=True, |
156 | 158 |
help_text=_('Designates whether this user should be treated as ' |
157 | 159 |
'active. Unselect this instead of deleting accounts.')) |
158 |
date_joined = models.DateTimeField(_('date joined'), default=timezone.now) |
|
159 | 160 |
ou = models.ForeignKey( |
160 | 161 |
verbose_name=_('organizational unit'), |
161 | 162 |
to='a2_rbac.OrganizationalUnit', |
162 | 163 |
blank=True, |
163 | 164 |
null=True, |
164 | 165 |
swappable=False) |
166 | ||
167 |
# events dates |
|
168 |
date_joined = models.DateTimeField(_('date joined'), default=timezone.now) |
|
165 | 169 |
modified = models.DateTimeField( |
166 | 170 |
verbose_name=_('Last modification time'), |
167 | 171 |
db_index=True, |
... | ... | |
170 | 174 |
verbose_name=_('Last account deletion alert'), |
171 | 175 |
null=True, |
172 | 176 |
blank=True) |
177 |
deleted = models.DateTimeField( |
|
178 |
verbose_name=_('Deletion date'), |
|
179 |
null=True, |
|
180 |
blank=True) |
|
173 | 181 | |
174 | 182 |
objects = UserManager.from_queryset(UserQuerySet)() |
175 | 183 |
attributes = AttributesDescriptor() |
... | ... | |
323 | 331 |
if hasattr(self, '_a2_attributes_cache'): |
324 | 332 |
del self._a2_attributes_cache |
325 | 333 |
return super(User, self).refresh_from_db(*args, **kwargs) |
334 | ||
335 |
def mark_as_deleted(self, timestamp=None, force=True, save=True): |
|
336 |
self.is_active = False |
|
337 |
self.email_verified = False |
|
338 |
if self.email and '#' not in self.email: |
|
339 |
self.email += '#%d' % random.randint(1, 10000000) |
|
340 |
if force or not self.deleted: |
|
341 |
self.deleted = timestamp or timezone.now() |
|
342 |
if save: |
|
343 |
self.save(update_fields=['email', 'email_verified', 'is_active', 'deleted']) |
src/authentic2/management/commands/clean-unused-accounts.py | ||
---|---|---|
27 | 27 |
from django.utils.six.moves.urllib import parse as urlparse |
28 | 28 |
from django_rbac.utils import get_ou_model |
29 | 29 | |
30 |
from authentic2.models import DeletedUser |
|
31 | 30 |
from authentic2.utils import send_templated_mail |
32 | 31 | |
33 | 32 |
from django.conf import settings |
... | ... | |
120 | 119 |
ctx = {'user': user} |
121 | 120 |
with atomic(): |
122 | 121 |
if not self.fake: |
123 |
DeletedUser.objects.delete_user(user)
|
|
122 |
user.mark_as_deleted(timestamp=self.now)
|
|
124 | 123 |
self.send_mail('authentic2/unused_account_delete', user, ctx) |
src/authentic2/manager/templates/authentic2/manager/user_detail.html | ||
---|---|---|
41 | 41 |
{% endblock %} |
42 | 42 | |
43 | 43 |
{% block other_actions %} |
44 | ||
45 |
{% if object.deletion %} |
|
46 |
<p class="a2-manager-user-deletion"> |
|
47 |
{% blocktrans with date=object.deletion.creation %}Prepared for deletion since {{ date }}{% endblocktrans %} |
|
48 |
</p> |
|
49 |
{% endif %} |
|
50 | ||
51 | 44 |
<p class="a2-manager-user-last-login"> |
52 | 45 |
{% if object.last_login %} |
53 | 46 |
{% blocktrans with date=object.last_login %}Last login on {{ date }}.{% endblocktrans %} |
src/authentic2/manager/user_views.py | ||
---|---|---|
30 | 30 |
from django.contrib.contenttypes.models import ContentType |
31 | 31 |
from django.contrib import messages |
32 | 32 |
from django.views.generic import FormView, TemplateView, DetailView |
33 |
from django.http import Http404, FileResponse |
|
33 |
from django.http import Http404, FileResponse, HttpResponseRedirect
|
|
34 | 34 | |
35 | 35 |
import tablib |
36 | 36 | |
... | ... | |
71 | 71 | |
72 | 72 |
def get_queryset(self): |
73 | 73 |
qs = super(UsersView, self).get_queryset() |
74 |
qs = qs.filter(deletion__isnull=True)
|
|
74 |
qs = qs.filter(deleted__isnull=True)
|
|
75 | 75 |
qs = qs.select_related('ou') |
76 | 76 |
qs = qs.prefetch_related('roles', 'roles__parent_relation__parent') |
77 | 77 |
return qs |
... | ... | |
219 | 219 |
template_name = 'authentic2/manager/user_detail.html' |
220 | 220 |
slug_field = 'uuid' |
221 | 221 | |
222 |
def get_queryset(self): |
|
223 |
qs = super(UserDetailView, self).get_queryset() |
|
224 |
qs = qs.filter(deleted__isnull=True) |
|
225 |
return qs |
|
226 | ||
222 | 227 |
@property |
223 | 228 |
def title(self): |
224 | 229 |
return self.object.get_full_name() |
... | ... | |
630 | 635 |
return reverse('a2-manager-users') |
631 | 636 | |
632 | 637 |
def delete(self, request, *args, **kwargs): |
633 |
response = super(UserDeleteView, self).delete(request, *args, **kwargs)
|
|
638 |
self.get_object().mark_as_deleted()
|
|
634 | 639 |
hooks.call_hooks('event', name='manager-delete-user', user=request.user, |
635 | 640 |
instance=self.object) |
636 |
return response
|
|
641 |
return HttpResponseRedirect(self.get_success_url())
|
|
637 | 642 | |
638 | 643 | |
639 | 644 |
user_delete = UserDeleteView.as_view() |
src/authentic2/managers.py | ||
---|---|---|
44 | 44 |
GetByNameManager = GetByNameQuerySet.as_manager |
45 | 45 | |
46 | 46 | |
47 |
class DeletedUserManager(models.Manager): |
|
48 |
def delete_user(self, user): |
|
49 |
user.is_active = False |
|
50 |
user.save() |
|
51 |
self.get_or_create(user=user) |
|
52 | ||
53 |
def cleanup(self, threshold=600, timestamp=None): |
|
54 |
'''Delete all deleted users for more than 10 minutes.''' |
|
55 |
not_after = (timestamp or now()) - timedelta(seconds=threshold) |
|
56 |
for deleted_user in self.filter(creation__lte=not_after): |
|
57 |
user = deleted_user.user |
|
58 |
deleted_user.delete() |
|
59 |
user.delete() |
|
60 |
logger.info(u'deleted account %s', user) |
|
61 | ||
62 | ||
63 | 47 |
class AuthenticationEventManager(models.Manager): |
64 | 48 |
def cleanup(self): |
65 | 49 |
# expire after one week |
src/authentic2/migrations/0027_auto_20200421_1609.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.29 on 2020-04-21 14:09 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
def noop(apps, schema_editor): |
|
9 |
pass |
|
10 | ||
11 | ||
12 |
def fill_deleted(apps, schema_editor): |
|
13 |
DeletedUser = apps.get_model('authentic2', 'DeletedUser') |
|
14 |
User = apps.get_model('custom_user', 'User') |
|
15 |
User.objects.update( |
|
16 |
deleted=models.Subquery(DeletedUser.objects.filter(user_id=models.OuterRef('id')).values_list('creation')[:1])) |
|
17 | ||
18 | ||
19 |
class Migration(migrations.Migration): |
|
20 | ||
21 |
dependencies = [ |
|
22 |
('authentic2', '0026_token'), |
|
23 |
('custom_user', '0019_user_deleted'), |
|
24 |
] |
|
25 | ||
26 |
operations = [ |
|
27 |
migrations.RunPython(fill_deleted, noop), |
|
28 |
migrations.RemoveField( |
|
29 |
model_name='deleteduser', |
|
30 |
name='user', |
|
31 |
), |
|
32 |
migrations.AlterField( |
|
33 |
model_name='attribute', |
|
34 |
name='scopes', |
|
35 |
field=models.CharField(blank=True, default='', help_text='scopes separated by spaces', max_length=256, verbose_name='scopes'), |
|
36 |
), |
|
37 |
migrations.DeleteModel( |
|
38 |
name='DeletedUser', |
|
39 |
), |
|
40 |
] |
src/authentic2/models.py | ||
---|---|---|
46 | 46 |
from .utils import ServiceAccessDenied |
47 | 47 | |
48 | 48 | |
49 |
class DeletedUser(models.Model): |
|
50 |
'''Record users to delete''' |
|
51 | ||
52 |
objects = managers.DeletedUserManager() |
|
53 | ||
54 |
user = models.OneToOneField( |
|
55 |
to=settings.AUTH_USER_MODEL, |
|
56 |
related_name='deletion', |
|
57 |
verbose_name=_('user')) |
|
58 |
creation = models.DateTimeField(auto_now_add=True, verbose_name=_('creation date')) |
|
59 | ||
60 |
class Meta: |
|
61 |
verbose_name = _('user to delete') |
|
62 |
verbose_name_plural = _('users to delete') |
|
63 | ||
64 | ||
65 | 49 |
@six.python_2_unicode_compatible |
66 | 50 |
class UserExternalId(models.Model): |
67 | 51 |
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user')) |
src/authentic2/views.py | ||
---|---|---|
1120 | 1120 |
user_pk = deletion_token['user_pk'] |
1121 | 1121 |
self.user = get_user_model().objects.get(pk=user_pk) |
1122 | 1122 |
# A user account wont be deactived twice |
1123 |
if self.user.deleted: |
|
1124 |
raise ValidationError( |
|
1125 |
_('This account has previously been deleted.')) |
|
1123 | 1126 |
if not self.user.is_active: |
1124 | 1127 |
raise ValidationError( |
1125 |
_('This account had previously been deactivated and will be deleted soon.'))
|
|
1128 |
_('This account is inactive, it cannot be deleted.'))
|
|
1126 | 1129 |
logger.info('user %s confirmed the deletion of their own account', self.user) |
1127 | 1130 |
except signing.SignatureExpired: |
1128 | 1131 |
error = _('The account deletion request is too old, try again') |
... | ... | |
1140 | 1143 |
return utils.redirect(request, 'auth_homepage') |
1141 | 1144 | |
1142 | 1145 |
def post(self, request, *args, **kwargs): |
1143 |
if 'cancel' not in request.POST: |
|
1146 |
if 'cancel' not in request.POST and not self.user.deleted:
|
|
1144 | 1147 |
utils.send_account_deletion_mail(self.request, self.user) |
1145 |
models.DeletedUser.objects.delete_user(self.user) |
|
1146 |
self.user.email += '#%d' % random.randint(1, 10000000) |
|
1147 |
self.user.email_verified = False |
|
1148 |
self.user.save(update_fields=['email', 'email_verified']) |
|
1148 |
self.user.mark_as_deleted() |
|
1149 | 1149 |
logger.info(u'deletion of account %s performed', self.user) |
1150 | 1150 |
hooks.call_hooks('event', name='delete-account', user=self.user) |
1151 | 1151 |
if self.user == request.user: |
tests/test_all.py | ||
---|---|---|
90 | 90 |
'user_permissions': [], |
91 | 91 |
'password': '', |
92 | 92 |
'ou': None, |
93 |
'deleted': None, |
|
93 | 94 |
} |
94 | 95 |
}, |
95 | 96 |
{ |
tests/test_api.py | ||
---|---|---|
417 | 417 |
'last_name_verified', 'date_joined', 'last_login', |
418 | 418 |
'username', 'password', 'email', 'is_active', 'title', |
419 | 419 |
'title_verified', 'modified', 'email_verified', |
420 |
'last_account_deletion_alert']) == set(resp.json.keys()) |
|
420 |
'last_account_deletion_alert', 'deleted']) == set(resp.json.keys())
|
|
421 | 421 |
assert resp.json['first_name'] == payload['first_name'] |
422 | 422 |
assert resp.json['last_name'] == payload['last_name'] |
423 | 423 |
assert resp.json['email'] == payload['email'] |
... | ... | |
484 | 484 |
'last_name_verified', 'date_joined', 'last_login', |
485 | 485 |
'username', 'password', 'email', 'is_active', 'title', |
486 | 486 |
'title_verified', 'modified', 'email_verified', |
487 |
'last_account_deletion_alert']) == set(resp.json.keys()) |
|
487 |
'last_account_deletion_alert', 'deleted']) == set(resp.json.keys())
|
|
488 | 488 |
user = get_user_model().objects.get(pk=resp.json['id']) |
489 | 489 |
assert AttributeValue.objects.with_owner(user).filter(verified=True).count() == 3 |
490 | 490 |
assert AttributeValue.objects.with_owner(user).filter(verified=False).count() == 0 |
tests/test_cleanup.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import datetime |
18 | 18 | |
19 |
from authentic2.models import DeletedUser |
|
20 | 19 |
from django.contrib.auth import get_user_model |
21 | 20 |
from django.utils.timezone import now |
22 | 21 | |
... | ... | |
25 | 24 |
User = get_user_model() |
26 | 25 |
u = User.objects.create(username='john.doe') |
27 | 26 |
assert User.objects.count() == 1 |
28 |
DeletedUser.objects.delete_user(u)
|
|
29 |
DeletedUser.objects.cleanup(timestamp=now() + datetime.timedelta(seconds=700))
|
|
27 |
u.mark_as_deleted()
|
|
28 |
User.objects.cleanup(timestamp=now() + datetime.timedelta(seconds=700)) |
|
30 | 29 |
assert User.objects.count() == 0 |
tests/test_commands.py | ||
---|---|---|
25 | 25 |
from django.utils.timezone import now |
26 | 26 |
import py |
27 | 27 | |
28 |
from authentic2.models import Attribute, DeletedUser |
|
29 | 28 |
from authentic2_auth_oidc.models import OIDCProvider |
30 | 29 |
from django_rbac.utils import get_ou_model |
31 | 30 | |
32 | 31 |
from utils import login |
33 | 32 | |
34 | 33 |
if six.PY2: |
35 |
FileType = file |
|
34 |
FileType = file # noqa: F821
|
|
36 | 35 |
else: |
37 | 36 |
from io import TextIOWrapper, BufferedReader, BufferedWriter |
38 | 37 |
FileType = (TextIOWrapper, BufferedReader, BufferedWriter) |
... | ... | |
61 | 60 |
simple_user.save() |
62 | 61 | |
63 | 62 |
management.call_command('clean-unused-accounts') |
64 |
assert not DeletedUser.objects.filter(user=simple_user).exists() |
|
63 |
simple_user.refresh_from_db() |
|
64 |
assert not simple_user.deleted |
|
65 | 65 |
assert len(mailoutbox) == 1 |
66 | 66 | |
67 | 67 |
freezer.move_to('2018-01-01 12:00:00') |
68 | 68 |
# no new mail, no deletion |
69 | 69 |
management.call_command('clean-unused-accounts') |
70 |
assert not DeletedUser.objects.filter(user=simple_user).exists() |
|
70 |
simple_user.refresh_from_db() |
|
71 |
assert not simple_user.deleted |
|
71 | 72 |
assert len(mailoutbox) == 1 |
72 | 73 | |
73 | 74 |
freezer.move_to('2018-01-02') |
74 | 75 |
management.call_command('clean-unused-accounts') |
75 |
assert DeletedUser.objects.filter(user=simple_user).exists() |
|
76 |
simple_user.refresh_from_db() |
|
77 |
assert simple_user.deleted |
|
76 | 78 |
assert len(mailoutbox) == 2 |
77 | 79 | |
78 | 80 | |
... | ... | |
92 | 94 | |
93 | 95 |
# the day of deletion, nothing happens |
94 | 96 |
freezer.move_to('2018-01-02') |
95 |
assert not DeletedUser.objects.filter(user=simple_user).exists() |
|
97 |
simple_user.refresh_from_db() |
|
98 |
assert not simple_user.deleted |
|
96 | 99 |
assert len(mailoutbox) == 1 |
97 | 100 | |
98 | 101 |
# when new alert delay is reached, user gets alerted again |
99 | 102 |
freezer.move_to('2018-01-04') |
100 | 103 |
management.call_command('clean-unused-accounts') |
101 |
assert not DeletedUser.objects.filter(user=simple_user).exists() |
|
104 |
simple_user.refresh_from_db() |
|
105 |
assert not simple_user.deleted |
|
102 | 106 |
assert len(mailoutbox) == 2 |
103 | 107 | |
104 | 108 | |
... | ... | |
107 | 111 |
simple_user.save() |
108 | 112 | |
109 | 113 |
management.call_command('clean-unused-accounts') |
110 |
assert not DeletedUser.objects.filter(user=simple_user).exists() |
|
114 |
simple_user.refresh_from_db() |
|
115 |
assert not simple_user.deleted |
|
111 | 116 |
assert len(mailoutbox) == 0 |
112 | 117 | |
113 | 118 | |
... | ... | |
121 | 126 | |
122 | 127 |
# even if account last login in past deletion delay, an alert is always sent first |
123 | 128 |
management.call_command('clean-unused-accounts') |
124 |
assert not len(DeletedUser.objects.filter(user=simple_user)) |
|
129 |
simple_user.refresh_from_db() |
|
130 |
assert not simple_user.deleted |
|
125 | 131 |
assert len(mailoutbox) == 1 |
126 | 132 | |
127 | 133 |
# and calling again as no effect, since one day must pass before account is deleted |
128 | 134 |
management.call_command('clean-unused-accounts') |
129 |
assert not len(DeletedUser.objects.filter(user=simple_user)) |
|
135 |
simple_user.refresh_from_db() |
|
136 |
assert not simple_user.deleted |
|
130 | 137 |
assert len(mailoutbox) == 1 |
131 | 138 | |
132 | 139 |
tests/test_user_manager.py | ||
---|---|---|
29 | 29 |
from django_rbac.utils import get_ou_model |
30 | 30 | |
31 | 31 |
from authentic2.custom_user.models import User |
32 |
from authentic2.models import Attribute, AttributeValue, DeletedUser
|
|
32 |
from authentic2.models import Attribute, AttributeValue |
|
33 | 33 |
from authentic2.a2_rbac.utils import get_default_ou |
34 | 34 |
from authentic2.manager import user_import |
35 | 35 | |
... | ... | |
428 | 428 | |
429 | 429 | |
430 | 430 |
def test_detail_view(app, admin, simple_user): |
431 |
url = '/manage/users/{user.id}/'.format(user=simple_user) |
|
432 |
response = login(app, admin, url) |
|
433 |
assert not response.pyquery('.a2-manager-user-deletion') |
|
434 |
DeletedUser.objects.create(user=simple_user) |
|
435 |
response = app.get(url) |
|
436 |
assert response.pyquery('.a2-manager-user-deletion') |
|
431 |
url = '/manage/users/{user.pk}/'.format(user=simple_user) |
|
432 |
login(app, admin, url) |
|
433 | ||
434 | ||
435 |
def test_detail_view_deleted(app, admin, simple_user): |
|
436 |
url = '/manage/users/{user.pk}/'.format(user=simple_user) |
|
437 |
login(app, admin, url) |
|
438 |
simple_user.mark_as_deleted() |
|
439 |
app.get(url, status=404) |
|
437 | 440 | |
438 | 441 | |
439 | 442 |
def test_user_import_row_error_display(transactional_db, app, admin, media): |
tests/test_views.py | ||
---|---|---|
51 | 51 |
page = app.get(link) |
52 | 52 |
# FIXME: webtest does not set the Referer header, so the logout page will always ask for |
53 | 53 |
# confirmation under tests |
54 |
response = page.form.submit(name='delete').follow() |
|
55 |
response = response.form.submit() |
|
56 |
assert not User.objects.get(pk=simple_user.pk).is_active |
|
54 |
response = page.form.submit(name='delete') |
|
55 |
assert '_auth_user_id' not in app.session |
|
56 |
email = simple_user.email |
|
57 |
simple_user.refresh_from_db() |
|
58 |
assert not simple_user.is_active |
|
59 |
assert simple_user.deleted |
|
60 |
assert simple_user.email != email |
|
57 | 61 |
assert len(mailoutbox) == 2 |
58 | 62 |
assert 'Account deletion on testserver' == mailoutbox[1].subject |
59 |
assert [simple_user.email] == mailoutbox[0].to
|
|
63 |
assert mailoutbox[0].to == [email]
|
|
60 | 64 |
assert urlparse(response.location).path == '/' |
61 | 65 |
response = response.follow().follow() |
62 | 66 |
assert response.request.url.endswith('/login/?next=/') |
... | ... | |
112 | 116 |
freezer.move_to('2019-08-01') |
113 | 117 |
page = login(app, simple_user, path=reverse('delete_account')) |
114 | 118 |
page.form.submit(name='submit').follow() |
115 |
freezer.move_to('2019-08-04') # Too late... |
|
119 |
freezer.move_to('2019-08-04') # Too late...
|
|
116 | 120 |
link = get_link_from_mail(mailoutbox[0]) |
117 | 121 |
response = app.get(link).follow() |
118 | 122 |
assert "The account deletion request is too old, try again" in response.text |
... | ... | |
133 | 137 |
link = get_link_from_mail(mailoutbox[0]) |
134 | 138 |
simple_user.is_active = False |
135 | 139 |
simple_user.save() |
136 |
response = app.get(link).follow() |
|
137 |
assert "This account had previously been deactivated" in response.text
|
|
140 |
response = app.get(link).maybe_follow()
|
|
141 |
assert 'This account is inactive, it cannot be deleted.' in response.text
|
|
138 | 142 | |
139 | 143 | |
140 | 144 |
def test_login_invalid_next(app): |
141 |
- |