udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30382__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 +135 -124
- 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 +56 -54
- 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/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/static/chunks/{11.e9b9ca1f3e03d4020377.js → 11.52e531c19f8de80c00cf.js} +3 -3
- udata/static/chunks/{11.e9b9ca1f3e03d4020377.js.map → 11.52e531c19f8de80c00cf.js.map} +1 -1
- udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js → 13.c3343a7f1070061c0e10.js} +2 -2
- udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js.map → 13.c3343a7f1070061c0e10.js.map} +1 -1
- udata/static/chunks/{16.0baa2b64a74a2dcde25c.js → 16.8fa42440ad75ca172e6d.js} +2 -2
- udata/static/chunks/{16.0baa2b64a74a2dcde25c.js.map → 16.8fa42440ad75ca172e6d.js.map} +1 -1
- udata/static/chunks/{19.350a9f150b074b4ecefa.js → 19.9c6c8412729cd6d59cfa.js} +3 -3
- udata/static/chunks/{19.350a9f150b074b4ecefa.js.map → 19.9c6c8412729cd6d59cfa.js.map} +1 -1
- udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js → 5.71d15c2e4f21feee2a9a.js} +3 -3
- udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js.map → 5.71d15c2e4f21feee2a9a.js.map} +1 -1
- udata/static/chunks/{6.d8a5f7b017bcbd083641.js → 6.9139dc098b8ea640b890.js} +3 -3
- udata/static/chunks/{6.d8a5f7b017bcbd083641.js.map → 6.9139dc098b8ea640b890.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- 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 +5 -6
- udata/tests/api/test_auth_api.py +395 -321
- udata/tests/api/test_base_api.py +31 -33
- 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 +76 -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_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.dev30382.dist-info}/METADATA +3 -2
- udata-9.1.2.dev30382.dist-info/RECORD +704 -0
- udata-9.1.2.dev30355.dist-info/RECORD +0 -704
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/LICENSE +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/WHEEL +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/entry_points.txt +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/top_level.txt +0 -0
udata/core/dataset/search.py
CHANGED
|
@@ -1,122 +1,133 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
from udata.core.dataset.api import DEFAULT_SORTING, DatasetApiParser
|
|
4
|
+
from udata.core.spatial.constants import ADMIN_LEVEL_MAX
|
|
5
|
+
from udata.core.spatial.models import admin_levels
|
|
6
|
+
from udata.models import Dataset, GeoZone, License, Organization, Topic, User
|
|
5
7
|
from udata.search import (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
BoolFilter,
|
|
9
|
+
Filter,
|
|
10
|
+
ModelSearchAdapter,
|
|
11
|
+
ModelTermsFilter,
|
|
12
|
+
TemporalCoverageFilter,
|
|
13
|
+
register,
|
|
9
14
|
)
|
|
10
|
-
from udata.core.spatial.models import admin_levels
|
|
11
|
-
from udata.core.spatial.constants import ADMIN_LEVEL_MAX
|
|
12
|
-
from udata.core.dataset.api import DatasetApiParser, DEFAULT_SORTING
|
|
13
15
|
from udata.utils import to_iso_datetime
|
|
14
16
|
|
|
15
|
-
__all__ = (
|
|
17
|
+
__all__ = ("DatasetSearch",)
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
@register
|
|
19
21
|
class DatasetSearch(ModelSearchAdapter):
|
|
20
22
|
model = Dataset
|
|
21
|
-
search_url =
|
|
23
|
+
search_url = "datasets/"
|
|
22
24
|
|
|
23
25
|
sorts = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
"created": "created_at_internal",
|
|
27
|
+
"last_update": "last_modified_internal",
|
|
28
|
+
"reuses": "metrics.reuses",
|
|
29
|
+
"followers": "metrics.followers",
|
|
30
|
+
"views": "metrics.views",
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
filters = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
"tag": Filter(),
|
|
35
|
+
"badge": Filter(),
|
|
36
|
+
"organization": ModelTermsFilter(model=Organization),
|
|
37
|
+
"owner": ModelTermsFilter(model=User),
|
|
38
|
+
"license": ModelTermsFilter(model=License),
|
|
39
|
+
"geozone": ModelTermsFilter(model=GeoZone),
|
|
40
|
+
"granularity": Filter(),
|
|
41
|
+
"format": Filter(),
|
|
42
|
+
"schema": Filter(),
|
|
43
|
+
"temporal_coverage": TemporalCoverageFilter(),
|
|
44
|
+
"featured": BoolFilter(),
|
|
45
|
+
"topic": ModelTermsFilter(model=Topic),
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
@classmethod
|
|
47
49
|
def is_indexable(cls, dataset):
|
|
48
|
-
return
|
|
49
|
-
not dataset.private)
|
|
50
|
+
return dataset.deleted is None and dataset.archived is None and not dataset.private
|
|
50
51
|
|
|
51
52
|
@classmethod
|
|
52
53
|
def mongo_search(cls, args):
|
|
53
54
|
datasets = Dataset.objects(archived=None, deleted=None, private=False)
|
|
54
55
|
datasets = DatasetApiParser.parse_filters(datasets, args)
|
|
55
56
|
|
|
56
|
-
sort =
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
sort = (
|
|
58
|
+
cls.parse_sort(args["sort"])
|
|
59
|
+
or ("$text_score" if args["q"] else None)
|
|
60
|
+
or DEFAULT_SORTING
|
|
61
|
+
)
|
|
62
|
+
offset = (args["page"] - 1) * args["page_size"]
|
|
63
|
+
return datasets.order_by(sort).skip(offset).limit(args["page_size"]), datasets.count()
|
|
59
64
|
|
|
60
65
|
@classmethod
|
|
61
66
|
def serialize(cls, dataset):
|
|
62
67
|
organization = None
|
|
63
68
|
owner = None
|
|
64
69
|
|
|
65
|
-
topics = Topic.objects(datasets=dataset).only(
|
|
70
|
+
topics = Topic.objects(datasets=dataset).only("id")
|
|
66
71
|
|
|
67
72
|
if dataset.organization:
|
|
68
73
|
org = Organization.objects(id=dataset.organization.id).first()
|
|
69
74
|
organization = {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
"id": str(org.id),
|
|
76
|
+
"name": org.name,
|
|
77
|
+
"public_service": 1 if org.public_service else 0,
|
|
78
|
+
"followers": org.metrics.get("followers", 0),
|
|
74
79
|
}
|
|
75
80
|
elif dataset.owner:
|
|
76
81
|
owner = User.objects(id=dataset.owner.id).first()
|
|
77
82
|
|
|
78
83
|
document = {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
"id": str(dataset.id),
|
|
85
|
+
"title": dataset.title,
|
|
86
|
+
"description": dataset.description,
|
|
87
|
+
"acronym": dataset.acronym or None,
|
|
88
|
+
"url": dataset.display_url,
|
|
89
|
+
"tags": dataset.tags,
|
|
90
|
+
"license": getattr(dataset.license, "id", None),
|
|
91
|
+
"badges": [badge.kind for badge in dataset.badges],
|
|
92
|
+
"frequency": dataset.frequency,
|
|
93
|
+
"created_at": to_iso_datetime(dataset.created_at),
|
|
94
|
+
"last_update": to_iso_datetime(dataset.last_update),
|
|
95
|
+
"views": dataset.metrics.get("views", 0),
|
|
96
|
+
"followers": dataset.metrics.get("followers", 0),
|
|
97
|
+
"reuses": dataset.metrics.get("reuses", 0),
|
|
98
|
+
"featured": 1 if dataset.featured else 0,
|
|
99
|
+
"resources_count": len(dataset.resources),
|
|
100
|
+
"organization": organization,
|
|
101
|
+
"owner": str(owner.id) if owner else None,
|
|
102
|
+
"format": [r.format.lower() for r in dataset.resources if r.format],
|
|
103
|
+
"schema": [r.schema.name for r in dataset.resources if r.schema],
|
|
104
|
+
"topics": [str(t.id) for t in topics if topics],
|
|
100
105
|
}
|
|
101
106
|
extras = {}
|
|
102
107
|
for key, value in dataset.extras.items():
|
|
103
108
|
extras[key] = to_iso_datetime(value) if isinstance(value, datetime.datetime) else value
|
|
104
|
-
document.update({
|
|
109
|
+
document.update({"extras": extras})
|
|
105
110
|
if dataset.harvest:
|
|
106
111
|
harvest = {}
|
|
107
112
|
for key, value in dataset.harvest._data.items():
|
|
108
|
-
harvest[key] =
|
|
109
|
-
|
|
113
|
+
harvest[key] = (
|
|
114
|
+
to_iso_datetime(value) if isinstance(value, datetime.datetime) else value
|
|
115
|
+
)
|
|
116
|
+
document.update({"harvest": harvest})
|
|
110
117
|
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
if (
|
|
119
|
+
dataset.temporal_coverage is not None
|
|
120
|
+
and dataset.temporal_coverage.start
|
|
121
|
+
and dataset.temporal_coverage.end
|
|
122
|
+
):
|
|
114
123
|
start = to_iso_datetime(dataset.temporal_coverage.start)
|
|
115
124
|
end = to_iso_datetime(dataset.temporal_coverage.end)
|
|
116
|
-
document.update(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
document.update(
|
|
126
|
+
{
|
|
127
|
+
"temporal_coverage_start": start,
|
|
128
|
+
"temporal_coverage_end": end,
|
|
129
|
+
}
|
|
130
|
+
)
|
|
120
131
|
|
|
121
132
|
if dataset.spatial is not None:
|
|
122
133
|
# Index precise zone labels to allow fast filtering.
|
|
@@ -125,13 +136,17 @@ class DatasetSearch(ModelSearchAdapter):
|
|
|
125
136
|
geozones = []
|
|
126
137
|
coverage_level = ADMIN_LEVEL_MAX
|
|
127
138
|
for zone in zones:
|
|
128
|
-
geozones.append(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
geozones.append(
|
|
140
|
+
{
|
|
141
|
+
"id": zone.id,
|
|
142
|
+
"name": zone.name,
|
|
143
|
+
}
|
|
144
|
+
)
|
|
132
145
|
coverage_level = min(coverage_level, admin_levels[zone.level])
|
|
133
|
-
document.update(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
document.update(
|
|
147
|
+
{
|
|
148
|
+
"geozones": geozones,
|
|
149
|
+
"granularity": dataset.spatial.granularity,
|
|
150
|
+
}
|
|
151
|
+
)
|
|
137
152
|
return document
|
udata/core/dataset/signals.py
CHANGED
udata/core/dataset/tasks.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import os
|
|
3
|
-
|
|
4
3
|
from datetime import datetime, timedelta
|
|
5
4
|
from tempfile import NamedTemporaryFile
|
|
6
5
|
|
|
@@ -13,28 +12,29 @@ from udata.core import storages
|
|
|
13
12
|
from udata.frontend import csv
|
|
14
13
|
from udata.harvest.models import HarvestJob
|
|
15
14
|
from udata.i18n import lazy_gettext as _
|
|
16
|
-
from udata.models import
|
|
17
|
-
Organization, Transfer, db)
|
|
15
|
+
from udata.models import Activity, Discussion, Follow, Organization, Topic, Transfer, db
|
|
18
16
|
from udata.tasks import job
|
|
19
17
|
|
|
20
|
-
from .models import Dataset, Resource, CommunityResource, Checksum
|
|
21
18
|
from .constants import UPDATE_FREQUENCIES
|
|
19
|
+
from .models import Checksum, CommunityResource, Dataset, Resource
|
|
22
20
|
|
|
23
21
|
log = get_task_logger(__name__)
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
def flatten(iterable):
|
|
27
25
|
for el in iterable:
|
|
28
|
-
if isinstance(el, collections.Iterable) and not (
|
|
26
|
+
if isinstance(el, collections.Iterable) and not (
|
|
27
|
+
isinstance(el, str) or isinstance(el, db.Document)
|
|
28
|
+
):
|
|
29
29
|
yield from flatten(el)
|
|
30
30
|
else:
|
|
31
31
|
yield el
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
@job(
|
|
34
|
+
@job("purge-datasets")
|
|
35
35
|
def purge_datasets(self):
|
|
36
36
|
for dataset in Dataset.objects(deleted__ne=None):
|
|
37
|
-
log.info(f
|
|
37
|
+
log.info(f"Purging dataset {dataset}")
|
|
38
38
|
# Remove followers
|
|
39
39
|
Follow.objects(following=dataset).delete()
|
|
40
40
|
# Remove discussions
|
|
@@ -72,19 +72,23 @@ def purge_datasets(self):
|
|
|
72
72
|
dataset.delete()
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
@job(
|
|
75
|
+
@job("send-frequency-reminder")
|
|
76
76
|
def send_frequency_reminder(self):
|
|
77
77
|
# We exclude irrelevant frequencies.
|
|
78
|
-
frequencies = [
|
|
79
|
-
|
|
78
|
+
frequencies = [
|
|
79
|
+
f
|
|
80
|
+
for f in UPDATE_FREQUENCIES.keys()
|
|
81
|
+
if f not in ("unknown", "realtime", "punctual", "irregular", "continuous")
|
|
82
|
+
]
|
|
80
83
|
now = datetime.utcnow()
|
|
81
84
|
reminded_orgs = {}
|
|
82
85
|
reminded_people = []
|
|
83
|
-
allowed_delay = current_app.config[
|
|
86
|
+
allowed_delay = current_app.config["DELAY_BEFORE_REMINDER_NOTIFICATION"]
|
|
84
87
|
for org in Organization.objects.visible():
|
|
85
88
|
outdated_datasets = []
|
|
86
89
|
for dataset in Dataset.objects.filter(
|
|
87
|
-
|
|
90
|
+
frequency__in=frequencies, organization=org
|
|
91
|
+
).visible():
|
|
88
92
|
if dataset.next_update + timedelta(days=allowed_delay) < now:
|
|
89
93
|
dataset.outdated = now - dataset.next_update
|
|
90
94
|
dataset.frequency_str = UPDATE_FREQUENCIES[dataset.frequency]
|
|
@@ -92,23 +96,28 @@ def send_frequency_reminder(self):
|
|
|
92
96
|
if outdated_datasets:
|
|
93
97
|
reminded_orgs[org] = outdated_datasets
|
|
94
98
|
for reminded_org, datasets in reminded_orgs.items():
|
|
95
|
-
print(
|
|
96
|
-
|
|
99
|
+
print(
|
|
100
|
+
"{org.name} will be emailed for {datasets_nb} datasets".format(
|
|
101
|
+
org=reminded_org, datasets_nb=len(datasets)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
97
104
|
recipients = [m.user for m in reminded_org.members]
|
|
98
105
|
reminded_people.append(recipients)
|
|
99
|
-
subject = _(
|
|
100
|
-
mail.send(subject, recipients,
|
|
101
|
-
org=reminded_org, datasets=datasets)
|
|
106
|
+
subject = _("You need to update some frequency-based datasets")
|
|
107
|
+
mail.send(subject, recipients, "frequency_reminder", org=reminded_org, datasets=datasets)
|
|
102
108
|
|
|
103
|
-
print(
|
|
109
|
+
print("{nb_orgs} orgs concerned".format(nb_orgs=len(reminded_orgs)))
|
|
104
110
|
reminded_people = list(flatten(reminded_people))
|
|
105
|
-
print(
|
|
106
|
-
nb_emails
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
print(
|
|
112
|
+
"{nb_emails} people contacted ({nb_emails_twice} twice)".format(
|
|
113
|
+
nb_emails=len(reminded_people),
|
|
114
|
+
nb_emails_twice=len(reminded_people) - len(set(reminded_people)),
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
print("Done")
|
|
109
118
|
|
|
110
119
|
|
|
111
|
-
@job(
|
|
120
|
+
@job("update-datasets-reuses-metrics")
|
|
112
121
|
def update_datasets_reuses_metrics(self):
|
|
113
122
|
all_datasets = Dataset.objects.visible().timeout(False)
|
|
114
123
|
for dataset in all_datasets:
|
|
@@ -117,10 +126,10 @@ def update_datasets_reuses_metrics(self):
|
|
|
117
126
|
|
|
118
127
|
def get_queryset(model_cls):
|
|
119
128
|
# special case for resources
|
|
120
|
-
if model_cls.__name__ ==
|
|
121
|
-
model_cls = getattr(udata_models,
|
|
129
|
+
if model_cls.__name__ == "Resource":
|
|
130
|
+
model_cls = getattr(udata_models, "Dataset")
|
|
122
131
|
params = {}
|
|
123
|
-
attrs = (
|
|
132
|
+
attrs = ("private", "deleted")
|
|
124
133
|
for attr in attrs:
|
|
125
134
|
if getattr(model_cls, attr, None):
|
|
126
135
|
params[attr] = False
|
|
@@ -131,7 +140,7 @@ def get_queryset(model_cls):
|
|
|
131
140
|
def get_or_create_resource(r_info, model, dataset):
|
|
132
141
|
resource = None
|
|
133
142
|
for r in dataset.resources:
|
|
134
|
-
if r.extras.get(
|
|
143
|
+
if r.extras.get("csv-export:model", "") == model:
|
|
135
144
|
resource = r
|
|
136
145
|
break
|
|
137
146
|
if resource:
|
|
@@ -140,45 +149,45 @@ def get_or_create_resource(r_info, model, dataset):
|
|
|
140
149
|
resource.save()
|
|
141
150
|
return False, resource
|
|
142
151
|
else:
|
|
143
|
-
r_info[
|
|
152
|
+
r_info["extras"] = {"csv-export:model": model}
|
|
144
153
|
return True, Resource(**r_info)
|
|
145
154
|
|
|
146
155
|
|
|
147
156
|
def store_resource(csvfile, model, dataset):
|
|
148
|
-
timestr = datetime.utcnow().strftime(
|
|
149
|
-
filename =
|
|
150
|
-
prefix =
|
|
157
|
+
timestr = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
|
158
|
+
filename = "export-%s-%s.csv" % (model, timestr)
|
|
159
|
+
prefix = "/".join((dataset.slug, timestr))
|
|
151
160
|
storage = storages.resources
|
|
152
|
-
with open(csvfile.name,
|
|
161
|
+
with open(csvfile.name, "rb") as infile:
|
|
153
162
|
stored_filename = storage.save(infile, prefix=prefix, filename=filename)
|
|
154
163
|
r_info = storage.metadata(stored_filename)
|
|
155
|
-
r_info[
|
|
156
|
-
r_info[
|
|
157
|
-
checksum = r_info.pop(
|
|
158
|
-
algo, checksum = checksum.split(
|
|
159
|
-
r_info[
|
|
160
|
-
r_info[
|
|
161
|
-
r_info[
|
|
162
|
-
del r_info[
|
|
163
|
-
r_info[
|
|
164
|
+
r_info["last_modified_internal"] = r_info.pop("modified")
|
|
165
|
+
r_info["fs_filename"] = stored_filename
|
|
166
|
+
checksum = r_info.pop("checksum")
|
|
167
|
+
algo, checksum = checksum.split(":", 1)
|
|
168
|
+
r_info["format"] = storages.utils.extension(stored_filename)
|
|
169
|
+
r_info["checksum"] = Checksum(type=algo, value=checksum)
|
|
170
|
+
r_info["filesize"] = r_info.pop("size")
|
|
171
|
+
del r_info["filename"]
|
|
172
|
+
r_info["title"] = filename
|
|
164
173
|
return get_or_create_resource(r_info, model, dataset)
|
|
165
174
|
|
|
166
175
|
|
|
167
176
|
def export_csv_for_model(model, dataset):
|
|
168
177
|
model_cls = getattr(udata_models, model.capitalize(), None)
|
|
169
178
|
if not model_cls:
|
|
170
|
-
log.error(
|
|
179
|
+
log.error("Unknow model %s" % model)
|
|
171
180
|
return
|
|
172
181
|
queryset = get_queryset(model_cls)
|
|
173
182
|
adapter = csv.get_adapter(model_cls)
|
|
174
183
|
if not adapter:
|
|
175
|
-
log.error(
|
|
184
|
+
log.error("No adapter found for %s" % model)
|
|
176
185
|
return
|
|
177
186
|
adapter = adapter(queryset)
|
|
178
187
|
|
|
179
|
-
log.info(
|
|
188
|
+
log.info("Exporting CSV for %s..." % model)
|
|
180
189
|
|
|
181
|
-
csvfile = NamedTemporaryFile(mode=
|
|
190
|
+
csvfile = NamedTemporaryFile(mode="w", encoding="utf8", delete=False)
|
|
182
191
|
try:
|
|
183
192
|
# write adapter results into a tmp file
|
|
184
193
|
writer = csv.get_writer(csvfile)
|
|
@@ -198,27 +207,27 @@ def export_csv_for_model(model, dataset):
|
|
|
198
207
|
os.unlink(csvfile.name)
|
|
199
208
|
|
|
200
209
|
|
|
201
|
-
@job(
|
|
210
|
+
@job("export-csv")
|
|
202
211
|
def export_csv(self, model=None):
|
|
203
|
-
|
|
212
|
+
"""
|
|
204
213
|
Generates a CSV export of all model objects as a resource of a dataset
|
|
205
|
-
|
|
206
|
-
ALLOWED_MODELS = current_app.config.get(
|
|
207
|
-
DATASET_ID = current_app.config.get(
|
|
214
|
+
"""
|
|
215
|
+
ALLOWED_MODELS = current_app.config.get("EXPORT_CSV_MODELS", [])
|
|
216
|
+
DATASET_ID = current_app.config.get("EXPORT_CSV_DATASET_ID")
|
|
208
217
|
|
|
209
218
|
if model and model not in ALLOWED_MODELS:
|
|
210
|
-
log.error(
|
|
219
|
+
log.error("Unknown or unallowed model: %s" % model)
|
|
211
220
|
return
|
|
212
221
|
|
|
213
222
|
if not DATASET_ID:
|
|
214
|
-
log.error(
|
|
223
|
+
log.error("EXPORT_CSV_DATASET_ID setting value not set")
|
|
215
224
|
return
|
|
216
225
|
try:
|
|
217
226
|
dataset = Dataset.objects.get(id=DATASET_ID)
|
|
218
227
|
except Dataset.DoesNotExist:
|
|
219
|
-
log.error(
|
|
228
|
+
log.error("EXPORT_CSV_DATASET_ID points to a non existent dataset")
|
|
220
229
|
return
|
|
221
230
|
|
|
222
|
-
models = (model,
|
|
231
|
+
models = (model,) if model else ALLOWED_MODELS
|
|
223
232
|
for model in models:
|
|
224
233
|
export_csv_for_model(model, dataset)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
from udata.models import
|
|
1
|
+
from udata.models import Dataset, Reuse
|
|
2
2
|
|
|
3
3
|
from .models import Discussion
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def discussions_for(user, only_open=True):
|
|
7
|
-
|
|
7
|
+
"""
|
|
8
8
|
Build a queryset to query discussions related to a given user's assets.
|
|
9
9
|
|
|
10
10
|
It includes discussions coming from the user's organizations
|
|
11
11
|
|
|
12
12
|
:param bool only_open: whether to include closed discussions or not.
|
|
13
|
-
|
|
13
|
+
"""
|
|
14
14
|
# Only fetch required fields for discussion filtering (id and slug)
|
|
15
15
|
# Greatly improve performances and memory usage
|
|
16
|
-
datasets = Dataset.objects.owned_by(user.id, *user.organizations).only(
|
|
17
|
-
reuses = Reuse.objects.owned_by(user.id, *user.organizations).only(
|
|
16
|
+
datasets = Dataset.objects.owned_by(user.id, *user.organizations).only("id", "slug")
|
|
17
|
+
reuses = Reuse.objects.owned_by(user.id, *user.organizations).only("id", "slug")
|
|
18
18
|
|
|
19
19
|
qs = Discussion.objects(subject__in=list(datasets) + list(reuses))
|
|
20
20
|
if only_open:
|