udata 12.0.2.dev15__py3-none-any.whl → 13.0.1.dev21__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.

Files changed (258) hide show
  1. udata/api/__init__.py +1 -0
  2. udata/api_fields.py +10 -4
  3. udata/app.py +11 -10
  4. udata/auth/__init__.py +9 -10
  5. udata/auth/mails.py +137 -45
  6. udata/auth/views.py +5 -12
  7. udata/commands/__init__.py +2 -3
  8. udata/commands/info.py +1 -3
  9. udata/commands/tests/test_fixtures.py +6 -3
  10. udata/core/access_type/api.py +18 -0
  11. udata/core/access_type/constants.py +98 -0
  12. udata/core/access_type/models.py +44 -0
  13. udata/core/activity/models.py +1 -1
  14. udata/core/badges/models.py +1 -1
  15. udata/core/badges/tasks.py +35 -1
  16. udata/core/badges/tests/test_commands.py +2 -4
  17. udata/core/badges/tests/test_model.py +2 -2
  18. udata/core/badges/tests/test_tasks.py +55 -0
  19. udata/core/constants.py +1 -0
  20. udata/core/contact_point/models.py +8 -0
  21. udata/core/dataservices/api.py +3 -3
  22. udata/core/dataservices/apiv2.py +3 -1
  23. udata/core/dataservices/constants.py +0 -29
  24. udata/core/dataservices/models.py +44 -44
  25. udata/core/dataservices/rdf.py +2 -1
  26. udata/core/dataservices/search.py +5 -9
  27. udata/core/dataservices/tasks.py +33 -0
  28. udata/core/dataset/api_fields.py +11 -0
  29. udata/core/dataset/apiv2.py +11 -0
  30. udata/core/dataset/constants.py +0 -1
  31. udata/core/dataset/forms.py +29 -0
  32. udata/core/dataset/models.py +16 -4
  33. udata/core/dataset/rdf.py +2 -1
  34. udata/core/dataset/search.py +2 -2
  35. udata/core/dataset/tasks.py +86 -8
  36. udata/core/discussions/mails.py +63 -0
  37. udata/core/discussions/tasks.py +4 -18
  38. udata/core/metrics/__init__.py +0 -6
  39. udata/core/organization/api.py +3 -1
  40. udata/core/organization/mails.py +144 -0
  41. udata/core/organization/models.py +2 -1
  42. udata/core/organization/search.py +1 -1
  43. udata/core/organization/tasks.py +21 -49
  44. udata/core/pages/tests/test_api.py +0 -2
  45. udata/core/reuse/api.py +27 -1
  46. udata/core/reuse/mails.py +21 -0
  47. udata/core/reuse/models.py +10 -1
  48. udata/core/reuse/search.py +1 -1
  49. udata/core/reuse/tasks.py +2 -3
  50. udata/core/site/models.py +2 -6
  51. udata/core/spatial/tests/test_api.py +17 -20
  52. udata/core/spatial/tests/test_models.py +3 -3
  53. udata/core/user/mails.py +54 -0
  54. udata/core/user/models.py +2 -3
  55. udata/core/user/tasks.py +8 -23
  56. udata/core/user/tests/test_user_model.py +2 -6
  57. udata/entrypoints.py +0 -5
  58. udata/features/identicon/tests/test_backends.py +3 -13
  59. udata/forms/fields.py +3 -3
  60. udata/forms/widgets.py +2 -2
  61. udata/frontend/__init__.py +3 -32
  62. udata/harvest/actions.py +4 -9
  63. udata/harvest/api.py +5 -14
  64. udata/harvest/backends/__init__.py +20 -11
  65. udata/harvest/backends/base.py +2 -2
  66. udata/harvest/backends/ckan/harvesters.py +2 -1
  67. udata/harvest/backends/dcat.py +3 -0
  68. udata/harvest/backends/maaf.py +1 -0
  69. udata/harvest/commands.py +6 -4
  70. udata/harvest/forms.py +9 -6
  71. udata/harvest/tasks.py +3 -5
  72. udata/harvest/tests/ckan/test_ckan_backend.py +300 -337
  73. udata/harvest/tests/ckan/test_ckan_backend_errors.py +94 -99
  74. udata/harvest/tests/ckan/test_ckan_backend_filters.py +128 -122
  75. udata/harvest/tests/ckan/test_dkan_backend.py +39 -51
  76. udata/harvest/tests/dcat/datara--5a26b0f6-0ccf-46ad-ac58-734054b91977.rdf.xml +255 -0
  77. udata/harvest/tests/dcat/datara--f40c3860-7236-4b30-a141-23b8ae33f7b2.rdf.xml +289 -0
  78. udata/harvest/tests/factories.py +1 -1
  79. udata/harvest/tests/test_actions.py +11 -9
  80. udata/harvest/tests/test_api.py +4 -5
  81. udata/harvest/tests/test_base_backend.py +5 -4
  82. udata/harvest/tests/test_dcat_backend.py +50 -19
  83. udata/harvest/tests/test_models.py +2 -4
  84. udata/harvest/tests/test_notifications.py +2 -4
  85. udata/harvest/tests/test_tasks.py +2 -3
  86. udata/mail.py +90 -53
  87. udata/migrations/2025-01-05-dataservices-fields-changes.py +8 -14
  88. udata/migrations/2025-10-21-remove-ckan-harvest-modified-at.py +28 -0
  89. udata/migrations/2025-10-29-harvesters-sources-integrity.py +27 -0
  90. udata/mongo/taglist_field.py +3 -3
  91. udata/rdf.py +32 -15
  92. udata/sentry.py +3 -4
  93. udata/settings.py +7 -2
  94. udata/tags.py +5 -5
  95. udata/tasks.py +3 -3
  96. udata/templates/mail/message.html +65 -0
  97. udata/templates/mail/message.txt +16 -0
  98. udata/tests/__init__.py +40 -58
  99. udata/tests/api/__init__.py +87 -2
  100. udata/tests/api/test_activities_api.py +17 -23
  101. udata/tests/api/test_auth_api.py +2 -4
  102. udata/tests/api/test_contact_points.py +48 -54
  103. udata/tests/api/test_dataservices_api.py +57 -37
  104. udata/tests/api/test_datasets_api.py +146 -49
  105. udata/tests/api/test_me_api.py +4 -6
  106. udata/tests/api/test_organizations_api.py +19 -38
  107. udata/tests/api/test_reports_api.py +0 -4
  108. udata/tests/api/test_reuses_api.py +92 -19
  109. udata/tests/api/test_security_api.py +124 -0
  110. udata/tests/api/test_swagger.py +2 -3
  111. udata/tests/api/test_tags_api.py +6 -7
  112. udata/tests/api/test_transfer_api.py +0 -2
  113. udata/tests/api/test_user_api.py +8 -10
  114. udata/tests/apiv2/test_datasets.py +0 -4
  115. udata/tests/apiv2/test_me_api.py +0 -2
  116. udata/tests/apiv2/test_organizations.py +0 -2
  117. udata/tests/apiv2/test_swagger.py +2 -3
  118. udata/tests/apiv2/test_topics.py +0 -2
  119. udata/tests/cli/test_cli_base.py +14 -12
  120. udata/tests/cli/test_db_cli.py +51 -54
  121. udata/tests/contact_point/test_contact_point_models.py +2 -2
  122. udata/tests/dataservice/test_csv_adapter.py +2 -5
  123. udata/tests/dataservice/test_dataservice_rdf.py +8 -6
  124. udata/tests/dataservice/test_dataservice_tasks.py +36 -38
  125. udata/tests/dataset/test_csv_adapter.py +2 -5
  126. udata/tests/dataset/test_dataset_actions.py +2 -4
  127. udata/tests/dataset/test_dataset_commands.py +2 -4
  128. udata/tests/dataset/test_dataset_events.py +3 -3
  129. udata/tests/dataset/test_dataset_model.py +6 -7
  130. udata/tests/dataset/test_dataset_rdf.py +201 -12
  131. udata/tests/dataset/test_dataset_recommendations.py +2 -2
  132. udata/tests/dataset/test_dataset_tasks.py +66 -68
  133. udata/tests/dataset/test_resource_preview.py +39 -48
  134. udata/tests/dataset/test_transport_tasks.py +2 -2
  135. udata/tests/features/territories/__init__.py +0 -6
  136. udata/tests/features/territories/test_territories_api.py +25 -24
  137. udata/tests/forms/test_current_user_field.py +2 -2
  138. udata/tests/forms/test_dict_field.py +2 -4
  139. udata/tests/forms/test_extras_fields.py +2 -3
  140. udata/tests/forms/test_image_field.py +2 -2
  141. udata/tests/forms/test_model_field.py +2 -4
  142. udata/tests/forms/test_publish_as_field.py +2 -4
  143. udata/tests/forms/test_user_forms.py +26 -29
  144. udata/tests/frontend/test_auth.py +2 -3
  145. udata/tests/frontend/test_csv.py +5 -6
  146. udata/tests/frontend/test_error_handlers.py +2 -3
  147. udata/tests/frontend/test_hooks.py +5 -7
  148. udata/tests/frontend/test_markdown.py +3 -4
  149. udata/tests/helpers.py +2 -7
  150. udata/tests/metrics/test_metrics.py +52 -48
  151. udata/tests/metrics/test_tasks.py +154 -150
  152. udata/tests/organization/test_csv_adapter.py +2 -5
  153. udata/tests/organization/test_notifications.py +2 -4
  154. udata/tests/organization/test_organization_model.py +3 -4
  155. udata/tests/organization/test_organization_rdf.py +2 -8
  156. udata/tests/plugin.py +6 -110
  157. udata/tests/reuse/test_reuse_model.py +3 -4
  158. udata/tests/site/test_site_api.py +0 -2
  159. udata/tests/site/test_site_csv_exports.py +0 -2
  160. udata/tests/site/test_site_metrics.py +2 -4
  161. udata/tests/site/test_site_model.py +2 -2
  162. udata/tests/site/test_site_rdf.py +4 -7
  163. udata/tests/test_activity.py +3 -3
  164. udata/tests/test_api_fields.py +6 -9
  165. udata/tests/test_cors.py +0 -2
  166. udata/tests/test_dcat_commands.py +2 -3
  167. udata/tests/test_discussions.py +2 -7
  168. udata/tests/test_mail.py +150 -114
  169. udata/tests/test_migrations.py +413 -419
  170. udata/tests/test_model.py +10 -11
  171. udata/tests/test_notifications.py +2 -3
  172. udata/tests/test_owned.py +3 -3
  173. udata/tests/test_rdf.py +19 -15
  174. udata/tests/test_routing.py +5 -5
  175. udata/tests/test_storages.py +6 -5
  176. udata/tests/test_tags.py +2 -4
  177. udata/tests/test_topics.py +2 -4
  178. udata/tests/test_transfer.py +4 -5
  179. udata/tests/topic/test_topic_tasks.py +25 -27
  180. udata/tests/user/test_user_rdf.py +2 -8
  181. udata/tests/user/test_user_tasks.py +3 -5
  182. udata/tests/workers/test_jobs_commands.py +2 -2
  183. udata/tests/workers/test_tasks_routing.py +27 -27
  184. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  185. udata/translations/ar/LC_MESSAGES/udata.po +369 -435
  186. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  187. udata/translations/de/LC_MESSAGES/udata.po +371 -437
  188. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  189. udata/translations/es/LC_MESSAGES/udata.po +369 -435
  190. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  191. udata/translations/fr/LC_MESSAGES/udata.po +381 -447
  192. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  193. udata/translations/it/LC_MESSAGES/udata.po +371 -437
  194. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  195. udata/translations/pt/LC_MESSAGES/udata.po +371 -437
  196. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  197. udata/translations/sr/LC_MESSAGES/udata.po +372 -438
  198. udata/translations/udata.pot +379 -440
  199. udata/utils.py +14 -2
  200. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/METADATA +1 -2
  201. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/RECORD +205 -242
  202. udata/templates/mail/account_deleted.html +0 -5
  203. udata/templates/mail/account_deleted.txt +0 -6
  204. udata/templates/mail/account_inactivity.html +0 -40
  205. udata/templates/mail/account_inactivity.txt +0 -31
  206. udata/templates/mail/badge_added_association.html +0 -33
  207. udata/templates/mail/badge_added_association.txt +0 -11
  208. udata/templates/mail/badge_added_certified.html +0 -33
  209. udata/templates/mail/badge_added_certified.txt +0 -11
  210. udata/templates/mail/badge_added_company.html +0 -33
  211. udata/templates/mail/badge_added_company.txt +0 -11
  212. udata/templates/mail/badge_added_local_authority.html +0 -33
  213. udata/templates/mail/badge_added_local_authority.txt +0 -11
  214. udata/templates/mail/badge_added_public_service.html +0 -33
  215. udata/templates/mail/badge_added_public_service.txt +0 -11
  216. udata/templates/mail/discussion_closed.html +0 -47
  217. udata/templates/mail/discussion_closed.txt +0 -16
  218. udata/templates/mail/inactive_account_deleted.html +0 -5
  219. udata/templates/mail/inactive_account_deleted.txt +0 -6
  220. udata/templates/mail/membership_refused.html +0 -20
  221. udata/templates/mail/membership_refused.txt +0 -11
  222. udata/templates/mail/membership_request.html +0 -46
  223. udata/templates/mail/membership_request.txt +0 -12
  224. udata/templates/mail/new_discussion.html +0 -44
  225. udata/templates/mail/new_discussion.txt +0 -15
  226. udata/templates/mail/new_discussion_comment.html +0 -45
  227. udata/templates/mail/new_discussion_comment.txt +0 -16
  228. udata/templates/mail/new_member.html +0 -27
  229. udata/templates/mail/new_member.txt +0 -11
  230. udata/templates/mail/new_reuse.html +0 -37
  231. udata/templates/mail/new_reuse.txt +0 -9
  232. udata/templates/mail/test.html +0 -6
  233. udata/templates/mail/test.txt +0 -6
  234. udata/templates/mail/user_mail_card.html +0 -26
  235. udata/templates/security/email/base.html +0 -105
  236. udata/templates/security/email/base.txt +0 -6
  237. udata/templates/security/email/button.html +0 -3
  238. udata/templates/security/email/change_notice.html +0 -22
  239. udata/templates/security/email/change_notice.txt +0 -8
  240. udata/templates/security/email/confirmation_instructions.html +0 -20
  241. udata/templates/security/email/confirmation_instructions.txt +0 -7
  242. udata/templates/security/email/login_instructions.html +0 -19
  243. udata/templates/security/email/login_instructions.txt +0 -7
  244. udata/templates/security/email/reset_instructions.html +0 -24
  245. udata/templates/security/email/reset_instructions.txt +0 -9
  246. udata/templates/security/email/reset_notice.html +0 -11
  247. udata/templates/security/email/reset_notice.txt +0 -4
  248. udata/templates/security/email/welcome.html +0 -24
  249. udata/templates/security/email/welcome.txt +0 -9
  250. udata/templates/security/email/welcome_existing.html +0 -32
  251. udata/templates/security/email/welcome_existing.txt +0 -14
  252. udata/terms.md +0 -6
  253. udata/tests/frontend/__init__.py +0 -23
  254. udata/tests/metrics/conftest.py +0 -15
  255. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/WHEEL +0 -0
  256. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/entry_points.txt +0 -0
  257. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/licenses/LICENSE +0 -0
  258. {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,12 @@ from udata import uris
15
15
  from udata.api import fields
16
16
  from udata.app import cache
17
17
  from udata.core import storages
18
+ from udata.core.access_type.constants import (
19
+ AccessAudienceCondition,
20
+ AccessAudienceType,
21
+ AccessType,
22
+ InspireLimitationCategory,
23
+ )
18
24
  from udata.core.badges.factories import badge_factory
19
25
  from udata.core.dataset.constants import (
20
26
  DEFAULT_LICENSE,
@@ -42,12 +48,12 @@ from udata.core.topic.factories import TopicElementDatasetFactory, TopicFactory
42
48
  from udata.core.user.factories import AdminFactory, UserFactory
43
49
  from udata.i18n import gettext as _
44
50
  from udata.models import CommunityResource, Dataset, Follow, Member, db
45
- from udata.tags import MAX_TAG_LENGTH, MIN_TAG_LENGTH
51
+ from udata.tags import TAG_MAX_LENGTH, TAG_MIN_LENGTH
46
52
  from udata.tests.features.territories import create_geozones_fixtures
47
53
  from udata.tests.helpers import assert200, assert404
48
54
  from udata.utils import faker, unique_string
49
55
 
50
- from . import APITestCase
56
+ from . import APITestCase, PytestOnlyAPITestCase
51
57
 
52
58
  SAMPLE_GEOM = {
53
59
  "type": "MultiPolygon",
@@ -67,8 +73,6 @@ def dataset_in_response(response: TestResponse, dataset: Dataset) -> bool:
67
73
 
68
74
 
69
75
  class DatasetAPITest(APITestCase):
70
- modules = []
71
-
72
76
  def test_dataset_api_list(self):
73
77
  """It should fetch a dataset list from the API"""
74
78
  datasets = [DatasetFactory() for i in range(2)]
@@ -632,19 +636,21 @@ class DatasetAPITest(APITestCase):
632
636
  dataset = Dataset.objects.first()
633
637
  self.assertEqual(dataset.tags, sorted(data["tags"]))
634
638
 
639
+ @pytest.mark.options(TAG_MIN_LENGTH=3, TAG_MAX_LENGTH=10)
635
640
  def test_dataset_api_fail_to_create_too_short_tags(self):
636
641
  """It should fail to create a dataset from the API because
637
642
  the tag is too short"""
638
643
  data = DatasetFactory.as_dict()
639
- data["tags"] = [unique_string(MIN_TAG_LENGTH - 1)]
644
+ data["tags"] = [unique_string(TAG_MIN_LENGTH - 1)]
640
645
  with self.api_user():
641
646
  response = self.post(url_for("api.datasets"), data)
642
647
  self.assertStatus(response, 400)
643
648
 
649
+ @pytest.mark.options(TAG_MIN_LENGTH=3, TAG_MAX_LENGTH=10)
644
650
  def test_dataset_api_fail_to_create_too_long_tags(self):
645
651
  """Should fail creating a dataset with a tag long"""
646
652
  data = DatasetFactory.as_dict()
647
- data["tags"] = [unique_string(MAX_TAG_LENGTH + 1)]
653
+ data["tags"] = [unique_string(TAG_MAX_LENGTH + 1)]
648
654
  with self.api_user():
649
655
  response = self.post(url_for("api.datasets"), data)
650
656
  self.assertStatus(response, 400)
@@ -741,6 +747,18 @@ class DatasetAPITest(APITestCase):
741
747
  self.assertEqual(Dataset.objects.count(), 1)
742
748
  self.assertEqual(Dataset.objects.first().description, "new description")
743
749
 
750
+ def test_dataset_api_update_with_null_frequency(self):
751
+ """It should update the item even though internal frequency is null"""
752
+ user = self.login()
753
+ dataset = DatasetFactory(owner=user, frequency=None)
754
+ data = dataset.to_dict()
755
+ # Update doesn't matter as long as we don't touch `frequency`
756
+ data["tags"] = ["test"]
757
+ response = self.put(url_for("api.dataset", dataset=dataset), data)
758
+ self.assert200(response)
759
+ self.assertEqual(Dataset.objects.count(), 1)
760
+ self.assertEqual(Dataset.objects.first().frequency, None)
761
+
744
762
  def test_dataset_api_update_valid_frequency(self):
745
763
  """It should update a dataset from the API"""
746
764
  user = self.login()
@@ -1321,6 +1339,107 @@ class DatasetAPITest(APITestCase):
1321
1339
  assert dataset.resources[0].title == "updated 2"
1322
1340
  assert dataset.resources[0].schema is None
1323
1341
 
1342
+ def test_add_access_type(self):
1343
+ self.login(AdminFactory())
1344
+ dataset = DatasetFactory()
1345
+ assert dataset.access_type == AccessType.OPEN
1346
+
1347
+ response = self.get(url_for("api.dataset", dataset=dataset))
1348
+
1349
+ self.assert200(response)
1350
+ assert response.json["access_type"] == AccessType.OPEN
1351
+
1352
+ response = self.put(
1353
+ url_for("api.dataset", dataset=dataset),
1354
+ {
1355
+ "access_type": AccessType.RESTRICTED,
1356
+ "access_audiences": [
1357
+ {
1358
+ "role": AccessAudienceType.ADMINISTRATION,
1359
+ "condition": AccessAudienceCondition.YES,
1360
+ },
1361
+ {"role": AccessAudienceType.COMPANY, "condition": AccessAudienceCondition.NO},
1362
+ {
1363
+ "role": AccessAudienceType.PRIVATE,
1364
+ "condition": AccessAudienceCondition.UNDER_CONDITIONS,
1365
+ },
1366
+ ],
1367
+ "authorization_request_url": "https://example.org",
1368
+ "access_type_reason_category": InspireLimitationCategory.INTERNATIONAL_RELATIONS,
1369
+ "access_type_reason": "Les données contiennent des information sensibles ou liées au secret défense",
1370
+ },
1371
+ )
1372
+
1373
+ self.assert200(response)
1374
+ assert response.json["access_type"] == AccessType.RESTRICTED
1375
+ assert (
1376
+ response.json["access_type_reason_category"]
1377
+ == InspireLimitationCategory.INTERNATIONAL_RELATIONS
1378
+ )
1379
+ assert (
1380
+ response.json["access_type_reason"]
1381
+ == "Les données contiennent des information sensibles ou liées au secret défense"
1382
+ )
1383
+ assert response.json["access_audiences"][0]["role"] == AccessAudienceType.ADMINISTRATION
1384
+ assert response.json["access_audiences"][0]["condition"] == AccessAudienceCondition.YES
1385
+ assert "id" not in response.json["access_audiences"][0]
1386
+
1387
+ dataset.reload()
1388
+ assert dataset.access_type == AccessType.RESTRICTED
1389
+ assert dataset.access_audiences[0].role == AccessAudienceType.ADMINISTRATION
1390
+ assert dataset.access_audiences[0].condition == AccessAudienceCondition.YES
1391
+ assert dataset.access_audiences[1].role == AccessAudienceType.COMPANY
1392
+ assert dataset.access_audiences[1].condition == AccessAudienceCondition.NO
1393
+ assert dataset.access_audiences[2].role == AccessAudienceType.PRIVATE
1394
+ assert dataset.access_audiences[2].condition == AccessAudienceCondition.UNDER_CONDITIONS
1395
+ assert dataset.authorization_request_url == "https://example.org"
1396
+ assert (
1397
+ dataset.access_type_reason_category == InspireLimitationCategory.INTERNATIONAL_RELATIONS
1398
+ )
1399
+ assert (
1400
+ dataset.access_type_reason
1401
+ == "Les données contiennent des information sensibles ou liées au secret défense"
1402
+ )
1403
+
1404
+ def test_cannot_duplicate_access_audiences(self):
1405
+ self.login(AdminFactory())
1406
+ dataset = DatasetFactory()
1407
+
1408
+ response = self.put(
1409
+ url_for("api.dataset", dataset=dataset),
1410
+ {
1411
+ "access_type": AccessType.RESTRICTED,
1412
+ "access_audiences": [
1413
+ {
1414
+ "role": AccessAudienceType.ADMINISTRATION,
1415
+ "condition": AccessAudienceCondition.YES,
1416
+ },
1417
+ {
1418
+ "role": AccessAudienceType.ADMINISTRATION,
1419
+ "condition": AccessAudienceCondition.YES,
1420
+ },
1421
+ ],
1422
+ },
1423
+ )
1424
+
1425
+ self.assert400(response)
1426
+
1427
+ def test_reset_license_on_restricted(self):
1428
+ self.login(AdminFactory())
1429
+ dataset = DatasetFactory(license=LicenseFactory(id="cc-by"))
1430
+
1431
+ response = self.put(
1432
+ url_for("api.dataset", dataset=dataset),
1433
+ {
1434
+ "access_type": AccessType.RESTRICTED,
1435
+ },
1436
+ )
1437
+
1438
+ self.assert200(response)
1439
+
1440
+ dataset.reload()
1441
+ assert dataset.license is None
1442
+
1324
1443
 
1325
1444
  class DatasetsFeedAPItest(APITestCase):
1326
1445
  @pytest.mark.options(DELAY_BEFORE_APPEARING_IN_RSS_FEED=10)
@@ -1411,7 +1530,7 @@ class DatasetsFeedAPItest(APITestCase):
1411
1530
  assert "<h1>" in response.text
1412
1531
  assert "<ul>" in response.text
1413
1532
 
1414
- @pytest.mark.options(DELAY_BEFORE_APPEARING_IN_RSS_FEED=0)
1533
+ @pytest.mark.options(DELAY_BEFORE_APPEARING_IN_RSS_FEED=0, CDATA_BASE_URL="http://example.org")
1415
1534
  def test_feed_id_uri_is_valid(self):
1416
1535
  DatasetFactory()
1417
1536
 
@@ -1491,8 +1610,6 @@ class DatasetBadgeAPITest(APITestCase):
1491
1610
 
1492
1611
 
1493
1612
  class DatasetResourceAPITest(APITestCase):
1494
- modules = None
1495
-
1496
1613
  def setUp(self):
1497
1614
  self.login()
1498
1615
  self.dataset = DatasetFactory(owner=self.user)
@@ -1941,35 +2058,25 @@ class DatasetResourceAPITest(APITestCase):
1941
2058
  self.assertEqual(Follow.objects.following(user).count(), 0)
1942
2059
  self.assertEqual(Follow.objects.followers(user).count(), 0)
1943
2060
 
2061
+ @pytest.mark.options(ALLOWED_RESOURCES_EXTENSIONS=["txt", "html", "kml", "kml-1", "qml", "xml"])
1944
2062
  def test_suggest_formats_api(self):
1945
- """It should suggest formats"""
1946
- DatasetFactory(
1947
- resources=[
1948
- ResourceFactory(format=f) for f in (faker.word(), faker.word(), "kml", "kml-1")
1949
- ]
1950
- )
1951
-
1952
- response = self.get(url_for("api.suggest_formats"), qs={"q": "km", "size": "5"})
2063
+ response = self.get(url_for("api.suggest_formats", q="km", size=5))
1953
2064
  self.assert200(response)
1954
2065
 
1955
- self.assertLessEqual(len(response.json), 5)
1956
- self.assertGreater(len(response.json), 1)
1957
-
1958
- for suggestion in response.json:
1959
- self.assertIn("text", suggestion)
1960
- self.assertIn("km", suggestion["text"])
2066
+ self.assertEqual(len(response.json), 2)
2067
+ self.assertEqual(response.json[0]["text"], "kml")
2068
+ self.assertEqual(response.json[1]["text"], "kml-1")
1961
2069
 
2070
+ @pytest.mark.options(ALLOWED_RESOURCES_EXTENSIONS=["txt", "html", "kml", "kml-1", "qml", "xml"])
1962
2071
  def test_suggest_format_api_no_match(self):
1963
- """It should not provide format suggestion if no match"""
1964
- DatasetFactory(resources=[ResourceFactory(format=faker.word()) for _ in range(3)])
1965
-
1966
- response = self.get(url_for("api.suggest_formats"), qs={"q": "test", "size": "5"})
2072
+ response = self.get(url_for("api.suggest_formats", q="test", size=5))
1967
2073
  self.assert200(response)
1968
2074
  self.assertEqual(len(response.json), 0)
1969
2075
 
2076
+ @pytest.mark.options(ALLOWED_RESOURCES_EXTENSIONS=[])
1970
2077
  def test_suggest_format_api_empty(self):
1971
2078
  """It should not provide format suggestion if no data"""
1972
- response = self.get(url_for("api.suggest_formats"), qs={"q": "test", "size": "5"})
2079
+ response = self.get(url_for("api.suggest_formats", q="txt", size=5))
1973
2080
  self.assert200(response)
1974
2081
  self.assertEqual(len(response.json), 0)
1975
2082
 
@@ -1987,7 +2094,7 @@ class DatasetResourceAPITest(APITestCase):
1987
2094
  ]
1988
2095
  )
1989
2096
 
1990
- response = self.get(url_for("api.suggest_mime"), qs={"q": "js", "size": "5"})
2097
+ response = self.get(url_for("api.suggest_mime", q="js", size=5))
1991
2098
  self.assert200(response)
1992
2099
  self.assertLessEqual(len(response.json), 5)
1993
2100
 
@@ -1998,7 +2105,7 @@ class DatasetResourceAPITest(APITestCase):
1998
2105
  """It should suggest mime types"""
1999
2106
  DatasetFactory(resources=[ResourceFactory(mime="application/xhtml+xml")])
2000
2107
 
2001
- response = self.get(url_for("api.suggest_mime"), qs={"q": "xml", "size": "5"})
2108
+ response = self.get(url_for("api.suggest_mime", q="xml", size=5))
2002
2109
  self.assert200(response)
2003
2110
 
2004
2111
  self.assertEqual(len(response.json), 5)
@@ -2007,13 +2114,13 @@ class DatasetResourceAPITest(APITestCase):
2007
2114
  """It should not provide format suggestion if no match"""
2008
2115
  DatasetFactory(resources=[ResourceFactory(mime=faker.word()) for _ in range(3)])
2009
2116
 
2010
- response = self.get(url_for("api.suggest_mime"), qs={"q": "test", "size": "5"})
2117
+ response = self.get(url_for("api.suggest_mime", q="test", size=5))
2011
2118
  self.assert200(response)
2012
2119
  self.assertEqual(len(response.json), 0)
2013
2120
 
2014
2121
  def test_suggest_mime_api_empty(self):
2015
2122
  """It should not provide mime suggestion if no data"""
2016
- response = self.get(url_for("api.suggest_mime"), qs={"q": "test", "size": "5"})
2123
+ response = self.get(url_for("api.suggest_mime", q="test", size=5))
2017
2124
  self.assert200(response)
2018
2125
  self.assertEqual(len(response.json), 0)
2019
2126
 
@@ -2029,7 +2136,7 @@ class DatasetResourceAPITest(APITestCase):
2029
2136
  title="title-test-4", visible=True, metrics={"followers": 10}
2030
2137
  )
2031
2138
 
2032
- response = self.get(url_for("api.suggest_datasets"), qs={"q": "title-test", "size": "5"})
2139
+ response = self.get(url_for("api.suggest_datasets", q="title-test", size=5))
2033
2140
  self.assert200(response)
2034
2141
 
2035
2142
  self.assertLessEqual(len(response.json), 5)
@@ -2052,7 +2159,7 @@ class DatasetResourceAPITest(APITestCase):
2052
2159
  visible=True,
2053
2160
  )
2054
2161
 
2055
- response = self.get(url_for("api.suggest_datasets"), qs={"q": "acronym-test", "size": "5"})
2162
+ response = self.get(url_for("api.suggest_datasets", q="acronym-test", size=5))
2056
2163
  self.assert200(response)
2057
2164
 
2058
2165
  self.assertLessEqual(len(response.json), 5)
@@ -2074,7 +2181,7 @@ class DatasetResourceAPITest(APITestCase):
2074
2181
  resources=[ResourceFactory()],
2075
2182
  )
2076
2183
 
2077
- response = self.get(url_for("api.suggest_datasets"), qs={"q": "title-testé", "size": "5"})
2184
+ response = self.get(url_for("api.suggest_datasets", q="title-testé", size=5))
2078
2185
  self.assert200(response)
2079
2186
 
2080
2187
  self.assertLessEqual(len(response.json), 5)
@@ -2092,13 +2199,13 @@ class DatasetResourceAPITest(APITestCase):
2092
2199
  for i in range(3):
2093
2200
  DatasetFactory(resources=[ResourceFactory()])
2094
2201
 
2095
- response = self.get(url_for("api.suggest_datasets"), qs={"q": "xxxxxx", "size": "5"})
2202
+ response = self.get(url_for("api.suggest_datasets", q="xxxxxx", size=5))
2096
2203
  self.assert200(response)
2097
2204
  self.assertEqual(len(response.json), 0)
2098
2205
 
2099
2206
  def test_suggest_datasets_api_empty(self):
2100
2207
  """It should not provide dataset suggestion if no data"""
2101
- response = self.get(url_for("api.suggest_datasets"), qs={"q": "xxxxxx", "size": "5"})
2208
+ response = self.get(url_for("api.suggest_datasets", q="xxxxxx", size=5))
2102
2209
  self.assert200(response)
2103
2210
  self.assertEqual(len(response.json), 0)
2104
2211
 
@@ -2128,8 +2235,6 @@ class DatasetReferencesAPITest(APITestCase):
2128
2235
 
2129
2236
 
2130
2237
  class DatasetArchivedAPITest(APITestCase):
2131
- modules = []
2132
-
2133
2238
  def test_dataset_api_search_archived(self):
2134
2239
  """It should search datasets from the API, excluding archived ones"""
2135
2240
  DatasetFactory(archived=None)
@@ -2148,8 +2253,6 @@ class DatasetArchivedAPITest(APITestCase):
2148
2253
 
2149
2254
 
2150
2255
  class CommunityResourceAPITest(APITestCase):
2151
- modules = []
2152
-
2153
2256
  def test_community_resource_api_get(self):
2154
2257
  """It should fetch a community resource from the API"""
2155
2258
  community_resource = CommunityResourceFactory()
@@ -2403,10 +2506,7 @@ class ResourcesTypesAPITest(APITestCase):
2403
2506
  self.assertEqual(len(response.json), len(RESOURCE_TYPES))
2404
2507
 
2405
2508
 
2406
- @pytest.mark.usefixtures("clean_db")
2407
- class DatasetSchemasAPITest:
2408
- modules = []
2409
-
2509
+ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
2410
2510
  def test_dataset_schemas_api_list(self, api, rmock, app):
2411
2511
  # Can't use @pytest.mark.options otherwise a request will be
2412
2512
  # made before setting up rmock at module load, resulting in a 404
@@ -2467,10 +2567,7 @@ class DatasetSchemasAPITest:
2467
2567
  )
2468
2568
 
2469
2569
 
2470
- @pytest.mark.usefixtures("clean_db")
2471
- class HarvestMetadataAPITest:
2472
- modules = []
2473
-
2570
+ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
2474
2571
  def test_dataset_with_harvest_metadata(self, api):
2475
2572
  date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
2476
2573
  harvest_metadata = HarvestDatasetMetadata(
@@ -18,8 +18,6 @@ from . import APITestCase
18
18
 
19
19
 
20
20
  class MeAPITest(APITestCase):
21
- modules = []
22
-
23
21
  def test_get_profile(self):
24
22
  """It should fetch my user data on GET"""
25
23
  self.login()
@@ -105,7 +103,7 @@ class MeAPITest(APITestCase):
105
103
  DatasetFactory(owner=user)
106
104
  DatasetFactory(organization=organization)
107
105
 
108
- response = self.get(url_for("api.my_org_datasets"), qs={"q": "foô"})
106
+ response = self.get(url_for("api.my_org_datasets", q="foô"))
109
107
  self.assert200(response)
110
108
  self.assertEqual(len(response.json), len(datasets) + len(org_datasets))
111
109
 
@@ -139,7 +137,7 @@ class MeAPITest(APITestCase):
139
137
  CommunityResourceFactory(owner=user)
140
138
  CommunityResourceFactory(organization=organization)
141
139
 
142
- response = self.get(url_for("api.my_org_community_resources"), qs={"q": "foô"})
140
+ response = self.get(url_for("api.my_org_community_resources", q="foô"))
143
141
  self.assert200(response)
144
142
  self.assertEqual(
145
143
  len(response.json), len(community_resources) + len(org_community_resources)
@@ -171,7 +169,7 @@ class MeAPITest(APITestCase):
171
169
  ReuseFactory(owner=user)
172
170
  ReuseFactory(organization=organization)
173
171
 
174
- response = self.get(url_for("api.my_org_reuses"), qs={"q": "foô"})
172
+ response = self.get(url_for("api.my_org_reuses", q="foô"))
175
173
  self.assert200(response)
176
174
  self.assertEqual(len(response.json), len(reuses) + len(org_reuses))
177
175
 
@@ -221,7 +219,7 @@ class MeAPITest(APITestCase):
221
219
  Discussion.objects.create(subject=DatasetFactory(), title="foô", user=user)
222
220
  Discussion.objects.create(subject=ReuseFactory(), title="foô", user=user)
223
221
 
224
- response = self.get(url_for("api.my_org_discussions"), qs={"q": "foô"})
222
+ response = self.get(url_for("api.my_org_discussions", q="foô"))
225
223
  self.assert200(response)
226
224
  self.assertEqual(len(response.json), len(discussions))
227
225
 
@@ -16,6 +16,7 @@ from udata.core.reuse.factories import ReuseFactory
16
16
  from udata.core.user.factories import AdminFactory, UserFactory
17
17
  from udata.i18n import _
18
18
  from udata.models import Discussion, Follow, Member, MembershipRequest, Organization
19
+ from udata.tests.api import PytestOnlyAPITestCase
19
20
  from udata.tests.helpers import (
20
21
  assert200,
21
22
  assert201,
@@ -31,14 +32,8 @@ from udata.tests.helpers import (
31
32
  )
32
33
  from udata.utils import faker
33
34
 
34
- pytestmark = [
35
- pytest.mark.usefixtures("clean_db"),
36
- ]
37
-
38
-
39
- class OrganizationAPITest:
40
- modules = []
41
35
 
36
+ class OrganizationAPITest(PytestOnlyAPITestCase):
42
37
  def test_organization_api_list(self, api):
43
38
  """It should fetch an organization list from the API"""
44
39
  organizations = OrganizationFactory.create_batch(3)
@@ -235,9 +230,7 @@ class OrganizationAPITest:
235
230
  assert Organization.objects[0].deleted is None
236
231
 
237
232
 
238
- class MembershipAPITest:
239
- modules = []
240
-
233
+ class MembershipAPITest(PytestOnlyAPITestCase):
241
234
  def test_request_membership(self, api):
242
235
  organization = OrganizationFactory()
243
236
  user = api.login()
@@ -678,7 +671,7 @@ class MembershipAPITest:
678
671
  name="test-{0}".format(i) if i % 2 else faker.word(), metrics={"followers": i}
679
672
  )
680
673
  max_follower_organization = OrganizationFactory(name="test-4", metrics={"followers": 10})
681
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "tes", "size": "5"})
674
+ response = api.get(url_for("api.suggest_organizations", q="tes", size=5))
682
675
  assert200(response)
683
676
 
684
677
  assert len(response.json) <= 5
@@ -698,7 +691,7 @@ class MembershipAPITest:
698
691
  for i in range(4):
699
692
  OrganizationFactory(name="testé-{0}".format(i) if i % 2 else faker.word())
700
693
 
701
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "testé", "size": "5"})
694
+ response = api.get(url_for("api.suggest_organizations", q="testé", size=5))
702
695
  assert200(response)
703
696
 
704
697
  assert len(response.json) <= 5
@@ -716,7 +709,7 @@ class MembershipAPITest:
716
709
  for i in range(4):
717
710
  OrganizationFactory(name="mon testé-{0}".format(i) if i % 2 else faker.word())
718
711
 
719
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "mon testé", "size": "5"})
712
+ response = api.get(url_for("api.suggest_organizations", q="mon testé", size=5))
720
713
  assert200(response)
721
714
 
722
715
  assert len(response.json) <= 5
@@ -736,7 +729,7 @@ class MembershipAPITest:
736
729
  name="Ministère de l'intérieur {0}".format(i) if i % 2 else faker.word()
737
730
  )
738
731
 
739
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "Ministère", "size": "5"})
732
+ response = api.get(url_for("api.suggest_organizations", q="Ministère", size=5))
740
733
  assert200(response)
741
734
 
742
735
  assert len(response.json) <= 5
@@ -753,13 +746,13 @@ class MembershipAPITest:
753
746
  """It should not provide organization suggestion if no match"""
754
747
  OrganizationFactory.create_batch(3)
755
748
 
756
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "xxxxxx", "size": "5"})
749
+ response = api.get(url_for("api.suggest_organizations", q="xxxxxx", size=5))
757
750
  assert200(response)
758
751
  assert len(response.json) == 0
759
752
 
760
753
  def test_suggest_organizations_api_empty(self, api):
761
754
  """It should not provide organization suggestion if no data"""
762
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "xxxxxx", "size": "5"})
755
+ response = api.get(url_for("api.suggest_organizations", q="xxxxxx", size=5))
763
756
  assert200(response)
764
757
  assert len(response.json) == 0
765
758
 
@@ -767,7 +760,7 @@ class MembershipAPITest:
767
760
  """It should suggest organizations and not deduplicate homonyms"""
768
761
  OrganizationFactory.create_batch(2, name="homonym")
769
762
 
770
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "homonym", "size": "5"})
763
+ response = api.get(url_for("api.suggest_organizations", q="homonym", size=5))
771
764
  assert200(response)
772
765
 
773
766
  assert len(response.json) == 2
@@ -787,7 +780,7 @@ class MembershipAPITest:
787
780
  max_follower_organization = OrganizationFactory(
788
781
  name=faker.word(), acronym="UDATA4", metrics={"followers": 10}
789
782
  )
790
- response = api.get(url_for("api.suggest_organizations"), qs={"q": "uDaTa", "size": "5"})
783
+ response = api.get(url_for("api.suggest_organizations", q="uDaTa", size=5))
791
784
  assert200(response)
792
785
 
793
786
  assert len(response.json) == 2
@@ -802,9 +795,7 @@ class MembershipAPITest:
802
795
  assert response.json[0]["id"] == str(max_follower_organization.id)
803
796
 
804
797
 
805
- class OrganizationDatasetsAPITest:
806
- modules = []
807
-
798
+ class OrganizationDatasetsAPITest(PytestOnlyAPITestCase):
808
799
  def test_list_org_datasets(self, api):
809
800
  """Should list organization datasets"""
810
801
  org = OrganizationFactory()
@@ -843,15 +834,13 @@ class OrganizationDatasetsAPITest:
843
834
  org = OrganizationFactory()
844
835
  DatasetFactory.create_batch(3, organization=org)
845
836
 
846
- response = api.get(url_for("api.org_datasets", org=org), qs={"page_size": 2})
837
+ response = api.get(url_for("api.org_datasets", org=org, page_size=2))
847
838
 
848
839
  assert200(response)
849
840
  assert len(response.json["data"]) == 2
850
841
 
851
842
 
852
- class OrganizationReusesAPITest:
853
- modules = []
854
-
843
+ class OrganizationReusesAPITest(PytestOnlyAPITestCase):
855
844
  def test_list_org_reuses(self, api):
856
845
  """Should list organization reuses"""
857
846
  org = OrganizationFactory()
@@ -886,9 +875,7 @@ class OrganizationReusesAPITest:
886
875
  assert len(response.json) == len(reuses)
887
876
 
888
877
 
889
- class OrganizationDiscussionsAPITest:
890
- modules = []
891
-
878
+ class OrganizationDiscussionsAPITest(PytestOnlyAPITestCase):
892
879
  def test_list_org_discussions(self, api):
893
880
  """Should list organization discussions"""
894
881
  user = UserFactory()
@@ -910,11 +897,9 @@ class OrganizationDiscussionsAPITest:
910
897
  assert discussion["id"] in discussions_ids
911
898
 
912
899
 
913
- class OrganizationBadgeAPITest:
914
- modules = []
915
-
900
+ class OrganizationBadgeAPITest(PytestOnlyAPITestCase):
916
901
  @pytest.fixture(autouse=True)
917
- def setUp(self, api, clean_db):
902
+ def setup_func(self, api):
918
903
  self.factory = badge_factory(Organization)
919
904
  self.user = api.login(AdminFactory())
920
905
  self.organization = OrganizationFactory()
@@ -978,9 +963,7 @@ class OrganizationBadgeAPITest:
978
963
  assert404(response)
979
964
 
980
965
 
981
- class OrganizationContactPointsAPITest:
982
- modules = []
983
-
966
+ class OrganizationContactPointsAPITest(PytestOnlyAPITestCase):
984
967
  def test_org_contact_points(self, api):
985
968
  user = api.login()
986
969
  member = Member(user=user, role="admin")
@@ -1027,9 +1010,7 @@ class OrganizationContactPointsAPITest:
1027
1010
  assert len(response.json) == 0
1028
1011
 
1029
1012
 
1030
- class OrganizationCsvExportsTest:
1031
- modules = []
1032
-
1013
+ class OrganizationCsvExportsTest(PytestOnlyAPITestCase):
1033
1014
  def test_datasets_csv(self, api):
1034
1015
  org = OrganizationFactory()
1035
1016
  [DatasetFactory(organization=org, resources=[ResourceFactory()]) for _ in range(3)]
@@ -15,8 +15,6 @@ from . import APITestCase
15
15
 
16
16
 
17
17
  class ReportsReasonsAPITest(APITestCase):
18
- modules = []
19
-
20
18
  def test_reports_reasons_api(self):
21
19
  response = self.get(url_for("api.reports_reasons"))
22
20
  self.assert200(response)
@@ -24,8 +22,6 @@ class ReportsReasonsAPITest(APITestCase):
24
22
 
25
23
 
26
24
  class ReportsAPITest(APITestCase):
27
- modules = []
28
-
29
25
  def test_reports_api_create(self):
30
26
  user = UserFactory()
31
27