udata 14.0.0__py3-none-any.whl → 14.5.1.dev6__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/api/__init__.py +2 -0
- udata/api_fields.py +35 -4
- udata/app.py +18 -20
- udata/auth/__init__.py +29 -6
- udata/auth/forms.py +2 -2
- udata/auth/views.py +13 -6
- udata/commands/dcat.py +1 -1
- udata/commands/serve.py +3 -11
- udata/commands/tests/test_fixtures.py +9 -9
- udata/core/access_type/api.py +1 -1
- udata/core/access_type/constants.py +12 -8
- udata/core/activity/api.py +5 -6
- udata/core/badges/tests/test_commands.py +6 -6
- udata/core/csv.py +5 -0
- udata/core/dataservices/api.py +8 -1
- udata/core/dataservices/apiv2.py +2 -5
- udata/core/dataservices/models.py +5 -2
- udata/core/dataservices/rdf.py +2 -1
- udata/core/dataservices/tasks.py +13 -2
- udata/core/dataset/api.py +10 -0
- udata/core/dataset/models.py +6 -6
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/rdf.py +8 -2
- udata/core/dataset/tasks.py +23 -7
- udata/core/discussions/api.py +15 -1
- udata/core/discussions/models.py +6 -0
- udata/core/legal/__init__.py +0 -0
- udata/core/legal/mails.py +128 -0
- udata/core/organization/api.py +16 -5
- udata/core/organization/apiv2.py +2 -3
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +15 -2
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/tests/test_api.py +32 -0
- udata/core/post/api.py +24 -69
- udata/core/post/models.py +84 -16
- udata/core/post/tests/test_api.py +24 -1
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/api.py +8 -0
- udata/core/reuse/apiv2.py +2 -5
- udata/core/reuse/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/spatial/forms.py +2 -2
- udata/core/topic/models.py +8 -2
- udata/core/user/api.py +10 -3
- udata/core/user/models.py +12 -2
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +56 -0
- udata/features/notifications/tasks.py +25 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/flask_mongoengine/pagination.py +1 -1
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +21 -1
- udata/harvest/api.py +25 -8
- udata/harvest/backends/base.py +27 -1
- udata/harvest/backends/ckan/harvesters.py +11 -2
- udata/harvest/backends/dcat.py +4 -1
- udata/harvest/commands.py +33 -0
- udata/harvest/filters.py +17 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tests/ckan/test_ckan_backend.py +33 -0
- udata/harvest/tests/test_actions.py +58 -5
- udata/harvest/tests/test_api.py +276 -122
- udata/harvest/tests/test_base_backend.py +86 -1
- udata/harvest/tests/test_dcat_backend.py +81 -10
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +1 -4
- udata/mail.py +19 -1
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +58 -10
- udata/routing.py +2 -2
- udata/settings.py +11 -0
- udata/tasks.py +1 -0
- udata/templates/mail/message.html +5 -31
- udata/tests/__init__.py +27 -2
- udata/tests/api/__init__.py +108 -21
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_auth_api.py +121 -95
- udata/tests/api/test_base_api.py +7 -4
- udata/tests/api/test_datasets_api.py +50 -19
- udata/tests/api/test_organizations_api.py +192 -197
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_reuses_api.py +147 -147
- udata/tests/api/test_security_api.py +12 -12
- udata/tests/api/test_swagger.py +4 -4
- udata/tests/api/test_tags_api.py +8 -8
- udata/tests/api/test_user_api.py +1 -1
- udata/tests/apiv2/test_search.py +30 -0
- udata/tests/apiv2/test_swagger.py +4 -4
- udata/tests/cli/test_cli_base.py +8 -9
- udata/tests/dataservice/test_dataservice_tasks.py +29 -0
- udata/tests/dataset/test_dataset_commands.py +4 -4
- udata/tests/dataset/test_dataset_model.py +66 -26
- udata/tests/dataset/test_dataset_rdf.py +99 -5
- udata/tests/dataset/test_dataset_tasks.py +25 -0
- udata/tests/frontend/test_auth.py +58 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +31 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/search/test_search_integration.py +33 -0
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_api_fields.py +10 -0
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_legal_mails.py +359 -0
- udata/tests/test_migrations.py +21 -21
- udata/tests/test_notifications.py +15 -57
- udata/tests/test_notifications_task.py +43 -0
- udata/tests/test_owned.py +81 -1
- udata/tests/test_storages.py +25 -19
- udata/tests/test_topics.py +77 -61
- udata/tests/test_uris.py +33 -0
- udata/tests/workers/test_jobs_commands.py +23 -23
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +187 -108
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +187 -108
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +187 -108
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +188 -109
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +187 -108
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +187 -108
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +187 -108
- udata/translations/udata.pot +215 -106
- udata/uris.py +0 -2
- udata-14.5.1.dev6.dist-info/METADATA +109 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/RECORD +143 -140
- udata/core/post/forms.py +0 -30
- udata/flask_mongoengine/json.py +0 -38
- udata/templates/mail/base.html +0 -105
- udata/templates/mail/base.txt +0 -6
- udata/templates/mail/button.html +0 -3
- udata/templates/mail/layouts/1-column.html +0 -19
- udata/templates/mail/layouts/2-columns.html +0 -20
- udata/templates/mail/layouts/center-panel.html +0 -16
- udata-14.0.0.dist-info/METADATA +0 -132
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/WHEEL +0 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/entry_points.txt +0 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/licenses/LICENSE +0 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/top_level.txt +0 -0
|
@@ -7,6 +7,7 @@ from flask import url_for
|
|
|
7
7
|
from udata.core import csv
|
|
8
8
|
from udata.core.dataservices.factories import DataserviceFactory
|
|
9
9
|
from udata.core.dataset import tasks as dataset_tasks
|
|
10
|
+
from udata.core.dataset.constants import SPD
|
|
10
11
|
from udata.core.dataset.factories import DatasetFactory, ResourceFactory
|
|
11
12
|
from udata.core.organization.factories import OrganizationFactory
|
|
12
13
|
from udata.core.reuse.factories import ReuseFactory
|
|
@@ -25,7 +26,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
25
26
|
|
|
26
27
|
self.assert200(response)
|
|
27
28
|
self.assertEqual(response.mimetype, "text/csv")
|
|
28
|
-
self.assertEqual(response.charset, "utf-8")
|
|
29
29
|
|
|
30
30
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
31
31
|
reader = csv.get_reader(csvfile)
|
|
@@ -73,7 +73,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
73
73
|
|
|
74
74
|
self.assert200(response)
|
|
75
75
|
self.assertEqual(response.mimetype, "text/csv")
|
|
76
|
-
self.assertEqual(response.charset, "utf-8")
|
|
77
76
|
|
|
78
77
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
79
78
|
reader = csv.get_reader(csvfile)
|
|
@@ -99,6 +98,27 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
99
98
|
self.assertNotIn(str(dataset.id), ids)
|
|
100
99
|
self.assertNotIn(str(hidden_dataset.id), ids)
|
|
101
100
|
|
|
101
|
+
def test_datasets_csv_with_badge_filter(self):
|
|
102
|
+
self.app.config["EXPORT_CSV_MODELS"] = []
|
|
103
|
+
dataset_with_badge = DatasetFactory(resources=[ResourceFactory()])
|
|
104
|
+
dataset_with_badge.add_badge(SPD)
|
|
105
|
+
dataset_without_badge = DatasetFactory(resources=[ResourceFactory()])
|
|
106
|
+
|
|
107
|
+
response = self.get(url_for("api.site_datasets_csv", badge=SPD))
|
|
108
|
+
|
|
109
|
+
self.assert200(response)
|
|
110
|
+
|
|
111
|
+
csvfile = StringIO(response.data.decode("utf8"))
|
|
112
|
+
reader = csv.get_reader(csvfile)
|
|
113
|
+
next(reader) # skip header
|
|
114
|
+
|
|
115
|
+
rows = list(reader)
|
|
116
|
+
ids = [row[0] for row in rows]
|
|
117
|
+
|
|
118
|
+
self.assertEqual(len(rows), 1)
|
|
119
|
+
self.assertIn(str(dataset_with_badge.id), ids)
|
|
120
|
+
self.assertNotIn(str(dataset_without_badge.id), ids)
|
|
121
|
+
|
|
102
122
|
def test_resources_csv(self):
|
|
103
123
|
self.app.config["EXPORT_CSV_MODELS"] = []
|
|
104
124
|
datasets = [
|
|
@@ -110,7 +130,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
110
130
|
|
|
111
131
|
self.assert200(response)
|
|
112
132
|
self.assertEqual(response.mimetype, "text/csv")
|
|
113
|
-
self.assertEqual(response.charset, "utf-8")
|
|
114
133
|
|
|
115
134
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
116
135
|
reader = csv.get_reader(csvfile)
|
|
@@ -164,7 +183,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
164
183
|
|
|
165
184
|
self.assert200(response)
|
|
166
185
|
self.assertEqual(response.mimetype, "text/csv")
|
|
167
|
-
self.assertEqual(response.charset, "utf-8")
|
|
168
186
|
|
|
169
187
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
170
188
|
reader = csv.get_reader(csvfile)
|
|
@@ -200,7 +218,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
200
218
|
|
|
201
219
|
self.assert200(response)
|
|
202
220
|
self.assertEqual(response.mimetype, "text/csv")
|
|
203
|
-
self.assertEqual(response.charset, "utf-8")
|
|
204
221
|
|
|
205
222
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
206
223
|
reader = csv.get_reader(csvfile)
|
|
@@ -245,7 +262,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
245
262
|
|
|
246
263
|
self.assert200(response)
|
|
247
264
|
self.assertEqual(response.mimetype, "text/csv")
|
|
248
|
-
self.assertEqual(response.charset, "utf-8")
|
|
249
265
|
|
|
250
266
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
251
267
|
reader = csv.get_reader(csvfile)
|
|
@@ -293,7 +309,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
293
309
|
|
|
294
310
|
self.assert200(response)
|
|
295
311
|
self.assertEqual(response.mimetype, "text/csv")
|
|
296
|
-
self.assertEqual(response.charset, "utf-8")
|
|
297
312
|
|
|
298
313
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
299
314
|
reader = csv.get_reader(csvfile)
|
|
@@ -332,7 +347,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
332
347
|
|
|
333
348
|
self.assert200(response)
|
|
334
349
|
self.assertEqual(response.mimetype, "text/csv")
|
|
335
|
-
self.assertEqual(response.charset, "utf-8")
|
|
336
350
|
|
|
337
351
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
338
352
|
reader = csv.get_reader(csvfile)
|
|
@@ -379,7 +393,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
379
393
|
|
|
380
394
|
self.assert200(response)
|
|
381
395
|
self.assertEqual(response.mimetype, "text/csv")
|
|
382
|
-
self.assertEqual(response.charset, "utf-8")
|
|
383
396
|
|
|
384
397
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
385
398
|
reader = csv.get_reader(csvfile)
|
|
@@ -424,7 +437,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
424
437
|
|
|
425
438
|
self.assert200(response)
|
|
426
439
|
self.assertEqual(response.mimetype, "text/csv")
|
|
427
|
-
self.assertEqual(response.charset, "utf-8")
|
|
428
440
|
|
|
429
441
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
430
442
|
reader = csv.get_reader(csvfile)
|
udata/tests/test_activity.py
CHANGED
|
@@ -211,9 +211,9 @@ class AuditableTest(APITestCase):
|
|
|
211
211
|
not_auditable="original",
|
|
212
212
|
)
|
|
213
213
|
|
|
214
|
-
def check_signal_update(
|
|
214
|
+
def check_signal_update(kwargs):
|
|
215
215
|
self.assertEqual(
|
|
216
|
-
|
|
216
|
+
kwargs["changed_fields"],
|
|
217
217
|
[
|
|
218
218
|
"name",
|
|
219
219
|
"tags",
|
|
@@ -224,13 +224,13 @@ class AuditableTest(APITestCase):
|
|
|
224
224
|
"embedded_list.1.name",
|
|
225
225
|
],
|
|
226
226
|
)
|
|
227
|
-
self.assertEqual(
|
|
228
|
-
self.assertEqual(
|
|
229
|
-
self.assertEqual(
|
|
230
|
-
self.assertEqual(
|
|
231
|
-
self.assertEqual(
|
|
232
|
-
self.assertEqual(
|
|
233
|
-
self.assertEqual(
|
|
227
|
+
self.assertEqual(kwargs["previous"]["name"], "fake")
|
|
228
|
+
self.assertEqual(kwargs["previous"]["tags"], ["some", "tags"])
|
|
229
|
+
self.assertEqual(kwargs["previous"]["some_date"], date(2020, 1, 1))
|
|
230
|
+
self.assertEqual(kwargs["previous"]["daterange_embedded.start"], date(2020, 1, 1))
|
|
231
|
+
self.assertEqual(kwargs["previous"]["daterange_embedded.end"], date(2020, 12, 31))
|
|
232
|
+
self.assertEqual(kwargs["previous"]["some_list"], ["some", "list"])
|
|
233
|
+
self.assertEqual(kwargs["previous"]["embedded_list.1.name"], "fake_embedded_1")
|
|
234
234
|
|
|
235
235
|
with assert_emit(FakeAuditableSubject.on_update, assertions_callback=check_signal_update):
|
|
236
236
|
fake.name = "different"
|
udata/tests/test_api_fields.py
CHANGED
|
@@ -354,3 +354,13 @@ class ApplyPaginationTest(PytestOnlyDBTestCase):
|
|
|
354
354
|
results: DBPaginator = Fake.apply_pagination(Fake.apply_sort_filters(Fake.objects))
|
|
355
355
|
assert results.page_size == 5
|
|
356
356
|
assert results.page == 3
|
|
357
|
+
|
|
358
|
+
def test_negative_page_size_returns_404(self, app) -> None:
|
|
359
|
+
"""Negative page_size should return a 404 error."""
|
|
360
|
+
from werkzeug.exceptions import NotFound
|
|
361
|
+
|
|
362
|
+
FakeFactory()
|
|
363
|
+
|
|
364
|
+
with app.test_request_context("/foobar", query_string={"page": 1, "page_size": -5}):
|
|
365
|
+
with pytest.raises(NotFound):
|
|
366
|
+
Fake.apply_pagination(Fake.apply_sort_filters(Fake.objects))
|
|
@@ -2,7 +2,7 @@ from udata.tests.api import PytestOnlyDBTestCase
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class ParseUrlCommandTest(PytestOnlyDBTestCase):
|
|
5
|
-
def test_parse_url(self,
|
|
5
|
+
def test_parse_url(self, requests_mock, caplog) -> None:
|
|
6
6
|
logs = []
|
|
7
7
|
|
|
8
8
|
def mock_echo(message: str) -> None:
|
|
@@ -15,7 +15,7 @@ class ParseUrlCommandTest(PytestOnlyDBTestCase):
|
|
|
15
15
|
requests_mock.get(mock_url, text=test_rdf_file.read())
|
|
16
16
|
requests_mock.head(mock_url, text="sig.oreme.rdf")
|
|
17
17
|
dataset_id = "0437a976-cff1-4fa6-807a-c23006df2f8f"
|
|
18
|
-
result = cli(
|
|
18
|
+
result = self.cli(
|
|
19
19
|
"dcat",
|
|
20
20
|
"parse-url",
|
|
21
21
|
mock_url,
|
udata/tests/test_discussions.py
CHANGED
|
@@ -142,11 +142,11 @@ class DiscussionsTest(APITestCase):
|
|
|
142
142
|
with assert_not_emit(on_new_discussion):
|
|
143
143
|
discussion_id = None
|
|
144
144
|
|
|
145
|
-
def check_signal(
|
|
145
|
+
def check_signal(kwargs):
|
|
146
146
|
self.assertIsNotNone(discussion_id)
|
|
147
147
|
self.assertIn(
|
|
148
|
-
f"https://data.gouv.fr/datasets/{dataset.slug}/discussions
|
|
149
|
-
|
|
148
|
+
f"https://data.gouv.fr/datasets/{dataset.slug}/discussions?discussion_id={discussion_id}",
|
|
149
|
+
kwargs["message"],
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
|
|
@@ -620,8 +620,8 @@ class DiscussionsTest(APITestCase):
|
|
|
620
620
|
self.login()
|
|
621
621
|
with assert_not_emit(on_new_discussion_comment):
|
|
622
622
|
|
|
623
|
-
def check_signal(
|
|
624
|
-
self.assertIn(discussion.url_for(),
|
|
623
|
+
def check_signal(kwargs):
|
|
624
|
+
self.assertIn(discussion.url_for(), kwargs["message"])
|
|
625
625
|
|
|
626
626
|
with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
|
|
627
627
|
response = self.post(
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from flask import url_for
|
|
3
|
+
|
|
4
|
+
from udata.core.dataservices.factories import DataserviceFactory
|
|
5
|
+
from udata.core.dataset.factories import DatasetFactory
|
|
6
|
+
from udata.core.discussions.factories import DiscussionFactory, MessageDiscussionFactory
|
|
7
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
8
|
+
from udata.core.reuse.factories import ReuseFactory
|
|
9
|
+
from udata.core.user.factories import AdminFactory, UserFactory
|
|
10
|
+
from udata.tests.api import APITestCase
|
|
11
|
+
from udata.tests.helpers import capture_mails
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AdminMailsOnDeleteTest(APITestCase):
|
|
15
|
+
"""Test admin mails are sent on delete when send_legal_notice=True and user is sysadmin"""
|
|
16
|
+
|
|
17
|
+
modules = []
|
|
18
|
+
|
|
19
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
20
|
+
def test_dataset_delete_with_mail_as_admin(self):
|
|
21
|
+
"""Admin deleting dataset with send_legal_notice=True should send email to owner"""
|
|
22
|
+
self.login(AdminFactory())
|
|
23
|
+
owner = UserFactory()
|
|
24
|
+
dataset = DatasetFactory(owner=owner)
|
|
25
|
+
|
|
26
|
+
with capture_mails() as mails:
|
|
27
|
+
response = self.delete(
|
|
28
|
+
url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self.assertStatus(response, 204)
|
|
32
|
+
assert len(mails) == 1
|
|
33
|
+
assert mails[0].recipients[0] == owner.email
|
|
34
|
+
assert "deletion" in mails[0].subject.lower()
|
|
35
|
+
|
|
36
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
37
|
+
def test_dataset_delete_without_mail_as_admin(self):
|
|
38
|
+
"""Admin deleting dataset without send_mail should not send email"""
|
|
39
|
+
self.login(AdminFactory())
|
|
40
|
+
owner = UserFactory()
|
|
41
|
+
dataset = DatasetFactory(owner=owner)
|
|
42
|
+
|
|
43
|
+
with capture_mails() as mails:
|
|
44
|
+
response = self.delete(url_for("api.dataset", dataset=dataset))
|
|
45
|
+
|
|
46
|
+
self.assertStatus(response, 204)
|
|
47
|
+
assert len(mails) == 0
|
|
48
|
+
|
|
49
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
50
|
+
def test_dataset_delete_with_mail_as_non_admin(self):
|
|
51
|
+
"""Non-admin deleting their dataset with send_legal_notice=True should not send email"""
|
|
52
|
+
owner = self.login()
|
|
53
|
+
dataset = DatasetFactory(owner=owner)
|
|
54
|
+
|
|
55
|
+
with capture_mails() as mails:
|
|
56
|
+
response = self.delete(
|
|
57
|
+
url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.assertStatus(response, 204)
|
|
61
|
+
assert len(mails) == 0
|
|
62
|
+
|
|
63
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
64
|
+
def test_dataset_delete_with_org_owner_sends_to_admins(self):
|
|
65
|
+
"""Deleting org-owned dataset should send email to org admins"""
|
|
66
|
+
self.login(AdminFactory())
|
|
67
|
+
org_admin = UserFactory()
|
|
68
|
+
org = OrganizationFactory(members=[{"user": org_admin, "role": "admin"}])
|
|
69
|
+
dataset = DatasetFactory(organization=org)
|
|
70
|
+
|
|
71
|
+
with capture_mails() as mails:
|
|
72
|
+
response = self.delete(
|
|
73
|
+
url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.assertStatus(response, 204)
|
|
77
|
+
assert len(mails) == 1
|
|
78
|
+
assert mails[0].recipients[0] == org_admin.email
|
|
79
|
+
|
|
80
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
81
|
+
def test_reuse_delete_with_mail_as_admin(self):
|
|
82
|
+
"""Admin deleting reuse with send_legal_notice=True should send email to owner"""
|
|
83
|
+
self.login(AdminFactory())
|
|
84
|
+
owner = UserFactory()
|
|
85
|
+
reuse = ReuseFactory(owner=owner)
|
|
86
|
+
|
|
87
|
+
with capture_mails() as mails:
|
|
88
|
+
response = self.delete(url_for("api.reuse", reuse=reuse) + "?send_legal_notice=true")
|
|
89
|
+
|
|
90
|
+
self.assertStatus(response, 204)
|
|
91
|
+
assert len(mails) == 1
|
|
92
|
+
assert mails[0].recipients[0] == owner.email
|
|
93
|
+
|
|
94
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
95
|
+
def test_reuse_delete_without_mail_as_admin(self):
|
|
96
|
+
"""Admin deleting reuse without send_mail should not send email"""
|
|
97
|
+
self.login(AdminFactory())
|
|
98
|
+
owner = UserFactory()
|
|
99
|
+
reuse = ReuseFactory(owner=owner)
|
|
100
|
+
|
|
101
|
+
with capture_mails() as mails:
|
|
102
|
+
response = self.delete(url_for("api.reuse", reuse=reuse))
|
|
103
|
+
|
|
104
|
+
self.assertStatus(response, 204)
|
|
105
|
+
assert len(mails) == 0
|
|
106
|
+
|
|
107
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
108
|
+
def test_dataservice_delete_with_mail_as_admin(self):
|
|
109
|
+
"""Admin deleting dataservice with send_legal_notice=True should send email to owner"""
|
|
110
|
+
self.login(AdminFactory())
|
|
111
|
+
owner = UserFactory()
|
|
112
|
+
dataservice = DataserviceFactory(owner=owner)
|
|
113
|
+
|
|
114
|
+
with capture_mails() as mails:
|
|
115
|
+
response = self.delete(
|
|
116
|
+
url_for("api.dataservice", dataservice=dataservice) + "?send_legal_notice=true"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self.assertStatus(response, 204)
|
|
120
|
+
assert len(mails) == 1
|
|
121
|
+
assert mails[0].recipients[0] == owner.email
|
|
122
|
+
|
|
123
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
124
|
+
def test_organization_delete_with_mail_as_admin(self):
|
|
125
|
+
"""Admin deleting organization with send_legal_notice=True should send email to org admins"""
|
|
126
|
+
self.login(AdminFactory())
|
|
127
|
+
org_admin = UserFactory()
|
|
128
|
+
org = OrganizationFactory(members=[{"user": org_admin, "role": "admin"}])
|
|
129
|
+
|
|
130
|
+
with capture_mails() as mails:
|
|
131
|
+
response = self.delete(url_for("api.organization", org=org) + "?send_legal_notice=true")
|
|
132
|
+
|
|
133
|
+
self.assertStatus(response, 204)
|
|
134
|
+
assert len(mails) == 1
|
|
135
|
+
assert mails[0].recipients[0] == org_admin.email
|
|
136
|
+
|
|
137
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
138
|
+
def test_user_delete_with_legal_notice_skips_simple_notification(self):
|
|
139
|
+
"""Admin deleting user with send_legal_notice=True automatically skips simple notification"""
|
|
140
|
+
self.login(AdminFactory())
|
|
141
|
+
user_to_delete = UserFactory()
|
|
142
|
+
|
|
143
|
+
with capture_mails() as mails:
|
|
144
|
+
response = self.delete(
|
|
145
|
+
url_for("api.user", user=user_to_delete) + "?send_legal_notice=true"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
self.assertStatus(response, 204)
|
|
149
|
+
# Only legal notice mail, simple notification is automatically skipped
|
|
150
|
+
assert len(mails) == 1
|
|
151
|
+
assert mails[0].recipients[0] == user_to_delete.email
|
|
152
|
+
# Verify it's the legal notice (with appeal info), not the simple notification
|
|
153
|
+
assert "Deletion of your" in mails[0].subject # Legal notice subject
|
|
154
|
+
assert "contest this decision" in mails[0].body # Legal notice contains appeal info
|
|
155
|
+
|
|
156
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
157
|
+
def test_discussion_delete_with_mail_as_admin(self):
|
|
158
|
+
"""Admin deleting discussion with send_legal_notice=True should send email to author"""
|
|
159
|
+
self.login(AdminFactory())
|
|
160
|
+
author = UserFactory()
|
|
161
|
+
dataset = DatasetFactory()
|
|
162
|
+
discussion = DiscussionFactory(subject=dataset, user=author)
|
|
163
|
+
|
|
164
|
+
with capture_mails() as mails:
|
|
165
|
+
response = self.delete(
|
|
166
|
+
url_for("api.discussion", id=discussion.id) + "?send_legal_notice=true"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self.assertStatus(response, 204)
|
|
170
|
+
assert len(mails) == 1
|
|
171
|
+
assert mails[0].recipients[0] == author.email
|
|
172
|
+
|
|
173
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
174
|
+
def test_message_delete_with_mail_as_admin(self):
|
|
175
|
+
"""Admin deleting message with send_legal_notice=True should send email to author"""
|
|
176
|
+
self.login(AdminFactory())
|
|
177
|
+
author = UserFactory()
|
|
178
|
+
message_author = UserFactory()
|
|
179
|
+
dataset = DatasetFactory()
|
|
180
|
+
discussion = DiscussionFactory(
|
|
181
|
+
subject=dataset,
|
|
182
|
+
user=author,
|
|
183
|
+
discussion=[
|
|
184
|
+
MessageDiscussionFactory(posted_by=author),
|
|
185
|
+
MessageDiscussionFactory(posted_by=message_author),
|
|
186
|
+
],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
with capture_mails() as mails:
|
|
190
|
+
response = self.delete(
|
|
191
|
+
url_for("api.discussion_comment", id=discussion.id, cidx=1)
|
|
192
|
+
+ "?send_legal_notice=true"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
self.assertStatus(response, 204)
|
|
196
|
+
assert len(mails) == 1
|
|
197
|
+
assert mails[0].recipients[0] == message_author.email
|
|
198
|
+
|
|
199
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
200
|
+
def test_dataset_delete_without_owner_no_mail_sent(self):
|
|
201
|
+
"""Deleting dataset without owner or organization should not send email"""
|
|
202
|
+
self.login(AdminFactory())
|
|
203
|
+
dataset = DatasetFactory(owner=None, organization=None)
|
|
204
|
+
|
|
205
|
+
with capture_mails() as mails:
|
|
206
|
+
response = self.delete(
|
|
207
|
+
url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
self.assertStatus(response, 204)
|
|
211
|
+
assert len(mails) == 0
|
|
212
|
+
|
|
213
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
214
|
+
def test_reuse_delete_without_owner_no_mail_sent(self):
|
|
215
|
+
"""Deleting reuse without owner or organization should not send email"""
|
|
216
|
+
self.login(AdminFactory())
|
|
217
|
+
reuse = ReuseFactory(owner=None, organization=None)
|
|
218
|
+
|
|
219
|
+
with capture_mails() as mails:
|
|
220
|
+
response = self.delete(url_for("api.reuse", reuse=reuse) + "?send_legal_notice=true")
|
|
221
|
+
|
|
222
|
+
self.assertStatus(response, 204)
|
|
223
|
+
assert len(mails) == 0
|
|
224
|
+
|
|
225
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
226
|
+
def test_dataservice_delete_without_owner_no_mail_sent(self):
|
|
227
|
+
"""Deleting dataservice without owner or organization should not send email"""
|
|
228
|
+
self.login(AdminFactory())
|
|
229
|
+
dataservice = DataserviceFactory(owner=None, organization=None)
|
|
230
|
+
|
|
231
|
+
with capture_mails() as mails:
|
|
232
|
+
response = self.delete(
|
|
233
|
+
url_for("api.dataservice", dataservice=dataservice) + "?send_legal_notice=true"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
self.assertStatus(response, 204)
|
|
237
|
+
assert len(mails) == 0
|
|
238
|
+
|
|
239
|
+
@pytest.mark.options(DEFAULT_LANGUAGE="en")
|
|
240
|
+
def test_organization_delete_without_admins_no_mail_sent(self):
|
|
241
|
+
"""Deleting organization without admin members should not send email"""
|
|
242
|
+
self.login(AdminFactory())
|
|
243
|
+
editor = UserFactory()
|
|
244
|
+
org = OrganizationFactory(members=[{"user": editor, "role": "editor"}])
|
|
245
|
+
|
|
246
|
+
with capture_mails() as mails:
|
|
247
|
+
response = self.delete(url_for("api.organization", org=org) + "?send_legal_notice=true")
|
|
248
|
+
|
|
249
|
+
self.assertStatus(response, 204)
|
|
250
|
+
assert len(mails) == 0
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class MailContentVariantsTest(APITestCase):
|
|
254
|
+
"""Test mail content varies based on settings"""
|
|
255
|
+
|
|
256
|
+
modules = []
|
|
257
|
+
|
|
258
|
+
@pytest.mark.options(
|
|
259
|
+
DEFAULT_LANGUAGE="en",
|
|
260
|
+
TERMS_OF_USE_URL="https://example.com/terms",
|
|
261
|
+
TERMS_OF_USE_DELETION_ARTICLE="5.1.2",
|
|
262
|
+
TELERECOURS_URL="https://telerecours.fr",
|
|
263
|
+
)
|
|
264
|
+
def test_mail_with_all_settings(self):
|
|
265
|
+
"""Mail should contain terms of use reference and telerecours when all settings defined"""
|
|
266
|
+
self.login(AdminFactory())
|
|
267
|
+
owner = UserFactory()
|
|
268
|
+
dataset = DatasetFactory(owner=owner)
|
|
269
|
+
|
|
270
|
+
with capture_mails() as mails:
|
|
271
|
+
self.delete(url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true")
|
|
272
|
+
|
|
273
|
+
assert len(mails) == 1
|
|
274
|
+
body = mails[0].body
|
|
275
|
+
assert "Our terms of use specify" in body
|
|
276
|
+
assert "5.1.2" in body
|
|
277
|
+
assert "Télérecours" in body
|
|
278
|
+
|
|
279
|
+
@pytest.mark.options(
|
|
280
|
+
DEFAULT_LANGUAGE="en",
|
|
281
|
+
TERMS_OF_USE_DELETION_ARTICLE=None,
|
|
282
|
+
TELERECOURS_URL=None,
|
|
283
|
+
)
|
|
284
|
+
def test_mail_without_settings(self):
|
|
285
|
+
"""Mail should use generic text when settings are not defined"""
|
|
286
|
+
self.login(AdminFactory())
|
|
287
|
+
owner = UserFactory()
|
|
288
|
+
dataset = DatasetFactory(owner=owner)
|
|
289
|
+
|
|
290
|
+
with capture_mails() as mails:
|
|
291
|
+
self.delete(url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true")
|
|
292
|
+
|
|
293
|
+
assert len(mails) == 1
|
|
294
|
+
body = mails[0].body
|
|
295
|
+
assert "Our terms of use specify" not in body
|
|
296
|
+
assert "Télérecours" not in body
|
|
297
|
+
assert "contacting us" in body
|
|
298
|
+
|
|
299
|
+
@pytest.mark.options(
|
|
300
|
+
DEFAULT_LANGUAGE="en",
|
|
301
|
+
TERMS_OF_USE_URL="https://example.com/terms",
|
|
302
|
+
TERMS_OF_USE_DELETION_ARTICLE="3.2",
|
|
303
|
+
TELERECOURS_URL=None,
|
|
304
|
+
)
|
|
305
|
+
def test_mail_with_terms_only(self):
|
|
306
|
+
"""Mail should contain terms of use but generic appeal when only terms defined"""
|
|
307
|
+
self.login(AdminFactory())
|
|
308
|
+
owner = UserFactory()
|
|
309
|
+
dataset = DatasetFactory(owner=owner)
|
|
310
|
+
|
|
311
|
+
with capture_mails() as mails:
|
|
312
|
+
self.delete(url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true")
|
|
313
|
+
|
|
314
|
+
assert len(mails) == 1
|
|
315
|
+
body = mails[0].body
|
|
316
|
+
assert "Our terms of use specify" in body
|
|
317
|
+
assert "3.2" in body
|
|
318
|
+
assert "Télérecours" not in body
|
|
319
|
+
assert "contacting us" in body
|
|
320
|
+
|
|
321
|
+
@pytest.mark.options(
|
|
322
|
+
DEFAULT_LANGUAGE="en",
|
|
323
|
+
TERMS_OF_USE_DELETION_ARTICLE=None,
|
|
324
|
+
TELERECOURS_URL="https://telerecours.fr",
|
|
325
|
+
)
|
|
326
|
+
def test_mail_with_telerecours_only(self):
|
|
327
|
+
"""Mail should contain telerecours but generic terms when only telerecours defined"""
|
|
328
|
+
self.login(AdminFactory())
|
|
329
|
+
owner = UserFactory()
|
|
330
|
+
dataset = DatasetFactory(owner=owner)
|
|
331
|
+
|
|
332
|
+
with capture_mails() as mails:
|
|
333
|
+
self.delete(url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true")
|
|
334
|
+
|
|
335
|
+
assert len(mails) == 1
|
|
336
|
+
body = mails[0].body
|
|
337
|
+
assert "Our terms of use specify" not in body
|
|
338
|
+
assert "Télérecours" in body
|
|
339
|
+
assert "contacting us" not in body
|
|
340
|
+
|
|
341
|
+
@pytest.mark.options(
|
|
342
|
+
DEFAULT_LANGUAGE="en",
|
|
343
|
+
TERMS_OF_USE_URL=None,
|
|
344
|
+
TERMS_OF_USE_DELETION_ARTICLE="5.1.2",
|
|
345
|
+
TELERECOURS_URL=None,
|
|
346
|
+
)
|
|
347
|
+
def test_mail_with_article_but_no_url(self):
|
|
348
|
+
"""Mail should use generic terms when article is defined but URL is missing"""
|
|
349
|
+
self.login(AdminFactory())
|
|
350
|
+
owner = UserFactory()
|
|
351
|
+
dataset = DatasetFactory(owner=owner)
|
|
352
|
+
|
|
353
|
+
with capture_mails() as mails:
|
|
354
|
+
self.delete(url_for("api.dataset", dataset=dataset) + "?send_legal_notice=true")
|
|
355
|
+
|
|
356
|
+
assert len(mails) == 1
|
|
357
|
+
body = mails[0].body
|
|
358
|
+
assert "Our terms of use specify" not in body
|
|
359
|
+
assert "5.1.2" not in body
|