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/routing.py
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
from bson import ObjectId
|
|
2
1
|
from uuid import UUID
|
|
3
2
|
|
|
4
|
-
from
|
|
3
|
+
from bson import ObjectId
|
|
4
|
+
from flask import redirect, request, url_for
|
|
5
5
|
from mongoengine.errors import InvalidQueryError, ValidationError
|
|
6
6
|
from werkzeug.exceptions import NotFound
|
|
7
7
|
from werkzeug.routing import BaseConverter, PathConverter
|
|
8
8
|
from werkzeug.urls import url_quote
|
|
9
9
|
|
|
10
10
|
from udata import models
|
|
11
|
-
from udata.mongo import db
|
|
12
|
-
from udata.core.spatial.models import GeoZone
|
|
13
11
|
from udata.core.dataservices.models import Dataservice
|
|
12
|
+
from udata.core.spatial.models import GeoZone
|
|
14
13
|
from udata.i18n import ISO_639_1_CODES
|
|
14
|
+
from udata.mongo import db
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class LazyRedirect(object):
|
|
18
|
-
|
|
18
|
+
"""Store location for lazy redirections"""
|
|
19
|
+
|
|
19
20
|
def __init__(self, arg):
|
|
20
21
|
self.arg = arg
|
|
21
22
|
|
|
@@ -23,25 +24,23 @@ class LazyRedirect(object):
|
|
|
23
24
|
class LanguagePrefixConverter(BaseConverter):
|
|
24
25
|
def __init__(self, map):
|
|
25
26
|
super(LanguagePrefixConverter, self).__init__(map)
|
|
26
|
-
self.regex =
|
|
27
|
+
self.regex = "(?:%s)" % "|".join(ISO_639_1_CODES)
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class ListConverter(BaseConverter):
|
|
30
31
|
def to_python(self, value):
|
|
31
|
-
return value.split(
|
|
32
|
+
return value.split(",")
|
|
32
33
|
|
|
33
34
|
def to_url(self, values):
|
|
34
|
-
return
|
|
35
|
-
for value in values)
|
|
35
|
+
return ",".join(super(ListConverter, self).to_url(value) for value in values)
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class PathListConverter(PathConverter):
|
|
39
39
|
def to_python(self, value):
|
|
40
|
-
return value.split(
|
|
40
|
+
return value.split(",")
|
|
41
41
|
|
|
42
42
|
def to_url(self, values):
|
|
43
|
-
return
|
|
44
|
-
for value in values)
|
|
43
|
+
return ",".join(super(PathListConverter, self).to_url(value) for value in values)
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
class UUIDConverter(BaseConverter):
|
|
@@ -53,7 +52,7 @@ class UUIDConverter(BaseConverter):
|
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
class ModelConverter(BaseConverter):
|
|
56
|
-
|
|
55
|
+
"""
|
|
57
56
|
A base class helper for model helper.
|
|
58
57
|
|
|
59
58
|
Allow to give model or slug or ObjectId as parameter to url_for().
|
|
@@ -66,13 +65,13 @@ class ModelConverter(BaseConverter):
|
|
|
66
65
|
* fetch by id
|
|
67
66
|
* fetch by slug
|
|
68
67
|
* raise 404
|
|
69
|
-
|
|
68
|
+
"""
|
|
70
69
|
|
|
71
70
|
model = None
|
|
72
71
|
|
|
73
72
|
@property
|
|
74
73
|
def has_slug(self):
|
|
75
|
-
return hasattr(self.model,
|
|
74
|
+
return hasattr(self.model, "slug") and isinstance(self.model.slug, db.SlugField)
|
|
76
75
|
|
|
77
76
|
@property
|
|
78
77
|
def has_redirected_slug(self):
|
|
@@ -110,9 +109,9 @@ class ModelConverter(BaseConverter):
|
|
|
110
109
|
return self.quote(obj)
|
|
111
110
|
elif isinstance(obj, (ObjectId, UUID)):
|
|
112
111
|
return str(obj)
|
|
113
|
-
elif getattr(obj,
|
|
112
|
+
elif getattr(obj, "slug", None):
|
|
114
113
|
return self.quote(obj.slug)
|
|
115
|
-
elif getattr(obj,
|
|
114
|
+
elif getattr(obj, "id", None):
|
|
116
115
|
return str(obj.id)
|
|
117
116
|
else:
|
|
118
117
|
raise ValueError('Unable to serialize "%s" to url' % obj)
|
|
@@ -155,7 +154,7 @@ class ContactPointConverter(ModelConverter):
|
|
|
155
154
|
|
|
156
155
|
|
|
157
156
|
class TerritoryConverter(PathConverter):
|
|
158
|
-
DEFAULT_PREFIX =
|
|
157
|
+
DEFAULT_PREFIX = "fr" # TODO: make it a setting parameter
|
|
159
158
|
|
|
160
159
|
def to_python(self, value):
|
|
161
160
|
"""
|
|
@@ -166,10 +165,10 @@ class TerritoryConverter(PathConverter):
|
|
|
166
165
|
|
|
167
166
|
Note that the slug is not significative but cannot be omitted.
|
|
168
167
|
"""
|
|
169
|
-
if
|
|
168
|
+
if "/" not in value:
|
|
170
169
|
return NotFound()
|
|
171
170
|
|
|
172
|
-
level, code = value.split(
|
|
171
|
+
level, code = value.split("/")[:2] # Ignore optional slug
|
|
173
172
|
|
|
174
173
|
geoid = GeoZone.SEPARATOR.join([level, code])
|
|
175
174
|
zone = GeoZone.objects.resolve(geoid)
|
|
@@ -186,27 +185,23 @@ class TerritoryConverter(PathConverter):
|
|
|
186
185
|
"""
|
|
187
186
|
Reconstruct the URL from level name, code or datagouv id and slug.
|
|
188
187
|
"""
|
|
189
|
-
level_name = getattr(obj,
|
|
188
|
+
level_name = getattr(obj, "level_name", None)
|
|
190
189
|
if not level_name:
|
|
191
190
|
raise ValueError('Unable to serialize "%s" to url' % obj)
|
|
192
191
|
|
|
193
|
-
code = getattr(obj,
|
|
194
|
-
slug = getattr(obj,
|
|
192
|
+
code = getattr(obj, "code", None)
|
|
193
|
+
slug = getattr(obj, "slug", None)
|
|
195
194
|
if code and slug:
|
|
196
|
-
return
|
|
197
|
-
level_name=level_name,
|
|
198
|
-
code=code,
|
|
199
|
-
slug=slug
|
|
200
|
-
)
|
|
195
|
+
return "{level_name}/{code}/{slug}".format(level_name=level_name, code=code, slug=slug)
|
|
201
196
|
else:
|
|
202
197
|
raise ValueError('Unable to serialize "%s" to url' % obj)
|
|
203
198
|
|
|
204
199
|
|
|
205
200
|
def lazy_raise_or_redirect():
|
|
206
|
-
|
|
201
|
+
"""
|
|
207
202
|
Raise exception lazily to ensure request.endpoint is set
|
|
208
203
|
Also perform redirect if needed
|
|
209
|
-
|
|
204
|
+
"""
|
|
210
205
|
if not request.view_args:
|
|
211
206
|
return
|
|
212
207
|
for name, value in request.view_args.items():
|
|
@@ -217,22 +212,22 @@ def lazy_raise_or_redirect():
|
|
|
217
212
|
new_args = request.view_args
|
|
218
213
|
new_args[name] = value.arg
|
|
219
214
|
new_url = url_for(request.endpoint, **new_args)
|
|
220
|
-
return redirect(new_url,
|
|
215
|
+
return redirect(new_url, 308)
|
|
221
216
|
|
|
222
217
|
|
|
223
218
|
def init_app(app):
|
|
224
219
|
app.before_request(lazy_raise_or_redirect)
|
|
225
|
-
app.url_map.converters[
|
|
226
|
-
app.url_map.converters[
|
|
227
|
-
app.url_map.converters[
|
|
228
|
-
app.url_map.converters[
|
|
229
|
-
app.url_map.converters[
|
|
230
|
-
app.url_map.converters[
|
|
231
|
-
app.url_map.converters[
|
|
232
|
-
app.url_map.converters[
|
|
233
|
-
app.url_map.converters[
|
|
234
|
-
app.url_map.converters[
|
|
235
|
-
app.url_map.converters[
|
|
236
|
-
app.url_map.converters[
|
|
237
|
-
app.url_map.converters[
|
|
238
|
-
app.url_map.converters[
|
|
220
|
+
app.url_map.converters["lang"] = LanguagePrefixConverter
|
|
221
|
+
app.url_map.converters["list"] = ListConverter
|
|
222
|
+
app.url_map.converters["pathlist"] = PathListConverter
|
|
223
|
+
app.url_map.converters["uuid"] = UUIDConverter
|
|
224
|
+
app.url_map.converters["dataset"] = DatasetConverter
|
|
225
|
+
app.url_map.converters["dataservice"] = DataserviceConverter
|
|
226
|
+
app.url_map.converters["crid"] = CommunityResourceConverter
|
|
227
|
+
app.url_map.converters["org"] = OrganizationConverter
|
|
228
|
+
app.url_map.converters["reuse"] = ReuseConverter
|
|
229
|
+
app.url_map.converters["user"] = UserConverter
|
|
230
|
+
app.url_map.converters["topic"] = TopicConverter
|
|
231
|
+
app.url_map.converters["post"] = PostConverter
|
|
232
|
+
app.url_map.converters["territory"] = TerritoryConverter
|
|
233
|
+
app.url_map.converters["contact_point"] = ContactPointConverter
|
udata/search/__init__.py
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
import requests
|
|
3
|
-
import udata.event # noqa
|
|
4
|
-
# Import udata event in order for datasets event hooks to be executed
|
|
5
4
|
|
|
5
|
+
# Import udata event in order for datasets event hooks to be executed
|
|
6
6
|
from flask import current_app
|
|
7
|
-
from mongoengine.signals import
|
|
7
|
+
from mongoengine.signals import post_delete, post_save
|
|
8
8
|
|
|
9
|
+
import udata.event # noqa
|
|
9
10
|
from udata.mongo import db
|
|
10
|
-
from udata.tasks import
|
|
11
|
+
from udata.tasks import as_task_param, task
|
|
11
12
|
|
|
12
13
|
log = logging.getLogger(__name__)
|
|
13
14
|
|
|
14
15
|
adapter_catalog = {}
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
@task(route=
|
|
18
|
+
@task(route="high.search")
|
|
18
19
|
def reindex(classname, id):
|
|
19
|
-
if not current_app.config[
|
|
20
|
+
if not current_app.config["SEARCH_SERVICE_API_URL"]:
|
|
20
21
|
return
|
|
21
22
|
model = db.resolve_model(classname)
|
|
22
23
|
obj = model.objects.get(pk=id)
|
|
23
24
|
adapter_class = adapter_catalog.get(model)
|
|
24
25
|
document = adapter_class.serialize(obj)
|
|
25
26
|
if adapter_class.is_indexable(obj):
|
|
26
|
-
log.info(
|
|
27
|
+
log.info("Indexing %s (%s)", model.__name__, obj.id)
|
|
27
28
|
url = f"{current_app.config['SEARCH_SERVICE_API_URL']}{adapter_class.search_url}index"
|
|
28
29
|
try:
|
|
29
|
-
payload = {
|
|
30
|
-
'document': document
|
|
31
|
-
}
|
|
30
|
+
payload = {"document": document}
|
|
32
31
|
r = requests.post(url, json=payload)
|
|
33
32
|
r.raise_for_status()
|
|
34
33
|
except Exception:
|
|
35
34
|
log.exception('Unable to index/unindex %s "%s"', model.__name__, str(obj.id))
|
|
36
35
|
else:
|
|
37
|
-
log.info(
|
|
36
|
+
log.info("Unindexing %s (%s)", model.__name__, obj.id)
|
|
38
37
|
url = f"{current_app.config['SEARCH_SERVICE_API_URL']}{adapter_class.search_url}{str(obj.id)}/unindex"
|
|
39
38
|
try:
|
|
40
39
|
r = requests.delete(url)
|
|
@@ -46,13 +45,13 @@ def reindex(classname, id):
|
|
|
46
45
|
log.exception('Unable to index/unindex %s "%s"', model.__name__, str(obj.id))
|
|
47
46
|
|
|
48
47
|
|
|
49
|
-
@task(route=
|
|
48
|
+
@task(route="high.search")
|
|
50
49
|
def unindex(classname, id):
|
|
51
|
-
if not current_app.config[
|
|
50
|
+
if not current_app.config["SEARCH_SERVICE_API_URL"]:
|
|
52
51
|
return
|
|
53
52
|
model = db.resolve_model(classname)
|
|
54
53
|
adapter_class = adapter_catalog.get(model)
|
|
55
|
-
log.info(
|
|
54
|
+
log.info("Unindexing %s (%s)", model.__name__, id)
|
|
56
55
|
try:
|
|
57
56
|
url = f"{current_app.config['SEARCH_SERVICE_API_URL']}{adapter_class.search_url}/{str(id)}/unindex"
|
|
58
57
|
r = requests.delete(url)
|
|
@@ -65,19 +64,19 @@ def unindex(classname, id):
|
|
|
65
64
|
|
|
66
65
|
|
|
67
66
|
def reindex_model_on_save(sender, document, **kwargs):
|
|
68
|
-
|
|
69
|
-
if current_app.config.get(
|
|
67
|
+
"""(Re/Un)Index Mongo document on post_save"""
|
|
68
|
+
if current_app.config.get("AUTO_INDEX") and current_app.config["SEARCH_SERVICE_API_URL"]:
|
|
70
69
|
reindex.delay(*as_task_param(document))
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
def unindex_model_on_delete(sender, document, **kwargs):
|
|
74
|
-
|
|
75
|
-
if current_app.config.get(
|
|
73
|
+
"""Unindex Mongo document on post_delete"""
|
|
74
|
+
if current_app.config.get("AUTO_INDEX") and current_app.config["SEARCH_SERVICE_API_URL"]:
|
|
76
75
|
unindex.delay(*as_task_param(document))
|
|
77
76
|
|
|
78
77
|
|
|
79
78
|
def register(adapter):
|
|
80
|
-
|
|
79
|
+
"""Register a search adapter"""
|
|
81
80
|
# register the class in the catalog
|
|
82
81
|
if adapter.model and adapter.model not in adapter_catalog:
|
|
83
82
|
adapter_catalog[adapter.model] = adapter
|
udata/search/adapter.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
from flask_restx.reqparse import RequestParser
|
|
3
|
-
from udata.search.query import SearchQuery
|
|
4
4
|
|
|
5
|
+
from udata.search.query import SearchQuery
|
|
5
6
|
|
|
6
7
|
log = logging.getLogger(__name__)
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class ModelSearchAdapter:
|
|
10
11
|
"""This class allow to describe and customize the search behavior."""
|
|
12
|
+
|
|
11
13
|
model = None
|
|
12
14
|
sorts = None
|
|
13
15
|
search_url = None
|
|
@@ -18,7 +20,7 @@ class ModelSearchAdapter:
|
|
|
18
20
|
"""By default use the ``to_dict`` method
|
|
19
21
|
and exclude ``_id``, ``_cls`` and ``owner`` fields
|
|
20
22
|
"""
|
|
21
|
-
return document.to_dict(exclude=(
|
|
23
|
+
return document.to_dict(exclude=("_id", "_cls", "owner"))
|
|
22
24
|
|
|
23
25
|
@classmethod
|
|
24
26
|
def is_indexable(cls, document):
|
|
@@ -28,40 +30,39 @@ class ModelSearchAdapter:
|
|
|
28
30
|
def as_request_parser(cls, paginate=True):
|
|
29
31
|
parser = RequestParser()
|
|
30
32
|
# q parameter
|
|
31
|
-
parser.add_argument(
|
|
32
|
-
help='The search query')
|
|
33
|
+
parser.add_argument("q", type=str, location="args", help="The search query")
|
|
33
34
|
# Add filters arguments
|
|
34
35
|
for name, type in cls.filters.items():
|
|
35
36
|
kwargs = type.as_request_parser_kwargs()
|
|
36
|
-
parser.add_argument(name, location=
|
|
37
|
+
parser.add_argument(name, location="args", **kwargs)
|
|
37
38
|
# Sort arguments
|
|
38
39
|
keys = list(cls.sorts)
|
|
39
|
-
choices = keys + [
|
|
40
|
-
help_msg =
|
|
41
|
-
parser.add_argument(
|
|
42
|
-
help=help_msg)
|
|
40
|
+
choices = keys + ["-" + k for k in keys]
|
|
41
|
+
help_msg = "The field (and direction) on which sorting apply"
|
|
42
|
+
parser.add_argument("sort", type=str, location="args", choices=choices, help=help_msg)
|
|
43
43
|
if paginate:
|
|
44
|
-
parser.add_argument(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"page", type=int, location="args", default=1, help="The page to display"
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"page_size", type=int, location="args", default=20, help="The page size"
|
|
49
|
+
)
|
|
48
50
|
return parser
|
|
49
51
|
|
|
50
52
|
@classmethod
|
|
51
53
|
def parse_sort(cls, sort):
|
|
52
54
|
if sort:
|
|
53
|
-
if sort.startswith(
|
|
55
|
+
if sort.startswith("-"):
|
|
54
56
|
# Keyerror because of the '-' character in front of the argument.
|
|
55
57
|
# It is removed to find the value in dict and added back.
|
|
56
58
|
arg_sort = sort[1:]
|
|
57
|
-
sort =
|
|
59
|
+
sort = "-" + cls.sorts[arg_sort]
|
|
58
60
|
else:
|
|
59
61
|
sort = cls.sorts[sort]
|
|
60
62
|
return sort
|
|
61
63
|
|
|
62
64
|
@classmethod
|
|
63
65
|
def temp_search(cls):
|
|
64
|
-
|
|
65
66
|
class TempSearch(SearchQuery):
|
|
66
67
|
adapter = cls
|
|
67
68
|
model = cls.model
|
udata/search/commands.py
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from flask import current_app
|
|
3
1
|
import logging
|
|
4
2
|
import sys
|
|
5
|
-
import
|
|
3
|
+
from datetime import datetime
|
|
6
4
|
|
|
7
5
|
import click
|
|
6
|
+
import requests
|
|
7
|
+
from flask import current_app
|
|
8
8
|
|
|
9
9
|
from udata.commands import cli
|
|
10
10
|
from udata.search import adapter_catalog
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
log = logging.getLogger(__name__)
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
@cli.group(
|
|
15
|
+
@cli.group("search")
|
|
17
16
|
def grp():
|
|
18
|
-
|
|
17
|
+
"""Search/Indexation related operations"""
|
|
19
18
|
pass
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
TIMESTAMP_FORMAT =
|
|
21
|
+
TIMESTAMP_FORMAT = "%Y-%m-%d-%H-%M"
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
def default_index_suffix_name(now):
|
|
26
|
-
|
|
25
|
+
"""Build a time based index suffix name"""
|
|
27
26
|
return now.strftime(TIMESTAMP_FORMAT)
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
def iter_adapters():
|
|
31
|
-
|
|
30
|
+
"""Iter over adapter in predictable way"""
|
|
32
31
|
adapters = adapter_catalog.values()
|
|
33
32
|
return sorted(adapters, key=lambda a: a.model.__name__)
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
def iter_qs(qs, adapter):
|
|
37
|
-
|
|
36
|
+
"""Safely iterate over a DB QuerySet yielding a tuple (indexability, serialized documents)"""
|
|
38
37
|
for obj in qs.no_cache().timeout(False):
|
|
39
38
|
indexable = adapter.is_indexable(obj)
|
|
40
39
|
try:
|
|
@@ -42,28 +41,25 @@ def iter_qs(qs, adapter):
|
|
|
42
41
|
yield indexable, doc
|
|
43
42
|
except Exception as e:
|
|
44
43
|
model = adapter.model.__name__
|
|
45
|
-
log.error('Unable to index %s "%s": %s', model, str(obj.id),
|
|
46
|
-
str(e), exc_info=True)
|
|
44
|
+
log.error('Unable to index %s "%s": %s', model, str(obj.id), str(e), exc_info=True)
|
|
47
45
|
|
|
48
46
|
|
|
49
47
|
def index_model(adapter, start, reindex=False, from_datetime=None):
|
|
50
|
-
|
|
48
|
+
"""Index or unindex all objects given a model"""
|
|
51
49
|
model = adapter.model
|
|
52
|
-
search_service_url = current_app.config[
|
|
53
|
-
log.info(
|
|
50
|
+
search_service_url = current_app.config["SEARCH_SERVICE_API_URL"]
|
|
51
|
+
log.info("Indexing %s objects", model.__name__)
|
|
54
52
|
qs = model.objects
|
|
55
53
|
if from_datetime:
|
|
56
|
-
date_property = (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
qs = qs.filter(**{f
|
|
54
|
+
date_property = (
|
|
55
|
+
"last_modified_internal" if model.__name__.lower() in ["dataset"] else "last_modified"
|
|
56
|
+
)
|
|
57
|
+
qs = qs.filter(**{f"{date_property}__gte": from_datetime})
|
|
60
58
|
model_name = adapter.model.__name__.lower()
|
|
61
59
|
index_name = model_name
|
|
62
60
|
if reindex:
|
|
63
|
-
index_name +=
|
|
64
|
-
payload = {
|
|
65
|
-
'index': index_name
|
|
66
|
-
}
|
|
61
|
+
index_name += "-" + default_index_suffix_name(start)
|
|
62
|
+
payload = {"index": index_name}
|
|
67
63
|
url = f"{search_service_url}/create-index"
|
|
68
64
|
r = requests.post(url, json=payload)
|
|
69
65
|
r.raise_for_status()
|
|
@@ -73,10 +69,7 @@ def index_model(adapter, start, reindex=False, from_datetime=None):
|
|
|
73
69
|
with requests.Session() as session:
|
|
74
70
|
try:
|
|
75
71
|
if indexable:
|
|
76
|
-
payload = {
|
|
77
|
-
'document': doc,
|
|
78
|
-
'index': index_name
|
|
79
|
-
}
|
|
72
|
+
payload = {"document": doc, "index": index_name}
|
|
80
73
|
url = f"{search_service_url}/{model_name}s/index"
|
|
81
74
|
r = session.post(url, json=payload)
|
|
82
75
|
r.raise_for_status()
|
|
@@ -88,46 +81,46 @@ def index_model(adapter, start, reindex=False, from_datetime=None):
|
|
|
88
81
|
else:
|
|
89
82
|
continue
|
|
90
83
|
except Exception as e:
|
|
91
|
-
log.error(
|
|
92
|
-
|
|
84
|
+
log.error(
|
|
85
|
+
'Unable to index %s "%s": %s', model, str(doc["id"]), str(e), exc_info=True
|
|
86
|
+
)
|
|
93
87
|
|
|
94
88
|
|
|
95
89
|
def finalize_reindex(models, start):
|
|
96
90
|
try:
|
|
97
91
|
url = f"{current_app.config['SEARCH_SERVICE_API_URL']}/set-index-alias"
|
|
98
|
-
payload = {
|
|
99
|
-
'index_suffix_name': default_index_suffix_name(start),
|
|
100
|
-
'indices': models
|
|
101
|
-
}
|
|
92
|
+
payload = {"index_suffix_name": default_index_suffix_name(start), "indices": models}
|
|
102
93
|
r = requests.post(url, json=payload)
|
|
103
94
|
r.raise_for_status()
|
|
104
95
|
except Exception:
|
|
105
|
-
log.exception(f
|
|
96
|
+
log.exception(f"Unable to set alias for index")
|
|
106
97
|
|
|
107
98
|
modified_since_reindex = 0
|
|
108
99
|
for adapter in iter_adapters():
|
|
109
100
|
if not models or adapter.model.__name__.lower() in models:
|
|
110
|
-
date_property = (
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
date_property = (
|
|
102
|
+
"last_modified_internal"
|
|
103
|
+
if adapter.model.__name__.lower() in ["dataset"]
|
|
104
|
+
else "last_modified"
|
|
105
|
+
)
|
|
113
106
|
modified_since_reindex += adapter.model.objects(
|
|
114
|
-
**{f
|
|
107
|
+
**{f"{date_property}__gte": start}
|
|
115
108
|
).count()
|
|
116
109
|
|
|
117
110
|
log.warning(
|
|
118
|
-
f
|
|
119
|
-
f
|
|
120
|
-
f
|
|
121
|
-
f
|
|
111
|
+
f"{modified_since_reindex} documents have been modified since reindexation start. "
|
|
112
|
+
f"After having set the appropriate alias, you can index last changes since the "
|
|
113
|
+
f"beginning of the indexation. Example, you can run:\n"
|
|
114
|
+
f"`udata search index -f {default_index_suffix_name(start)}`"
|
|
122
115
|
)
|
|
123
116
|
|
|
124
117
|
|
|
125
118
|
@grp.command()
|
|
126
|
-
@click.argument(
|
|
127
|
-
@click.option(
|
|
128
|
-
@click.option(
|
|
119
|
+
@click.argument("models", nargs=-1, metavar="[<model> ...]")
|
|
120
|
+
@click.option("-r", "--reindex", default=False, type=bool)
|
|
121
|
+
@click.option("-f", "--from_datetime", type=str)
|
|
129
122
|
def index(models=None, reindex=True, from_datetime=None):
|
|
130
|
-
|
|
123
|
+
"""
|
|
131
124
|
Initialize or rebuild the search index
|
|
132
125
|
|
|
133
126
|
Models to reindex can optionally be specified as arguments.
|
|
@@ -136,9 +129,9 @@ def index(models=None, reindex=True, from_datetime=None):
|
|
|
136
129
|
If reindex is true, indexation will be made on a new index and unindexable documents ignored.
|
|
137
130
|
|
|
138
131
|
If from_datetime is specified, only models modified since this datetime will be indexed.
|
|
139
|
-
|
|
140
|
-
if not current_app.config[
|
|
141
|
-
log.error(
|
|
132
|
+
"""
|
|
133
|
+
if not current_app.config["SEARCH_SERVICE_API_URL"]:
|
|
134
|
+
log.error("Missing URL for search service")
|
|
142
135
|
sys.exit(-1)
|
|
143
136
|
|
|
144
137
|
start = datetime.utcnow()
|
|
@@ -146,10 +139,10 @@ def index(models=None, reindex=True, from_datetime=None):
|
|
|
146
139
|
from_datetime = datetime.strptime(from_datetime, TIMESTAMP_FORMAT)
|
|
147
140
|
|
|
148
141
|
doc_types_names = [m.__name__.lower() for m in adapter_catalog.keys()]
|
|
149
|
-
models = [model.lower().rstrip(
|
|
142
|
+
models = [model.lower().rstrip("s") for model in (models or [])]
|
|
150
143
|
for model in models:
|
|
151
144
|
if model not in doc_types_names:
|
|
152
|
-
log.error(
|
|
145
|
+
log.error("Unknown model %s", model)
|
|
153
146
|
sys.exit(-1)
|
|
154
147
|
|
|
155
148
|
for adapter in iter_adapters():
|
udata/search/fields.py
CHANGED
|
@@ -2,39 +2,36 @@ import logging
|
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
4
|
from bson.objectid import ObjectId
|
|
5
|
-
|
|
6
5
|
from flask_restx import inputs
|
|
7
6
|
|
|
8
7
|
from udata.utils import clean_string
|
|
9
8
|
|
|
10
9
|
log = logging.getLogger(__name__)
|
|
11
10
|
|
|
12
|
-
__all__ = (
|
|
13
|
-
'BoolFilter', 'ModelTermsFilter', 'TemporalCoverageFilter', 'Filter'
|
|
14
|
-
)
|
|
11
|
+
__all__ = ("BoolFilter", "ModelTermsFilter", "TemporalCoverageFilter", "Filter")
|
|
15
12
|
|
|
16
13
|
|
|
17
|
-
ES_NUM_FAILURES =
|
|
14
|
+
ES_NUM_FAILURES = "-Infinity", "Infinity", "NaN", None
|
|
18
15
|
|
|
19
|
-
RE_TIME_COVERAGE = re.compile(r
|
|
16
|
+
RE_TIME_COVERAGE = re.compile(r"\d{4}-\d{2}-\d{2}-\d{4}-\d{2}-\d{2}")
|
|
20
17
|
|
|
21
|
-
OR_SEPARATOR =
|
|
18
|
+
OR_SEPARATOR = "|"
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
class Filter:
|
|
25
22
|
@staticmethod
|
|
26
23
|
def as_request_parser_kwargs():
|
|
27
|
-
return {
|
|
24
|
+
return {"type": clean_string}
|
|
28
25
|
|
|
29
26
|
|
|
30
27
|
class BoolFilter(Filter):
|
|
31
28
|
@staticmethod
|
|
32
29
|
def as_request_parser_kwargs():
|
|
33
|
-
return {
|
|
30
|
+
return {"type": inputs.boolean}
|
|
34
31
|
|
|
35
32
|
|
|
36
33
|
class ModelTermsFilter(Filter):
|
|
37
|
-
def __init__(self, model, field_name=
|
|
34
|
+
def __init__(self, model, field_name="id"):
|
|
38
35
|
self.model = model
|
|
39
36
|
self.field_name = field_name
|
|
40
37
|
|
|
@@ -46,10 +43,7 @@ class ModelTermsFilter(Filter):
|
|
|
46
43
|
if isinstance(value, ObjectId):
|
|
47
44
|
return value
|
|
48
45
|
try:
|
|
49
|
-
return [
|
|
50
|
-
self.model_field.to_mongo(v)
|
|
51
|
-
for v in value.split(OR_SEPARATOR)
|
|
52
|
-
]
|
|
46
|
+
return [self.model_field.to_mongo(v) for v in value.split(OR_SEPARATOR)]
|
|
53
47
|
except Exception:
|
|
54
48
|
raise ValueError('"{0}" is not valid identifier'.format(value))
|
|
55
49
|
|
|
@@ -57,8 +51,7 @@ class ModelTermsFilter(Filter):
|
|
|
57
51
|
class TemporalCoverageFilter(Filter):
|
|
58
52
|
@classmethod
|
|
59
53
|
def validate_parameter(cls, value):
|
|
60
|
-
if not isinstance(value, str)
|
|
61
|
-
or not RE_TIME_COVERAGE.match(value):
|
|
54
|
+
if not isinstance(value, str) or not RE_TIME_COVERAGE.match(value):
|
|
62
55
|
msg = '"{0}" does not match YYYY-MM-DD-YYYY-MM-DD'.format(value)
|
|
63
56
|
raise ValueError(msg)
|
|
64
57
|
return value
|
|
@@ -66,8 +59,8 @@ class TemporalCoverageFilter(Filter):
|
|
|
66
59
|
@classmethod
|
|
67
60
|
def as_request_parser_kwargs(cls):
|
|
68
61
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
"type": cls.validate_parameter,
|
|
63
|
+
"help": "A date range expressed as start-end "
|
|
64
|
+
"where both dates are in iso format "
|
|
65
|
+
"(ie. YYYY-MM-DD-YYYY-MM-DD)",
|
|
73
66
|
}
|