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/discussions/tasks.py
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
from udata import mail
|
|
2
|
-
from udata.i18n import lazy_gettext as _
|
|
3
2
|
from udata.core.dataset.models import Dataset
|
|
4
|
-
from udata.core.reuse.models import Reuse
|
|
5
3
|
from udata.core.post.models import Post
|
|
4
|
+
from udata.core.reuse.models import Reuse
|
|
5
|
+
from udata.i18n import lazy_gettext as _
|
|
6
6
|
from udata.tasks import connect, get_logger
|
|
7
7
|
|
|
8
8
|
from .models import Discussion, Message
|
|
9
|
-
from .signals import
|
|
10
|
-
on_new_discussion, on_new_discussion_comment, on_discussion_closed
|
|
11
|
-
)
|
|
9
|
+
from .signals import on_discussion_closed, on_new_discussion, on_new_discussion_comment
|
|
12
10
|
|
|
13
11
|
log = get_logger(__name__)
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
def owner_recipients(discussion):
|
|
17
|
-
if getattr(discussion.subject,
|
|
15
|
+
if getattr(discussion.subject, "organization", None):
|
|
18
16
|
return [m.user for m in discussion.subject.organization.members]
|
|
19
|
-
elif getattr(discussion.subject,
|
|
17
|
+
elif getattr(discussion.subject, "owner", None):
|
|
20
18
|
return [discussion.subject.owner]
|
|
21
19
|
else:
|
|
22
20
|
return []
|
|
@@ -27,13 +25,16 @@ def notify_new_discussion(discussion_id):
|
|
|
27
25
|
discussion = Discussion.objects.get(pk=discussion_id)
|
|
28
26
|
if isinstance(discussion.subject, (Dataset, Reuse, Post)):
|
|
29
27
|
recipients = owner_recipients(discussion)
|
|
30
|
-
subject = _(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
subject = _("Your %(type)s have a new discussion", type=discussion.subject.verbose_name)
|
|
29
|
+
mail.send(
|
|
30
|
+
subject,
|
|
31
|
+
recipients,
|
|
32
|
+
"new_discussion",
|
|
33
|
+
discussion=discussion,
|
|
34
|
+
message=discussion.discussion[0],
|
|
35
|
+
)
|
|
34
36
|
else:
|
|
35
|
-
log.warning(
|
|
36
|
-
type(discussion.subject))
|
|
37
|
+
log.warning("Unrecognized discussion subject type %s", type(discussion.subject))
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@connect(on_new_discussion_comment, by_id=True)
|
|
@@ -41,17 +42,15 @@ def notify_new_discussion_comment(discussion_id, message=None):
|
|
|
41
42
|
discussion = Discussion.objects.get(pk=discussion_id)
|
|
42
43
|
message = discussion.discussion[message]
|
|
43
44
|
if isinstance(discussion.subject, (Dataset, Reuse, Post)):
|
|
44
|
-
recipients = owner_recipients(discussion) + [
|
|
45
|
-
m.posted_by for m in discussion.discussion]
|
|
45
|
+
recipients = owner_recipients(discussion) + [m.posted_by for m in discussion.discussion]
|
|
46
46
|
recipients = list({u.id: u for u in recipients if u != message.posted_by}.values())
|
|
47
|
-
subject = _(
|
|
48
|
-
user=message.posted_by.fullname)
|
|
47
|
+
subject = _("%(user)s commented your discussion", user=message.posted_by.fullname)
|
|
49
48
|
|
|
50
|
-
mail.send(
|
|
51
|
-
|
|
49
|
+
mail.send(
|
|
50
|
+
subject, recipients, "new_discussion_comment", discussion=discussion, message=message
|
|
51
|
+
)
|
|
52
52
|
else:
|
|
53
|
-
log.warning(
|
|
54
|
-
type(discussion.subject))
|
|
53
|
+
log.warning("Unrecognized discussion subject type %s", type(discussion.subject))
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
@connect(on_discussion_closed, by_id=True)
|
|
@@ -59,12 +58,9 @@ def notify_discussion_closed(discussion_id, message=None):
|
|
|
59
58
|
discussion = Discussion.objects.get(pk=discussion_id)
|
|
60
59
|
message = discussion.discussion[message]
|
|
61
60
|
if isinstance(discussion.subject, (Dataset, Reuse, Post)):
|
|
62
|
-
recipients = owner_recipients(discussion) + [
|
|
63
|
-
m.posted_by for m in discussion.discussion]
|
|
61
|
+
recipients = owner_recipients(discussion) + [m.posted_by for m in discussion.discussion]
|
|
64
62
|
recipients = list({u.id: u for u in recipients if u != message.posted_by}.values())
|
|
65
|
-
subject = _(
|
|
66
|
-
mail.send(subject, recipients,
|
|
67
|
-
discussion=discussion, message=message)
|
|
63
|
+
subject = _("A discussion has been closed")
|
|
64
|
+
mail.send(subject, recipients, "discussion_closed", discussion=discussion, message=message)
|
|
68
65
|
else:
|
|
69
|
-
log.warning(
|
|
70
|
-
type(discussion.subject))
|
|
66
|
+
log.warning("Unrecognized discussion subject type %s", type(discussion.subject))
|
udata/core/followers/api.py
CHANGED
|
@@ -4,72 +4,71 @@ from flask import current_app, request
|
|
|
4
4
|
from flask_security import current_user
|
|
5
5
|
|
|
6
6
|
from udata import tracking
|
|
7
|
-
from udata.api import
|
|
8
|
-
from udata.models import Follow
|
|
7
|
+
from udata.api import API, api, fields
|
|
9
8
|
from udata.core.user.api_fields import user_ref_fields
|
|
9
|
+
from udata.models import Follow
|
|
10
10
|
from udata.utils import id_or_404
|
|
11
11
|
|
|
12
12
|
from .signals import on_new_follow
|
|
13
13
|
|
|
14
|
+
follow_fields = api.model(
|
|
15
|
+
"Follow",
|
|
16
|
+
{
|
|
17
|
+
"id": fields.String(description="The follow object technical ID", readonly=True),
|
|
18
|
+
"follower": fields.Nested(user_ref_fields, description="The follower", readonly=True),
|
|
19
|
+
"since": fields.ISODateTime(
|
|
20
|
+
description="The date from which the user started following", readonly=True
|
|
21
|
+
),
|
|
22
|
+
},
|
|
23
|
+
)
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
'id': fields.String(
|
|
17
|
-
description='The follow object technical ID', readonly=True),
|
|
18
|
-
'follower': fields.Nested(
|
|
19
|
-
user_ref_fields, description='The follower', readonly=True),
|
|
20
|
-
'since': fields.ISODateTime(
|
|
21
|
-
description='The date from which the user started following',
|
|
22
|
-
readonly=True)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
follow_page_fields = api.model('FollowPage', fields.pager(follow_fields))
|
|
25
|
+
follow_page_fields = api.model("FollowPage", fields.pager(follow_fields))
|
|
26
26
|
|
|
27
27
|
parser = api.parser()
|
|
28
|
+
parser.add_argument("page", type=int, default=1, location="args", help="The page to fetch")
|
|
28
29
|
parser.add_argument(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'page_size', type=int, default=20, location='args',
|
|
32
|
-
help='The page size to fetch')
|
|
30
|
+
"page_size", type=int, default=20, location="args", help="The page size to fetch"
|
|
31
|
+
)
|
|
33
32
|
|
|
34
|
-
NOTE =
|
|
33
|
+
NOTE = "Returns the number of followers left after the operation"
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
class FollowAPI(API):
|
|
38
|
-
|
|
37
|
+
"""
|
|
39
38
|
Base Follow Model API.
|
|
40
|
-
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
41
|
model = None
|
|
42
42
|
|
|
43
43
|
@api.expect(parser)
|
|
44
44
|
@api.marshal_with(follow_page_fields)
|
|
45
45
|
def get(self, id):
|
|
46
|
-
|
|
46
|
+
"""List all followers for a given object"""
|
|
47
47
|
args = parser.parse_args()
|
|
48
|
-
model = self.model.objects.only(
|
|
48
|
+
model = self.model.objects.only("id").get_or_404(id=id_or_404(id))
|
|
49
49
|
qs = Follow.objects(following=model, until=None)
|
|
50
|
-
return qs.paginate(args[
|
|
50
|
+
return qs.paginate(args["page"], args["page_size"])
|
|
51
51
|
|
|
52
52
|
@api.secure
|
|
53
53
|
@api.doc(description=NOTE)
|
|
54
54
|
def post(self, id):
|
|
55
|
-
|
|
55
|
+
"""Follow an object given its ID"""
|
|
56
56
|
model = self.model.objects.get_or_404(id=id_or_404(id))
|
|
57
57
|
follow, created = Follow.objects.get_or_create(
|
|
58
|
-
follower=current_user.id, following=model, until=None
|
|
58
|
+
follower=current_user.id, following=model, until=None
|
|
59
|
+
)
|
|
59
60
|
count = Follow.objects.followers(model).count()
|
|
60
|
-
if not current_app.config[
|
|
61
|
+
if not current_app.config["TESTING"]:
|
|
61
62
|
tracking.send_signal(on_new_follow, request, current_user)
|
|
62
|
-
return {
|
|
63
|
+
return {"followers": count}, 201 if created else 200
|
|
63
64
|
|
|
64
65
|
@api.secure
|
|
65
66
|
@api.doc(description=NOTE)
|
|
66
67
|
def delete(self, id):
|
|
67
|
-
|
|
68
|
-
model = self.model.objects.only(
|
|
69
|
-
follow = Follow.objects.get_or_404(follower=current_user.id,
|
|
70
|
-
following=model,
|
|
71
|
-
until=None)
|
|
68
|
+
"""Unfollow an object given its ID"""
|
|
69
|
+
model = self.model.objects.only("id").get_or_404(id=id_or_404(id))
|
|
70
|
+
follow = Follow.objects.get_or_404(follower=current_user.id, following=model, until=None)
|
|
72
71
|
follow.until = datetime.utcnow()
|
|
73
72
|
follow.save()
|
|
74
73
|
count = Follow.objects.followers(model).count()
|
|
75
|
-
return {
|
|
74
|
+
return {"followers": count}, 200
|
udata/core/followers/models.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
|
|
3
3
|
from udata.mongo import db
|
|
4
|
-
from .signals import on_follow, on_unfollow
|
|
5
4
|
|
|
5
|
+
from .signals import on_follow, on_unfollow
|
|
6
6
|
|
|
7
|
-
__all__ = (
|
|
7
|
+
__all__ = ("Follow",)
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class FollowQuerySet(db.BaseQuerySet):
|
|
@@ -19,19 +19,19 @@ class FollowQuerySet(db.BaseQuerySet):
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class Follow(db.Document):
|
|
22
|
-
follower = db.ReferenceField(
|
|
22
|
+
follower = db.ReferenceField("User", required=True)
|
|
23
23
|
following = db.GenericReferenceField()
|
|
24
24
|
since = db.DateTimeField(required=True, default=datetime.utcnow)
|
|
25
25
|
until = db.DateTimeField()
|
|
26
26
|
|
|
27
27
|
meta = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(
|
|
32
|
-
(
|
|
28
|
+
"indexes": [
|
|
29
|
+
"follower",
|
|
30
|
+
"following",
|
|
31
|
+
("follower", "until"),
|
|
32
|
+
("following", "until"),
|
|
33
33
|
],
|
|
34
|
-
|
|
34
|
+
"queryset_class": FollowQuerySet,
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
|
udata/core/followers/signals.py
CHANGED
|
@@ -3,13 +3,13 @@ from blinker import Namespace
|
|
|
3
3
|
namespace = Namespace()
|
|
4
4
|
|
|
5
5
|
#: Trigerred when an user follow someone or something
|
|
6
|
-
on_follow = namespace.signal(
|
|
6
|
+
on_follow = namespace.signal("on-follow")
|
|
7
7
|
|
|
8
8
|
#: Trigerred when an user follow someone or something (again)
|
|
9
9
|
# We cannot reuse the `on_follow` one because we need to trigger it
|
|
10
10
|
# from the view to pass user information for tracking
|
|
11
11
|
# (vs `db.post_save.connect` signal).
|
|
12
|
-
on_new_follow = namespace.signal(
|
|
12
|
+
on_new_follow = namespace.signal("on-new-follow")
|
|
13
13
|
|
|
14
14
|
#: Trigerred when an user unfollow someone or something
|
|
15
|
-
on_unfollow = namespace.signal(
|
|
15
|
+
on_unfollow = namespace.signal("on-unfollow")
|
udata/core/jobs/actions.py
CHANGED
|
@@ -9,20 +9,20 @@ def run(name, args, kwargs):
|
|
|
9
9
|
args = args or []
|
|
10
10
|
kwargs = dict(k.split() for k in kwargs) if kwargs else {}
|
|
11
11
|
if name not in celery.tasks:
|
|
12
|
-
log.error(
|
|
12
|
+
log.error("Job %s not found", name)
|
|
13
13
|
job = celery.tasks[name]
|
|
14
|
-
log.info(
|
|
14
|
+
log.info("Running job %s", name)
|
|
15
15
|
job.run(*args, **kwargs)
|
|
16
|
-
log.info(
|
|
16
|
+
log.info("Job %s done", name)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def delay(name, args, kwargs):
|
|
20
|
-
|
|
20
|
+
"""Run a job asynchronously"""
|
|
21
21
|
args = args or []
|
|
22
22
|
kwargs = dict(k.split() for k in kwargs) if kwargs else {}
|
|
23
23
|
if name not in celery.tasks:
|
|
24
|
-
log.error(
|
|
24
|
+
log.error("Job %s not found", name)
|
|
25
25
|
job = celery.tasks[name]
|
|
26
|
-
log.info(
|
|
26
|
+
log.info("Sending job %s", name)
|
|
27
27
|
async_result = job.delay(*args, **kwargs)
|
|
28
|
-
log.info(
|
|
28
|
+
log.info("Job %s sended to workers", async_result.id)
|
udata/core/jobs/api.py
CHANGED
|
@@ -1,103 +1,108 @@
|
|
|
1
1
|
from celery import states
|
|
2
2
|
from celery.result import AsyncResult
|
|
3
3
|
from celery.utils import get_full_cls_name
|
|
4
|
-
from kombu.utils.encoding import safe_repr
|
|
5
4
|
from flask import request
|
|
5
|
+
from kombu.utils.encoding import safe_repr
|
|
6
6
|
|
|
7
|
-
from udata.api import
|
|
7
|
+
from udata.api import API, api, fields
|
|
8
8
|
from udata.auth import admin_permission
|
|
9
|
-
from udata.tasks import
|
|
9
|
+
from udata.tasks import celery, schedulables
|
|
10
10
|
from udata.utils import id_or_404
|
|
11
11
|
|
|
12
12
|
from .forms import CrontabTaskForm, IntervalTaskForm
|
|
13
|
-
from .models import
|
|
14
|
-
|
|
15
|
-
ns = api.namespace(
|
|
16
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
fields.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
description=
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
description=
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
13
|
+
from .models import PERIODS, PeriodicTask
|
|
14
|
+
|
|
15
|
+
ns = api.namespace("workers", "Asynchronous workers related operations", path="")
|
|
16
|
+
|
|
17
|
+
crontab_fields = api.model(
|
|
18
|
+
"Crontab",
|
|
19
|
+
{
|
|
20
|
+
"minute": fields.String(
|
|
21
|
+
description="Cron expression for minute", required=True, default="*"
|
|
22
|
+
),
|
|
23
|
+
"hour": fields.String(description="Cron expression for hour", required=True, default="*"),
|
|
24
|
+
"day_of_week": fields.String(
|
|
25
|
+
description="Cron expression for day of week", required=True, default="*"
|
|
26
|
+
),
|
|
27
|
+
"day_of_month": fields.String(
|
|
28
|
+
description="Cron expression for day of month", required=True, default="*"
|
|
29
|
+
),
|
|
30
|
+
"month_of_year": fields.String(
|
|
31
|
+
description="Cron expression for month of year", required=True, default="*"
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
interval_fields = api.model(
|
|
37
|
+
"Interval",
|
|
38
|
+
{
|
|
39
|
+
"every": fields.Integer(description="The interval without unit", required=True),
|
|
40
|
+
"period": fields.String(
|
|
41
|
+
description="The period/interval type", required=True, enum=PERIODS
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
job_fields = api.model(
|
|
47
|
+
"Job",
|
|
48
|
+
{
|
|
49
|
+
"id": fields.String(description="The job unique identifier", readonly=True),
|
|
50
|
+
"name": fields.String(description="The job unique name", required=True),
|
|
51
|
+
"description": fields.String(description="The job description"),
|
|
52
|
+
"task": fields.String(
|
|
53
|
+
description="The task name", required=True, enum=[job.name for job in schedulables()]
|
|
54
|
+
),
|
|
55
|
+
"crontab": fields.Nested(crontab_fields, allow_null=True),
|
|
56
|
+
"interval": fields.Nested(interval_fields, allow_null=True),
|
|
57
|
+
"args": fields.List(fields.Raw, description="The job execution arguments", default=[]),
|
|
58
|
+
"kwargs": fields.Raw(description="The job execution keyword arguments", default={}),
|
|
59
|
+
"schedule": fields.String(
|
|
60
|
+
attribute="schedule_display", description="The schedule display", readonly=True
|
|
61
|
+
),
|
|
62
|
+
"last_run_at": fields.ISODateTime(description="The last job execution date", readonly=True),
|
|
63
|
+
"last_run_id": fields.String(description="The last execution task id", readonly=True),
|
|
64
|
+
"enabled": fields.Boolean(description="Is this job enabled", default=False),
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
task_fields = api.model(
|
|
69
|
+
"Task",
|
|
70
|
+
{
|
|
71
|
+
"id": fields.String(description="Tha task execution ID", readonly=True),
|
|
72
|
+
"status": fields.String(
|
|
73
|
+
description="Cron expression for hour", readonly=True, enum=list(states.ALL_STATES)
|
|
74
|
+
),
|
|
75
|
+
"result": fields.String(description="The task results if exists"),
|
|
76
|
+
"exc": fields.String(description="The exception thrown during execution"),
|
|
77
|
+
"traceback": fields.String(description="The execution traceback"),
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@ns.route("/jobs/", endpoint="jobs")
|
|
78
83
|
class JobsAPI(API):
|
|
79
|
-
@api.doc(id=
|
|
84
|
+
@api.doc(id="list_jobs")
|
|
80
85
|
@api.marshal_list_with(job_fields)
|
|
81
86
|
def get(self):
|
|
82
|
-
|
|
87
|
+
"""List all scheduled jobs"""
|
|
83
88
|
return list(PeriodicTask.objects)
|
|
84
89
|
|
|
85
90
|
@api.secure(admin_permission)
|
|
86
91
|
@api.expect(job_fields)
|
|
87
92
|
@api.marshal_with(job_fields)
|
|
88
93
|
def post(self):
|
|
89
|
-
|
|
90
|
-
if
|
|
91
|
-
api.abort(400,
|
|
92
|
-
if
|
|
94
|
+
"""Create a new scheduled job"""
|
|
95
|
+
if "crontab" in request.json and "interval" in request.json:
|
|
96
|
+
api.abort(400, "Cannot define both interval and crontab schedule")
|
|
97
|
+
if "crontab" in request.json:
|
|
93
98
|
form = api.validate(CrontabTaskForm)
|
|
94
99
|
else:
|
|
95
100
|
form = api.validate(IntervalTaskForm)
|
|
96
101
|
return form.save(), 201
|
|
97
102
|
|
|
98
103
|
|
|
99
|
-
@ns.route(
|
|
100
|
-
@api.param(
|
|
104
|
+
@ns.route("/jobs/<string:id>", endpoint="job")
|
|
105
|
+
@api.param("id", "A job ID")
|
|
101
106
|
class JobAPI(API):
|
|
102
107
|
def get_or_404(self, id):
|
|
103
108
|
task = PeriodicTask.objects(id=id).first()
|
|
@@ -107,15 +112,15 @@ class JobAPI(API):
|
|
|
107
112
|
|
|
108
113
|
@api.marshal_with(job_fields)
|
|
109
114
|
def get(self, id):
|
|
110
|
-
|
|
115
|
+
"""Fetch a single scheduled job"""
|
|
111
116
|
return self.get_or_404(id_or_404(id))
|
|
112
117
|
|
|
113
118
|
@api.secure(admin_permission)
|
|
114
119
|
@api.marshal_with(job_fields)
|
|
115
120
|
def put(self, id):
|
|
116
|
-
|
|
121
|
+
"""Update a single scheduled job"""
|
|
117
122
|
task = self.get_or_404(id_or_404(id))
|
|
118
|
-
if
|
|
123
|
+
if "crontab" in request.json:
|
|
119
124
|
task.interval = None
|
|
120
125
|
task.crontab = PeriodicTask.Crontab()
|
|
121
126
|
form = api.validate(CrontabTaskForm, task)
|
|
@@ -126,35 +131,37 @@ class JobAPI(API):
|
|
|
126
131
|
return form.save()
|
|
127
132
|
|
|
128
133
|
@api.secure(admin_permission)
|
|
129
|
-
@api.response(204,
|
|
134
|
+
@api.response(204, "Successfuly deleted")
|
|
130
135
|
def delete(self, id):
|
|
131
|
-
|
|
136
|
+
"""Delete a single scheduled job"""
|
|
132
137
|
task = self.get_or_404(id_or_404(id))
|
|
133
138
|
task.delete()
|
|
134
|
-
return
|
|
139
|
+
return "", 204
|
|
135
140
|
|
|
136
141
|
|
|
137
|
-
@ns.route(
|
|
142
|
+
@ns.route("/tasks/<string:id>", endpoint="task")
|
|
138
143
|
class TaskAPI(API):
|
|
139
144
|
@api.marshal_with(task_fields)
|
|
140
145
|
def get(self, id):
|
|
141
|
-
|
|
146
|
+
"""Get a tasks status given its ID"""
|
|
142
147
|
result = AsyncResult(id, app=celery)
|
|
143
148
|
status, retval = result.status, result.result
|
|
144
|
-
data = {
|
|
149
|
+
data = {"id": id, "status": status, "result": retval}
|
|
145
150
|
if status in states.EXCEPTION_STATES:
|
|
146
151
|
traceback = result.traceback
|
|
147
|
-
data.update(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
data.update(
|
|
153
|
+
{
|
|
154
|
+
"result": safe_repr(retval),
|
|
155
|
+
"exc": get_full_cls_name(retval.__class__),
|
|
156
|
+
"traceback": traceback,
|
|
157
|
+
}
|
|
158
|
+
)
|
|
152
159
|
return data
|
|
153
160
|
|
|
154
161
|
|
|
155
|
-
@ns.route(
|
|
162
|
+
@ns.route("/jobs/schedulables", endpoint="schedulable_jobs")
|
|
156
163
|
class JobsReferenceAPI(API):
|
|
157
164
|
@api.doc(model=[str])
|
|
158
165
|
def get(self):
|
|
159
|
-
|
|
166
|
+
"""List all schedulable jobs"""
|
|
160
167
|
return [job.name for job in schedulables()]
|