udata 14.0.0__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.
- 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 +6 -3
- 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/models.py +1 -1
- udata/core/dataservices/tasks.py +7 -0
- udata/core/dataset/api.py +2 -0
- udata/core/dataset/models.py +2 -2
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/tasks.py +17 -5
- udata/core/discussions/models.py +1 -0
- udata/core/organization/api.py +8 -5
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +9 -1
- 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/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/spatial/forms.py +2 -2
- udata/core/user/models.py +5 -1
- 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/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/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 +57 -10
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +1 -4
- udata/mail.py +5 -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 +45 -6
- udata/routing.py +2 -2
- udata/settings.py +7 -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 +44 -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_swagger.py +4 -4
- udata/tests/cli/test_cli_base.py +8 -9
- 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/frontend/test_auth.py +24 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +25 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- 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.4.1.dev7.dist-info/METADATA +109 -0
- {udata-14.0.0.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +121 -123
- 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.4.1.dev7.dist-info}/WHEEL +0 -0
- {udata-14.0.0.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +0 -0
- {udata-14.0.0.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
- {udata-14.0.0.dist-info → udata-14.4.1.dev7.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()
|
|
@@ -1834,6 +1847,18 @@ class DatasetResourceAPITest(APITestCase):
|
|
|
1834
1847
|
f"Resource ids must match existing ones in dataset, ie: {set(str(r.id) for r in self.dataset.resources)}",
|
|
1835
1848
|
)
|
|
1836
1849
|
|
|
1850
|
+
def test_invalid_reorder_dict_without_id(self):
|
|
1851
|
+
"""It should return 400 when dict in resources list has no 'id' key"""
|
|
1852
|
+
self.dataset.resources = ResourceFactory.build_batch(3)
|
|
1853
|
+
self.dataset.save()
|
|
1854
|
+
|
|
1855
|
+
# Dict without 'id' key should fail gracefully, not raise KeyError
|
|
1856
|
+
wrong_order_dict_without_id = [{"title": "foo"}, {"title": "bar"}, {"title": "baz"}]
|
|
1857
|
+
response = self.put(
|
|
1858
|
+
url_for("api.resources", dataset=self.dataset), wrong_order_dict_without_id
|
|
1859
|
+
)
|
|
1860
|
+
self.assertStatus(response, 400)
|
|
1861
|
+
|
|
1837
1862
|
def test_update_local(self):
|
|
1838
1863
|
resource = ResourceFactory()
|
|
1839
1864
|
self.dataset.resources.append(resource)
|
|
@@ -2506,13 +2531,13 @@ class ResourcesTypesAPITest(APITestCase):
|
|
|
2506
2531
|
|
|
2507
2532
|
|
|
2508
2533
|
class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
2509
|
-
def test_dataset_schemas_api_list(self,
|
|
2534
|
+
def test_dataset_schemas_api_list(self, rmock, app):
|
|
2510
2535
|
# Can't use @pytest.mark.options otherwise a request will be
|
|
2511
2536
|
# made before setting up rmock at module load, resulting in a 404
|
|
2512
2537
|
app.config["SCHEMA_CATALOG_URL"] = "https://example.com/schemas"
|
|
2513
2538
|
|
|
2514
2539
|
rmock.get("https://example.com/schemas", json=ResourceSchemaMockData.get_mock_data())
|
|
2515
|
-
response =
|
|
2540
|
+
response = self.get(url_for("api.schemas"))
|
|
2516
2541
|
|
|
2517
2542
|
assert200(response)
|
|
2518
2543
|
assert (
|
|
@@ -2520,27 +2545,27 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2520
2545
|
)
|
|
2521
2546
|
|
|
2522
2547
|
@pytest.mark.options(SCHEMA_CATALOG_URL=None)
|
|
2523
|
-
def test_dataset_schemas_api_list_no_catalog_url(self
|
|
2524
|
-
response =
|
|
2548
|
+
def test_dataset_schemas_api_list_no_catalog_url(self):
|
|
2549
|
+
response = self.get(url_for("api.schemas"))
|
|
2525
2550
|
|
|
2526
2551
|
assert200(response)
|
|
2527
2552
|
assert response.json == []
|
|
2528
2553
|
|
|
2529
2554
|
@pytest.mark.options(SCHEMA_CATALOG_URL="https://example.com/notfound")
|
|
2530
|
-
def test_dataset_schemas_api_list_not_found(self,
|
|
2555
|
+
def test_dataset_schemas_api_list_not_found(self, rmock):
|
|
2531
2556
|
rmock.get("https://example.com/notfound", status_code=404)
|
|
2532
|
-
response =
|
|
2557
|
+
response = self.get(url_for("api.schemas"))
|
|
2533
2558
|
assert404(response)
|
|
2534
2559
|
|
|
2535
2560
|
@pytest.mark.options(SCHEMA_CATALOG_URL="https://example.com/schemas")
|
|
2536
|
-
def test_dataset_schemas_api_list_error_no_cache(self,
|
|
2561
|
+
def test_dataset_schemas_api_list_error_no_cache(self, rmock):
|
|
2537
2562
|
rmock.get("https://example.com/schemas", status_code=500)
|
|
2538
2563
|
|
|
2539
|
-
response =
|
|
2564
|
+
response = self.get(url_for("api.schemas"))
|
|
2540
2565
|
assert response.status_code == 503
|
|
2541
2566
|
|
|
2542
2567
|
@pytest.mark.options(SCHEMA_CATALOG_URL="https://example.com/schemas")
|
|
2543
|
-
def test_dataset_schemas_api_list_error_w_cache(self,
|
|
2568
|
+
def test_dataset_schemas_api_list_error_w_cache(self, rmock, mocker):
|
|
2544
2569
|
cache_mock_set = mocker.patch.object(cache, "set")
|
|
2545
2570
|
mocker.patch.object(
|
|
2546
2571
|
cache, "get", return_value=ResourceSchemaMockData.get_mock_data()["schemas"]
|
|
@@ -2548,7 +2573,7 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2548
2573
|
|
|
2549
2574
|
# Fill cache
|
|
2550
2575
|
rmock.get("https://example.com/schemas", json=ResourceSchemaMockData.get_mock_data())
|
|
2551
|
-
response =
|
|
2576
|
+
response = self.get(url_for("api.schemas"))
|
|
2552
2577
|
assert200(response)
|
|
2553
2578
|
assert (
|
|
2554
2579
|
response.json == ResourceSchemaMockData.get_expected_assignable_schemas_from_mock_data()
|
|
@@ -2559,7 +2584,7 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2559
2584
|
rmock.get("https://example.com/schemas", status_code=500)
|
|
2560
2585
|
|
|
2561
2586
|
# Long term cache is used
|
|
2562
|
-
response =
|
|
2587
|
+
response = self.get(url_for("api.schemas"))
|
|
2563
2588
|
assert200(response)
|
|
2564
2589
|
assert (
|
|
2565
2590
|
response.json == ResourceSchemaMockData.get_expected_assignable_schemas_from_mock_data()
|
|
@@ -2567,7 +2592,7 @@ class DatasetSchemasAPITest(PytestOnlyAPITestCase):
|
|
|
2567
2592
|
|
|
2568
2593
|
|
|
2569
2594
|
class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
2570
|
-
def test_dataset_with_harvest_metadata(self
|
|
2595
|
+
def test_dataset_with_harvest_metadata(self):
|
|
2571
2596
|
date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
|
|
2572
2597
|
harvest_metadata = HarvestDatasetMetadata(
|
|
2573
2598
|
backend="DCAT",
|
|
@@ -2585,7 +2610,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2585
2610
|
)
|
|
2586
2611
|
dataset = DatasetFactory(harvest=harvest_metadata)
|
|
2587
2612
|
|
|
2588
|
-
response =
|
|
2613
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2589
2614
|
assert200(response)
|
|
2590
2615
|
assert response.json["harvest"] == {
|
|
2591
2616
|
"backend": "DCAT",
|
|
@@ -2602,7 +2627,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2602
2627
|
"archived": "not-on-remote",
|
|
2603
2628
|
}
|
|
2604
2629
|
|
|
2605
|
-
def test_dataset_with_resource_harvest_metadata(self
|
|
2630
|
+
def test_dataset_with_resource_harvest_metadata(self):
|
|
2606
2631
|
date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
|
|
2607
2632
|
|
|
2608
2633
|
harvest_metadata = HarvestResourceMetadata(
|
|
@@ -2612,7 +2637,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2612
2637
|
)
|
|
2613
2638
|
dataset = DatasetFactory(resources=[ResourceFactory(harvest=harvest_metadata)])
|
|
2614
2639
|
|
|
2615
|
-
response =
|
|
2640
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2616
2641
|
assert200(response)
|
|
2617
2642
|
assert response.json["resources"][0]["harvest"] == {
|
|
2618
2643
|
"issued_at": date.isoformat(),
|
|
@@ -2620,7 +2645,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2620
2645
|
"uri": "http://domain.gouv.fr/dataset/uri",
|
|
2621
2646
|
}
|
|
2622
2647
|
|
|
2623
|
-
def test_dataset_with_harvest_computed_dates(self
|
|
2648
|
+
def test_dataset_with_harvest_computed_dates(self):
|
|
2624
2649
|
# issued_date takes precedence over internal creation date and harvest created_at on dataset
|
|
2625
2650
|
issued_date = datetime(2022, 2, 22, tzinfo=pytz.UTC)
|
|
2626
2651
|
creation_date = datetime(2022, 2, 23, tzinfo=pytz.UTC)
|
|
@@ -2631,7 +2656,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2631
2656
|
modified_at=modification_date,
|
|
2632
2657
|
)
|
|
2633
2658
|
dataset = DatasetFactory(harvest=harvest_metadata)
|
|
2634
|
-
response =
|
|
2659
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2635
2660
|
assert200(response)
|
|
2636
2661
|
assert response.json["created_at"] == issued_date.isoformat()
|
|
2637
2662
|
assert response.json["last_modified"] == modification_date.isoformat()
|
|
@@ -2643,7 +2668,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2643
2668
|
modified_at=modification_date,
|
|
2644
2669
|
)
|
|
2645
2670
|
dataset = DatasetFactory(harvest=harvest_metadata)
|
|
2646
|
-
response =
|
|
2671
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2647
2672
|
assert200(response)
|
|
2648
2673
|
assert response.json["created_at"] == creation_date.isoformat()
|
|
2649
2674
|
assert response.json["last_modified"] == modification_date.isoformat()
|
|
@@ -2654,7 +2679,7 @@ class HarvestMetadataAPITest(PytestOnlyAPITestCase):
|
|
|
2654
2679
|
modified_at=modification_date,
|
|
2655
2680
|
)
|
|
2656
2681
|
dataset = DatasetFactory(resources=[ResourceFactory(harvest=resource_harvest_metadata)])
|
|
2657
|
-
response =
|
|
2682
|
+
response = self.get(url_for("api.dataset", dataset=dataset))
|
|
2658
2683
|
assert200(response)
|
|
2659
2684
|
assert response.json["resources"][0]["created_at"] == issued_date.isoformat()
|
|
2660
2685
|
assert response.json["resources"][0]["last_modified"] == modification_date.isoformat()
|