udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30454__py2.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.
- tasks/__init__.py +109 -107
- tasks/helpers.py +18 -18
- udata/__init__.py +4 -4
- udata/admin/views.py +5 -5
- udata/api/__init__.py +111 -134
- udata/api/commands.py +45 -37
- udata/api/errors.py +5 -4
- udata/api/fields.py +23 -21
- udata/api/oauth2.py +55 -74
- udata/api/parsers.py +15 -15
- udata/api/signals.py +1 -1
- udata/api_fields.py +137 -89
- udata/app.py +58 -55
- udata/assets.py +5 -5
- udata/auth/__init__.py +37 -26
- udata/auth/forms.py +23 -15
- udata/auth/helpers.py +1 -1
- udata/auth/mails.py +3 -3
- udata/auth/password_validation.py +19 -15
- udata/auth/views.py +94 -68
- udata/commands/__init__.py +71 -69
- udata/commands/cache.py +7 -7
- udata/commands/db.py +201 -140
- udata/commands/dcat.py +36 -30
- udata/commands/fixtures.py +100 -84
- udata/commands/images.py +21 -20
- udata/commands/info.py +17 -20
- udata/commands/init.py +10 -10
- udata/commands/purge.py +12 -13
- udata/commands/serve.py +41 -29
- udata/commands/static.py +16 -18
- udata/commands/test.py +20 -20
- udata/commands/tests/fixtures.py +26 -24
- udata/commands/worker.py +31 -33
- udata/core/__init__.py +12 -12
- udata/core/activity/__init__.py +0 -1
- udata/core/activity/api.py +59 -49
- udata/core/activity/models.py +28 -26
- udata/core/activity/signals.py +1 -1
- udata/core/activity/tasks.py +16 -10
- udata/core/badges/api.py +6 -6
- udata/core/badges/commands.py +14 -13
- udata/core/badges/fields.py +8 -5
- udata/core/badges/forms.py +7 -4
- udata/core/badges/models.py +16 -31
- udata/core/badges/permissions.py +1 -3
- udata/core/badges/signals.py +2 -2
- udata/core/badges/tasks.py +3 -2
- udata/core/badges/tests/test_commands.py +10 -10
- udata/core/badges/tests/test_model.py +24 -31
- udata/core/contact_point/api.py +19 -18
- udata/core/contact_point/api_fields.py +21 -14
- udata/core/contact_point/factories.py +2 -2
- udata/core/contact_point/forms.py +7 -6
- udata/core/contact_point/models.py +3 -5
- udata/core/dataservices/api.py +26 -21
- udata/core/dataservices/factories.py +13 -11
- udata/core/dataservices/models.py +35 -40
- udata/core/dataservices/permissions.py +4 -4
- udata/core/dataservices/rdf.py +40 -17
- udata/core/dataservices/tasks.py +4 -3
- udata/core/dataset/actions.py +10 -10
- udata/core/dataset/activities.py +21 -23
- udata/core/dataset/api.py +321 -298
- udata/core/dataset/api_fields.py +443 -271
- udata/core/dataset/apiv2.py +305 -229
- udata/core/dataset/commands.py +38 -36
- udata/core/dataset/constants.py +61 -54
- udata/core/dataset/csv.py +70 -74
- udata/core/dataset/events.py +39 -32
- udata/core/dataset/exceptions.py +8 -4
- udata/core/dataset/factories.py +57 -65
- udata/core/dataset/forms.py +87 -63
- udata/core/dataset/models.py +336 -280
- udata/core/dataset/permissions.py +9 -6
- udata/core/dataset/preview.py +15 -17
- udata/core/dataset/rdf.py +156 -122
- udata/core/dataset/search.py +92 -77
- udata/core/dataset/signals.py +1 -1
- udata/core/dataset/tasks.py +63 -54
- udata/core/discussions/actions.py +5 -5
- udata/core/discussions/api.py +124 -120
- udata/core/discussions/factories.py +2 -2
- udata/core/discussions/forms.py +9 -7
- udata/core/discussions/metrics.py +1 -3
- udata/core/discussions/models.py +25 -24
- udata/core/discussions/notifications.py +18 -14
- udata/core/discussions/permissions.py +3 -3
- udata/core/discussions/signals.py +4 -4
- udata/core/discussions/tasks.py +24 -28
- udata/core/followers/api.py +32 -33
- udata/core/followers/models.py +9 -9
- udata/core/followers/signals.py +3 -3
- udata/core/jobs/actions.py +7 -7
- udata/core/jobs/api.py +99 -92
- udata/core/jobs/commands.py +48 -49
- udata/core/jobs/forms.py +11 -11
- udata/core/jobs/models.py +6 -6
- udata/core/metrics/__init__.py +2 -2
- udata/core/metrics/commands.py +34 -30
- udata/core/metrics/models.py +2 -4
- udata/core/metrics/signals.py +1 -1
- udata/core/metrics/tasks.py +3 -3
- udata/core/organization/activities.py +12 -15
- udata/core/organization/api.py +167 -174
- udata/core/organization/api_fields.py +183 -124
- udata/core/organization/apiv2.py +32 -32
- udata/core/organization/commands.py +20 -22
- udata/core/organization/constants.py +11 -11
- udata/core/organization/csv.py +17 -15
- udata/core/organization/factories.py +8 -11
- udata/core/organization/forms.py +32 -26
- udata/core/organization/metrics.py +2 -1
- udata/core/organization/models.py +87 -67
- udata/core/organization/notifications.py +18 -14
- udata/core/organization/permissions.py +10 -11
- udata/core/organization/rdf.py +14 -14
- udata/core/organization/search.py +30 -28
- udata/core/organization/signals.py +7 -7
- udata/core/organization/tasks.py +42 -61
- udata/core/owned.py +38 -27
- udata/core/post/api.py +82 -81
- udata/core/post/constants.py +8 -5
- udata/core/post/factories.py +4 -4
- udata/core/post/forms.py +13 -14
- udata/core/post/models.py +20 -22
- udata/core/post/tests/test_api.py +30 -32
- udata/core/reports/api.py +8 -7
- udata/core/reports/constants.py +1 -3
- udata/core/reports/models.py +10 -10
- udata/core/reuse/activities.py +15 -19
- udata/core/reuse/api.py +123 -126
- udata/core/reuse/api_fields.py +120 -85
- udata/core/reuse/apiv2.py +11 -10
- udata/core/reuse/constants.py +23 -23
- udata/core/reuse/csv.py +18 -18
- udata/core/reuse/factories.py +5 -9
- udata/core/reuse/forms.py +24 -21
- udata/core/reuse/models.py +55 -51
- udata/core/reuse/permissions.py +2 -2
- udata/core/reuse/search.py +49 -46
- udata/core/reuse/signals.py +1 -1
- udata/core/reuse/tasks.py +4 -5
- udata/core/site/api.py +47 -50
- udata/core/site/factories.py +2 -2
- udata/core/site/forms.py +4 -5
- udata/core/site/models.py +94 -63
- udata/core/site/rdf.py +14 -14
- udata/core/spam/api.py +16 -9
- udata/core/spam/constants.py +4 -4
- udata/core/spam/fields.py +13 -7
- udata/core/spam/models.py +27 -20
- udata/core/spam/signals.py +1 -1
- udata/core/spam/tests/test_spam.py +6 -5
- udata/core/spatial/api.py +72 -80
- udata/core/spatial/api_fields.py +73 -58
- udata/core/spatial/commands.py +67 -64
- udata/core/spatial/constants.py +3 -3
- udata/core/spatial/factories.py +37 -54
- udata/core/spatial/forms.py +27 -26
- udata/core/spatial/geoids.py +17 -17
- udata/core/spatial/models.py +43 -47
- udata/core/spatial/tasks.py +2 -1
- udata/core/spatial/tests/test_api.py +115 -130
- udata/core/spatial/tests/test_fields.py +74 -77
- udata/core/spatial/tests/test_geoid.py +22 -22
- udata/core/spatial/tests/test_models.py +5 -7
- udata/core/spatial/translations.py +16 -16
- udata/core/storages/__init__.py +16 -18
- udata/core/storages/api.py +66 -64
- udata/core/storages/tasks.py +7 -7
- udata/core/storages/utils.py +15 -15
- udata/core/storages/views.py +5 -6
- udata/core/tags/api.py +17 -14
- udata/core/tags/csv.py +4 -4
- udata/core/tags/models.py +8 -5
- udata/core/tags/tasks.py +11 -13
- udata/core/tags/views.py +4 -4
- udata/core/topic/api.py +84 -73
- udata/core/topic/apiv2.py +157 -127
- udata/core/topic/factories.py +3 -4
- udata/core/topic/forms.py +12 -14
- udata/core/topic/models.py +14 -19
- udata/core/topic/parsers.py +26 -26
- udata/core/user/activities.py +30 -29
- udata/core/user/api.py +151 -152
- udata/core/user/api_fields.py +132 -100
- udata/core/user/apiv2.py +7 -7
- udata/core/user/commands.py +38 -38
- udata/core/user/factories.py +8 -9
- udata/core/user/forms.py +14 -11
- udata/core/user/metrics.py +2 -2
- udata/core/user/models.py +68 -69
- udata/core/user/permissions.py +4 -5
- udata/core/user/rdf.py +7 -8
- udata/core/user/tasks.py +2 -2
- udata/core/user/tests/test_user_model.py +24 -16
- udata/cors.py +99 -0
- udata/db/tasks.py +2 -1
- udata/entrypoints.py +35 -31
- udata/errors.py +2 -1
- udata/event/values.py +6 -6
- udata/factories.py +2 -2
- udata/features/identicon/api.py +5 -6
- udata/features/identicon/backends.py +48 -55
- udata/features/identicon/tests/test_backends.py +4 -5
- udata/features/notifications/__init__.py +0 -1
- udata/features/notifications/actions.py +9 -9
- udata/features/notifications/api.py +17 -13
- udata/features/territories/__init__.py +12 -10
- udata/features/territories/api.py +14 -15
- udata/features/territories/models.py +23 -28
- udata/features/transfer/actions.py +8 -11
- udata/features/transfer/api.py +84 -77
- udata/features/transfer/factories.py +2 -1
- udata/features/transfer/models.py +11 -12
- udata/features/transfer/notifications.py +19 -15
- udata/features/transfer/permissions.py +5 -5
- udata/forms/__init__.py +5 -2
- udata/forms/fields.py +164 -172
- udata/forms/validators.py +19 -22
- udata/forms/widgets.py +9 -13
- udata/frontend/__init__.py +31 -26
- udata/frontend/csv.py +68 -58
- udata/frontend/markdown.py +40 -44
- udata/harvest/actions.py +89 -77
- udata/harvest/api.py +294 -238
- udata/harvest/backends/__init__.py +4 -4
- udata/harvest/backends/base.py +128 -111
- udata/harvest/backends/dcat.py +80 -66
- udata/harvest/commands.py +56 -60
- udata/harvest/csv.py +8 -8
- udata/harvest/exceptions.py +6 -3
- udata/harvest/filters.py +24 -23
- udata/harvest/forms.py +27 -28
- udata/harvest/models.py +88 -80
- udata/harvest/notifications.py +15 -10
- udata/harvest/signals.py +13 -13
- udata/harvest/tasks.py +11 -10
- udata/harvest/tests/factories.py +23 -24
- udata/harvest/tests/test_actions.py +136 -166
- udata/harvest/tests/test_api.py +220 -214
- udata/harvest/tests/test_base_backend.py +117 -112
- udata/harvest/tests/test_dcat_backend.py +380 -308
- udata/harvest/tests/test_filters.py +33 -22
- udata/harvest/tests/test_models.py +11 -14
- udata/harvest/tests/test_notifications.py +6 -7
- udata/harvest/tests/test_tasks.py +7 -6
- udata/i18n.py +237 -78
- udata/linkchecker/backends.py +5 -11
- udata/linkchecker/checker.py +23 -22
- udata/linkchecker/commands.py +4 -6
- udata/linkchecker/models.py +6 -6
- udata/linkchecker/tasks.py +18 -20
- udata/mail.py +21 -21
- udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
- udata/migrations/2020-08-24-add-fs-filename.py +9 -8
- udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
- udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
- udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
- udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
- udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
- udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
- udata/migrations/2021-08-17-follow-integrity.py +5 -4
- udata/migrations/2021-08-17-harvest-integrity.py +13 -12
- udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
- udata/migrations/2021-08-17-transfer-integrity.py +5 -4
- udata/migrations/2021-08-17-users-integrity.py +9 -8
- udata/migrations/2021-12-14-reuse-topics.py +7 -6
- udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
- udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
- udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
- udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
- udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
- udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
- udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
- udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
- udata/migrations/__init__.py +123 -105
- udata/models/__init__.py +4 -4
- udata/mongo/__init__.py +13 -11
- udata/mongo/badges_field.py +3 -2
- udata/mongo/datetime_fields.py +13 -12
- udata/mongo/document.py +17 -16
- udata/mongo/engine.py +15 -16
- udata/mongo/errors.py +2 -1
- udata/mongo/extras_fields.py +30 -20
- udata/mongo/queryset.py +12 -12
- udata/mongo/slug_fields.py +38 -28
- udata/mongo/taglist_field.py +1 -2
- udata/mongo/url_field.py +5 -5
- udata/mongo/uuid_fields.py +4 -3
- udata/notifications/__init__.py +1 -1
- udata/notifications/mattermost.py +10 -9
- udata/rdf.py +167 -188
- udata/routing.py +40 -45
- udata/search/__init__.py +18 -19
- udata/search/adapter.py +17 -16
- udata/search/commands.py +44 -51
- udata/search/fields.py +13 -20
- udata/search/query.py +23 -18
- udata/search/result.py +9 -10
- udata/sentry.py +21 -19
- udata/settings.py +262 -198
- udata/sitemap.py +8 -6
- udata/storage/s3.py +20 -13
- udata/tags.py +4 -5
- udata/tasks.py +43 -42
- udata/tests/__init__.py +9 -6
- udata/tests/api/__init__.py +8 -6
- udata/tests/api/test_auth_api.py +395 -321
- udata/tests/api/test_base_api.py +33 -35
- udata/tests/api/test_contact_points.py +7 -9
- udata/tests/api/test_dataservices_api.py +211 -158
- udata/tests/api/test_datasets_api.py +823 -812
- udata/tests/api/test_follow_api.py +13 -15
- udata/tests/api/test_me_api.py +95 -112
- udata/tests/api/test_organizations_api.py +301 -339
- udata/tests/api/test_reports_api.py +35 -25
- udata/tests/api/test_reuses_api.py +134 -139
- udata/tests/api/test_swagger.py +5 -5
- udata/tests/api/test_tags_api.py +18 -25
- udata/tests/api/test_topics_api.py +94 -94
- udata/tests/api/test_transfer_api.py +53 -48
- udata/tests/api/test_user_api.py +128 -141
- udata/tests/apiv2/test_datasets.py +290 -198
- udata/tests/apiv2/test_me_api.py +10 -11
- udata/tests/apiv2/test_organizations.py +56 -74
- udata/tests/apiv2/test_swagger.py +5 -5
- udata/tests/apiv2/test_topics.py +69 -87
- udata/tests/cli/test_cli_base.py +8 -8
- udata/tests/cli/test_db_cli.py +21 -19
- udata/tests/dataservice/test_dataservice_tasks.py +8 -12
- udata/tests/dataset/test_csv_adapter.py +44 -35
- udata/tests/dataset/test_dataset_actions.py +2 -3
- udata/tests/dataset/test_dataset_commands.py +7 -8
- udata/tests/dataset/test_dataset_events.py +36 -29
- udata/tests/dataset/test_dataset_model.py +224 -217
- udata/tests/dataset/test_dataset_rdf.py +142 -131
- udata/tests/dataset/test_dataset_tasks.py +15 -15
- udata/tests/dataset/test_resource_preview.py +10 -13
- udata/tests/features/territories/__init__.py +9 -13
- udata/tests/features/territories/test_territories_api.py +71 -91
- udata/tests/forms/test_basic_fields.py +7 -7
- udata/tests/forms/test_current_user_field.py +39 -66
- udata/tests/forms/test_daterange_field.py +31 -39
- udata/tests/forms/test_dict_field.py +28 -26
- udata/tests/forms/test_extras_fields.py +102 -76
- udata/tests/forms/test_form_field.py +8 -8
- udata/tests/forms/test_image_field.py +33 -26
- udata/tests/forms/test_model_field.py +134 -123
- udata/tests/forms/test_model_list_field.py +7 -7
- udata/tests/forms/test_nested_model_list_field.py +117 -79
- udata/tests/forms/test_publish_as_field.py +36 -65
- udata/tests/forms/test_reference_field.py +34 -53
- udata/tests/forms/test_user_forms.py +23 -21
- udata/tests/forms/test_uuid_field.py +6 -10
- udata/tests/frontend/__init__.py +9 -6
- udata/tests/frontend/test_auth.py +7 -6
- udata/tests/frontend/test_csv.py +81 -96
- udata/tests/frontend/test_hooks.py +43 -43
- udata/tests/frontend/test_markdown.py +211 -191
- udata/tests/helpers.py +32 -37
- udata/tests/models.py +2 -2
- udata/tests/organization/test_csv_adapter.py +21 -16
- udata/tests/organization/test_notifications.py +11 -18
- udata/tests/organization/test_organization_model.py +13 -13
- udata/tests/organization/test_organization_rdf.py +29 -22
- udata/tests/organization/test_organization_tasks.py +16 -17
- udata/tests/plugin.py +79 -73
- udata/tests/reuse/test_reuse_model.py +21 -21
- udata/tests/reuse/test_reuse_task.py +11 -13
- udata/tests/search/__init__.py +11 -12
- udata/tests/search/test_adapter.py +60 -70
- udata/tests/search/test_query.py +16 -16
- udata/tests/search/test_results.py +10 -7
- udata/tests/site/test_site_api.py +11 -16
- udata/tests/site/test_site_metrics.py +20 -30
- udata/tests/site/test_site_model.py +4 -5
- udata/tests/site/test_site_rdf.py +94 -78
- udata/tests/test_activity.py +17 -17
- udata/tests/test_cors.py +62 -0
- udata/tests/test_discussions.py +292 -299
- udata/tests/test_i18n.py +37 -40
- udata/tests/test_linkchecker.py +91 -85
- udata/tests/test_mail.py +13 -17
- udata/tests/test_migrations.py +219 -180
- udata/tests/test_model.py +164 -157
- udata/tests/test_notifications.py +17 -17
- udata/tests/test_owned.py +14 -14
- udata/tests/test_rdf.py +25 -23
- udata/tests/test_routing.py +89 -93
- udata/tests/test_storages.py +137 -128
- udata/tests/test_tags.py +44 -46
- udata/tests/test_topics.py +7 -7
- udata/tests/test_transfer.py +42 -49
- udata/tests/test_uris.py +160 -161
- udata/tests/test_utils.py +79 -71
- udata/tests/user/test_user_rdf.py +5 -9
- udata/tests/workers/test_jobs_commands.py +57 -58
- udata/tests/workers/test_tasks_routing.py +23 -29
- udata/tests/workers/test_workers_api.py +125 -131
- udata/tests/workers/test_workers_helpers.py +6 -6
- udata/tracking.py +4 -6
- udata/uris.py +45 -46
- udata/utils.py +68 -66
- udata/wsgi.py +1 -1
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/METADATA +7 -3
- udata-9.1.2.dev30454.dist-info/RECORD +706 -0
- udata-9.1.2.dev30355.dist-info/RECORD +0 -704
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/LICENSE +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/WHEEL +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/entry_points.txt +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/top_level.txt +0 -0
udata/core/dataset/apiv2.py
CHANGED
|
@@ -1,239 +1,314 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import mongoengine
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import mongoengine
|
|
4
|
+
from flask import abort, request, url_for
|
|
5
5
|
from flask_restx import marshal
|
|
6
6
|
|
|
7
7
|
from udata import search
|
|
8
|
-
from udata.api import
|
|
9
|
-
from udata.
|
|
10
|
-
|
|
8
|
+
from udata.api import API, apiv2, fields
|
|
9
|
+
from udata.core.contact_point.api_fields import contact_point_fields
|
|
11
10
|
from udata.core.organization.api_fields import member_user_with_email_fields
|
|
11
|
+
from udata.core.spatial.api_fields import geojson
|
|
12
|
+
from udata.utils import get_by, multi_to_dict
|
|
13
|
+
|
|
14
|
+
from .api import ResourceMixin
|
|
12
15
|
from .api_fields import (
|
|
13
16
|
badge_fields,
|
|
14
|
-
|
|
15
|
-
resource_fields,
|
|
16
|
-
spatial_coverage_fields,
|
|
17
|
-
temporal_coverage_fields,
|
|
18
|
-
user_ref_fields,
|
|
17
|
+
catalog_schema_fields,
|
|
19
18
|
checksum_fields,
|
|
20
19
|
dataset_harvest_fields,
|
|
21
20
|
dataset_internal_fields,
|
|
21
|
+
org_ref_fields,
|
|
22
|
+
resource_fields,
|
|
22
23
|
resource_harvest_fields,
|
|
23
24
|
resource_internal_fields,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
schema_fields,
|
|
26
|
+
spatial_coverage_fields,
|
|
27
|
+
temporal_coverage_fields,
|
|
28
|
+
user_ref_fields,
|
|
26
29
|
)
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from .models import Dataset, CommunityResource
|
|
30
|
-
from .constants import UPDATE_FREQUENCIES, DEFAULT_FREQUENCY, DEFAULT_LICENSE
|
|
31
|
-
from .api import ResourceMixin
|
|
30
|
+
from .constants import DEFAULT_FREQUENCY, DEFAULT_LICENSE, UPDATE_FREQUENCIES
|
|
31
|
+
from .models import CommunityResource, Dataset
|
|
32
32
|
from .permissions import DatasetEditPermission, ResourceEditPermission
|
|
33
33
|
from .search import DatasetSearch
|
|
34
34
|
|
|
35
35
|
DEFAULT_PAGE_SIZE = 50
|
|
36
36
|
|
|
37
37
|
#: Default mask to make it lightweight by default
|
|
38
|
-
DEFAULT_MASK_APIV2 =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
DEFAULT_MASK_APIV2 = ",".join(
|
|
39
|
+
(
|
|
40
|
+
"id",
|
|
41
|
+
"title",
|
|
42
|
+
"acronym",
|
|
43
|
+
"slug",
|
|
44
|
+
"description",
|
|
45
|
+
"created_at",
|
|
46
|
+
"last_modified",
|
|
47
|
+
"deleted",
|
|
48
|
+
"private",
|
|
49
|
+
"tags",
|
|
50
|
+
"badges",
|
|
51
|
+
"resources",
|
|
52
|
+
"community_resources",
|
|
53
|
+
"frequency",
|
|
54
|
+
"frequency_date",
|
|
55
|
+
"extras",
|
|
56
|
+
"metrics",
|
|
57
|
+
"organization",
|
|
58
|
+
"owner",
|
|
59
|
+
"temporal_coverage",
|
|
60
|
+
"spatial",
|
|
61
|
+
"license",
|
|
62
|
+
"uri",
|
|
63
|
+
"page",
|
|
64
|
+
"last_update",
|
|
65
|
+
"archived",
|
|
66
|
+
"quality",
|
|
67
|
+
"harvest",
|
|
68
|
+
"internal",
|
|
69
|
+
"contact_point",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
44
72
|
|
|
45
73
|
log = logging.getLogger(__name__)
|
|
46
74
|
|
|
47
|
-
ns = apiv2.namespace(
|
|
75
|
+
ns = apiv2.namespace("datasets", "Dataset related operations")
|
|
48
76
|
search_parser = DatasetSearch.as_request_parser()
|
|
49
77
|
resources_parser = apiv2.parser()
|
|
50
78
|
resources_parser.add_argument(
|
|
51
|
-
|
|
79
|
+
"page", type=int, default=1, location="args", help="The page to fetch"
|
|
80
|
+
)
|
|
52
81
|
resources_parser.add_argument(
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
"page_size", type=int, default=DEFAULT_PAGE_SIZE, location="args", help="The page size to fetch"
|
|
83
|
+
)
|
|
55
84
|
resources_parser.add_argument(
|
|
56
|
-
|
|
57
|
-
|
|
85
|
+
"type", type=str, location="args", help="The type of resources to fetch"
|
|
86
|
+
)
|
|
58
87
|
resources_parser.add_argument(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
common_doc = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
description
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
description=
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
fields.Nested(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
88
|
+
"q", type=str, location="args", help="query string to search through resources titles"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
common_doc = {"params": {"dataset": "The dataset ID or slug"}}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
dataset_fields = apiv2.model(
|
|
95
|
+
"Dataset",
|
|
96
|
+
{
|
|
97
|
+
"id": fields.String(description="The dataset identifier", readonly=True),
|
|
98
|
+
"title": fields.String(description="The dataset title", required=True),
|
|
99
|
+
"acronym": fields.String(description="An optional dataset acronym"),
|
|
100
|
+
"slug": fields.String(description="The dataset permalink string", required=True),
|
|
101
|
+
"description": fields.Markdown(
|
|
102
|
+
description="The dataset description in markdown", required=True
|
|
103
|
+
),
|
|
104
|
+
"created_at": fields.ISODateTime(
|
|
105
|
+
description="The dataset creation date", required=True, readonly=True
|
|
106
|
+
),
|
|
107
|
+
"last_modified": fields.ISODateTime(
|
|
108
|
+
description="The dataset last modification date", required=True, readonly=True
|
|
109
|
+
),
|
|
110
|
+
"deleted": fields.ISODateTime(description="The deletion date if deleted", readonly=True),
|
|
111
|
+
"archived": fields.ISODateTime(description="The archival date if archived"),
|
|
112
|
+
"featured": fields.Boolean(description="Is the dataset featured"),
|
|
113
|
+
"private": fields.Boolean(
|
|
114
|
+
description="Is the dataset private to the owner or the organization"
|
|
115
|
+
),
|
|
116
|
+
"tags": fields.List(fields.String),
|
|
117
|
+
"badges": fields.List(
|
|
118
|
+
fields.Nested(badge_fields), description="The dataset badges", readonly=True
|
|
119
|
+
),
|
|
120
|
+
"resources": fields.Raw(
|
|
121
|
+
attribute=lambda o: {
|
|
122
|
+
"rel": "subsection",
|
|
123
|
+
"href": url_for(
|
|
124
|
+
"apiv2.resources",
|
|
125
|
+
dataset=o.id,
|
|
126
|
+
page=1,
|
|
127
|
+
page_size=DEFAULT_PAGE_SIZE,
|
|
128
|
+
_external=True,
|
|
129
|
+
),
|
|
130
|
+
"type": "GET",
|
|
131
|
+
"total": len(o.resources),
|
|
132
|
+
},
|
|
133
|
+
description="Link to the dataset resources",
|
|
134
|
+
),
|
|
135
|
+
"community_resources": fields.Raw(
|
|
136
|
+
attribute=lambda o: {
|
|
137
|
+
"rel": "subsection",
|
|
138
|
+
"href": url_for(
|
|
139
|
+
"api.community_resources",
|
|
140
|
+
dataset=o.id,
|
|
141
|
+
page=1,
|
|
142
|
+
page_size=DEFAULT_PAGE_SIZE,
|
|
143
|
+
_external=True,
|
|
144
|
+
),
|
|
145
|
+
"type": "GET",
|
|
146
|
+
"total": len(o.community_resources),
|
|
147
|
+
},
|
|
148
|
+
description="Link to the dataset community resources",
|
|
149
|
+
),
|
|
150
|
+
"frequency": fields.String(
|
|
151
|
+
description="The update frequency",
|
|
152
|
+
required=True,
|
|
153
|
+
enum=list(UPDATE_FREQUENCIES),
|
|
154
|
+
default=DEFAULT_FREQUENCY,
|
|
155
|
+
),
|
|
156
|
+
"frequency_date": fields.ISODateTime(
|
|
157
|
+
description=(
|
|
158
|
+
"Next expected update date, you will be notified " "once that date is reached."
|
|
159
|
+
)
|
|
160
|
+
),
|
|
161
|
+
"harvest": fields.Nested(
|
|
162
|
+
dataset_harvest_fields,
|
|
163
|
+
readonly=True,
|
|
164
|
+
allow_null=True,
|
|
165
|
+
description="Dataset harvest metadata attributes",
|
|
166
|
+
skip_none=True,
|
|
167
|
+
),
|
|
168
|
+
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
169
|
+
"metrics": fields.Raw(
|
|
170
|
+
attribute=lambda o: o.get_metrics(), description="The dataset metrics"
|
|
171
|
+
),
|
|
172
|
+
"organization": fields.Nested(
|
|
173
|
+
org_ref_fields, allow_null=True, description="The producer organization"
|
|
174
|
+
),
|
|
175
|
+
"owner": fields.Nested(
|
|
176
|
+
user_ref_fields, allow_null=True, description="The user information"
|
|
177
|
+
),
|
|
178
|
+
"temporal_coverage": fields.Nested(
|
|
179
|
+
temporal_coverage_fields, allow_null=True, description="The temporal coverage"
|
|
180
|
+
),
|
|
181
|
+
"spatial": fields.Nested(
|
|
182
|
+
spatial_coverage_fields, allow_null=True, description="The spatial coverage"
|
|
183
|
+
),
|
|
184
|
+
"license": fields.String(
|
|
185
|
+
attribute="license.id", default=DEFAULT_LICENSE["id"], description="The dataset license"
|
|
186
|
+
),
|
|
187
|
+
"uri": fields.UrlFor(
|
|
188
|
+
"api.dataset",
|
|
189
|
+
lambda o: {"dataset": o},
|
|
190
|
+
description="The dataset API URI",
|
|
191
|
+
required=True,
|
|
192
|
+
),
|
|
193
|
+
"page": fields.UrlFor(
|
|
194
|
+
"datasets.show",
|
|
195
|
+
lambda o: {"dataset": o},
|
|
196
|
+
description="The dataset page URL",
|
|
197
|
+
required=True,
|
|
198
|
+
fallback_endpoint="api.dataset",
|
|
199
|
+
),
|
|
200
|
+
"quality": fields.Raw(description="The dataset quality", readonly=True),
|
|
201
|
+
"last_update": fields.ISODateTime(
|
|
202
|
+
description="The resources last modification date", required=True
|
|
203
|
+
),
|
|
204
|
+
"internal": fields.Nested(
|
|
205
|
+
dataset_internal_fields,
|
|
206
|
+
readonly=True,
|
|
207
|
+
description="Site internal and specific object's data",
|
|
208
|
+
),
|
|
209
|
+
"contact_point": fields.Nested(
|
|
210
|
+
contact_point_fields, allow_null=True, description="The dataset's contact point"
|
|
211
|
+
),
|
|
212
|
+
},
|
|
213
|
+
mask=DEFAULT_MASK_APIV2,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
resource_page_fields = apiv2.model(
|
|
218
|
+
"ResourcePage",
|
|
219
|
+
{
|
|
220
|
+
"data": fields.List(fields.Nested(resource_fields, description="The dataset resources")),
|
|
221
|
+
"next_page": fields.String(),
|
|
222
|
+
"previous_page": fields.String(),
|
|
223
|
+
"page": fields.Integer(),
|
|
224
|
+
"page_size": fields.Integer(),
|
|
225
|
+
"total": fields.Integer(),
|
|
226
|
+
},
|
|
227
|
+
)
|
|
154
228
|
|
|
155
229
|
dataset_page_fields = apiv2.model(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
230
|
+
"DatasetPage", fields.pager(dataset_fields), mask="data{{{0}}},*".format(DEFAULT_MASK_APIV2)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
specific_resource_fields = apiv2.model(
|
|
234
|
+
"SpecificResource",
|
|
235
|
+
{
|
|
236
|
+
"resource": fields.Nested(resource_fields, description="The dataset resources"),
|
|
237
|
+
"dataset_id": fields.String(),
|
|
238
|
+
},
|
|
159
239
|
)
|
|
160
240
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
apiv2.inherit(
|
|
167
|
-
apiv2.inherit(
|
|
168
|
-
apiv2.inherit(
|
|
169
|
-
apiv2.inherit(
|
|
170
|
-
apiv2.inherit(
|
|
171
|
-
apiv2.inherit(
|
|
172
|
-
apiv2.inherit(
|
|
173
|
-
apiv2.inherit(
|
|
174
|
-
apiv2.inherit(
|
|
175
|
-
apiv2.inherit(
|
|
176
|
-
apiv2.inherit(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
apiv2.inherit('Schema', schema_fields)
|
|
181
|
-
apiv2.inherit('CatalogSchema', catalog_schema_fields)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@ns.route('/search/', endpoint='dataset_search')
|
|
241
|
+
apiv2.inherit("Badge", badge_fields)
|
|
242
|
+
apiv2.inherit("OrganizationReference", org_ref_fields)
|
|
243
|
+
apiv2.inherit("UserReference", user_ref_fields)
|
|
244
|
+
apiv2.inherit("MemberUserWithEmail", member_user_with_email_fields)
|
|
245
|
+
apiv2.inherit("Resource", resource_fields)
|
|
246
|
+
apiv2.inherit("SpatialCoverage", spatial_coverage_fields)
|
|
247
|
+
apiv2.inherit("TemporalCoverage", temporal_coverage_fields)
|
|
248
|
+
apiv2.inherit("GeoJSON", geojson)
|
|
249
|
+
apiv2.inherit("Checksum", checksum_fields)
|
|
250
|
+
apiv2.inherit("HarvestDatasetMetadata", dataset_harvest_fields)
|
|
251
|
+
apiv2.inherit("HarvestResourceMetadata", resource_harvest_fields)
|
|
252
|
+
apiv2.inherit("DatasetInternals", dataset_internal_fields)
|
|
253
|
+
apiv2.inherit("ResourceInternals", resource_internal_fields)
|
|
254
|
+
apiv2.inherit("ContactPoint", contact_point_fields)
|
|
255
|
+
apiv2.inherit("Schema", schema_fields)
|
|
256
|
+
apiv2.inherit("CatalogSchema", catalog_schema_fields)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@ns.route("/search/", endpoint="dataset_search")
|
|
185
260
|
class DatasetSearchAPI(API):
|
|
186
|
-
|
|
187
|
-
|
|
261
|
+
"""Datasets collection search endpoint"""
|
|
262
|
+
|
|
263
|
+
@apiv2.doc("search_datasets")
|
|
188
264
|
@apiv2.expect(search_parser)
|
|
189
265
|
@apiv2.marshal_with(dataset_page_fields)
|
|
190
266
|
def get(self):
|
|
191
|
-
|
|
267
|
+
"""List or search all datasets"""
|
|
192
268
|
search_parser.parse_args()
|
|
193
269
|
try:
|
|
194
270
|
return search.query(Dataset, **multi_to_dict(request.args))
|
|
195
271
|
except NotImplementedError:
|
|
196
|
-
abort(501,
|
|
272
|
+
abort(501, "Search endpoint not enabled")
|
|
197
273
|
except RuntimeError:
|
|
198
|
-
abort(500,
|
|
274
|
+
abort(500, "Internal search service error")
|
|
199
275
|
|
|
200
276
|
|
|
201
|
-
@ns.route(
|
|
202
|
-
@apiv2.response(404,
|
|
203
|
-
@apiv2.response(410,
|
|
277
|
+
@ns.route("/<dataset:dataset>/", endpoint="dataset", doc=common_doc)
|
|
278
|
+
@apiv2.response(404, "Dataset not found")
|
|
279
|
+
@apiv2.response(410, "Dataset has been deleted")
|
|
204
280
|
class DatasetAPI(API):
|
|
205
|
-
@apiv2.doc(
|
|
281
|
+
@apiv2.doc("get_dataset")
|
|
206
282
|
@apiv2.marshal_with(dataset_fields)
|
|
207
283
|
def get(self, dataset):
|
|
208
|
-
|
|
284
|
+
"""Get a dataset given its identifier"""
|
|
209
285
|
if dataset.deleted and not DatasetEditPermission(dataset).can():
|
|
210
|
-
apiv2.abort(410,
|
|
286
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
211
287
|
return dataset
|
|
212
288
|
|
|
213
289
|
|
|
214
|
-
@ns.route(
|
|
215
|
-
|
|
216
|
-
@apiv2.response(400,
|
|
217
|
-
@apiv2.response(
|
|
218
|
-
@apiv2.response(
|
|
219
|
-
@apiv2.response(410, 'Dataset has been deleted')
|
|
290
|
+
@ns.route("/<dataset:dataset>/extras/", endpoint="dataset_extras", doc=common_doc)
|
|
291
|
+
@apiv2.response(400, "Wrong payload format, dict expected")
|
|
292
|
+
@apiv2.response(400, "Wrong payload format, list expected")
|
|
293
|
+
@apiv2.response(404, "Dataset not found")
|
|
294
|
+
@apiv2.response(410, "Dataset has been deleted")
|
|
220
295
|
class DatasetExtrasAPI(API):
|
|
221
|
-
@apiv2.doc(
|
|
296
|
+
@apiv2.doc("get_dataset_extras")
|
|
222
297
|
def get(self, dataset):
|
|
223
|
-
|
|
298
|
+
"""Get a dataset extras given its identifier"""
|
|
224
299
|
if dataset.deleted and not DatasetEditPermission(dataset).can():
|
|
225
|
-
apiv2.abort(410,
|
|
300
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
226
301
|
return dataset.extras
|
|
227
302
|
|
|
228
303
|
@apiv2.secure
|
|
229
|
-
@apiv2.doc(
|
|
304
|
+
@apiv2.doc("update_dataset_extras")
|
|
230
305
|
def put(self, dataset):
|
|
231
|
-
|
|
306
|
+
"""Update a given dataset extras"""
|
|
232
307
|
data = request.json
|
|
233
308
|
if not isinstance(data, dict):
|
|
234
|
-
apiv2.abort(400,
|
|
309
|
+
apiv2.abort(400, "Wrong payload format, dict expected")
|
|
235
310
|
if dataset.deleted:
|
|
236
|
-
apiv2.abort(410,
|
|
311
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
237
312
|
DatasetEditPermission(dataset).test()
|
|
238
313
|
# first remove extras key associated to a None value in payload
|
|
239
314
|
for key in [k for k in data if data[k] is None]:
|
|
@@ -242,52 +317,52 @@ class DatasetExtrasAPI(API):
|
|
|
242
317
|
# then update the extras with the remaining payload
|
|
243
318
|
dataset.extras.update(data)
|
|
244
319
|
try:
|
|
245
|
-
dataset.save(signal_kwargs={
|
|
320
|
+
dataset.save(signal_kwargs={"ignores": ["post_save"]})
|
|
246
321
|
except mongoengine.errors.ValidationError as e:
|
|
247
322
|
apiv2.abort(400, e.message)
|
|
248
323
|
return dataset.extras
|
|
249
324
|
|
|
250
325
|
@apiv2.secure
|
|
251
|
-
@apiv2.doc(
|
|
326
|
+
@apiv2.doc("delete_dataset_extras")
|
|
252
327
|
def delete(self, dataset):
|
|
253
|
-
|
|
328
|
+
"""Delete a given dataset extras key on a given dataset"""
|
|
254
329
|
data = request.json
|
|
255
330
|
if not isinstance(data, list):
|
|
256
|
-
apiv2.abort(400,
|
|
331
|
+
apiv2.abort(400, "Wrong payload format, list expected")
|
|
257
332
|
if dataset.deleted:
|
|
258
|
-
apiv2.abort(410,
|
|
333
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
259
334
|
DatasetEditPermission(dataset).test()
|
|
260
335
|
for key in data:
|
|
261
336
|
try:
|
|
262
337
|
del dataset.extras[key]
|
|
263
338
|
except KeyError:
|
|
264
339
|
pass
|
|
265
|
-
dataset.save(signal_kwargs={
|
|
340
|
+
dataset.save(signal_kwargs={"ignores": ["post_save"]})
|
|
266
341
|
return dataset.extras, 204
|
|
267
342
|
|
|
268
343
|
|
|
269
|
-
@ns.route(
|
|
344
|
+
@ns.route("/<dataset:dataset>/resources/", endpoint="resources")
|
|
270
345
|
class ResourcesAPI(API):
|
|
271
|
-
@apiv2.doc(
|
|
346
|
+
@apiv2.doc("list_resources")
|
|
272
347
|
@apiv2.expect(resources_parser)
|
|
273
348
|
@apiv2.marshal_with(resource_page_fields)
|
|
274
349
|
def get(self, dataset):
|
|
275
|
-
|
|
350
|
+
"""Get the given dataset resources, paginated."""
|
|
276
351
|
args = resources_parser.parse_args()
|
|
277
|
-
page = args[
|
|
278
|
-
page_size = args[
|
|
279
|
-
list_resources_url = url_for(
|
|
352
|
+
page = args["page"]
|
|
353
|
+
page_size = args["page_size"]
|
|
354
|
+
list_resources_url = url_for("apiv2.resources", dataset=dataset.id, _external=True)
|
|
280
355
|
next_page = f"{list_resources_url}?page={page + 1}&page_size={page_size}"
|
|
281
356
|
previous_page = f"{list_resources_url}?page={page - 1}&page_size={page_size}"
|
|
282
357
|
res = dataset.resources
|
|
283
358
|
|
|
284
|
-
if args[
|
|
285
|
-
res = [elem for elem in res if elem[
|
|
359
|
+
if args["type"]:
|
|
360
|
+
res = [elem for elem in res if elem["type"] == args["type"]]
|
|
286
361
|
next_page += f"&type={args['type']}"
|
|
287
362
|
previous_page += f"&type={args['type']}"
|
|
288
363
|
|
|
289
|
-
if args[
|
|
290
|
-
res = [elem for elem in res if args[
|
|
364
|
+
if args["q"]:
|
|
365
|
+
res = [elem for elem in res if args["q"].lower() in elem["title"].lower()]
|
|
291
366
|
next_page += f"&q={args['q']}"
|
|
292
367
|
previous_page += f"&q={args['q']}"
|
|
293
368
|
|
|
@@ -295,63 +370,64 @@ class ResourcesAPI(API):
|
|
|
295
370
|
offset = page_size * (page - 1)
|
|
296
371
|
else:
|
|
297
372
|
offset = 0
|
|
298
|
-
paginated_result = res[offset:(page_size + offset if page_size is not None else None)]
|
|
373
|
+
paginated_result = res[offset : (page_size + offset if page_size is not None else None)]
|
|
299
374
|
|
|
300
375
|
return {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
376
|
+
"data": paginated_result,
|
|
377
|
+
"next_page": next_page if page_size + offset < len(res) else None,
|
|
378
|
+
"page": page,
|
|
379
|
+
"page_size": page_size,
|
|
380
|
+
"previous_page": previous_page if page > 1 else None,
|
|
381
|
+
"total": len(res),
|
|
307
382
|
}
|
|
308
383
|
|
|
309
384
|
|
|
310
|
-
@ns.route(
|
|
385
|
+
@ns.route("/resources/<uuid:rid>/", endpoint="resource")
|
|
311
386
|
class ResourceAPI(API):
|
|
312
|
-
@apiv2.doc(
|
|
387
|
+
@apiv2.doc("get_resource")
|
|
313
388
|
def get(self, rid):
|
|
314
389
|
dataset = Dataset.objects(resources__id=rid).first()
|
|
315
390
|
if dataset:
|
|
316
|
-
resource = get_by(dataset.resources,
|
|
391
|
+
resource = get_by(dataset.resources, "id", rid)
|
|
317
392
|
else:
|
|
318
393
|
resource = CommunityResource.objects(id=rid).first()
|
|
319
394
|
if not resource:
|
|
320
|
-
apiv2.abort(404,
|
|
395
|
+
apiv2.abort(404, "Resource does not exist")
|
|
321
396
|
|
|
322
397
|
# Manually marshalling to make sure resource.dataset is in the scope.
|
|
323
398
|
# See discussions in https://github.com/opendatateam/udata/pull/2732/files
|
|
324
|
-
return marshal(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
@ns.route(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
@apiv2.
|
|
334
|
-
@apiv2.response(400,
|
|
335
|
-
@apiv2.response(
|
|
336
|
-
@apiv2.response(
|
|
399
|
+
return marshal(
|
|
400
|
+
{"resource": resource, "dataset_id": dataset.id if dataset else None},
|
|
401
|
+
specific_resource_fields,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@ns.route(
|
|
406
|
+
"/<dataset:dataset>/resources/<uuid:rid>/extras/", endpoint="resource_extras", doc=common_doc
|
|
407
|
+
)
|
|
408
|
+
@apiv2.param("rid", "The resource unique identifier")
|
|
409
|
+
@apiv2.response(400, "Wrong payload format, dict expected")
|
|
410
|
+
@apiv2.response(400, "Wrong payload format, list expected")
|
|
411
|
+
@apiv2.response(404, "Key not found in existing extras")
|
|
412
|
+
@apiv2.response(410, "Dataset has been deleted")
|
|
337
413
|
class ResourceExtrasAPI(ResourceMixin, API):
|
|
338
|
-
@apiv2.doc(
|
|
414
|
+
@apiv2.doc("get_resource_extras")
|
|
339
415
|
def get(self, dataset, rid):
|
|
340
|
-
|
|
416
|
+
"""Get a resource extras given its identifier"""
|
|
341
417
|
if dataset.deleted and not DatasetEditPermission(dataset).can():
|
|
342
|
-
apiv2.abort(410,
|
|
418
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
343
419
|
resource = self.get_resource_or_404(dataset, rid)
|
|
344
420
|
return resource.extras
|
|
345
421
|
|
|
346
422
|
@apiv2.secure
|
|
347
|
-
@apiv2.doc(
|
|
423
|
+
@apiv2.doc("update_resource_extras")
|
|
348
424
|
def put(self, dataset, rid):
|
|
349
|
-
|
|
425
|
+
"""Update a given resource extras on a given dataset"""
|
|
350
426
|
data = request.json
|
|
351
427
|
if not isinstance(data, dict):
|
|
352
|
-
apiv2.abort(400,
|
|
428
|
+
apiv2.abort(400, "Wrong payload format, dict expected")
|
|
353
429
|
if dataset.deleted:
|
|
354
|
-
apiv2.abort(410,
|
|
430
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
355
431
|
ResourceEditPermission(dataset).test()
|
|
356
432
|
resource = self.get_resource_or_404(dataset, rid)
|
|
357
433
|
# first remove extras key associated to a None value in payload
|
|
@@ -360,24 +436,24 @@ class ResourceExtrasAPI(ResourceMixin, API):
|
|
|
360
436
|
data.pop(key)
|
|
361
437
|
# then update the extras with the remaining payload
|
|
362
438
|
resource.extras.update(data)
|
|
363
|
-
resource.save(signal_kwargs={
|
|
439
|
+
resource.save(signal_kwargs={"ignores": ["post_save"]})
|
|
364
440
|
return resource.extras
|
|
365
441
|
|
|
366
442
|
@apiv2.secure
|
|
367
|
-
@apiv2.doc(
|
|
443
|
+
@apiv2.doc("delete_resource_extras")
|
|
368
444
|
def delete(self, dataset, rid):
|
|
369
|
-
|
|
445
|
+
"""Delete a given resource extras key on a given dataset"""
|
|
370
446
|
data = request.json
|
|
371
447
|
if not isinstance(data, list):
|
|
372
|
-
apiv2.abort(400,
|
|
448
|
+
apiv2.abort(400, "Wrong payload format, list expected")
|
|
373
449
|
if dataset.deleted:
|
|
374
|
-
apiv2.abort(410,
|
|
450
|
+
apiv2.abort(410, "Dataset has been deleted")
|
|
375
451
|
ResourceEditPermission(dataset).test()
|
|
376
452
|
resource = self.get_resource_or_404(dataset, rid)
|
|
377
453
|
try:
|
|
378
454
|
for key in data:
|
|
379
455
|
del resource.extras[key]
|
|
380
456
|
except KeyError:
|
|
381
|
-
apiv2.abort(404,
|
|
382
|
-
resource.save(signal_kwargs={
|
|
457
|
+
apiv2.abort(404, "Key not found in existing extras")
|
|
458
|
+
resource.save(signal_kwargs={"ignores": ["post_save"]})
|
|
383
459
|
return resource.extras, 204
|