udata 13.0.1.dev12__py3-none-any.whl → 14.4.1.dev7__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 (177) hide show
  1. udata/api/__init__.py +2 -8
  2. udata/api_fields.py +35 -4
  3. udata/app.py +30 -50
  4. udata/auth/__init__.py +29 -6
  5. udata/auth/forms.py +8 -6
  6. udata/auth/views.py +6 -3
  7. udata/commands/__init__.py +2 -14
  8. udata/commands/db.py +13 -25
  9. udata/commands/info.py +0 -16
  10. udata/commands/serve.py +3 -11
  11. udata/commands/tests/test_fixtures.py +9 -9
  12. udata/core/access_type/api.py +1 -1
  13. udata/core/access_type/constants.py +12 -8
  14. udata/core/activity/api.py +5 -6
  15. udata/core/avatars/api.py +43 -0
  16. udata/core/avatars/test_avatar_api.py +30 -0
  17. udata/core/badges/tests/test_commands.py +6 -6
  18. udata/core/csv.py +5 -0
  19. udata/core/dataservices/models.py +15 -3
  20. udata/core/dataservices/tasks.py +7 -0
  21. udata/core/dataset/api.py +2 -0
  22. udata/core/dataset/models.py +2 -2
  23. udata/core/dataset/permissions.py +31 -0
  24. udata/core/dataset/tasks.py +50 -10
  25. udata/core/discussions/models.py +1 -0
  26. udata/core/metrics/__init__.py +0 -6
  27. udata/core/organization/api.py +8 -5
  28. udata/core/organization/mails.py +1 -1
  29. udata/core/organization/models.py +9 -1
  30. udata/core/organization/notifications.py +84 -0
  31. udata/core/organization/permissions.py +1 -1
  32. udata/core/organization/tasks.py +3 -0
  33. udata/core/pages/tests/test_api.py +32 -0
  34. udata/core/post/api.py +24 -69
  35. udata/core/post/models.py +84 -16
  36. udata/core/post/tests/test_api.py +24 -1
  37. udata/core/reports/api.py +18 -0
  38. udata/core/reports/models.py +42 -2
  39. udata/core/reuse/models.py +1 -1
  40. udata/core/reuse/tasks.py +7 -0
  41. udata/core/site/models.py +2 -6
  42. udata/core/spatial/commands.py +2 -4
  43. udata/core/spatial/forms.py +2 -2
  44. udata/core/spatial/models.py +0 -10
  45. udata/core/spatial/tests/test_api.py +1 -36
  46. udata/core/user/models.py +15 -2
  47. udata/cors.py +2 -5
  48. udata/db/migrations.py +279 -0
  49. udata/features/notifications/api.py +7 -18
  50. udata/features/notifications/models.py +56 -0
  51. udata/features/notifications/tasks.py +25 -0
  52. udata/flask_mongoengine/engine.py +0 -4
  53. udata/frontend/__init__.py +3 -122
  54. udata/frontend/markdown.py +2 -1
  55. udata/harvest/actions.py +24 -9
  56. udata/harvest/api.py +30 -22
  57. udata/harvest/backends/__init__.py +21 -9
  58. udata/harvest/backends/base.py +29 -3
  59. udata/harvest/backends/ckan/harvesters.py +13 -2
  60. udata/harvest/backends/dcat.py +3 -0
  61. udata/harvest/backends/maaf.py +1 -0
  62. udata/harvest/commands.py +39 -4
  63. udata/harvest/filters.py +17 -6
  64. udata/harvest/forms.py +9 -6
  65. udata/harvest/models.py +16 -0
  66. udata/harvest/permissions.py +27 -0
  67. udata/harvest/tasks.py +3 -5
  68. udata/harvest/tests/ckan/test_ckan_backend.py +35 -2
  69. udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
  70. udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
  71. udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
  72. udata/harvest/tests/dcat/udata.xml +6 -6
  73. udata/harvest/tests/factories.py +1 -1
  74. udata/harvest/tests/test_actions.py +63 -8
  75. udata/harvest/tests/test_api.py +278 -123
  76. udata/harvest/tests/test_base_backend.py +88 -1
  77. udata/harvest/tests/test_dcat_backend.py +60 -13
  78. udata/harvest/tests/test_filters.py +6 -0
  79. udata/i18n.py +11 -273
  80. udata/mail.py +5 -1
  81. udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
  82. udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
  83. udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
  84. udata/models/__init__.py +0 -8
  85. udata/mongo/slug_fields.py +1 -1
  86. udata/rdf.py +45 -6
  87. udata/routing.py +2 -10
  88. udata/sentry.py +4 -10
  89. udata/settings.py +23 -17
  90. udata/tasks.py +4 -3
  91. udata/templates/mail/message.html +5 -31
  92. udata/tests/__init__.py +28 -12
  93. udata/tests/api/__init__.py +108 -21
  94. udata/tests/api/test_activities_api.py +36 -0
  95. udata/tests/api/test_auth_api.py +121 -95
  96. udata/tests/api/test_base_api.py +7 -4
  97. udata/tests/api/test_dataservices_api.py +29 -1
  98. udata/tests/api/test_datasets_api.py +45 -21
  99. udata/tests/api/test_organizations_api.py +192 -197
  100. udata/tests/api/test_reports_api.py +157 -0
  101. udata/tests/api/test_reuses_api.py +147 -147
  102. udata/tests/api/test_security_api.py +12 -12
  103. udata/tests/api/test_swagger.py +4 -4
  104. udata/tests/api/test_tags_api.py +8 -8
  105. udata/tests/api/test_user_api.py +13 -1
  106. udata/tests/apiv2/test_swagger.py +4 -4
  107. udata/tests/apiv2/test_topics.py +1 -1
  108. udata/tests/cli/test_cli_base.py +8 -9
  109. udata/tests/dataset/test_dataset_commands.py +4 -4
  110. udata/tests/dataset/test_dataset_model.py +66 -26
  111. udata/tests/dataset/test_dataset_rdf.py +99 -5
  112. udata/tests/dataset/test_resource_preview.py +0 -1
  113. udata/tests/frontend/test_auth.py +24 -1
  114. udata/tests/frontend/test_csv.py +0 -3
  115. udata/tests/helpers.py +37 -27
  116. udata/tests/organization/test_notifications.py +67 -2
  117. udata/tests/plugin.py +6 -261
  118. udata/tests/site/test_site_csv_exports.py +22 -10
  119. udata/tests/test_activity.py +9 -9
  120. udata/tests/test_cors.py +1 -1
  121. udata/tests/test_dcat_commands.py +2 -2
  122. udata/tests/test_discussions.py +5 -5
  123. udata/tests/test_migrations.py +181 -481
  124. udata/tests/test_notifications.py +15 -57
  125. udata/tests/test_notifications_task.py +43 -0
  126. udata/tests/test_owned.py +81 -1
  127. udata/tests/test_storages.py +25 -19
  128. udata/tests/test_topics.py +77 -61
  129. udata/tests/test_uris.py +33 -0
  130. udata/tests/workers/test_jobs_commands.py +23 -23
  131. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  132. udata/translations/ar/LC_MESSAGES/udata.po +187 -108
  133. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  134. udata/translations/de/LC_MESSAGES/udata.po +187 -108
  135. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  136. udata/translations/es/LC_MESSAGES/udata.po +187 -108
  137. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  138. udata/translations/fr/LC_MESSAGES/udata.po +188 -109
  139. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  140. udata/translations/it/LC_MESSAGES/udata.po +187 -108
  141. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  142. udata/translations/pt/LC_MESSAGES/udata.po +187 -108
  143. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  144. udata/translations/sr/LC_MESSAGES/udata.po +187 -108
  145. udata/translations/udata.pot +215 -106
  146. udata/uris.py +0 -2
  147. udata/utils.py +5 -0
  148. udata-14.4.1.dev7.dist-info/METADATA +109 -0
  149. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +153 -166
  150. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +3 -5
  151. udata/core/followers/views.py +0 -15
  152. udata/core/post/forms.py +0 -30
  153. udata/entrypoints.py +0 -93
  154. udata/features/identicon/__init__.py +0 -0
  155. udata/features/identicon/api.py +0 -13
  156. udata/features/identicon/backends.py +0 -131
  157. udata/features/identicon/tests/__init__.py +0 -0
  158. udata/features/identicon/tests/test_backends.py +0 -18
  159. udata/features/territories/__init__.py +0 -49
  160. udata/features/territories/api.py +0 -25
  161. udata/features/territories/models.py +0 -51
  162. udata/flask_mongoengine/json.py +0 -38
  163. udata/migrations/__init__.py +0 -367
  164. udata/templates/mail/base.html +0 -105
  165. udata/templates/mail/base.txt +0 -6
  166. udata/templates/mail/button.html +0 -3
  167. udata/templates/mail/layouts/1-column.html +0 -19
  168. udata/templates/mail/layouts/2-columns.html +0 -20
  169. udata/templates/mail/layouts/center-panel.html +0 -16
  170. udata/tests/cli/test_db_cli.py +0 -68
  171. udata/tests/features/territories/__init__.py +0 -20
  172. udata/tests/features/territories/test_territories_api.py +0 -185
  173. udata/tests/frontend/test_hooks.py +0 -149
  174. udata-13.0.1.dev12.dist-info/METADATA +0 -133
  175. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/WHEEL +0 -0
  176. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
  177. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from datetime import datetime
2
+
1
3
  from flask import url_for
2
4
 
3
5
  from udata.core.dataset.factories import DatasetFactory
@@ -148,6 +150,28 @@ class ReportsAPITest(APITestCase):
148
150
 
149
151
  self.assertEqual(payload["data"][1]["subject"]["id"], str(spam_reuse.id))
150
152
 
153
+ def test_reports_api_list_sort_by_reported_at(self):
154
+ user = UserFactory()
155
+
156
+ dataset1 = DatasetFactory.create(owner=user)
157
+ dataset2 = DatasetFactory.create(owner=user)
158
+ dataset3 = DatasetFactory.create(owner=user)
159
+
160
+ # Create reports with different reported_at times
161
+ report1 = Report(subject=dataset1, reason="spam", reported_at=datetime(2024, 1, 1)).save()
162
+ report2 = Report(subject=dataset2, reason="spam", reported_at=datetime(2024, 1, 3)).save()
163
+ report3 = Report(subject=dataset3, reason="spam", reported_at=datetime(2024, 1, 2)).save()
164
+
165
+ self.login(AdminFactory())
166
+
167
+ # Sort by -reported_at (most recent first)
168
+ response = self.get(url_for("api.reports", sort="-reported_at"))
169
+ self.assert200(response)
170
+ payload = response.json
171
+ self.assertEqual(payload["data"][0]["id"], str(report2.id))
172
+ self.assertEqual(payload["data"][1]["id"], str(report3.id))
173
+ self.assertEqual(payload["data"][2]["id"], str(report1.id))
174
+
151
175
  def test_reports_api_get(self):
152
176
  user = UserFactory()
153
177
 
@@ -165,3 +189,136 @@ class ReportsAPITest(APITestCase):
165
189
 
166
190
  payload = response.json
167
191
  self.assertEqual(payload["subject"]["id"], str(spam_dataset.id))
192
+
193
+ def test_reports_api_dismiss(self):
194
+ user = UserFactory()
195
+ admin = AdminFactory()
196
+
197
+ spam_dataset = DatasetFactory.create(owner=user)
198
+ report = Report(subject=spam_dataset, reason="spam").save()
199
+
200
+ dismiss_time = datetime.utcnow().isoformat()
201
+
202
+ # Should require admin
203
+ response = self.patch(url_for("api.report", report=report), {"dismissed_at": dismiss_time})
204
+ self.assert401(response)
205
+
206
+ self.login(user)
207
+ response = self.patch(url_for("api.report", report=report), {"dismissed_at": dismiss_time})
208
+ self.assert403(response)
209
+
210
+ self.login(admin)
211
+ response = self.patch(url_for("api.report", report=report), {"dismissed_at": dismiss_time})
212
+ self.assert200(response)
213
+
214
+ payload = response.json
215
+ self.assertIsNotNone(payload["dismissed_at"])
216
+ self.assertEqual(payload["dismissed_by"]["id"], str(admin.id))
217
+
218
+ report.reload()
219
+ self.assertIsNotNone(report.dismissed_at)
220
+ self.assertEqual(report.dismissed_by.id, admin.id)
221
+
222
+ def test_reports_api_undismiss(self):
223
+ user = UserFactory()
224
+ admin = AdminFactory()
225
+
226
+ spam_dataset = DatasetFactory.create(owner=user)
227
+ report = Report(
228
+ subject=spam_dataset,
229
+ reason="spam",
230
+ dismissed_at=datetime.utcnow(),
231
+ dismissed_by=admin,
232
+ ).save()
233
+
234
+ # Verify report is dismissed
235
+ self.assertIsNotNone(report.dismissed_at)
236
+ self.assertIsNotNone(report.dismissed_by)
237
+
238
+ # Should require admin
239
+ response = self.patch(url_for("api.report", report=report), {"dismissed_at": None})
240
+ self.assert401(response)
241
+
242
+ self.login(user)
243
+ response = self.patch(url_for("api.report", report=report), {"dismissed_at": None})
244
+ self.assert403(response)
245
+
246
+ self.login(admin)
247
+ response = self.patch(url_for("api.report", report=report), {"dismissed_at": None})
248
+ self.assert200(response)
249
+
250
+ payload = response.json
251
+ self.assertIsNone(payload["dismissed_at"])
252
+ self.assertIsNone(payload["dismissed_by"])
253
+
254
+ report.reload()
255
+ self.assertIsNone(report.dismissed_at)
256
+ self.assertIsNone(report.dismissed_by)
257
+
258
+ def test_reports_api_filter_by_handled(self):
259
+ user = UserFactory()
260
+ admin = AdminFactory()
261
+
262
+ dataset1 = DatasetFactory.create(owner=user)
263
+ dataset2 = DatasetFactory.create(owner=user)
264
+
265
+ # Unhandled report (not dismissed)
266
+ ongoing_report = Report(subject=dataset1, reason="spam").save()
267
+
268
+ # Handled report (dismissed)
269
+ dismissed_report = Report(
270
+ subject=dataset2, reason="spam", dismissed_at=datetime.utcnow(), dismissed_by=admin
271
+ ).save()
272
+
273
+ self.login(admin)
274
+
275
+ # Filter by unhandled
276
+ response = self.get(url_for("api.reports", handled="false"))
277
+ self.assert200(response)
278
+ payload = response.json
279
+ self.assertEqual(payload["total"], 1)
280
+ self.assertEqual(payload["data"][0]["id"], str(ongoing_report.id))
281
+
282
+ # Filter by handled
283
+ response = self.get(url_for("api.reports", handled="true"))
284
+ self.assert200(response)
285
+ payload = response.json
286
+ self.assertEqual(payload["total"], 1)
287
+ self.assertEqual(payload["data"][0]["id"], str(dismissed_report.id))
288
+
289
+ # No filter (all reports)
290
+ response = self.get(url_for("api.reports"))
291
+ self.assert200(response)
292
+ payload = response.json
293
+ self.assertEqual(payload["total"], 2)
294
+
295
+ def test_reports_api_filter_handled_with_deleted_subject(self):
296
+ """Reports with deleted subjects should appear when handled="true", not handled="false"."""
297
+ user = UserFactory()
298
+ admin = AdminFactory()
299
+
300
+ dataset1 = DatasetFactory.create(owner=user)
301
+ dataset2 = DatasetFactory.create(owner=user)
302
+
303
+ # Unhandled report (not dismissed, subject exists)
304
+ ongoing_report = Report(subject=dataset1, reason="spam").save()
305
+
306
+ # Report with deleted subject (should appear in "handled", not "unhandled")
307
+ deleted_subject_report = Report(subject=dataset2, reason="spam").save()
308
+ dataset2.delete()
309
+
310
+ self.login(admin)
311
+
312
+ # Filter by unhandled - should only return the report with existing subject
313
+ response = self.get(url_for("api.reports", handled="false"))
314
+ self.assert200(response)
315
+ payload = response.json
316
+ self.assertEqual(payload["total"], 1)
317
+ self.assertEqual(payload["data"][0]["id"], str(ongoing_report.id))
318
+
319
+ # Filter by handled - should return the report with deleted subject
320
+ response = self.get(url_for("api.reports", handled="true"))
321
+ self.assert200(response)
322
+ payload = response.json
323
+ self.assertEqual(payload["total"], 1)
324
+ self.assertEqual(payload["data"][0]["id"], str(deleted_subject_report.id))