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/activity/tasks.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
from udata.models import
|
|
4
|
-
from udata.tasks import
|
|
3
|
+
from udata.models import Organization, User, db
|
|
4
|
+
from udata.tasks import celery, task
|
|
5
5
|
|
|
6
6
|
from .signals import new_activity
|
|
7
7
|
|
|
@@ -16,16 +16,23 @@ def delay_activity(cls, related_to, actor, organization=None, extras=None):
|
|
|
16
16
|
related_to_cls=related_to.__class__.__name__,
|
|
17
17
|
related_to_id=str(related_to.id),
|
|
18
18
|
organization_id=str(organization.id) if organization else None,
|
|
19
|
-
extras=extras
|
|
19
|
+
extras=extras,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@task
|
|
24
|
-
def emit_activity(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
def emit_activity(
|
|
25
|
+
classname, actor_id, related_to_cls, related_to_id, organization_id=None, extras=None
|
|
26
|
+
):
|
|
27
|
+
log.debug(
|
|
28
|
+
"Emit new activity: %s %s %s %s %s %s",
|
|
29
|
+
classname,
|
|
30
|
+
actor_id,
|
|
31
|
+
related_to_cls,
|
|
32
|
+
related_to_id,
|
|
33
|
+
organization_id,
|
|
34
|
+
extras,
|
|
35
|
+
)
|
|
29
36
|
cls = db.resolve_model(classname)
|
|
30
37
|
actor = User.objects.get(pk=actor_id)
|
|
31
38
|
related_to = db.resolve_model(related_to_cls).objects.get(pk=related_to_id)
|
|
@@ -33,5 +40,4 @@ def emit_activity(classname, actor_id, related_to_cls, related_to_id,
|
|
|
33
40
|
organization = Organization.objects.get(pk=organization_id)
|
|
34
41
|
else:
|
|
35
42
|
organization = None
|
|
36
|
-
cls.objects.create(actor=actor, related_to=related_to,
|
|
37
|
-
organization=organization, extras=extras)
|
|
43
|
+
cls.objects.create(actor=actor, related_to=related_to, organization=organization, extras=extras)
|
udata/core/badges/api.py
CHANGED
|
@@ -4,14 +4,14 @@ from .forms import badge_form
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def add(obj):
|
|
7
|
-
|
|
7
|
+
"""
|
|
8
8
|
Handle a badge add API.
|
|
9
9
|
|
|
10
10
|
- Expecting badge_fieds as payload
|
|
11
11
|
- Return the badge as payload
|
|
12
12
|
- Return 200 if the badge is already
|
|
13
13
|
- Return 201 if the badge is added
|
|
14
|
-
|
|
14
|
+
"""
|
|
15
15
|
Form = badge_form(obj.__class__)
|
|
16
16
|
form = api.validate(Form)
|
|
17
17
|
kind = form.kind.data
|
|
@@ -23,13 +23,13 @@ def add(obj):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def remove(obj, kind):
|
|
26
|
-
|
|
26
|
+
"""
|
|
27
27
|
Handle badge removal API
|
|
28
28
|
|
|
29
29
|
- Returns 404 if the badge for this kind is absent
|
|
30
30
|
- Returns 204 on success
|
|
31
|
-
|
|
31
|
+
"""
|
|
32
32
|
if not obj.get_badge(kind):
|
|
33
|
-
api.abort(404,
|
|
33
|
+
api.abort(404, "Badge does not exists")
|
|
34
34
|
obj.remove_badge(kind)
|
|
35
|
-
return
|
|
35
|
+
return "", 204
|
udata/core/badges/commands.py
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
import click
|
|
2
1
|
import logging
|
|
3
|
-
|
|
4
2
|
from os.path import exists
|
|
5
3
|
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
6
|
from udata.commands import cli
|
|
7
7
|
from udata.models import Organization
|
|
8
8
|
|
|
9
9
|
log = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@cli.group(
|
|
12
|
+
@cli.group("badges")
|
|
13
13
|
def grp():
|
|
14
|
-
|
|
14
|
+
"""Badges related operations"""
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def toggle_badge(id_or_slug, badge_kind):
|
|
18
18
|
organization = Organization.objects.get_by_id_or_slug(id_or_slug)
|
|
19
19
|
if not organization:
|
|
20
|
-
log.error(
|
|
20
|
+
log.error("No organization found for %s", id_or_slug)
|
|
21
21
|
return
|
|
22
|
-
log.info(
|
|
23
|
-
kind
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
log.info(
|
|
23
|
+
"Toggling badge {kind} for organization {org}".format(
|
|
24
|
+
kind=badge_kind, org=organization.name
|
|
25
|
+
)
|
|
26
|
+
)
|
|
26
27
|
organization.toggle_badge(badge_kind)
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
@grp.command()
|
|
30
|
-
@click.argument(
|
|
31
|
-
@click.argument(
|
|
31
|
+
@click.argument("path_or_id")
|
|
32
|
+
@click.argument("badge_kind")
|
|
32
33
|
def toggle(path_or_id, badge_kind):
|
|
33
|
-
|
|
34
|
+
"""Toggle a `badge_kind` for a given `path_or_id`
|
|
34
35
|
|
|
35
36
|
The `path_or_id` is either an id, a slug or a file containing a list
|
|
36
37
|
of ids or slugs.
|
|
37
|
-
|
|
38
|
+
"""
|
|
38
39
|
if exists(path_or_id):
|
|
39
40
|
with open(path_or_id) as open_file:
|
|
40
41
|
for id_or_slug in open_file.readlines():
|
udata/core/badges/fields.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from udata.api import api, fields
|
|
2
2
|
|
|
3
|
-
badge_fields = api.model(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
badge_fields = api.model(
|
|
4
|
+
"Badge",
|
|
5
|
+
{
|
|
6
|
+
"kind": fields.String(
|
|
7
|
+
description=("Kind of badge (certified, etc), " "specific to each model"), required=True
|
|
8
|
+
),
|
|
9
|
+
},
|
|
10
|
+
)
|
udata/core/badges/forms.py
CHANGED
|
@@ -2,17 +2,20 @@ from udata.forms import ModelForm, fields, validators
|
|
|
2
2
|
from udata.i18n import lazy_gettext as _
|
|
3
3
|
from udata.models import Badge
|
|
4
4
|
|
|
5
|
-
__all__ = (
|
|
5
|
+
__all__ = ("badge_form",)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def badge_form(model):
|
|
9
|
-
|
|
9
|
+
"""A form factory for a given model badges"""
|
|
10
|
+
|
|
10
11
|
class BadgeForm(ModelForm):
|
|
11
12
|
model_class = Badge
|
|
12
13
|
|
|
13
14
|
kind = fields.RadioField(
|
|
14
|
-
_(
|
|
15
|
+
_("Kind"),
|
|
16
|
+
[validators.DataRequired()],
|
|
15
17
|
choices=list(model.__badges__.items()),
|
|
16
|
-
description=_(
|
|
18
|
+
description=_("Kind of badge (certified, etc)"),
|
|
19
|
+
)
|
|
17
20
|
|
|
18
21
|
return BadgeForm
|
udata/core/badges/models.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
|
|
5
4
|
from mongoengine.signals import post_save
|
|
@@ -8,26 +7,26 @@ from udata.api_fields import field
|
|
|
8
7
|
from udata.auth import current_user
|
|
9
8
|
from udata.mongo import db
|
|
10
9
|
|
|
11
|
-
from .signals import on_badge_added, on_badge_removed
|
|
12
10
|
from .fields import badge_fields
|
|
11
|
+
from .signals import on_badge_added, on_badge_removed
|
|
13
12
|
|
|
14
13
|
log = logging.getLogger(__name__)
|
|
15
14
|
|
|
16
|
-
__all__ = (
|
|
15
|
+
__all__ = ("Badge", "BadgeMixin")
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class Badge(db.EmbeddedDocument):
|
|
20
19
|
kind = db.StringField(required=True)
|
|
21
20
|
created = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
22
|
-
created_by = db.ReferenceField(
|
|
21
|
+
created_by = db.ReferenceField("User")
|
|
23
22
|
|
|
24
23
|
def __str__(self):
|
|
25
24
|
return self.kind
|
|
26
25
|
|
|
27
26
|
def validate(self, clean=True):
|
|
28
|
-
badges = getattr(self._instance,
|
|
27
|
+
badges = getattr(self._instance, "__badges__", {})
|
|
29
28
|
if self.kind not in badges.keys():
|
|
30
|
-
raise db.ValidationError(
|
|
29
|
+
raise db.ValidationError("Unknown badge type %s" % self.kind)
|
|
31
30
|
return super(Badge, self).validate(clean=clean)
|
|
32
31
|
|
|
33
32
|
|
|
@@ -38,9 +37,7 @@ class BadgesList(db.EmbeddedDocumentListField):
|
|
|
38
37
|
def validate(self, value):
|
|
39
38
|
kinds = [b.kind for b in value]
|
|
40
39
|
if len(kinds) > len(set(kinds)):
|
|
41
|
-
raise db.ValidationError(
|
|
42
|
-
'Duplicate badges for a given kind is not allowed'
|
|
43
|
-
)
|
|
40
|
+
raise db.ValidationError("Duplicate badges for a given kind is not allowed")
|
|
44
41
|
return super(BadgesList, self).validate(value)
|
|
45
42
|
|
|
46
43
|
|
|
@@ -48,49 +45,37 @@ class BadgeMixin(object):
|
|
|
48
45
|
badges = BadgesList()
|
|
49
46
|
|
|
50
47
|
def get_badge(self, kind):
|
|
51
|
-
|
|
48
|
+
"""Get a badge given its kind if present"""
|
|
52
49
|
candidates = [b for b in self.badges if b.kind == kind]
|
|
53
50
|
return candidates[0] if candidates else None
|
|
54
51
|
|
|
55
52
|
def add_badge(self, kind):
|
|
56
|
-
|
|
53
|
+
"""Perform an atomic prepend for a new badge"""
|
|
57
54
|
badge = self.get_badge(kind)
|
|
58
55
|
if badge:
|
|
59
56
|
return badge
|
|
60
|
-
if kind not in getattr(self,
|
|
61
|
-
msg =
|
|
62
|
-
raise db.ValidationError(msg.format(model=self.__class__.__name__,
|
|
63
|
-
kind=kind))
|
|
57
|
+
if kind not in getattr(self, "__badges__", {}):
|
|
58
|
+
msg = "Unknown badge type for {model}: {kind}"
|
|
59
|
+
raise db.ValidationError(msg.format(model=self.__class__.__name__, kind=kind))
|
|
64
60
|
badge = Badge(kind=kind)
|
|
65
61
|
if current_user.is_authenticated:
|
|
66
62
|
badge.created_by = current_user.id
|
|
67
63
|
|
|
68
|
-
self.update(__raw__={
|
|
69
|
-
'$push': {
|
|
70
|
-
'badges': {
|
|
71
|
-
'$each': [badge.to_mongo()],
|
|
72
|
-
'$position': 0
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
})
|
|
64
|
+
self.update(__raw__={"$push": {"badges": {"$each": [badge.to_mongo()], "$position": 0}}})
|
|
76
65
|
self.reload()
|
|
77
66
|
post_save.send(self.__class__, document=self)
|
|
78
67
|
on_badge_added.send(self, kind=kind)
|
|
79
68
|
return self.get_badge(kind)
|
|
80
69
|
|
|
81
70
|
def remove_badge(self, kind):
|
|
82
|
-
|
|
83
|
-
self.update(__raw__={
|
|
84
|
-
'$pull': {
|
|
85
|
-
'badges': {'kind': kind}
|
|
86
|
-
}
|
|
87
|
-
})
|
|
71
|
+
"""Perform an atomic removal for a given badge"""
|
|
72
|
+
self.update(__raw__={"$pull": {"badges": {"kind": kind}}})
|
|
88
73
|
self.reload()
|
|
89
74
|
on_badge_removed.send(self, kind=kind)
|
|
90
75
|
post_save.send(self.__class__, document=self)
|
|
91
76
|
|
|
92
77
|
def toggle_badge(self, kind):
|
|
93
|
-
|
|
78
|
+
"""Toggle a bdage given its kind"""
|
|
94
79
|
badge = self.get_badge(kind)
|
|
95
80
|
if badge:
|
|
96
81
|
return self.remove_badge(kind)
|
|
@@ -98,6 +83,6 @@ class BadgeMixin(object):
|
|
|
98
83
|
return self.add_badge(kind)
|
|
99
84
|
|
|
100
85
|
def badge_label(self, badge):
|
|
101
|
-
|
|
86
|
+
"""Display the badge label for a given kind"""
|
|
102
87
|
kind = badge.kind if isinstance(badge, Badge) else badge
|
|
103
88
|
return self.__badges__[kind]
|
udata/core/badges/permissions.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
from udata.auth import Permission
|
|
2
|
-
|
|
3
2
|
from udata.core.organization.permissions import OrganizationAdminNeed
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
class BadgePermission(Permission):
|
|
7
|
-
|
|
8
6
|
def __init__(self, badge):
|
|
9
7
|
needs = []
|
|
10
8
|
subject = badge.subject
|
|
11
|
-
if getattr(subject,
|
|
9
|
+
if getattr(subject, "organization", False): # This is a Dataset.
|
|
12
10
|
organization_id = subject.organization.id
|
|
13
11
|
else: # This is an Organization.
|
|
14
12
|
organization_id = subject.id
|
udata/core/badges/signals.py
CHANGED
|
@@ -3,7 +3,7 @@ from blinker import Namespace
|
|
|
3
3
|
namespace = Namespace()
|
|
4
4
|
|
|
5
5
|
#: Trigerred when a badge is added
|
|
6
|
-
on_badge_added = namespace.signal(
|
|
6
|
+
on_badge_added = namespace.signal("on-badge-added")
|
|
7
7
|
|
|
8
8
|
#: Trigerred when a badge is removed
|
|
9
|
-
on_badge_removed = namespace.signal(
|
|
9
|
+
on_badge_removed = namespace.signal("on-badge-removed")
|
udata/core/badges/tasks.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from udata.tasks import
|
|
1
|
+
from udata.tasks import get_logger, task
|
|
2
2
|
|
|
3
3
|
from .signals import on_badge_added
|
|
4
4
|
|
|
@@ -10,9 +10,10 @@ def notify_new_badge(cls, kind):
|
|
|
10
10
|
t = task(func)
|
|
11
11
|
|
|
12
12
|
def call_task(sender, **kwargs):
|
|
13
|
-
if isinstance(sender, cls) and kwargs.get(
|
|
13
|
+
if isinstance(sender, cls) and kwargs.get("kind") == kind:
|
|
14
14
|
t.delay(str(sender.pk))
|
|
15
15
|
|
|
16
16
|
on_badge_added.connect(call_task, weak=False)
|
|
17
17
|
return t
|
|
18
|
+
|
|
18
19
|
return wrapper
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
1
|
from tempfile import NamedTemporaryFile
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
6
5
|
from udata.core.organization.constants import CERTIFIED, PUBLIC_SERVICE
|
|
7
6
|
from udata.core.organization.factories import OrganizationFactory
|
|
7
|
+
from udata.models import Badge
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@pytest.mark.usefixtures(
|
|
10
|
+
@pytest.mark.usefixtures("clean_db")
|
|
11
11
|
class BadgeCommandTest:
|
|
12
12
|
def toggle(self, path_or_id, kind):
|
|
13
|
-
return self.cli(
|
|
13
|
+
return self.cli("badges", "toggle", path_or_id, kind)
|
|
14
14
|
|
|
15
15
|
def test_toggle_badge_on(self, cli):
|
|
16
16
|
org = OrganizationFactory()
|
|
17
17
|
|
|
18
|
-
cli(
|
|
18
|
+
cli("badges", "toggle", str(org.id), PUBLIC_SERVICE)
|
|
19
19
|
|
|
20
20
|
org.reload()
|
|
21
21
|
assert org.badges[0].kind == PUBLIC_SERVICE
|
|
@@ -25,7 +25,7 @@ class BadgeCommandTest:
|
|
|
25
25
|
certified_badge = Badge(kind=CERTIFIED)
|
|
26
26
|
org = OrganizationFactory(badges=[ps_badge, certified_badge])
|
|
27
27
|
|
|
28
|
-
cli(
|
|
28
|
+
cli("badges", "toggle", str(org.id), PUBLIC_SERVICE)
|
|
29
29
|
|
|
30
30
|
org.reload()
|
|
31
31
|
assert org.badges[0].kind == CERTIFIED
|
|
@@ -33,11 +33,11 @@ class BadgeCommandTest:
|
|
|
33
33
|
def test_toggle_badge_on_from_file(self, cli):
|
|
34
34
|
orgs = [OrganizationFactory() for _ in range(2)]
|
|
35
35
|
|
|
36
|
-
with NamedTemporaryFile(mode=
|
|
37
|
-
temp.write(
|
|
36
|
+
with NamedTemporaryFile(mode="w") as temp:
|
|
37
|
+
temp.write("\n".join((str(org.id) for org in orgs)))
|
|
38
38
|
temp.flush()
|
|
39
39
|
|
|
40
|
-
cli(
|
|
40
|
+
cli("badges", "toggle", temp.name, PUBLIC_SERVICE)
|
|
41
41
|
|
|
42
42
|
for org in orgs:
|
|
43
43
|
org.reload()
|
|
@@ -1,45 +1,41 @@
|
|
|
1
1
|
from udata.auth import login_user
|
|
2
|
-
from udata.mongo import db
|
|
3
|
-
from udata.tests import TestCase, DBTestMixin
|
|
4
2
|
from udata.core.user.factories import UserFactory
|
|
3
|
+
from udata.mongo import db
|
|
4
|
+
from udata.tests import DBTestMixin, TestCase
|
|
5
5
|
|
|
6
6
|
from ..models import Badge, BadgeMixin
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
OTHER = 'other'
|
|
8
|
+
TEST = "test"
|
|
9
|
+
OTHER = "other"
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class Fake(db.Document, BadgeMixin):
|
|
14
13
|
__badges__ = {
|
|
15
|
-
TEST:
|
|
16
|
-
OTHER:
|
|
14
|
+
TEST: "Test",
|
|
15
|
+
OTHER: "Other",
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class BadgeMixinTest(DBTestMixin, TestCase):
|
|
21
20
|
def test_attributes(self):
|
|
22
|
-
|
|
21
|
+
"""It should have a badge list"""
|
|
23
22
|
fake = Fake.objects.create()
|
|
24
23
|
self.assertIsInstance(fake.badges, (list, tuple))
|
|
25
24
|
|
|
26
25
|
def test_get_badge_found(self):
|
|
27
|
-
|
|
28
|
-
fake = Fake.objects.create(badges=[
|
|
29
|
-
Badge(kind=TEST),
|
|
30
|
-
Badge(kind=OTHER)
|
|
31
|
-
])
|
|
26
|
+
"""It allow to get a badge by kind if present"""
|
|
27
|
+
fake = Fake.objects.create(badges=[Badge(kind=TEST), Badge(kind=OTHER)])
|
|
32
28
|
badge = fake.get_badge(TEST)
|
|
33
29
|
self.assertEqual(badge.kind, TEST)
|
|
34
30
|
|
|
35
31
|
def test_get_badge_not_found(self):
|
|
36
|
-
|
|
32
|
+
"""It should return None if badge is absent"""
|
|
37
33
|
fake = Fake.objects.create(badges=[Badge(kind=OTHER)])
|
|
38
34
|
badge = fake.get_badge(TEST)
|
|
39
35
|
self.assertIsNone(badge)
|
|
40
36
|
|
|
41
37
|
def test_add_badge(self):
|
|
42
|
-
|
|
38
|
+
"""It should add a badge of a given kind"""
|
|
43
39
|
fake = Fake.objects.create()
|
|
44
40
|
|
|
45
41
|
result = fake.add_badge(TEST)
|
|
@@ -52,7 +48,7 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
52
48
|
self.assertIsNone(badge.created_by)
|
|
53
49
|
|
|
54
50
|
def test_add_2nd_badge(self):
|
|
55
|
-
|
|
51
|
+
"""It should add badges to the top of the list"""
|
|
56
52
|
fake = Fake.objects.create(badges=[Badge(kind=OTHER)])
|
|
57
53
|
|
|
58
54
|
result = fake.add_badge(TEST)
|
|
@@ -65,7 +61,7 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
65
61
|
self.assertEqual(fake.badges[-1].kind, OTHER)
|
|
66
62
|
|
|
67
63
|
def test_add_badge_with_logged_user(self):
|
|
68
|
-
|
|
64
|
+
"""It should track the user that add a badge"""
|
|
69
65
|
user = UserFactory()
|
|
70
66
|
fake = Fake.objects.create()
|
|
71
67
|
|
|
@@ -80,16 +76,16 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
80
76
|
self.assertEqual(badge.created_by, user)
|
|
81
77
|
|
|
82
78
|
def test_add_unknown_badge(self):
|
|
83
|
-
|
|
79
|
+
"""It should not allow to add an unknown badge kind"""
|
|
84
80
|
fake = Fake.objects.create()
|
|
85
81
|
|
|
86
82
|
with self.assertRaises(db.ValidationError):
|
|
87
|
-
fake.add_badge(
|
|
83
|
+
fake.add_badge("unknown")
|
|
88
84
|
|
|
89
85
|
self.assertEqual(len(fake.badges), 0)
|
|
90
86
|
|
|
91
87
|
def test_remove_badge(self):
|
|
92
|
-
|
|
88
|
+
"""It should remove a badge given its kind"""
|
|
93
89
|
badge = Badge(kind=TEST)
|
|
94
90
|
fake = Fake.objects.create(badges=[badge])
|
|
95
91
|
|
|
@@ -98,7 +94,7 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
98
94
|
self.assertEqual(len(fake.badges), 0)
|
|
99
95
|
|
|
100
96
|
def test_add_badge_twice(self):
|
|
101
|
-
|
|
97
|
+
"""It should not duplicate badges"""
|
|
102
98
|
fake = Fake.objects.create()
|
|
103
99
|
|
|
104
100
|
result1 = fake.add_badge(TEST)
|
|
@@ -112,7 +108,7 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
112
108
|
self.assertIsNone(badge.created_by)
|
|
113
109
|
|
|
114
110
|
def test_toggle_add_badge(self):
|
|
115
|
-
|
|
111
|
+
"""Toggle should add a badge of a given kind if absent"""
|
|
116
112
|
fake = Fake.objects.create()
|
|
117
113
|
|
|
118
114
|
result = fake.toggle_badge(TEST)
|
|
@@ -124,7 +120,7 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
124
120
|
self.assertIsNotNone(badge.created)
|
|
125
121
|
|
|
126
122
|
def test_toggle_remove_badge(self):
|
|
127
|
-
|
|
123
|
+
"""Toggle should remove a badge given its kind if present"""
|
|
128
124
|
badge = Badge(kind=TEST)
|
|
129
125
|
fake = Fake.objects.create(badges=[badge])
|
|
130
126
|
|
|
@@ -133,11 +129,8 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
133
129
|
self.assertEqual(len(fake.badges), 0)
|
|
134
130
|
|
|
135
131
|
def test_create_with_badges(self):
|
|
136
|
-
|
|
137
|
-
fake = Fake.objects.create(badges=[
|
|
138
|
-
Badge(kind=TEST),
|
|
139
|
-
Badge(kind=OTHER)
|
|
140
|
-
])
|
|
132
|
+
"""It should allow object creation with badges"""
|
|
133
|
+
fake = Fake.objects.create(badges=[Badge(kind=TEST), Badge(kind=OTHER)])
|
|
141
134
|
|
|
142
135
|
self.assertEqual(len(fake.badges), 2)
|
|
143
136
|
for badge, kind in zip(fake.badges, (TEST, OTHER)):
|
|
@@ -145,11 +138,11 @@ class BadgeMixinTest(DBTestMixin, TestCase):
|
|
|
145
138
|
self.assertIsNotNone(badge.created)
|
|
146
139
|
|
|
147
140
|
def test_create_disallow_duplicate_badges(self):
|
|
148
|
-
|
|
141
|
+
"""It should not allow object creation with duplicate badges"""
|
|
149
142
|
with self.assertRaises(db.ValidationError):
|
|
150
143
|
Fake.objects.create(badges=[Badge(kind=TEST), Badge(kind=TEST)])
|
|
151
144
|
|
|
152
145
|
def test_create_disallow_unknown_badges(self):
|
|
153
|
-
|
|
146
|
+
"""It should not allow object creation with unknown badges"""
|
|
154
147
|
with self.assertRaises(db.ValidationError):
|
|
155
|
-
Fake.objects.create(badges=[Badge(kind=
|
|
148
|
+
Fake.objects.create(badges=[Badge(kind="unknown")])
|
udata/core/contact_point/api.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from udata.api import
|
|
1
|
+
from udata.api import API, api
|
|
2
2
|
from udata.api.parsers import ModelApiParser
|
|
3
3
|
from udata.auth import admin_permission
|
|
4
4
|
|
|
@@ -14,49 +14,50 @@ class ContactPointApiParser(ModelApiParser):
|
|
|
14
14
|
super().__init__()
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
ns = api.namespace(
|
|
17
|
+
ns = api.namespace("contacts", "Contact points related operations")
|
|
18
18
|
|
|
19
19
|
contact_point_parser = ContactPointApiParser()
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
@ns.route(
|
|
22
|
+
@ns.route("/", endpoint="contact_points")
|
|
23
23
|
class ContactPointsListAPI(API):
|
|
24
|
-
|
|
24
|
+
"""Contact points collection endpoint"""
|
|
25
|
+
|
|
25
26
|
@api.secure
|
|
26
|
-
@api.doc(
|
|
27
|
+
@api.doc("create_contact_point")
|
|
27
28
|
@api.expect(contact_point_fields)
|
|
28
29
|
@api.marshal_with(contact_point_fields)
|
|
29
|
-
@api.response(400,
|
|
30
|
+
@api.response(400, "Validation error")
|
|
30
31
|
def post(self):
|
|
31
|
-
|
|
32
|
+
"""Creates a contact point"""
|
|
32
33
|
form = api.validate(ContactPointForm)
|
|
33
34
|
contact_point = form.save()
|
|
34
35
|
return contact_point, 201
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
@ns.route(
|
|
38
|
-
@api.response(404,
|
|
38
|
+
@ns.route("/<contact_point:contact_point>/", endpoint="contact_point")
|
|
39
|
+
@api.response(404, "Contact point not found")
|
|
39
40
|
class ContactPointAPI(API):
|
|
40
|
-
@api.doc(
|
|
41
|
+
@api.doc("get_contact_point")
|
|
41
42
|
@api.marshal_with(contact_point_fields)
|
|
42
43
|
def get(self, contact_point):
|
|
43
|
-
|
|
44
|
+
"""Get a contact point given its identifier"""
|
|
44
45
|
return contact_point
|
|
45
46
|
|
|
46
47
|
@api.secure
|
|
47
|
-
@api.doc(
|
|
48
|
+
@api.doc("update_contact_point")
|
|
48
49
|
@api.expect(contact_point_fields)
|
|
49
50
|
@api.marshal_with(contact_point_fields)
|
|
50
|
-
@api.response(400,
|
|
51
|
+
@api.response(400, "Validation error")
|
|
51
52
|
def put(self, contact_point):
|
|
52
|
-
|
|
53
|
+
"""Updates a contact point given its identifier"""
|
|
53
54
|
form = api.validate(ContactPointForm, contact_point)
|
|
54
55
|
return form.save()
|
|
55
56
|
|
|
56
57
|
@api.secure
|
|
57
|
-
@api.doc(
|
|
58
|
-
@api.response(204,
|
|
58
|
+
@api.doc("delete_contact_point")
|
|
59
|
+
@api.response(204, "Contact point deleted")
|
|
59
60
|
def delete(self, contact_point):
|
|
60
|
-
|
|
61
|
+
"""Deletes a contact point given its identifier"""
|
|
61
62
|
contact_point.delete()
|
|
62
|
-
return
|
|
63
|
+
return "", 204
|
|
@@ -2,19 +2,26 @@ from udata.api import api, fields
|
|
|
2
2
|
from udata.core.organization.api_fields import org_ref_fields
|
|
3
3
|
from udata.core.user.api_fields import user_ref_fields
|
|
4
4
|
|
|
5
|
+
DEFAULT_MASK = ",".join(("id", "name", "email"))
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
contact_point_fields = api.model(
|
|
8
|
+
"ContactPoint",
|
|
9
|
+
{
|
|
10
|
+
"id": fields.String(description="The contact point's identifier", readonly=True),
|
|
11
|
+
"name": fields.String(description="The contact point's name", required=True),
|
|
12
|
+
"email": fields.String(description="The contact point's email", required=True),
|
|
13
|
+
"organization": fields.Nested(
|
|
14
|
+
org_ref_fields, allow_null=True, description="The producer organization"
|
|
15
|
+
),
|
|
16
|
+
"owner": fields.Nested(
|
|
17
|
+
user_ref_fields, allow_null=True, description="The user information"
|
|
18
|
+
),
|
|
19
|
+
},
|
|
20
|
+
mask=DEFAULT_MASK,
|
|
21
|
+
)
|
|
7
22
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
org_ref_fields, allow_null=True,
|
|
14
|
-
description='The producer organization'),
|
|
15
|
-
'owner': fields.Nested(
|
|
16
|
-
user_ref_fields, allow_null=True, description='The user information')
|
|
17
|
-
}, mask=DEFAULT_MASK)
|
|
18
|
-
|
|
19
|
-
contact_point_page_fields = api.model('ContactPointPage', fields.pager(contact_point_fields),
|
|
20
|
-
mask='data{{{0}}},*'.format(DEFAULT_MASK))
|
|
23
|
+
contact_point_page_fields = api.model(
|
|
24
|
+
"ContactPointPage",
|
|
25
|
+
fields.pager(contact_point_fields),
|
|
26
|
+
mask="data{{{0}}},*".format(DEFAULT_MASK),
|
|
27
|
+
)
|