udata 10.1.2.dev34172__py2.py3-none-any.whl → 10.1.3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- udata/__init__.py +1 -1
- udata/commands/fixtures.py +1 -1
- udata/core/dataservices/constants.py +11 -0
- udata/core/dataservices/csv.py +3 -3
- udata/core/dataservices/models.py +27 -12
- udata/core/dataservices/rdf.py +5 -3
- udata/core/dataservices/search.py +13 -5
- udata/core/dataset/api.py +18 -3
- udata/core/dataset/forms.py +8 -4
- udata/core/dataset/models.py +6 -0
- udata/core/metrics/commands.py +20 -1
- udata/core/organization/api_fields.py +3 -1
- udata/core/user/api.py +8 -1
- udata/core/user/api_fields.py +5 -0
- udata/core/user/models.py +16 -11
- udata/core/user/tasks.py +81 -2
- udata/core/user/tests/test_user_model.py +29 -12
- udata/features/transfer/api.py +7 -4
- udata/harvest/actions.py +5 -0
- udata/harvest/backends/base.py +22 -2
- udata/harvest/models.py +19 -0
- udata/harvest/tests/test_actions.py +12 -0
- udata/harvest/tests/test_base_backend.py +74 -8
- udata/harvest/tests/test_dcat_backend.py +1 -1
- udata/migrations/2025-01-05-dataservices-fields-changes.py +136 -0
- udata/settings.py +5 -0
- udata/templates/mail/account_inactivity.html +29 -0
- udata/templates/mail/account_inactivity.txt +22 -0
- udata/templates/mail/inactive_account_deleted.html +5 -0
- udata/templates/mail/inactive_account_deleted.txt +6 -0
- udata/tests/api/test_dataservices_api.py +41 -2
- udata/tests/api/test_datasets_api.py +58 -0
- udata/tests/api/test_me_api.py +1 -1
- udata/tests/api/test_transfer_api.py +38 -0
- udata/tests/api/test_user_api.py +47 -8
- udata/tests/dataservice/test_csv_adapter.py +2 -0
- udata/tests/dataset/test_dataset_model.py +14 -0
- udata/tests/user/test_user_tasks.py +144 -0
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +88 -60
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +88 -60
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +88 -60
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +88 -60
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +88 -60
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +88 -60
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +88 -60
- udata/translations/udata.pot +83 -54
- {udata-10.1.2.dev34172.dist-info → udata-10.1.3.dist-info}/METADATA +15 -2
- {udata-10.1.2.dev34172.dist-info → udata-10.1.3.dist-info}/RECORD +59 -52
- {udata-10.1.2.dev34172.dist-info → udata-10.1.3.dist-info}/LICENSE +0 -0
- {udata-10.1.2.dev34172.dist-info → udata-10.1.3.dist-info}/WHEEL +0 -0
- {udata-10.1.2.dev34172.dist-info → udata-10.1.3.dist-info}/entry_points.txt +0 -0
- {udata-10.1.2.dev34172.dist-info → udata-10.1.3.dist-info}/top_level.txt +0 -0
|
@@ -558,6 +558,18 @@ class DatasetAPITest(APITestCase):
|
|
|
558
558
|
self.assertEqual(Dataset.objects.count(), 1)
|
|
559
559
|
self.assertEqual(Dataset.objects.first().description, "new description")
|
|
560
560
|
|
|
561
|
+
def test_cannot_modify_dataset_id(self):
|
|
562
|
+
user = self.login()
|
|
563
|
+
dataset = DatasetFactory(owner=user)
|
|
564
|
+
|
|
565
|
+
data = dataset.to_dict()
|
|
566
|
+
data["id"] = "7776aa373aa050e302b5714d"
|
|
567
|
+
|
|
568
|
+
response = self.put(url_for("api.dataset", dataset=dataset.id), data)
|
|
569
|
+
|
|
570
|
+
self.assert200(response)
|
|
571
|
+
self.assertEqual(response.json["id"], str(dataset.id))
|
|
572
|
+
|
|
561
573
|
def test_dataset_api_update_org(self):
|
|
562
574
|
"""It shouldn't update the dataset org"""
|
|
563
575
|
user = self.login()
|
|
@@ -1130,6 +1142,37 @@ class DatasetResourceAPITest(APITestCase):
|
|
|
1130
1142
|
# should fail because the POST endpoint only supports URL setting for remote resources
|
|
1131
1143
|
self.assert400(response)
|
|
1132
1144
|
|
|
1145
|
+
def test_creating_and_updating_resource_uuid(self):
|
|
1146
|
+
uuid_a = "c312cfb0-60f7-417c-9cf9-3d985196b22a"
|
|
1147
|
+
uuid_b = "e8262134-5ff0-4bd8-98bc-5db76bb27856"
|
|
1148
|
+
|
|
1149
|
+
data = ResourceFactory.as_dict()
|
|
1150
|
+
data["filetype"] = "remote"
|
|
1151
|
+
data["id"] = uuid_a
|
|
1152
|
+
response = self.post(url_for("api.resources", dataset=self.dataset), data)
|
|
1153
|
+
self.assert201(response)
|
|
1154
|
+
self.assertNotEqual(response.json["id"], uuid_a)
|
|
1155
|
+
|
|
1156
|
+
first_generated_uuid = response.json["id"]
|
|
1157
|
+
|
|
1158
|
+
# Sending the same UUID twice doesn't change anything…
|
|
1159
|
+
data = ResourceFactory.as_dict()
|
|
1160
|
+
data["filetype"] = "remote"
|
|
1161
|
+
data["id"] = first_generated_uuid
|
|
1162
|
+
response = self.post(url_for("api.resources", dataset=self.dataset), data)
|
|
1163
|
+
self.assert201(response)
|
|
1164
|
+
self.assertNotEqual(response.json["id"], first_generated_uuid)
|
|
1165
|
+
|
|
1166
|
+
# Cannot modify the ID of an existing resource
|
|
1167
|
+
data = response.json
|
|
1168
|
+
data["id"] = uuid_b
|
|
1169
|
+
response = self.put(
|
|
1170
|
+
url_for("api.resource", dataset=self.dataset, rid=first_generated_uuid),
|
|
1171
|
+
data,
|
|
1172
|
+
)
|
|
1173
|
+
self.assert200(response)
|
|
1174
|
+
self.assertEqual(response.json["id"], first_generated_uuid)
|
|
1175
|
+
|
|
1133
1176
|
def test_create_normalize_format(self):
|
|
1134
1177
|
_format = " FORMAT "
|
|
1135
1178
|
data = ResourceFactory.as_dict()
|
|
@@ -1295,6 +1338,21 @@ class DatasetResourceAPITest(APITestCase):
|
|
|
1295
1338
|
self.assertEqual(updated.url, data["url"])
|
|
1296
1339
|
self.assertEqual(updated.extras, {"extra:id": "id"})
|
|
1297
1340
|
|
|
1341
|
+
def test_cannot_update_resource_filetype(self):
|
|
1342
|
+
user = self.login()
|
|
1343
|
+
resource = ResourceFactory(filetype="file")
|
|
1344
|
+
dataset = DatasetFactory(owner=user, resources=[resource])
|
|
1345
|
+
|
|
1346
|
+
data = {
|
|
1347
|
+
"filetype": "remote",
|
|
1348
|
+
"url": faker.url(),
|
|
1349
|
+
}
|
|
1350
|
+
response = self.put(url_for("api.resource", dataset=dataset, rid=str(resource.id)), data)
|
|
1351
|
+
self.assert400(response)
|
|
1352
|
+
|
|
1353
|
+
dataset.reload()
|
|
1354
|
+
self.assertEqual(dataset.resources[0].filetype, "file")
|
|
1355
|
+
|
|
1298
1356
|
def test_bulk_update(self):
|
|
1299
1357
|
resources = ResourceFactory.build_batch(2)
|
|
1300
1358
|
self.dataset.resources.extend(resources)
|
udata/tests/api/test_me_api.py
CHANGED
|
@@ -332,7 +332,7 @@ class MeAPITest(APITestCase):
|
|
|
332
332
|
|
|
333
333
|
# The discussions are kept but the messages are anonymized
|
|
334
334
|
self.assertEqual(len(discussion.discussion), 2)
|
|
335
|
-
self.assertEqual(discussion.discussion[0].
|
|
335
|
+
self.assertEqual(discussion.discussion[0].posted_by.fullname, "DELETED DELETED")
|
|
336
336
|
self.assertEqual(discussion.discussion[1].content, other_disc_msg_content)
|
|
337
337
|
|
|
338
338
|
# The datasets are unchanged
|
|
@@ -2,8 +2,10 @@ from bson import ObjectId
|
|
|
2
2
|
from flask import url_for
|
|
3
3
|
|
|
4
4
|
from udata.core.dataset.factories import DatasetFactory
|
|
5
|
+
from udata.core.dataset.models import Dataset
|
|
5
6
|
from udata.core.organization.factories import OrganizationFactory
|
|
6
7
|
from udata.core.user.factories import UserFactory
|
|
8
|
+
from udata.core.user.models import User
|
|
7
9
|
from udata.utils import faker
|
|
8
10
|
|
|
9
11
|
from . import APITestCase
|
|
@@ -214,3 +216,39 @@ class TransferAPITest(APITestCase):
|
|
|
214
216
|
data = response.json
|
|
215
217
|
|
|
216
218
|
self.assertIn("recipient", data["errors"])
|
|
219
|
+
|
|
220
|
+
def test_cannot_accept_or_refuse_transfer_after_accepting_or_refusing(self):
|
|
221
|
+
user = self.login()
|
|
222
|
+
new_user = UserFactory()
|
|
223
|
+
dataset = DatasetFactory(owner=user)
|
|
224
|
+
|
|
225
|
+
response = self._create_transfer(dataset, new_user)
|
|
226
|
+
self.assert201(response)
|
|
227
|
+
|
|
228
|
+
transfer = response.json
|
|
229
|
+
|
|
230
|
+
self.login(new_user)
|
|
231
|
+
response = self.post(url_for("api.transfer", id=transfer["id"]), {"response": "accept"})
|
|
232
|
+
self.assert200(response)
|
|
233
|
+
|
|
234
|
+
response = self.post(url_for("api.transfer", id=transfer["id"]), {"response": "accept"})
|
|
235
|
+
self.assert400(response)
|
|
236
|
+
|
|
237
|
+
response = self.post(url_for("api.transfer", id=transfer["id"]), {"response": "refuse"})
|
|
238
|
+
self.assert400(response)
|
|
239
|
+
|
|
240
|
+
def _create_transfer(self, source: Dataset, destination: User):
|
|
241
|
+
return self.post(
|
|
242
|
+
url_for("api.transfers"),
|
|
243
|
+
{
|
|
244
|
+
"subject": {
|
|
245
|
+
"class": "Dataset",
|
|
246
|
+
"id": str(source.id),
|
|
247
|
+
},
|
|
248
|
+
"recipient": {
|
|
249
|
+
"class": "User",
|
|
250
|
+
"id": str(destination.id),
|
|
251
|
+
},
|
|
252
|
+
"comment": "Some comment",
|
|
253
|
+
},
|
|
254
|
+
)
|
udata/tests/api/test_user_api.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from flask import url_for
|
|
2
2
|
|
|
3
3
|
from udata.core import storages
|
|
4
|
+
from udata.core.discussions.factories import DiscussionFactory, MessageDiscussionFactory
|
|
4
5
|
from udata.core.user.factories import AdminFactory, UserFactory
|
|
5
|
-
from udata.models import Follow
|
|
6
|
+
from udata.models import Discussion, Follow
|
|
6
7
|
from udata.tests.helpers import capture_mails, create_test_image
|
|
7
8
|
from udata.utils import faker
|
|
8
9
|
|
|
@@ -352,36 +353,74 @@ class UserAPITest(APITestCase):
|
|
|
352
353
|
def test_delete_user(self):
|
|
353
354
|
user = AdminFactory()
|
|
354
355
|
self.login(user)
|
|
355
|
-
|
|
356
|
+
user_to_delete = UserFactory()
|
|
356
357
|
file = create_test_image()
|
|
358
|
+
discussion = DiscussionFactory(
|
|
359
|
+
user=user_to_delete,
|
|
360
|
+
discussion=[
|
|
361
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
362
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
363
|
+
],
|
|
364
|
+
)
|
|
357
365
|
|
|
358
366
|
response = self.post(
|
|
359
|
-
url_for("api.user_avatar", user=
|
|
367
|
+
url_for("api.user_avatar", user=user_to_delete),
|
|
360
368
|
{"file": (file, "test.png")},
|
|
361
369
|
json=False,
|
|
362
370
|
)
|
|
363
371
|
with capture_mails() as mails:
|
|
364
|
-
response = self.delete(url_for("api.user", user=
|
|
372
|
+
response = self.delete(url_for("api.user", user=user_to_delete))
|
|
365
373
|
self.assertEqual(list(storages.avatars.list_files()), [])
|
|
366
374
|
self.assert204(response)
|
|
367
375
|
self.assertEquals(len(mails), 1)
|
|
368
376
|
|
|
369
|
-
|
|
370
|
-
response = self.delete(url_for("api.user", user=
|
|
377
|
+
user_to_delete.reload()
|
|
378
|
+
response = self.delete(url_for("api.user", user=user_to_delete))
|
|
371
379
|
self.assert410(response)
|
|
372
380
|
response = self.delete(url_for("api.user", user=user))
|
|
373
381
|
self.assert403(response)
|
|
374
382
|
|
|
383
|
+
# discussions are kept by default
|
|
384
|
+
discussion.reload()
|
|
385
|
+
assert len(discussion.discussion) == 2
|
|
386
|
+
assert discussion.discussion[1].content != "DELETED"
|
|
387
|
+
|
|
375
388
|
def test_delete_user_without_notify(self):
|
|
376
389
|
user = AdminFactory()
|
|
377
390
|
self.login(user)
|
|
378
|
-
|
|
391
|
+
user_to_delete = UserFactory()
|
|
379
392
|
|
|
380
393
|
with capture_mails() as mails:
|
|
381
|
-
response = self.delete(url_for("api.user", user=
|
|
394
|
+
response = self.delete(url_for("api.user", user=user_to_delete, no_mail=True))
|
|
382
395
|
self.assert204(response)
|
|
383
396
|
self.assertEqual(len(mails), 0)
|
|
384
397
|
|
|
398
|
+
def test_delete_user_with_comments_deletion(self):
|
|
399
|
+
user = AdminFactory()
|
|
400
|
+
self.login(user)
|
|
401
|
+
user_to_delete = UserFactory()
|
|
402
|
+
discussion_only_user = DiscussionFactory(
|
|
403
|
+
user=user_to_delete,
|
|
404
|
+
discussion=[
|
|
405
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
406
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
407
|
+
],
|
|
408
|
+
)
|
|
409
|
+
discussion_with_other = DiscussionFactory(
|
|
410
|
+
user=user,
|
|
411
|
+
discussion=[
|
|
412
|
+
MessageDiscussionFactory(posted_by=user),
|
|
413
|
+
MessageDiscussionFactory(posted_by=user_to_delete),
|
|
414
|
+
],
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
response = self.delete(url_for("api.user", user=user_to_delete, delete_comments=True))
|
|
418
|
+
self.assert204(response)
|
|
419
|
+
|
|
420
|
+
assert Discussion.objects(id=discussion_only_user.id).first() is None
|
|
421
|
+
discussion_with_other.reload()
|
|
422
|
+
assert discussion_with_other.discussion[1].content == "DELETED"
|
|
423
|
+
|
|
385
424
|
def test_contact_points(self):
|
|
386
425
|
user = AdminFactory()
|
|
387
426
|
self.login(user)
|
|
@@ -18,6 +18,7 @@ class DataserviceCSVAdapterTest:
|
|
|
18
18
|
metadata_modified_at=datetime(2023, 1, 1),
|
|
19
19
|
organization=OrganizationFactory(),
|
|
20
20
|
datasets=[DatasetFactory(), DatasetFactory()],
|
|
21
|
+
metrics={"views": 42},
|
|
21
22
|
)
|
|
22
23
|
[DataserviceFactory() for _ in range(10)]
|
|
23
24
|
adapter = DataserviceCsvAdapter(Dataservice.objects.all())
|
|
@@ -41,3 +42,4 @@ class DataserviceCSVAdapterTest:
|
|
|
41
42
|
assert dataservice_values["datasets"] == ",".join(
|
|
42
43
|
str(dataset.id) for dataset in dataservice.datasets
|
|
43
44
|
)
|
|
45
|
+
assert dataservice_values["metric.views"] == dataservice.metrics["views"]
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from datetime import datetime, timedelta
|
|
2
|
+
from uuid import uuid4
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
4
5
|
import requests
|
|
5
6
|
from flask import current_app
|
|
7
|
+
from mongoengine import ValidationError as MongoEngineValidationError
|
|
6
8
|
from mongoengine import post_save
|
|
7
9
|
|
|
8
10
|
from udata.app import cache
|
|
@@ -59,6 +61,18 @@ class DatasetModelTest:
|
|
|
59
61
|
assert len(dataset.resources) == 2
|
|
60
62
|
assert dataset.resources[0].id == resource.id
|
|
61
63
|
|
|
64
|
+
def test_add_two_resources_with_same_id(self):
|
|
65
|
+
uuid = uuid4()
|
|
66
|
+
user = UserFactory()
|
|
67
|
+
dataset = DatasetFactory(owner=user)
|
|
68
|
+
resource_a = ResourceFactory(id=uuid)
|
|
69
|
+
resource_b = ResourceFactory(id=uuid)
|
|
70
|
+
|
|
71
|
+
dataset.add_resource(resource_a)
|
|
72
|
+
dataset.add_resource(ResourceFactory())
|
|
73
|
+
with pytest.raises(MongoEngineValidationError):
|
|
74
|
+
dataset.add_resource(resource_b)
|
|
75
|
+
|
|
62
76
|
def test_add_resource_missing_checksum_type(self):
|
|
63
77
|
user = UserFactory()
|
|
64
78
|
dataset = DatasetFactory(owner=user)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from flask import current_app
|
|
5
|
+
|
|
6
|
+
from udata.core.discussions.factories import DiscussionFactory
|
|
7
|
+
from udata.core.user import tasks
|
|
8
|
+
from udata.core.user.factories import UserFactory
|
|
9
|
+
from udata.core.user.models import User
|
|
10
|
+
from udata.i18n import gettext as _
|
|
11
|
+
from udata.tests.api import APITestCase
|
|
12
|
+
from udata.tests.helpers import capture_mails
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UserTasksTest(APITestCase):
|
|
16
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
17
|
+
def test_notify_inactive_users(self):
|
|
18
|
+
notification_comparison_date = (
|
|
19
|
+
datetime.utcnow()
|
|
20
|
+
- timedelta(days=current_app.config["YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION"] * 365)
|
|
21
|
+
+ timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
22
|
+
- timedelta(days=1) # add margin
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
inactive_user = UserFactory(current_login_at=notification_comparison_date)
|
|
26
|
+
UserFactory(current_login_at=datetime.utcnow()) # Active user
|
|
27
|
+
|
|
28
|
+
with capture_mails() as mails:
|
|
29
|
+
tasks.notify_inactive_users()
|
|
30
|
+
|
|
31
|
+
# Assert (only one) mail has been sent
|
|
32
|
+
self.assertEqual(len(mails), 1)
|
|
33
|
+
self.assertEqual(mails[0].send_to, set([inactive_user.email]))
|
|
34
|
+
self.assertEqual(
|
|
35
|
+
mails[0].subject,
|
|
36
|
+
_("Inactivity of your {site} account").format(site=current_app.config["SITE_TITLE"]),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# We shouldn't notify users twice
|
|
40
|
+
with capture_mails() as mails:
|
|
41
|
+
tasks.notify_inactive_users()
|
|
42
|
+
|
|
43
|
+
self.assertEqual(len(mails), 0)
|
|
44
|
+
|
|
45
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
46
|
+
@pytest.mark.options(MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS=10)
|
|
47
|
+
def test_notify_inactive_users_max_notifications(self):
|
|
48
|
+
notification_comparison_date = (
|
|
49
|
+
datetime.utcnow()
|
|
50
|
+
- timedelta(days=current_app.config["YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION"] * 365)
|
|
51
|
+
+ timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
52
|
+
- timedelta(days=1) # add margin
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
NB_USERS_TO_NOTIFY = 15
|
|
56
|
+
|
|
57
|
+
[UserFactory(current_login_at=notification_comparison_date) for _ in range(15)]
|
|
58
|
+
UserFactory(current_login_at=datetime.utcnow()) # Active user
|
|
59
|
+
|
|
60
|
+
with capture_mails() as mails:
|
|
61
|
+
tasks.notify_inactive_users()
|
|
62
|
+
|
|
63
|
+
# Assert MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS mails have been sent
|
|
64
|
+
self.assertEqual(
|
|
65
|
+
len(mails), current_app.config["MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS"]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Second batch
|
|
69
|
+
with capture_mails() as mails:
|
|
70
|
+
tasks.notify_inactive_users()
|
|
71
|
+
|
|
72
|
+
# Assert what's left have been sent
|
|
73
|
+
self.assertEqual(
|
|
74
|
+
len(mails),
|
|
75
|
+
NB_USERS_TO_NOTIFY - current_app.config["MAX_NUMBER_OF_USER_INACTIVITY_NOTIFICATIONS"],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
79
|
+
def test_delete_inactive_users(self):
|
|
80
|
+
deletion_comparison_date = (
|
|
81
|
+
datetime.utcnow()
|
|
82
|
+
- timedelta(days=current_app.config["YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION"] * 365)
|
|
83
|
+
- timedelta(days=1) # add margin
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
notification_comparison_date = (
|
|
87
|
+
datetime.utcnow()
|
|
88
|
+
- timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
89
|
+
- timedelta(days=1) # add margin
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
inactive_user_to_delete = UserFactory(
|
|
93
|
+
current_login_at=deletion_comparison_date,
|
|
94
|
+
inactive_deletion_notified_at=notification_comparison_date,
|
|
95
|
+
)
|
|
96
|
+
UserFactory(current_login_at=datetime.utcnow()) # Active user
|
|
97
|
+
discussion = DiscussionFactory(user=inactive_user_to_delete)
|
|
98
|
+
discussion_title = discussion.title
|
|
99
|
+
|
|
100
|
+
with capture_mails() as mails:
|
|
101
|
+
tasks.delete_inactive_users()
|
|
102
|
+
|
|
103
|
+
# Assert (only one) mail has been sent
|
|
104
|
+
self.assertEqual(len(mails), 1)
|
|
105
|
+
self.assertEqual(mails[0].send_to, set([inactive_user_to_delete.email]))
|
|
106
|
+
self.assertEqual(
|
|
107
|
+
mails[0].subject,
|
|
108
|
+
_(
|
|
109
|
+
_("Deletion of your inactive {site} account").format(
|
|
110
|
+
site=current_app.config["SITE_TITLE"]
|
|
111
|
+
)
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Assert user has been deleted but not its discussion
|
|
116
|
+
inactive_user_to_delete.reload()
|
|
117
|
+
discussion.reload()
|
|
118
|
+
self.assertEqual(inactive_user_to_delete.fullname, "DELETED DELETED")
|
|
119
|
+
self.assertEqual(discussion.title, discussion_title)
|
|
120
|
+
|
|
121
|
+
@pytest.mark.options(YEARS_OF_INACTIVITY_BEFORE_DEACTIVATION=3)
|
|
122
|
+
def test_keep_inactive_users_that_logged_in(self):
|
|
123
|
+
notification_comparison_date = (
|
|
124
|
+
datetime.utcnow()
|
|
125
|
+
- timedelta(days=current_app.config["DAYS_BEFORE_ACCOUNT_INACTIVITY_NOTIFY_DELAY"])
|
|
126
|
+
- timedelta(days=1) # add margin
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
inactive_user_that_logged_in_since_notification = UserFactory(
|
|
130
|
+
current_login_at=datetime.utcnow(),
|
|
131
|
+
inactive_deletion_notified_at=notification_comparison_date,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
with capture_mails() as mails:
|
|
135
|
+
tasks.delete_inactive_users()
|
|
136
|
+
|
|
137
|
+
# Assert no mail has been sent
|
|
138
|
+
self.assertEqual(len(mails), 0)
|
|
139
|
+
|
|
140
|
+
# Assert user hasn't been deleted and won't be deleted
|
|
141
|
+
self.assertEqual(User.objects().count(), 1)
|
|
142
|
+
user = User.objects().first()
|
|
143
|
+
self.assertEqual(user, inactive_user_that_logged_in_since_notification)
|
|
144
|
+
self.assertIsNone(user.inactive_deletion_notified_at)
|
|
Binary file
|