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/harvest/filters.py
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import dateutil.parser
|
|
2
|
-
|
|
3
2
|
from voluptuous import Invalid
|
|
4
3
|
|
|
5
4
|
from udata import tags, uris
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def boolean(value):
|
|
9
|
-
|
|
8
|
+
"""
|
|
10
9
|
Convert the content of a string (or a number) to a boolean.
|
|
11
10
|
Do nothing when input value is already a boolean.
|
|
12
11
|
|
|
13
12
|
This filter accepts usual values for ``True`` and ``False``:
|
|
14
13
|
"0", "f", "false", "n", etc.
|
|
15
|
-
|
|
14
|
+
"""
|
|
16
15
|
if value is None or isinstance(value, bool):
|
|
17
16
|
return value
|
|
18
17
|
|
|
@@ -22,29 +21,29 @@ def boolean(value):
|
|
|
22
21
|
lower_value = value.strip().lower()
|
|
23
22
|
if not lower_value:
|
|
24
23
|
return None
|
|
25
|
-
if lower_value in (
|
|
24
|
+
if lower_value in ("f", "false", "n", "no", "off"):
|
|
26
25
|
return False
|
|
27
|
-
if lower_value in (
|
|
26
|
+
if lower_value in ("on", "t", "true", "y", "yes"):
|
|
28
27
|
return True
|
|
29
|
-
raise Invalid(
|
|
28
|
+
raise Invalid("Unable to parse boolean {0}".format(value))
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def to_date(value):
|
|
33
|
-
|
|
32
|
+
"""Parse a date"""
|
|
34
33
|
return dateutil.parser.parse(value).date()
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
def email(value):
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
raise Invalid(
|
|
37
|
+
"""Validate an email"""
|
|
38
|
+
if "@" not in value:
|
|
39
|
+
raise Invalid("This email is invalid.")
|
|
41
40
|
return value
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
def force_list(value):
|
|
45
|
-
|
|
44
|
+
"""
|
|
46
45
|
Ensure single elements are wrapped into list
|
|
47
|
-
|
|
46
|
+
"""
|
|
48
47
|
if not isinstance(value, (list, tuple)):
|
|
49
48
|
return [value]
|
|
50
49
|
return value
|
|
@@ -63,12 +62,12 @@ def taglist(value):
|
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
def empty_none(value):
|
|
66
|
-
|
|
65
|
+
"""Replace falsy values with None"""
|
|
67
66
|
return value if value else None
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
def strip(value):
|
|
71
|
-
|
|
70
|
+
"""Strip spaces from a string and remove it when empty."""
|
|
72
71
|
return empty_none(value.strip())
|
|
73
72
|
|
|
74
73
|
|
|
@@ -76,37 +75,39 @@ def line_endings(value):
|
|
|
76
75
|
"""Replaces CR + LF or CR to LF in a string,
|
|
77
76
|
then strip spaces and remove it when empty.
|
|
78
77
|
"""
|
|
79
|
-
return value.replace(
|
|
78
|
+
return value.replace("\r\n", "\n").replace("\r", "\n")
|
|
80
79
|
|
|
81
80
|
|
|
82
81
|
def normalize_string(value):
|
|
83
82
|
return strip(line_endings(value))
|
|
84
83
|
|
|
85
84
|
|
|
86
|
-
def is_url(default_scheme=
|
|
85
|
+
def is_url(default_scheme="http", **kwargs):
|
|
87
86
|
"""Return a converter that converts a clean string to an URL."""
|
|
87
|
+
|
|
88
88
|
def converter(value):
|
|
89
89
|
if value is None:
|
|
90
90
|
return value
|
|
91
|
-
if
|
|
92
|
-
value =
|
|
91
|
+
if "://" not in value and default_scheme:
|
|
92
|
+
value = "://".join((default_scheme, value.strip()))
|
|
93
93
|
try:
|
|
94
94
|
return uris.validate(value)
|
|
95
95
|
except uris.ValidationError as e:
|
|
96
96
|
raise Invalid(str(e))
|
|
97
|
+
|
|
97
98
|
return converter
|
|
98
99
|
|
|
99
100
|
|
|
100
101
|
def hash(value):
|
|
101
|
-
|
|
102
|
+
"""Detect an hash type"""
|
|
102
103
|
if not value:
|
|
103
104
|
return
|
|
104
105
|
elif len(value) == 32:
|
|
105
|
-
type =
|
|
106
|
+
type = "md5"
|
|
106
107
|
elif len(value) == 40:
|
|
107
|
-
type =
|
|
108
|
+
type = "sha1"
|
|
108
109
|
elif len(value) == 64:
|
|
109
|
-
type =
|
|
110
|
+
type = "sha256"
|
|
110
111
|
else:
|
|
111
112
|
return None
|
|
112
|
-
return {
|
|
113
|
+
return {"type": type, "value": value}
|
udata/harvest/forms.py
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
from udata.utils import safe_unicode
|
|
2
1
|
from udata.forms import Form, fields, validators
|
|
3
2
|
from udata.i18n import lazy_gettext as _
|
|
4
|
-
|
|
3
|
+
from udata.utils import safe_unicode
|
|
5
4
|
|
|
6
5
|
from .actions import list_backends
|
|
7
|
-
from .models import
|
|
6
|
+
from .models import VALIDATION_REFUSED, VALIDATION_STATES
|
|
8
7
|
|
|
9
|
-
__all__ =
|
|
8
|
+
__all__ = "HarvestSourceForm", "HarvestSourceValidationForm"
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class HarvestConfigField(fields.DictField):
|
|
13
|
-
|
|
12
|
+
"""
|
|
14
13
|
A DictField with extras validations on known configurations
|
|
15
|
-
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
16
|
def get_backend(self, form):
|
|
17
17
|
return next(b for b in list_backends() if b.name == form.backend.data)
|
|
18
18
|
|
|
@@ -28,27 +28,27 @@ class HarvestConfigField(fields.DictField):
|
|
|
28
28
|
if self.data:
|
|
29
29
|
backend = self.get_backend(form)
|
|
30
30
|
# Validate filters
|
|
31
|
-
for f in
|
|
32
|
-
if not (
|
|
33
|
-
msg =
|
|
31
|
+
for f in self.data.get("filters") or []:
|
|
32
|
+
if not ("key" in f and "value" in f):
|
|
33
|
+
msg = "A field should have both key and value properties"
|
|
34
34
|
raise validators.ValidationError(msg)
|
|
35
|
-
specs = self.get_filter_specs(backend, f[
|
|
35
|
+
specs = self.get_filter_specs(backend, f["key"])
|
|
36
36
|
if not specs:
|
|
37
37
|
msg = 'Unknown filter key "{0}" for "{1}" backend'
|
|
38
|
-
msg = msg.format(f[
|
|
38
|
+
msg = msg.format(f["key"], backend.name)
|
|
39
39
|
raise validators.ValidationError(msg)
|
|
40
40
|
|
|
41
|
-
if isinstance(f[
|
|
42
|
-
f[
|
|
41
|
+
if isinstance(f["value"], str):
|
|
42
|
+
f["value"] = safe_unicode(f["value"]) # Fix encoding error
|
|
43
43
|
|
|
44
|
-
if not isinstance(f[
|
|
44
|
+
if not isinstance(f["value"], specs.type):
|
|
45
45
|
msg = '"{0}" filter should of type "{1}"'
|
|
46
46
|
msg = msg.format(specs.key, specs.type.__name__)
|
|
47
47
|
raise validators.ValidationError(msg)
|
|
48
48
|
# Validate features
|
|
49
|
-
for key, value in (self.data.get(
|
|
49
|
+
for key, value in (self.data.get("features") or {}).items():
|
|
50
50
|
if not isinstance(value, bool):
|
|
51
|
-
msg =
|
|
51
|
+
msg = "A feature should be a boolean"
|
|
52
52
|
raise validators.ValidationError(msg)
|
|
53
53
|
if not self.get_feature_specs(backend, key):
|
|
54
54
|
msg = 'Unknown feature "{0}" for "{1}" backend'
|
|
@@ -57,16 +57,16 @@ class HarvestConfigField(fields.DictField):
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class HarvestSourceForm(Form):
|
|
60
|
-
name = fields.StringField(_(
|
|
60
|
+
name = fields.StringField(_("Name"), [validators.DataRequired()])
|
|
61
61
|
description = fields.MarkdownField(
|
|
62
|
-
_(
|
|
63
|
-
|
|
64
|
-
url = fields.URLField(_(
|
|
65
|
-
backend = fields.SelectField(
|
|
66
|
-
(b.name, b.display_name) for b in list_backends()
|
|
67
|
-
|
|
62
|
+
_("Description"), description=_("Some optional details about this harvester")
|
|
63
|
+
)
|
|
64
|
+
url = fields.URLField(_("URL"), [validators.DataRequired()])
|
|
65
|
+
backend = fields.SelectField(
|
|
66
|
+
_("Backend"), choices=lambda: [(b.name, b.display_name) for b in list_backends()]
|
|
67
|
+
)
|
|
68
68
|
owner = fields.CurrentUserField()
|
|
69
|
-
organization = fields.PublishAsField(_(
|
|
69
|
+
organization = fields.PublishAsField(_("Publish as"))
|
|
70
70
|
active = fields.BooleanField()
|
|
71
71
|
autoarchive = fields.BooleanField()
|
|
72
72
|
|
|
@@ -75,7 +75,6 @@ class HarvestSourceForm(Form):
|
|
|
75
75
|
|
|
76
76
|
class HarvestSourceValidationForm(Form):
|
|
77
77
|
state = fields.SelectField(choices=list(VALIDATION_STATES.items()))
|
|
78
|
-
comment = fields.StringField(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)])
|
|
78
|
+
comment = fields.StringField(
|
|
79
|
+
_("Comment"), [validators.RequiredIfVal("state", VALIDATION_REFUSED)]
|
|
80
|
+
)
|
udata/harvest/models.py
CHANGED
|
@@ -1,65 +1,75 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from collections import OrderedDict
|
|
2
3
|
from datetime import datetime
|
|
3
|
-
import logging
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
5
|
|
|
6
|
-
from udata.core.dataservices.models import Dataservice
|
|
7
6
|
from werkzeug.utils import cached_property
|
|
8
7
|
|
|
8
|
+
from udata.core.dataservices.models import Dataservice
|
|
9
9
|
from udata.core.dataset.models import HarvestDatasetMetadata
|
|
10
|
-
from udata.models import db, Dataset
|
|
11
|
-
from udata.i18n import lazy_gettext as _
|
|
12
10
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
11
|
+
from udata.i18n import lazy_gettext as _
|
|
12
|
+
from udata.models import Dataset, db
|
|
13
13
|
|
|
14
14
|
log = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
-
HARVEST_FREQUENCIES = OrderedDict(
|
|
17
|
-
(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
(
|
|
39
|
-
|
|
40
|
-
))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
16
|
+
HARVEST_FREQUENCIES = OrderedDict(
|
|
17
|
+
(
|
|
18
|
+
("manual", _("Manual")),
|
|
19
|
+
("monthly", _("Monthly")),
|
|
20
|
+
("weekly", _("Weekly")),
|
|
21
|
+
("daily", _("Daily")),
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
HARVEST_JOB_STATUS = OrderedDict(
|
|
26
|
+
(
|
|
27
|
+
("pending", _("Pending")),
|
|
28
|
+
("initializing", _("Initializing")),
|
|
29
|
+
("initialized", _("Initialized")),
|
|
30
|
+
("processing", _("Processing")),
|
|
31
|
+
("done", _("Done")),
|
|
32
|
+
("done-errors", _("Done with errors")),
|
|
33
|
+
("failed", _("Failed")),
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
HARVEST_ITEM_STATUS = OrderedDict(
|
|
38
|
+
(
|
|
39
|
+
("pending", _("Pending")),
|
|
40
|
+
("started", _("Started")),
|
|
41
|
+
("done", _("Done")),
|
|
42
|
+
("failed", _("Failed")),
|
|
43
|
+
("skipped", _("Skipped")),
|
|
44
|
+
("archived", _("Archived")),
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
DEFAULT_HARVEST_FREQUENCY = "manual"
|
|
49
|
+
DEFAULT_HARVEST_JOB_STATUS = "pending"
|
|
50
|
+
DEFAULT_HARVEST_ITEM_STATUS = "pending"
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
class HarvestError(db.EmbeddedDocument):
|
|
48
|
-
|
|
54
|
+
"""Store harvesting errors"""
|
|
55
|
+
|
|
49
56
|
created_at = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
50
57
|
message = db.StringField()
|
|
51
58
|
details = db.StringField()
|
|
52
59
|
|
|
60
|
+
|
|
53
61
|
class HarvestLog(db.EmbeddedDocument):
|
|
54
62
|
level = db.StringField()
|
|
55
63
|
message = db.StringField()
|
|
56
64
|
|
|
65
|
+
|
|
57
66
|
class HarvestItem(db.EmbeddedDocument):
|
|
58
67
|
remote_id = db.StringField()
|
|
59
68
|
dataset = db.ReferenceField(Dataset)
|
|
60
69
|
dataservice = db.ReferenceField(Dataservice)
|
|
61
|
-
status = db.StringField(
|
|
62
|
-
|
|
70
|
+
status = db.StringField(
|
|
71
|
+
choices=list(HARVEST_ITEM_STATUS), default=DEFAULT_HARVEST_ITEM_STATUS, required=True
|
|
72
|
+
)
|
|
63
73
|
created = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
64
74
|
started = db.DateTimeField()
|
|
65
75
|
ended = db.DateTimeField()
|
|
@@ -69,23 +79,24 @@ class HarvestItem(db.EmbeddedDocument):
|
|
|
69
79
|
kwargs = db.DictField()
|
|
70
80
|
|
|
71
81
|
|
|
72
|
-
VALIDATION_ACCEPTED =
|
|
73
|
-
VALIDATION_REFUSED =
|
|
74
|
-
VALIDATION_PENDING =
|
|
82
|
+
VALIDATION_ACCEPTED = "accepted"
|
|
83
|
+
VALIDATION_REFUSED = "refused"
|
|
84
|
+
VALIDATION_PENDING = "pending"
|
|
75
85
|
|
|
76
86
|
VALIDATION_STATES = {
|
|
77
|
-
VALIDATION_PENDING: _(
|
|
78
|
-
VALIDATION_ACCEPTED: _(
|
|
79
|
-
VALIDATION_REFUSED: _(
|
|
87
|
+
VALIDATION_PENDING: _("Pending"),
|
|
88
|
+
VALIDATION_ACCEPTED: _("Accepted"),
|
|
89
|
+
VALIDATION_REFUSED: _("Refused"),
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
|
|
83
93
|
class HarvestSourceValidation(db.EmbeddedDocument):
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
"""Store harvest source validation details"""
|
|
95
|
+
|
|
96
|
+
state = db.StringField(
|
|
97
|
+
choices=list(VALIDATION_STATES), default=VALIDATION_PENDING, required=True
|
|
98
|
+
)
|
|
99
|
+
by = db.ReferenceField("User")
|
|
89
100
|
on = db.DateTimeField()
|
|
90
101
|
comment = db.StringField()
|
|
91
102
|
|
|
@@ -97,29 +108,28 @@ class HarvestSourceQuerySet(OwnedQuerySet):
|
|
|
97
108
|
|
|
98
109
|
class HarvestSource(Owned, db.Document):
|
|
99
110
|
name = db.StringField(max_length=255)
|
|
100
|
-
slug = db.SlugField(
|
|
101
|
-
|
|
111
|
+
slug = db.SlugField(
|
|
112
|
+
max_length=255, required=True, unique=True, populate_from="name", update=True
|
|
113
|
+
)
|
|
102
114
|
description = db.StringField()
|
|
103
115
|
url = db.StringField(required=True)
|
|
104
116
|
backend = db.StringField(required=True)
|
|
105
117
|
config = db.DictField()
|
|
106
|
-
periodic_task = db.ReferenceField(
|
|
107
|
-
reverse_delete_rule=db.NULLIFY)
|
|
118
|
+
periodic_task = db.ReferenceField("PeriodicTask", reverse_delete_rule=db.NULLIFY)
|
|
108
119
|
created_at = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
109
|
-
frequency = db.StringField(
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
frequency = db.StringField(
|
|
121
|
+
choices=list(HARVEST_FREQUENCIES), default=DEFAULT_HARVEST_FREQUENCY, required=True
|
|
122
|
+
)
|
|
112
123
|
active = db.BooleanField(default=True)
|
|
113
124
|
autoarchive = db.BooleanField(default=True)
|
|
114
|
-
validation = db.EmbeddedDocumentField(HarvestSourceValidation,
|
|
115
|
-
default=HarvestSourceValidation)
|
|
125
|
+
validation = db.EmbeddedDocumentField(HarvestSourceValidation, default=HarvestSourceValidation)
|
|
116
126
|
|
|
117
127
|
deleted = db.DateTimeField()
|
|
118
128
|
|
|
119
129
|
@property
|
|
120
130
|
def domain(self):
|
|
121
131
|
parsed = urlparse(self.url)
|
|
122
|
-
return parsed.netloc.split(
|
|
132
|
+
return parsed.netloc.split(":")[0]
|
|
123
133
|
|
|
124
134
|
@classmethod
|
|
125
135
|
def get(cls, ident):
|
|
@@ -128,9 +138,9 @@ class HarvestSource(Owned, db.Document):
|
|
|
128
138
|
def get_last_job(self, reduced=False):
|
|
129
139
|
qs = HarvestJob.objects(source=self)
|
|
130
140
|
if reduced:
|
|
131
|
-
qs = qs.exclude(
|
|
141
|
+
qs = qs.exclude("source", "items", "errors", "data")
|
|
132
142
|
qs = qs.no_dereference()
|
|
133
|
-
return qs.order_by(
|
|
143
|
+
return qs.order_by("-created").first()
|
|
134
144
|
|
|
135
145
|
@cached_property
|
|
136
146
|
def last_job(self):
|
|
@@ -143,48 +153,46 @@ class HarvestSource(Owned, db.Document):
|
|
|
143
153
|
return self.periodic_task.schedule_display
|
|
144
154
|
|
|
145
155
|
meta = {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
(
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
"indexes": [
|
|
157
|
+
"-created_at",
|
|
158
|
+
"slug",
|
|
159
|
+
("deleted", "-created_at"),
|
|
160
|
+
]
|
|
161
|
+
+ Owned.meta["indexes"],
|
|
162
|
+
"ordering": ["-created_at"],
|
|
163
|
+
"queryset_class": HarvestSourceQuerySet,
|
|
153
164
|
}
|
|
154
165
|
|
|
155
166
|
def __str__(self):
|
|
156
|
-
return self.name or
|
|
167
|
+
return self.name or ""
|
|
157
168
|
|
|
158
169
|
|
|
159
170
|
class HarvestJob(db.Document):
|
|
160
|
-
|
|
171
|
+
"""Keep track of harvestings"""
|
|
172
|
+
|
|
161
173
|
created = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
162
174
|
started = db.DateTimeField()
|
|
163
175
|
ended = db.DateTimeField()
|
|
164
|
-
status = db.StringField(
|
|
165
|
-
|
|
176
|
+
status = db.StringField(
|
|
177
|
+
choices=list(HARVEST_JOB_STATUS), default=DEFAULT_HARVEST_JOB_STATUS, required=True
|
|
178
|
+
)
|
|
166
179
|
errors = db.ListField(db.EmbeddedDocumentField(HarvestError))
|
|
167
180
|
items = db.ListField(db.EmbeddedDocumentField(HarvestItem))
|
|
168
181
|
source = db.ReferenceField(HarvestSource, reverse_delete_rule=db.CASCADE)
|
|
169
182
|
data = db.DictField()
|
|
170
183
|
|
|
171
184
|
meta = {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
'source',
|
|
175
|
-
('source', '-created'),
|
|
176
|
-
'items.dataset'
|
|
177
|
-
],
|
|
178
|
-
'ordering': ['-created'],
|
|
185
|
+
"indexes": ["-created", "source", ("source", "-created"), "items.dataset"],
|
|
186
|
+
"ordering": ["-created"],
|
|
179
187
|
}
|
|
180
188
|
|
|
181
189
|
|
|
182
190
|
def archive_harvested_dataset(dataset, reason, dryrun=False):
|
|
183
|
-
|
|
191
|
+
"""
|
|
184
192
|
Archive an harvested dataset, setting extras accordingly.
|
|
185
193
|
If `dryrun` is True, the dataset is not saved but validated only.
|
|
186
|
-
|
|
187
|
-
log.debug(
|
|
194
|
+
"""
|
|
195
|
+
log.debug("Archiving dataset %s", dataset.id)
|
|
188
196
|
archival_date = datetime.utcnow()
|
|
189
197
|
dataset.archived = archival_date
|
|
190
198
|
if not dataset.harvest:
|
udata/harvest/notifications.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
2
|
|
|
3
|
-
from .
|
|
3
|
+
from udata.features.notifications.actions import notifier
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from .models import VALIDATION_PENDING, HarvestSource
|
|
6
6
|
|
|
7
7
|
log = logging.getLogger(__name__)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@notifier(
|
|
10
|
+
@notifier("validate_harvester")
|
|
11
11
|
def validate_harvester_notifications(user):
|
|
12
|
-
|
|
12
|
+
"""Notify admins about pending harvester validation"""
|
|
13
13
|
if not user.sysadmin:
|
|
14
14
|
return []
|
|
15
15
|
|
|
@@ -18,12 +18,17 @@ def validate_harvester_notifications(user):
|
|
|
18
18
|
# Only fetch required fields for notification serialization
|
|
19
19
|
# Greatly improve performances and memory usage
|
|
20
20
|
qs = HarvestSource.objects(validation__state=VALIDATION_PENDING)
|
|
21
|
-
qs = qs.only(
|
|
21
|
+
qs = qs.only("id", "created_at", "name")
|
|
22
22
|
|
|
23
23
|
for source in qs:
|
|
24
|
-
notifications.append(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
notifications.append(
|
|
25
|
+
(
|
|
26
|
+
source.created_at,
|
|
27
|
+
{
|
|
28
|
+
"id": source.id,
|
|
29
|
+
"name": source.name,
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
)
|
|
28
33
|
|
|
29
34
|
return notifications
|
udata/harvest/signals.py
CHANGED
|
@@ -7,40 +7,40 @@ log = logging.getLogger(__name__)
|
|
|
7
7
|
ns = Namespace()
|
|
8
8
|
|
|
9
9
|
#: Sent when a new HarvestSource is created
|
|
10
|
-
harvest_source_created = ns.signal(
|
|
10
|
+
harvest_source_created = ns.signal("harvest:source-created")
|
|
11
11
|
|
|
12
12
|
#: Sent when a HarvestSource is updated
|
|
13
|
-
harvest_source_updated = ns.signal(
|
|
13
|
+
harvest_source_updated = ns.signal("harvest:source-updated")
|
|
14
14
|
|
|
15
15
|
#: Sent when a HarvestSource is deleted
|
|
16
|
-
harvest_source_deleted = ns.signal(
|
|
16
|
+
harvest_source_deleted = ns.signal("harvest:source-deleted")
|
|
17
17
|
|
|
18
18
|
#: Run before each harvest job
|
|
19
|
-
before_harvest_job = ns.signal(
|
|
19
|
+
before_harvest_job = ns.signal("harvest:before-job")
|
|
20
20
|
|
|
21
21
|
#: Run before each harvest job
|
|
22
|
-
after_harvest_job = ns.signal(
|
|
22
|
+
after_harvest_job = ns.signal("harvest:after-job")
|
|
23
23
|
|
|
24
24
|
#: Sent when a new HarvestJob started
|
|
25
|
-
harvest_job_started = ns.signal(
|
|
25
|
+
harvest_job_started = ns.signal("harvest:job-started")
|
|
26
26
|
|
|
27
27
|
#: Sent when a HarvestJob is done
|
|
28
|
-
harvest_job_done = ns.signal(
|
|
28
|
+
harvest_job_done = ns.signal("harvest:job-done")
|
|
29
29
|
|
|
30
30
|
#: Sent when a HarvestJob failed
|
|
31
|
-
harvest_job_failed = ns.signal(
|
|
31
|
+
harvest_job_failed = ns.signal("harvest:job-failed")
|
|
32
32
|
|
|
33
33
|
#: Sent when a HarvestItem start
|
|
34
|
-
harvest_item_started = ns.signal(
|
|
34
|
+
harvest_item_started = ns.signal("harvest:item-started")
|
|
35
35
|
|
|
36
36
|
#: Sent when a HarvestItem is done
|
|
37
|
-
harvest_item_done = ns.signal(
|
|
37
|
+
harvest_item_done = ns.signal("harvest:item-done")
|
|
38
38
|
|
|
39
39
|
#: Sent when a HarvestItem failed
|
|
40
|
-
harvest_item_failed = ns.signal(
|
|
40
|
+
harvest_item_failed = ns.signal("harvest:item-failed")
|
|
41
41
|
|
|
42
42
|
#: Sent when a HarvestSource is scheduled
|
|
43
|
-
harvest_source_scheduled = ns.signal(
|
|
43
|
+
harvest_source_scheduled = ns.signal("harvest:source-scheduled")
|
|
44
44
|
|
|
45
45
|
#: Sent when a HarvestSource is unscheduled
|
|
46
|
-
harvest_source_unscheduled = ns.signal(
|
|
46
|
+
harvest_source_unscheduled = ns.signal("harvest:source-unscheduled")
|
udata/harvest/tasks.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from celery import chord
|
|
2
2
|
from flask import current_app
|
|
3
3
|
|
|
4
|
-
from udata.tasks import
|
|
4
|
+
from udata.tasks import get_logger, job, task
|
|
5
5
|
|
|
6
6
|
from . import backends
|
|
7
|
-
from .models import
|
|
7
|
+
from .models import HarvestJob, HarvestSource
|
|
8
8
|
|
|
9
9
|
log = get_logger(__name__)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@job(
|
|
12
|
+
@job("harvest", route="low.harvest")
|
|
13
13
|
def harvest(self, ident):
|
|
14
14
|
log.info('Launching harvest job for source "%s"', ident)
|
|
15
15
|
|
|
@@ -21,9 +21,8 @@ def harvest(self, ident):
|
|
|
21
21
|
|
|
22
22
|
backend.harvest()
|
|
23
23
|
|
|
24
|
-
|
|
25
24
|
|
|
26
|
-
@task(ignore_result=False, route=
|
|
25
|
+
@task(ignore_result=False, route="low.harvest")
|
|
27
26
|
def harvest_job_item(job_id, item_id):
|
|
28
27
|
log.info('Harvesting item %s for job "%s"', item_id, job_id)
|
|
29
28
|
|
|
@@ -37,7 +36,7 @@ def harvest_job_item(job_id, item_id):
|
|
|
37
36
|
return item_id
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
@task(ignore_result=False, route=
|
|
39
|
+
@task(ignore_result=False, route="low.harvest")
|
|
41
40
|
def harvest_job_finalize(results, job_id):
|
|
42
41
|
log.info('Finalize harvesting for job "%s"', job_id)
|
|
43
42
|
job = HarvestJob.objects.get(pk=job_id)
|
|
@@ -46,15 +45,17 @@ def harvest_job_finalize(results, job_id):
|
|
|
46
45
|
backend.finalize()
|
|
47
46
|
|
|
48
47
|
|
|
49
|
-
@job(
|
|
48
|
+
@job("purge-harvesters", route="low.harvest")
|
|
50
49
|
def purge_harvest_sources(self):
|
|
51
|
-
log.info(
|
|
50
|
+
log.info("Purging HarvestSources flagged as deleted")
|
|
52
51
|
from .actions import purge_sources
|
|
52
|
+
|
|
53
53
|
purge_sources()
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
@job(
|
|
56
|
+
@job("purge-harvest-jobs", route="low.harvest")
|
|
57
57
|
def purge_harvest_jobs(self):
|
|
58
|
-
log.info(
|
|
58
|
+
log.info("Purging HarvestJobs older than retention policy")
|
|
59
59
|
from .actions import purge_jobs
|
|
60
|
+
|
|
60
61
|
purge_jobs()
|