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/topic/api.py
CHANGED
|
@@ -1,134 +1,145 @@
|
|
|
1
|
-
from udata.api import api, fields
|
|
1
|
+
from udata.api import API, api, fields
|
|
2
2
|
from udata.core.dataset.api_fields import dataset_fields
|
|
3
3
|
from udata.core.discussions.models import Discussion
|
|
4
4
|
from udata.core.organization.api_fields import org_ref_fields
|
|
5
5
|
from udata.core.reuse.api_fields import reuse_fields
|
|
6
6
|
from udata.core.spatial.api_fields import spatial_coverage_fields
|
|
7
|
-
from udata.core.topic.permissions import TopicEditPermission
|
|
8
7
|
from udata.core.topic.parsers import TopicApiParser
|
|
8
|
+
from udata.core.topic.permissions import TopicEditPermission
|
|
9
9
|
from udata.core.user.api_fields import user_ref_fields
|
|
10
10
|
|
|
11
|
-
from .models import Topic
|
|
12
11
|
from .forms import TopicForm
|
|
12
|
+
from .models import Topic
|
|
13
13
|
|
|
14
|
-
DEFAULT_SORTING =
|
|
14
|
+
DEFAULT_SORTING = "-created_at"
|
|
15
15
|
|
|
16
|
-
ns = api.namespace(
|
|
16
|
+
ns = api.namespace("topics", "Topics related operations")
|
|
17
17
|
|
|
18
|
-
topic_fields = api.model(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
description=
|
|
23
|
-
|
|
24
|
-
description
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
description=
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
18
|
+
topic_fields = api.model(
|
|
19
|
+
"Topic",
|
|
20
|
+
{
|
|
21
|
+
"id": fields.String(description="The topic identifier"),
|
|
22
|
+
"name": fields.String(description="The topic name", required=True),
|
|
23
|
+
"slug": fields.String(description="The topic permalink string", readonly=True),
|
|
24
|
+
"description": fields.Markdown(
|
|
25
|
+
description="The topic description in Markdown", required=True
|
|
26
|
+
),
|
|
27
|
+
"tags": fields.List(
|
|
28
|
+
fields.String, description="Some keywords to help in search", required=True
|
|
29
|
+
),
|
|
30
|
+
"datasets": fields.List(
|
|
31
|
+
fields.Nested(dataset_fields),
|
|
32
|
+
description="The topic datasets",
|
|
33
|
+
attribute=lambda o: [d.fetch() for d in o.datasets],
|
|
34
|
+
),
|
|
35
|
+
"reuses": fields.List(
|
|
36
|
+
fields.Nested(reuse_fields),
|
|
37
|
+
description="The topic reuses",
|
|
38
|
+
attribute=lambda o: [r.fetch() for r in o.reuses],
|
|
39
|
+
),
|
|
40
|
+
"featured": fields.Boolean(description="Is the topic featured"),
|
|
41
|
+
"private": fields.Boolean(description="Is the topic private"),
|
|
42
|
+
"created_at": fields.ISODateTime(description="The topic creation date", readonly=True),
|
|
43
|
+
"spatial": fields.Nested(
|
|
44
|
+
spatial_coverage_fields, allow_null=True, description="The spatial coverage"
|
|
45
|
+
),
|
|
46
|
+
"last_modified": fields.ISODateTime(
|
|
47
|
+
description="The topic last modification date", readonly=True
|
|
48
|
+
),
|
|
49
|
+
"organization": fields.Nested(
|
|
50
|
+
org_ref_fields,
|
|
51
|
+
allow_null=True,
|
|
52
|
+
description="The publishing organization",
|
|
53
|
+
readonly=True,
|
|
54
|
+
),
|
|
55
|
+
"owner": fields.Nested(
|
|
56
|
+
user_ref_fields, description="The owner user", readonly=True, allow_null=True
|
|
57
|
+
),
|
|
58
|
+
"uri": fields.UrlFor(
|
|
59
|
+
"api.topic", lambda o: {"topic": o}, description="The topic API URI", readonly=True
|
|
60
|
+
),
|
|
61
|
+
"page": fields.UrlFor(
|
|
62
|
+
"topics.display",
|
|
63
|
+
lambda o: {"topic": o},
|
|
64
|
+
description="The topic page URL",
|
|
65
|
+
readonly=True,
|
|
66
|
+
fallback_endpoint="api.topic",
|
|
67
|
+
),
|
|
68
|
+
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
69
|
+
},
|
|
70
|
+
mask="*,datasets{id,title,uri,page},reuses{id,title,image,image_thumbnail,uri,page}",
|
|
71
|
+
)
|
|
60
72
|
|
|
61
|
-
topic_page_fields = api.model(
|
|
73
|
+
topic_page_fields = api.model("TopicPage", fields.pager(topic_fields))
|
|
62
74
|
|
|
63
75
|
topic_parser = TopicApiParser()
|
|
64
76
|
|
|
65
77
|
|
|
66
|
-
@ns.route(
|
|
78
|
+
@ns.route("/", endpoint="topics")
|
|
67
79
|
class TopicsAPI(API):
|
|
68
80
|
"""
|
|
69
81
|
Warning: querying a list with a topic containing a lot of related objects (datasets, reuses)
|
|
70
82
|
will fail/take a lot of time because every object is dereferenced. Use api v2 if you can.
|
|
71
83
|
"""
|
|
72
84
|
|
|
73
|
-
@api.doc(
|
|
85
|
+
@api.doc("list_topics")
|
|
74
86
|
@api.expect(topic_parser.parser)
|
|
75
87
|
@api.marshal_with(topic_page_fields)
|
|
76
88
|
def get(self):
|
|
77
|
-
|
|
89
|
+
"""List all topics"""
|
|
78
90
|
args = topic_parser.parse()
|
|
79
91
|
topics = Topic.objects()
|
|
80
92
|
topics = topic_parser.parse_filters(topics, args)
|
|
81
|
-
sort = args[
|
|
82
|
-
return
|
|
83
|
-
.paginate(args['page'], args['page_size']))
|
|
93
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
|
|
94
|
+
return topics.order_by(sort).paginate(args["page"], args["page_size"])
|
|
84
95
|
|
|
85
|
-
@api.doc(
|
|
96
|
+
@api.doc("create_topic")
|
|
86
97
|
@api.expect(topic_fields)
|
|
87
98
|
@api.marshal_with(topic_fields)
|
|
88
|
-
@api.response(400,
|
|
99
|
+
@api.response(400, "Validation error")
|
|
89
100
|
def post(self):
|
|
90
|
-
|
|
101
|
+
"""Create a topic"""
|
|
91
102
|
form = api.validate(TopicForm)
|
|
92
103
|
return form.save(), 201
|
|
93
104
|
|
|
94
105
|
|
|
95
|
-
@ns.route(
|
|
96
|
-
@api.param(
|
|
97
|
-
@api.response(404,
|
|
106
|
+
@ns.route("/<topic:topic>/", endpoint="topic")
|
|
107
|
+
@api.param("topic", "The topic ID or slug")
|
|
108
|
+
@api.response(404, "Object not found")
|
|
98
109
|
class TopicAPI(API):
|
|
99
110
|
"""
|
|
100
111
|
Warning: querying a topic containing a lot of related objects (datasets, reuses)
|
|
101
112
|
will fail/take a lot of time because every object is dereferenced. Use api v2 if you can.
|
|
102
113
|
"""
|
|
103
114
|
|
|
104
|
-
@api.doc(
|
|
115
|
+
@api.doc("get_topic")
|
|
105
116
|
@api.marshal_with(topic_fields)
|
|
106
117
|
def get(self, topic):
|
|
107
|
-
|
|
118
|
+
"""Get a given topic"""
|
|
108
119
|
return topic
|
|
109
120
|
|
|
110
121
|
@api.secure
|
|
111
|
-
@api.doc(
|
|
122
|
+
@api.doc("update_topic")
|
|
112
123
|
@api.expect(topic_fields)
|
|
113
124
|
@api.marshal_with(topic_fields)
|
|
114
|
-
@api.response(400,
|
|
115
|
-
@api.response(403,
|
|
125
|
+
@api.response(400, "Validation error")
|
|
126
|
+
@api.response(403, "Forbidden")
|
|
116
127
|
def put(self, topic):
|
|
117
|
-
|
|
128
|
+
"""Update a given topic"""
|
|
118
129
|
if not TopicEditPermission(topic).can():
|
|
119
|
-
api.abort(403,
|
|
130
|
+
api.abort(403, "Forbidden")
|
|
120
131
|
form = api.validate(TopicForm, topic)
|
|
121
132
|
return form.save()
|
|
122
133
|
|
|
123
134
|
@api.secure
|
|
124
|
-
@api.doc(
|
|
125
|
-
@api.response(204,
|
|
126
|
-
@api.response(403,
|
|
135
|
+
@api.doc("delete_topic")
|
|
136
|
+
@api.response(204, "Object deleted")
|
|
137
|
+
@api.response(403, "Forbidden")
|
|
127
138
|
def delete(self, topic):
|
|
128
|
-
|
|
139
|
+
"""Delete a given topic"""
|
|
129
140
|
if not TopicEditPermission(topic).can():
|
|
130
|
-
api.abort(403,
|
|
141
|
+
api.abort(403, "Forbidden")
|
|
131
142
|
# Remove discussions linked to the topic
|
|
132
143
|
Discussion.objects(subject=topic).delete()
|
|
133
144
|
topic.delete()
|
|
134
|
-
return
|
|
145
|
+
return "", 204
|
udata/core/topic/apiv2.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
import mongoengine
|
|
4
|
-
|
|
5
4
|
from bson import ObjectId
|
|
6
|
-
from flask import
|
|
5
|
+
from flask import request, url_for
|
|
7
6
|
|
|
8
|
-
from udata.api import
|
|
7
|
+
from udata.api import API, apiv2, fields
|
|
9
8
|
from udata.core.dataset.api import DatasetApiParser
|
|
10
9
|
from udata.core.dataset.apiv2 import dataset_page_fields
|
|
11
10
|
from udata.core.dataset.models import Dataset
|
|
@@ -19,142 +18,169 @@ from udata.core.topic.parsers import TopicApiParser
|
|
|
19
18
|
from udata.core.topic.permissions import TopicEditPermission
|
|
20
19
|
from udata.core.user.api_fields import user_ref_fields
|
|
21
20
|
|
|
22
|
-
DEFAULT_SORTING =
|
|
21
|
+
DEFAULT_SORTING = "-created_at"
|
|
23
22
|
DEFAULT_PAGE_SIZE = 20
|
|
24
23
|
|
|
25
24
|
log = logging.getLogger(__name__)
|
|
26
25
|
|
|
27
|
-
ns = apiv2.namespace(
|
|
26
|
+
ns = apiv2.namespace("topics", "Topics related operations")
|
|
28
27
|
|
|
29
28
|
topic_parser = TopicApiParser()
|
|
30
29
|
generic_parser = apiv2.page_parser()
|
|
31
30
|
dataset_parser = DatasetApiParser()
|
|
32
31
|
reuse_parser = ReuseApiParser()
|
|
33
32
|
|
|
34
|
-
common_doc = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
description
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
description=
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
33
|
+
common_doc = {"params": {"topic": "The topic ID"}}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
topic_fields = apiv2.model(
|
|
37
|
+
"Topic",
|
|
38
|
+
{
|
|
39
|
+
"id": fields.String(description="The topic identifier"),
|
|
40
|
+
"name": fields.String(description="The topic name", required=True),
|
|
41
|
+
"slug": fields.String(description="The topic permalink string", readonly=True),
|
|
42
|
+
"description": fields.Markdown(
|
|
43
|
+
description="The topic description in Markdown", required=True
|
|
44
|
+
),
|
|
45
|
+
"tags": fields.List(
|
|
46
|
+
fields.String, description="Some keywords to help in search", required=True
|
|
47
|
+
),
|
|
48
|
+
"datasets": fields.Raw(
|
|
49
|
+
attribute=lambda o: {
|
|
50
|
+
"rel": "subsection",
|
|
51
|
+
"href": url_for(
|
|
52
|
+
"apiv2.topic_datasets",
|
|
53
|
+
topic=o.id,
|
|
54
|
+
page=1,
|
|
55
|
+
page_size=DEFAULT_PAGE_SIZE,
|
|
56
|
+
_external=True,
|
|
57
|
+
),
|
|
58
|
+
"type": "GET",
|
|
59
|
+
"total": len(o.datasets),
|
|
60
|
+
},
|
|
61
|
+
description="Link to the topic datasets",
|
|
62
|
+
),
|
|
63
|
+
"reuses": fields.Raw(
|
|
64
|
+
attribute=lambda o: {
|
|
65
|
+
"rel": "subsection",
|
|
66
|
+
"href": url_for(
|
|
67
|
+
"apiv2.topic_reuses",
|
|
68
|
+
topic=o.id,
|
|
69
|
+
page=1,
|
|
70
|
+
page_size=DEFAULT_PAGE_SIZE,
|
|
71
|
+
_external=True,
|
|
72
|
+
),
|
|
73
|
+
"type": "GET",
|
|
74
|
+
"total": len(o.reuses),
|
|
75
|
+
},
|
|
76
|
+
description="Link to the topic reuses",
|
|
77
|
+
),
|
|
78
|
+
"featured": fields.Boolean(description="Is the topic featured"),
|
|
79
|
+
"private": fields.Boolean(description="Is the topic private"),
|
|
80
|
+
"created_at": fields.ISODateTime(description="The topic creation date", readonly=True),
|
|
81
|
+
"spatial": fields.Nested(
|
|
82
|
+
spatial_coverage_fields, allow_null=True, description="The spatial coverage"
|
|
83
|
+
),
|
|
84
|
+
"last_modified": fields.ISODateTime(
|
|
85
|
+
description="The topic last modification date", readonly=True
|
|
86
|
+
),
|
|
87
|
+
"organization": fields.Nested(
|
|
88
|
+
org_ref_fields,
|
|
89
|
+
allow_null=True,
|
|
90
|
+
description="The publishing organization",
|
|
91
|
+
readonly=True,
|
|
92
|
+
),
|
|
93
|
+
"owner": fields.Nested(
|
|
94
|
+
user_ref_fields, description="The owner user", readonly=True, allow_null=True
|
|
95
|
+
),
|
|
96
|
+
"uri": fields.UrlFor(
|
|
97
|
+
"api.topic", lambda o: {"topic": o}, description="The topic API URI", readonly=True
|
|
98
|
+
),
|
|
99
|
+
"page": fields.UrlFor(
|
|
100
|
+
"topics.display",
|
|
101
|
+
lambda o: {"topic": o},
|
|
102
|
+
description="The topic page URL",
|
|
103
|
+
readonly=True,
|
|
104
|
+
fallback_endpoint="api.topic",
|
|
105
|
+
),
|
|
106
|
+
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
topic_page_fields = apiv2.model("TopicPage", fields.pager(topic_fields))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@ns.route("/", endpoint="topics_list")
|
|
91
114
|
class TopicsAPI(API):
|
|
92
115
|
@apiv2.expect(topic_parser.parser)
|
|
93
116
|
@apiv2.marshal_with(topic_page_fields)
|
|
94
117
|
def get(self):
|
|
95
|
-
|
|
118
|
+
"""List all topics"""
|
|
96
119
|
args = topic_parser.parse()
|
|
97
120
|
topics = Topic.objects()
|
|
98
121
|
topics = topic_parser.parse_filters(topics, args)
|
|
99
|
-
sort = args[
|
|
100
|
-
return
|
|
101
|
-
.paginate(args['page'], args['page_size']))
|
|
122
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
|
|
123
|
+
return topics.order_by(sort).paginate(args["page"], args["page_size"])
|
|
102
124
|
|
|
103
125
|
|
|
104
|
-
@ns.route(
|
|
105
|
-
@apiv2.response(404,
|
|
126
|
+
@ns.route("/<topic:topic>/", endpoint="topic", doc=common_doc)
|
|
127
|
+
@apiv2.response(404, "Topic not found")
|
|
106
128
|
class TopicAPI(API):
|
|
107
|
-
@apiv2.doc(
|
|
129
|
+
@apiv2.doc("get_topic")
|
|
108
130
|
@apiv2.marshal_with(topic_fields)
|
|
109
131
|
def get(self, topic):
|
|
110
|
-
|
|
132
|
+
"""Get a given topic"""
|
|
111
133
|
return topic
|
|
112
134
|
|
|
113
135
|
|
|
114
|
-
topic_add_items_fields = apiv2.model(
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
topic_add_items_fields = apiv2.model(
|
|
137
|
+
"TopicItemsAdd",
|
|
138
|
+
{
|
|
139
|
+
"id": fields.String(description="Id of the item to add", required=True),
|
|
140
|
+
},
|
|
141
|
+
location="json",
|
|
142
|
+
)
|
|
117
143
|
|
|
118
144
|
|
|
119
|
-
@ns.route(
|
|
145
|
+
@ns.route("/<topic:topic>/datasets/", endpoint="topic_datasets", doc=common_doc)
|
|
120
146
|
class TopicDatasetsAPI(API):
|
|
121
|
-
@apiv2.doc(
|
|
147
|
+
@apiv2.doc("topic_datasets")
|
|
122
148
|
@apiv2.expect(dataset_parser.parser)
|
|
123
149
|
@apiv2.marshal_with(dataset_page_fields)
|
|
124
150
|
def get(self, topic):
|
|
125
|
-
|
|
151
|
+
"""Get a given topic datasets, with filters"""
|
|
126
152
|
args = dataset_parser.parse()
|
|
127
|
-
args[
|
|
153
|
+
args["topic"] = topic.id
|
|
128
154
|
datasets = Dataset.objects(archived=None, deleted=None, private=False)
|
|
129
155
|
datasets = dataset_parser.parse_filters(datasets, args)
|
|
130
|
-
sort = args[
|
|
131
|
-
return datasets.order_by(sort).paginate(args[
|
|
156
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or "-created_at_internal"
|
|
157
|
+
return datasets.order_by(sort).paginate(args["page"], args["page_size"])
|
|
132
158
|
|
|
133
159
|
@apiv2.secure
|
|
134
|
-
@apiv2.doc(
|
|
160
|
+
@apiv2.doc("topic_datasets_create")
|
|
135
161
|
@apiv2.expect([topic_add_items_fields])
|
|
136
162
|
@apiv2.marshal_with(topic_fields)
|
|
137
|
-
@apiv2.response(400,
|
|
138
|
-
@apiv2.response(400,
|
|
139
|
-
@apiv2.response(400,
|
|
140
|
-
@apiv2.response(404,
|
|
141
|
-
@apiv2.response(403,
|
|
163
|
+
@apiv2.response(400, "Malformed object id(s) in request")
|
|
164
|
+
@apiv2.response(400, "Expecting a list")
|
|
165
|
+
@apiv2.response(400, "Expecting a list of dicts with id attribute")
|
|
166
|
+
@apiv2.response(404, "Topic not found")
|
|
167
|
+
@apiv2.response(403, "Forbidden")
|
|
142
168
|
def post(self, topic):
|
|
143
169
|
if not TopicEditPermission(topic).can():
|
|
144
|
-
apiv2.abort(403,
|
|
170
|
+
apiv2.abort(403, "Forbidden")
|
|
145
171
|
|
|
146
172
|
data = request.json
|
|
147
173
|
|
|
148
174
|
if not isinstance(data, list):
|
|
149
|
-
apiv2.abort(400,
|
|
150
|
-
if not all(isinstance(d, dict) and d.get(
|
|
151
|
-
apiv2.abort(400,
|
|
175
|
+
apiv2.abort(400, "Expecting a list")
|
|
176
|
+
if not all(isinstance(d, dict) and d.get("id") for d in data):
|
|
177
|
+
apiv2.abort(400, "Expecting a list of dicts with id attribute")
|
|
152
178
|
|
|
153
179
|
try:
|
|
154
|
-
datasets = Dataset.objects.filter(id__in=[d[
|
|
180
|
+
datasets = Dataset.objects.filter(id__in=[d["id"] for d in data]).only("id")
|
|
155
181
|
diff = set(d.id for d in datasets) - set(d.id for d in topic.datasets)
|
|
156
182
|
except mongoengine.errors.ValidationError:
|
|
157
|
-
apiv2.abort(400,
|
|
183
|
+
apiv2.abort(400, "Malformed object id(s) in request")
|
|
158
184
|
|
|
159
185
|
if diff:
|
|
160
186
|
topic.datasets += [ObjectId(did) for did in diff]
|
|
@@ -163,69 +189,71 @@ class TopicDatasetsAPI(API):
|
|
|
163
189
|
return topic, 201
|
|
164
190
|
|
|
165
191
|
|
|
166
|
-
@ns.route(
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
@ns.route(
|
|
193
|
+
"/<topic:topic>/datasets/<dataset:dataset>/",
|
|
194
|
+
endpoint="topic_dataset",
|
|
195
|
+
doc={"params": {"topic": "The topic ID", "dataset": "The dataset ID"}},
|
|
196
|
+
)
|
|
169
197
|
class TopicDatasetAPI(API):
|
|
170
198
|
@apiv2.secure
|
|
171
|
-
@apiv2.response(404,
|
|
172
|
-
@apiv2.response(404,
|
|
173
|
-
@apiv2.response(204,
|
|
199
|
+
@apiv2.response(404, "Topic not found")
|
|
200
|
+
@apiv2.response(404, "Dataset not found in topic")
|
|
201
|
+
@apiv2.response(204, "Success")
|
|
174
202
|
def delete(self, topic, dataset):
|
|
175
|
-
|
|
203
|
+
"""Delete a given dataset from the given topic"""
|
|
176
204
|
if not TopicEditPermission(topic).can():
|
|
177
|
-
apiv2.abort(403,
|
|
205
|
+
apiv2.abort(403, "Forbidden")
|
|
178
206
|
|
|
179
207
|
if dataset.id not in (d.id for d in topic.datasets):
|
|
180
|
-
apiv2.abort(404,
|
|
208
|
+
apiv2.abort(404, "Dataset not found in topic")
|
|
181
209
|
topic.datasets = [d for d in topic.datasets if d.id != dataset.id]
|
|
182
210
|
topic.save()
|
|
183
211
|
|
|
184
212
|
return None, 204
|
|
185
213
|
|
|
186
214
|
|
|
187
|
-
@ns.route(
|
|
215
|
+
@ns.route("/<topic:topic>/reuses/", endpoint="topic_reuses", doc=common_doc)
|
|
188
216
|
class TopicReusesAPI(API):
|
|
189
|
-
@apiv2.doc(
|
|
217
|
+
@apiv2.doc("topic_reuses")
|
|
190
218
|
@apiv2.expect(reuse_parser.parser)
|
|
191
219
|
@apiv2.marshal_with(reuse_page_fields)
|
|
192
220
|
def get(self, topic):
|
|
193
|
-
|
|
221
|
+
"""Get a given topic reuses, with filters"""
|
|
194
222
|
args = reuse_parser.parse()
|
|
195
223
|
reuses = Reuse.objects(deleted=None, private__ne=True).filter(
|
|
196
224
|
id__in=[d.id for d in topic.reuses]
|
|
197
225
|
)
|
|
198
226
|
# warning: topic in reuse_parser is different from Topic
|
|
199
227
|
reuses = reuse_parser.parse_filters(reuses, args)
|
|
200
|
-
sort = args[
|
|
201
|
-
return reuses.order_by(sort).paginate(args[
|
|
228
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
|
|
229
|
+
return reuses.order_by(sort).paginate(args["page"], args["page_size"])
|
|
202
230
|
|
|
203
231
|
@apiv2.secure
|
|
204
|
-
@apiv2.doc(
|
|
232
|
+
@apiv2.doc("topic_reuses_create")
|
|
205
233
|
@apiv2.expect([topic_add_items_fields])
|
|
206
234
|
@apiv2.marshal_with(topic_fields)
|
|
207
|
-
@apiv2.response(400,
|
|
208
|
-
@apiv2.response(400,
|
|
209
|
-
@apiv2.response(400,
|
|
210
|
-
@apiv2.response(404,
|
|
211
|
-
@apiv2.response(403,
|
|
235
|
+
@apiv2.response(400, "Malformed object id(s) in request")
|
|
236
|
+
@apiv2.response(400, "Expecting a list")
|
|
237
|
+
@apiv2.response(400, "Expecting a list of dicts with id attribute")
|
|
238
|
+
@apiv2.response(404, "Topic not found")
|
|
239
|
+
@apiv2.response(403, "Forbidden")
|
|
212
240
|
def post(self, topic):
|
|
213
|
-
|
|
241
|
+
"""Add reuses to a given topic from a list of reuses ids"""
|
|
214
242
|
if not TopicEditPermission(topic).can():
|
|
215
|
-
apiv2.abort(403,
|
|
243
|
+
apiv2.abort(403, "Forbidden")
|
|
216
244
|
|
|
217
245
|
data = request.json
|
|
218
246
|
|
|
219
247
|
if not isinstance(data, list):
|
|
220
|
-
apiv2.abort(400,
|
|
221
|
-
if not all(isinstance(d, dict) and d.get(
|
|
222
|
-
apiv2.abort(400,
|
|
248
|
+
apiv2.abort(400, "Expecting a list")
|
|
249
|
+
if not all(isinstance(d, dict) and d.get("id") for d in data):
|
|
250
|
+
apiv2.abort(400, "Expecting a list of dicts with id attribute")
|
|
223
251
|
|
|
224
252
|
try:
|
|
225
|
-
reuses = Reuse.objects.filter(id__in=[r[
|
|
253
|
+
reuses = Reuse.objects.filter(id__in=[r["id"] for r in data]).only("id")
|
|
226
254
|
diff = set(d.id for d in reuses) - set(d.id for d in topic.reuses)
|
|
227
255
|
except mongoengine.errors.ValidationError:
|
|
228
|
-
apiv2.abort(400,
|
|
256
|
+
apiv2.abort(400, "Malformed object id(s) in request")
|
|
229
257
|
|
|
230
258
|
if diff:
|
|
231
259
|
topic.reuses += [ObjectId(rid) for rid in diff]
|
|
@@ -234,21 +262,23 @@ class TopicReusesAPI(API):
|
|
|
234
262
|
return topic, 201
|
|
235
263
|
|
|
236
264
|
|
|
237
|
-
@ns.route(
|
|
238
|
-
|
|
239
|
-
|
|
265
|
+
@ns.route(
|
|
266
|
+
"/<topic:topic>/reuses/<reuse:reuse>/",
|
|
267
|
+
endpoint="topic_reuse",
|
|
268
|
+
doc={"params": {"topic": "The topic ID", "reuse": "The reuse ID"}},
|
|
269
|
+
)
|
|
240
270
|
class TopicReuseAPI(API):
|
|
241
271
|
@apiv2.secure
|
|
242
|
-
@apiv2.response(404,
|
|
243
|
-
@apiv2.response(404,
|
|
244
|
-
@apiv2.response(204,
|
|
272
|
+
@apiv2.response(404, "Topic not found")
|
|
273
|
+
@apiv2.response(404, "Reuse not found in topic")
|
|
274
|
+
@apiv2.response(204, "Success")
|
|
245
275
|
def delete(self, topic, reuse):
|
|
246
|
-
|
|
276
|
+
"""Delete a given reuse from the given topic"""
|
|
247
277
|
if not TopicEditPermission(topic).can():
|
|
248
|
-
apiv2.abort(403,
|
|
278
|
+
apiv2.abort(403, "Forbidden")
|
|
249
279
|
|
|
250
280
|
if reuse.id not in (d.id for d in topic.reuses):
|
|
251
|
-
apiv2.abort(404,
|
|
281
|
+
apiv2.abort(404, "Reuse not found in topic")
|
|
252
282
|
topic.reuses = [d for d in topic.reuses if d.id != reuse.id]
|
|
253
283
|
topic.save()
|
|
254
284
|
|
udata/core/topic/factories.py
CHANGED
|
@@ -12,10 +12,9 @@ class TopicFactory(ModelFactory):
|
|
|
12
12
|
class Meta:
|
|
13
13
|
model = Topic
|
|
14
14
|
|
|
15
|
-
name = factory.Faker(
|
|
16
|
-
description = factory.Faker(
|
|
17
|
-
tags = factory.LazyAttribute(lambda o: [utils.unique_string(16)
|
|
18
|
-
for _ in range(3)])
|
|
15
|
+
name = factory.Faker("sentence")
|
|
16
|
+
description = factory.Faker("text")
|
|
17
|
+
tags = factory.LazyAttribute(lambda o: [utils.unique_string(16) for _ in range(3)])
|
|
19
18
|
private = False
|
|
20
19
|
|
|
21
20
|
@factory.lazy_attribute
|