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
udata/tests/api/test_base_api.py
CHANGED
|
@@ -27,7 +27,7 @@ class FakeFormAPI(API):
|
|
|
27
27
|
class OptionsCORSTest(APITestCase):
|
|
28
28
|
def test_should_allow_options_and_cors(self):
|
|
29
29
|
"""Should allow OPTIONS operation and give cors parameters"""
|
|
30
|
-
response = self.
|
|
30
|
+
response = self.options(
|
|
31
31
|
url_for("api.fake-options"),
|
|
32
32
|
headers={
|
|
33
33
|
"Origin": "http://localhost",
|
|
@@ -38,7 +38,7 @@ class OptionsCORSTest(APITestCase):
|
|
|
38
38
|
self.assert204(response)
|
|
39
39
|
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "http://localhost")
|
|
40
40
|
|
|
41
|
-
response = self.
|
|
41
|
+
response = self.options(
|
|
42
42
|
url_for("api.fake-options"),
|
|
43
43
|
headers={
|
|
44
44
|
"Origin": "http://localhost",
|
|
@@ -55,8 +55,11 @@ class OptionsCORSTest(APITestCase):
|
|
|
55
55
|
class JSONFormRequestTest(APITestCase):
|
|
56
56
|
def test_non_json_content_type(self):
|
|
57
57
|
"""We expect JSON requests for forms and enforce it"""
|
|
58
|
-
response = self.
|
|
59
|
-
url_for("api.fake-form"),
|
|
58
|
+
response = self.post(
|
|
59
|
+
url_for("api.fake-form"),
|
|
60
|
+
{},
|
|
61
|
+
json=False,
|
|
62
|
+
headers={"Content-Type": "multipart/form-data"},
|
|
60
63
|
)
|
|
61
64
|
self.assert400(response)
|
|
62
65
|
self.assertEqual(response.json, {"errors": {"Content-Type": "expecting application/json"}})
|
|
@@ -724,6 +724,19 @@ class DatasetAPITest(APITestCase):
|
|
|
724
724
|
dataset = Dataset.objects.first()
|
|
725
725
|
self.assertEqual(dataset.spatial.geom, SAMPLE_GEOM)
|
|
726
726
|
|
|
727
|
+
def test_dataset_api_create_with_invalid_geom_coordinates(self):
|
|
728
|
+
"""It should return 400 with invalid GeoJSON coordinates, not 500"""
|
|
729
|
+
self.login()
|
|
730
|
+
data = DatasetFactory.as_dict()
|
|
731
|
+
# Invalid GeoJSON: {} in coordinates instead of numbers (Sentry issue)
|
|
732
|
+
data["spatial"] = {"geom": {"type": "Point", "coordinates": {}}}
|
|
733
|
+
response = self.post(url_for("api.datasets"), data)
|
|
734
|
+
self.assert400(response)
|
|
735
|
+
self.assertEqual(Dataset.objects.count(), 0)
|
|
736
|
+
# Verify error is properly captured in form validation errors
|
|
737
|
+
self.assertIn("errors", response.json)
|
|
738
|
+
self.assertIn("spatial", response.json["errors"])
|
|
739
|
+
|
|
727
740
|
def test_dataset_api_create_with_legacy_frequency(self):
|
|
728
741
|
"""It should create a dataset from the API with a legacy frequency"""
|
|
729
742
|
self.login()
|
|
@@ -1693,6 +1706,12 @@ class DatasetResourceAPITest(APITestCase):
|
|
|
1693
1706
|
self.dataset.reload()
|
|
1694
1707
|
self.assertEqual(len(self.dataset.resources), 2)
|
|
1695
1708
|
|
|
1709
|
+
def test_create_with_list_returns_400(self):
|
|
1710
|
+
"""It should return 400 when sending a list instead of a dict"""
|
|
1711
|
+
data = [ResourceFactory.as_dict()]
|
|
1712
|
+
response = self.post(url_for("api.resources", dataset=self.dataset), data)
|
|
1713
|
+
self.assert400(response)
|
|
1714
|
+
|
|
1696
1715
|
def test_create_with_file(self):
|
|
1697
1716
|
"""It should create a resource from the API with a file"""
|
|
1698
1717
|
user = self.login()
|
|
@@ -1834,6 +1853,18 @@ class DatasetResourceAPITest(APITestCase):
|
|
|
1834
1853
|
f"Resource ids must match existing ones in dataset, ie: {set(str(r.id) for r in self.dataset.resources)}",
|
|
1835
1854
|
)
|
|
1836
1855
|
|
|
1856
|
+
def test_invalid_reorder_dict_without_id(self):
|
|
1857
|
+
"""It should return 400 when dict in resources list has no 'id' key"""
|
|
1858
|
+
self.dataset.resources = ResourceFactory.build_batch(3)
|
|
1859
|
+
self.dataset.save()
|
|
1860
|
+
|
|
1861
|
+
# Dict without 'id' key should fail gracefully, not raise KeyError
|
|
1862
|
+
wrong_order_dict_without_id = [{"title": "foo"}, {"title": "bar"}, {"title": "baz"}]
|
|
1863
|
+
response = self.put(
|
|
1864
|
+
url_for("api.resources", dataset=self.dataset), wrong_order_dict_without_id
|
|
1865
|
+
)
|
|
1866
|
+
self.assertStatus(response, 400)
|
|
1867
|
+
|
|
1837
1868
|
def test_update_local(self):
|
|
1838
1869
|
resource = ResourceFactory()
|
|
1839
1870
|
self.dataset.resources.append(resource)
|
|
@@ -2506,13 +2537,13 @@ class ResourcesTypesAPITest(APITestCase):
|
|
|
2506
2537
|
|
|
2507
2538
|
|
|
2508
2539
|
class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
2509
|
-
def test_dataset_schemas_api_list(self,
|
|
2540
|
+
def test_dataset_schemas_api_list(self, rmock, app):
|
|
2510
2541
|
# Can't use @pytest.mark.options otherwise a request will be
|
|
2511
2542
|
# made before setting up rmock at module load, resulting in a 404
|
|
2512
2543
|
app.config["SCHEMA_CATALOG_URL"] = "https://example.com/schemas"
|
|
2513
2544
|
|
|
2514
2545
|
rmock.get("https://example.com/schemas", json=ResourceSchemaMockData.get_mock_data())
|
|
2515
|
-
response =
|
|
2546
|
+
response = self.get(url_for("api.schemas"))
|
|
2516
2547
|
|
|
2517
2548
|
assert200(response)
|
|
2518
2549
|
assert (
|
|
@@ -2520,27 +2551,27 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2520
2551
|
)
|
|
2521
2552
|
|
|
2522
2553
|
@pytest.mark.options(SCHEMA_CATALOG_URL=None)
|
|
2523
|
-
def test_dataset_schemas_api_list_no_catalog_url(self
|
|
2524
|
-
response =
|
|
2554
|
+
def test_dataset_schemas_api_list_no_catalog_url(self):
|
|
2555
|
+
response = self.get(url_for("api.schemas"))
|
|
2525
2556
|
|
|
2526
2557
|
assert200(response)
|
|
2527
2558
|
assert response.json == []
|
|
2528
2559
|
|
|
2529
2560
|
@pytest.mark.options(SCHEMA_CATALOG_URL="https://example.com/notfound")
|
|
2530
|
-
def test_dataset_schemas_api_list_not_found(self,
|
|
2561
|
+
def test_dataset_schemas_api_list_not_found(self, rmock):
|
|
2531
2562
|
rmock.get("https://example.com/notfound", status_code=404)
|
|
2532
|
-
response =
|
|
2563
|
+
response = self.get(url_for("api.schemas"))
|
|
2533
2564
|
assert404(response)
|
|
2534
2565
|
|
|
2535
2566
|
@pytest.mark.options(SCHEMA_CATALOG_URL="https://example.com/schemas")
|
|
2536
|
-
def test_dataset_schemas_api_list_error_no_cache(self,
|
|
2567
|
+
def test_dataset_schemas_api_list_error_no_cache(self, rmock):
|
|
2537
2568
|
rmock.get("https://example.com/schemas", status_code=500)
|
|
2538
2569
|
|
|
2539
|
-
response =
|
|
2570
|
+
response = self.get(url_for("api.schemas"))
|
|
2540
2571
|
assert response.status_code == 503
|
|
2541
2572
|
|
|
2542
2573
|
@pytest.mark.options(SCHEMA_CATALOG_URL="https://example.com/schemas")
|
|
2543
|
-
def test_dataset_schemas_api_list_error_w_cache(self,
|
|
2574
|
+
def test_dataset_schemas_api_list_error_w_cache(self, rmock, mocker):
|
|
2544
2575
|
cache_mock_set = mocker.patch.object(cache, "set")
|
|
2545
2576
|
mocker.patch.object(
|
|
2546
2577
|
cache, "get", return_value=ResourceSchemaMockData.get_mock_data()["schemas"]
|
|
@@ -2548,7 +2579,7 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2548
2579
|
|
|
2549
2580
|
# Fill cache
|
|
2550
2581
|
rmock.get("https://example.com/schemas", json=ResourceSchemaMockData.get_mock_data())
|
|
2551
|
-
response =
|
|
2582
|
+
response = self.get(url_for("api.schemas"))
|
|
2552
2583
|
assert200(response)
|
|
2553
2584
|
assert (
|
|
2554
2585
|
response.json == ResourceSchemaMockData.get_expected_assignable_schemas_from_mock_data()
|
|
@@ -2559,7 +2590,7 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2559
2590
|
rmock.get("https://example.com/schemas", status_code=500)
|
|
2560
2591
|
|
|
2561
2592
|
# Long term cache is used
|
|
2562
|
-
response =
|
|
2593
|
+
response = self.get(url_for("api.schemas"))
|
|
2563
2594
|
assert200(response)
|
|
2564
2595
|
assert (
|
|
2565
2596
|
response.json == ResourceSchemaMockData.get_expected_assignable_schemas_from_mock_data()
|
|
@@ -2567,7 +2598,7 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2567
2598
|
|
|
2568
2599
|
|
|
2569
2600
|
class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
2570
|
-
def test_dataset_with_harvest_metadata(self
|
|
2601
|
+
def test_dataset_with_harvest_metadata(self):
|
|
2571
2602
|
date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
|
|
2572
2603
|
harvest_metadata = HarvestDatasetMetadata(
|
|
2573
2604
|
backend="DCAT",
|
|
@@ -2585,7 +2616,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2585
2616
|
)
|
|
2586
2617
|
dataset = DatasetFactory(harvest=harvest_metadata)
|
|
2587
2618
|
|
|
2588
|
-
response =
|
|
2619
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2589
2620
|
assert200(response)
|
|
2590
2621
|
assert response.json["harvest"] == {
|
|
2591
2622
|
"backend": "DCAT",
|
|
@@ -2602,7 +2633,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2602
2633
|
"archived": "not-on-remote",
|
|
2603
2634
|
}
|
|
2604
2635
|
|
|
2605
|
-
def test_dataset_with_resource_harvest_metadata(self
|
|
2636
|
+
def test_dataset_with_resource_harvest_metadata(self):
|
|
2606
2637
|
date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
|
|
2607
2638
|
|
|
2608
2639
|
harvest_metadata = HarvestResourceMetadata(
|
|
@@ -2612,7 +2643,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2612
2643
|
)
|
|
2613
2644
|
dataset = DatasetFactory(resources=[ResourceFactory(harvest=harvest_metadata)])
|
|
2614
2645
|
|
|
2615
|
-
response =
|
|
2646
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2616
2647
|
assert200(response)
|
|
2617
2648
|
assert response.json["resources"][0]["harvest"] == {
|
|
2618
2649
|
"issued_at": date.isoformat(),
|
|
@@ -2620,7 +2651,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2620
2651
|
"uri": "http://domain.gouv.fr/dataset/uri",
|
|
2621
2652
|
}
|
|
2622
2653
|
|
|
2623
|
-
def test_dataset_with_harvest_computed_dates(self
|
|
2654
|
+
def test_dataset_with_harvest_computed_dates(self):
|
|
2624
2655
|
# issued_date takes precedence over internal creation date and harvest created_at on dataset
|
|
2625
2656
|
issued_date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
|
|
2626
2657
|
creation_date = datetime(2022, 2, 23, tzinfo=pytz.UTC)
|
|
@@ -2631,7 +2662,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2631
2662
|
modified_at=modification_date,
|
|
2632
2663
|
)
|
|
2633
2664
|
dataset = DatasetFactory(harvest=harvest_metadata)
|
|
2634
|
-
response =
|
|
2665
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2635
2666
|
assert200(response)
|
|
2636
2667
|
assert response.json["created_at"] == issued_date.isoformat()
|
|
2637
2668
|
assert response.json["last_modified"] == modification_date.isoformat()
|
|
@@ -2643,7 +2674,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2643
2674
|
modified_at=modification_date,
|
|
2644
2675
|
)
|
|
2645
2676
|
dataset = DatasetFactory(harvest=harvest_metadata)
|
|
2646
|
-
response =
|
|
2677
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2647
2678
|
assert200(response)
|
|
2648
2679
|
assert response.json["created_at"] == creation_date.isoformat()
|
|
2649
2680
|
assert response.json["last_modified"] == modification_date.isoformat()
|
|
@@ -2654,7 +2685,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2654
2685
|
modified_at=modification_date,
|
|
2655
2686
|
)
|
|
2656
2687
|
dataset = DatasetFactory(resources=[ResourceFactory(harvest=resource_harvest_metadata)])
|
|
2657
|
-
response =
|
|
2688
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2658
2689
|
assert200(response)
|
|
2659
2690
|
assert response.json["resources"][0]["created_at"] == issued_date.isoformat()
|
|
2660
2691
|
assert response.json["resources"][0]["last_modified"] == modification_date.isoformat()
|