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/api/__init__.py
CHANGED
|
@@ -1,67 +1,70 @@
|
|
|
1
|
-
import itertools
|
|
2
1
|
import inspect
|
|
2
|
+
import itertools
|
|
3
3
|
import logging
|
|
4
4
|
import urllib.parse
|
|
5
|
-
|
|
6
5
|
from functools import wraps
|
|
7
6
|
from importlib import import_module
|
|
8
7
|
|
|
9
8
|
from flask import (
|
|
10
|
-
|
|
9
|
+
Blueprint,
|
|
10
|
+
current_app,
|
|
11
|
+
g,
|
|
12
|
+
json,
|
|
13
|
+
make_response,
|
|
14
|
+
redirect,
|
|
15
|
+
request,
|
|
16
|
+
url_for,
|
|
11
17
|
)
|
|
12
|
-
from flask_storage import UnauthorizedFileType
|
|
13
|
-
from flask_restx import Api, Resource
|
|
14
18
|
from flask_cors import CORS
|
|
19
|
+
from flask_restx import Api, Resource
|
|
20
|
+
from flask_storage import UnauthorizedFileType
|
|
15
21
|
|
|
16
|
-
from udata import
|
|
22
|
+
from udata import entrypoints, tracking
|
|
17
23
|
from udata.app import csrf
|
|
24
|
+
from udata.auth import Permission, PermissionDenied, RoleNeed, current_user, login_user
|
|
18
25
|
from udata.i18n import get_locale
|
|
19
|
-
from udata.auth import (
|
|
20
|
-
current_user, login_user, Permission, RoleNeed, PermissionDenied
|
|
21
|
-
)
|
|
22
|
-
from udata.utils import safe_unicode
|
|
23
26
|
from udata.mongo.errors import FieldValidationError
|
|
27
|
+
from udata.utils import safe_unicode
|
|
24
28
|
|
|
25
29
|
from . import fields
|
|
26
30
|
from .signals import on_api_call
|
|
27
31
|
|
|
28
|
-
|
|
29
32
|
log = logging.getLogger(__name__)
|
|
30
33
|
|
|
31
|
-
apiv1_blueprint = Blueprint(
|
|
32
|
-
apiv2_blueprint = Blueprint(
|
|
34
|
+
apiv1_blueprint = Blueprint("api", __name__, url_prefix="/api/1")
|
|
35
|
+
apiv2_blueprint = Blueprint("apiv2", __name__, url_prefix="/api/2")
|
|
33
36
|
|
|
34
37
|
DEFAULT_PAGE_SIZE = 50
|
|
35
|
-
HEADER_API_KEY =
|
|
38
|
+
HEADER_API_KEY = "X-API-KEY"
|
|
36
39
|
|
|
37
40
|
# TODO: make upstream flask-restplus automatically handle
|
|
38
41
|
# flask-restplus headers and allow lazy evaluation
|
|
39
42
|
# of headers (ie. callable)
|
|
40
43
|
PREFLIGHT_HEADERS = (
|
|
41
44
|
HEADER_API_KEY,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
"X-Fields",
|
|
46
|
+
"Content-Type",
|
|
47
|
+
"Accept",
|
|
48
|
+
"Accept-Charset",
|
|
49
|
+
"Accept-Language",
|
|
50
|
+
"Authorization",
|
|
51
|
+
"Cache-Control",
|
|
52
|
+
"Content-Encoding",
|
|
53
|
+
"Content-Length",
|
|
54
|
+
"Content-Security-Policy",
|
|
55
|
+
"Content-Type",
|
|
56
|
+
"Cookie",
|
|
57
|
+
"ETag",
|
|
58
|
+
"Host",
|
|
59
|
+
"If-Modified-Since",
|
|
60
|
+
"Keep-Alive",
|
|
61
|
+
"Last-Modified",
|
|
62
|
+
"Origin",
|
|
63
|
+
"Referer",
|
|
64
|
+
"User-Agent",
|
|
65
|
+
"X-Forwarded-For",
|
|
66
|
+
"X-Forwarded-Port",
|
|
67
|
+
"X-Forwarded-Proto",
|
|
65
68
|
)
|
|
66
69
|
|
|
67
70
|
cors = CORS(allow_headers=PREFLIGHT_HEADERS)
|
|
@@ -69,21 +72,15 @@ cors = CORS(allow_headers=PREFLIGHT_HEADERS)
|
|
|
69
72
|
|
|
70
73
|
class UDataApi(Api):
|
|
71
74
|
def __init__(self, app=None, **kwargs):
|
|
72
|
-
decorators = kwargs.pop(
|
|
73
|
-
kwargs[
|
|
75
|
+
decorators = kwargs.pop("decorators", []) or []
|
|
76
|
+
kwargs["decorators"] = [self.authentify] + decorators
|
|
74
77
|
super(UDataApi, self).__init__(app, **kwargs)
|
|
75
|
-
self.authorizations = {
|
|
76
|
-
'apikey': {
|
|
77
|
-
'type': 'apiKey',
|
|
78
|
-
'in': 'header',
|
|
79
|
-
'name': HEADER_API_KEY
|
|
80
|
-
}
|
|
81
|
-
}
|
|
78
|
+
self.authorizations = {"apikey": {"type": "apiKey", "in": "header", "name": HEADER_API_KEY}}
|
|
82
79
|
|
|
83
80
|
def secure(self, func):
|
|
84
|
-
|
|
81
|
+
"""Enforce authentication on a given method/verb
|
|
85
82
|
and optionally check a given permission
|
|
86
|
-
|
|
83
|
+
"""
|
|
87
84
|
if isinstance(func, str):
|
|
88
85
|
return self._apply_permission(Permission(RoleNeed(func)))
|
|
89
86
|
elif isinstance(func, Permission):
|
|
@@ -94,21 +91,25 @@ class UDataApi(Api):
|
|
|
94
91
|
def _apply_permission(self, permission):
|
|
95
92
|
def wrapper(func):
|
|
96
93
|
return self._apply_secure(func, permission)
|
|
94
|
+
|
|
97
95
|
return wrapper
|
|
98
96
|
|
|
99
97
|
def _apply_secure(self, func, permission=None):
|
|
100
|
-
|
|
101
|
-
self._build_doc(func, {
|
|
98
|
+
"""Enforce authentication on a given method/verb"""
|
|
99
|
+
self._build_doc(func, {"security": "apikey"})
|
|
102
100
|
|
|
103
101
|
@wraps(func)
|
|
104
102
|
def wrapper(*args, **kwargs):
|
|
105
103
|
if (
|
|
106
|
-
not current_user.is_anonymous
|
|
107
|
-
not current_user.sysadmin
|
|
108
|
-
current_app.config[
|
|
109
|
-
any(ext in str(func) for ext in current_app.config[
|
|
104
|
+
not current_user.is_anonymous
|
|
105
|
+
and not current_user.sysadmin
|
|
106
|
+
and current_app.config["READ_ONLY_MODE"]
|
|
107
|
+
and any(ext in str(func) for ext in current_app.config["METHOD_BLOCKLIST"])
|
|
110
108
|
):
|
|
111
|
-
self.abort(
|
|
109
|
+
self.abort(
|
|
110
|
+
423,
|
|
111
|
+
"Due to security reasons, the creation of new content is currently disabled.",
|
|
112
|
+
)
|
|
112
113
|
|
|
113
114
|
if not current_user.is_authenticated:
|
|
114
115
|
self.abort(401)
|
|
@@ -125,11 +126,12 @@ class UDataApi(Api):
|
|
|
125
126
|
return wrapper
|
|
126
127
|
|
|
127
128
|
def authentify(self, func):
|
|
128
|
-
|
|
129
|
+
"""Authentify the user if credentials are given"""
|
|
130
|
+
|
|
129
131
|
@wraps(func)
|
|
130
132
|
def wrapper(*args, **kwargs):
|
|
131
|
-
from udata.core.user.models import User
|
|
132
133
|
from udata.api.oauth2 import check_credentials
|
|
134
|
+
from udata.core.user.models import User
|
|
133
135
|
|
|
134
136
|
if current_user.is_authenticated:
|
|
135
137
|
return func(*args, **kwargs)
|
|
@@ -139,72 +141,79 @@ class UDataApi(Api):
|
|
|
139
141
|
try:
|
|
140
142
|
user = User.objects.get(apikey=apikey)
|
|
141
143
|
except User.DoesNotExist:
|
|
142
|
-
self.abort(401,
|
|
144
|
+
self.abort(401, "Invalid API Key")
|
|
143
145
|
|
|
144
146
|
if not login_user(user, False):
|
|
145
|
-
self.abort(401,
|
|
147
|
+
self.abort(401, "Inactive user")
|
|
146
148
|
else:
|
|
147
149
|
check_credentials()
|
|
148
150
|
return func(*args, **kwargs)
|
|
151
|
+
|
|
149
152
|
return wrapper
|
|
150
153
|
|
|
151
154
|
def validate(self, form_cls, obj=None):
|
|
152
|
-
|
|
153
|
-
if
|
|
154
|
-
errors = {
|
|
155
|
+
"""Validate a form from the request and handle errors"""
|
|
156
|
+
if "application/json" not in request.headers.get("Content-Type", ""):
|
|
157
|
+
errors = {"Content-Type": "expecting application/json"}
|
|
155
158
|
self.abort(400, errors=errors)
|
|
156
|
-
form = form_cls.from_json(request.json, obj=obj, instance=obj,
|
|
157
|
-
meta={'csrf': False})
|
|
159
|
+
form = form_cls.from_json(request.json, obj=obj, instance=obj, meta={"csrf": False})
|
|
158
160
|
if not form.validate():
|
|
159
161
|
self.abort(400, errors=form.errors)
|
|
160
162
|
return form
|
|
161
163
|
|
|
162
164
|
def render_ui(self):
|
|
163
|
-
return redirect(current_app.config.get(
|
|
165
|
+
return redirect(current_app.config.get("API_DOC_EXTERNAL_LINK"))
|
|
164
166
|
|
|
165
167
|
def unauthorized(self, response):
|
|
166
|
-
|
|
167
|
-
realm = current_app.config.get(
|
|
168
|
+
"""Override to change the WWW-Authenticate challenge"""
|
|
169
|
+
realm = current_app.config.get("HTTP_OAUTH_REALM", "uData")
|
|
168
170
|
challenge = 'Bearer realm="{0}"'.format(realm)
|
|
169
171
|
|
|
170
|
-
response.headers[
|
|
172
|
+
response.headers["WWW-Authenticate"] = challenge
|
|
171
173
|
return response
|
|
172
174
|
|
|
173
175
|
def page_parser(self):
|
|
174
176
|
parser = self.parser()
|
|
175
|
-
parser.add_argument(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
parser.add_argument("page", type=int, default=1, location="args", help="The page to fetch")
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
"page_size", type=int, default=20, location="args", help="The page size to fetch"
|
|
180
|
+
)
|
|
179
181
|
return parser
|
|
180
182
|
|
|
181
183
|
|
|
182
184
|
api = UDataApi(
|
|
183
185
|
apiv1_blueprint,
|
|
184
186
|
decorators=[csrf.exempt],
|
|
185
|
-
version=
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
version="1.0",
|
|
188
|
+
title="uData API",
|
|
189
|
+
description="uData API",
|
|
190
|
+
default="site",
|
|
191
|
+
default_label="Site global namespace",
|
|
188
192
|
)
|
|
189
193
|
|
|
190
194
|
apiv2 = UDataApi(
|
|
191
195
|
apiv2_blueprint,
|
|
192
196
|
decorators=[csrf.exempt],
|
|
193
|
-
version=
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
version="2.0",
|
|
198
|
+
title="uData API",
|
|
199
|
+
description="udata API v2",
|
|
200
|
+
default="site",
|
|
201
|
+
default_label="Site global namespace",
|
|
196
202
|
)
|
|
197
203
|
|
|
198
204
|
|
|
199
|
-
api.model_reference = api.model(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
205
|
+
api.model_reference = api.model(
|
|
206
|
+
"ModelReference",
|
|
207
|
+
{
|
|
208
|
+
"class": fields.ClassName(description="The model class", required=True),
|
|
209
|
+
"id": fields.String(description="The object identifier", required=True),
|
|
210
|
+
},
|
|
211
|
+
)
|
|
203
212
|
|
|
204
213
|
|
|
205
|
-
@api.representation(
|
|
214
|
+
@api.representation("application/json")
|
|
206
215
|
def output_json(data, code, headers=None):
|
|
207
|
-
|
|
216
|
+
"""Use Flask JSON to serialize"""
|
|
208
217
|
resp = make_response(json.dumps(data), code)
|
|
209
218
|
resp.headers.extend(headers or {})
|
|
210
219
|
return resp
|
|
@@ -213,8 +222,8 @@ def output_json(data, code, headers=None):
|
|
|
213
222
|
@apiv1_blueprint.before_request
|
|
214
223
|
@apiv2_blueprint.before_request
|
|
215
224
|
def set_api_language():
|
|
216
|
-
if
|
|
217
|
-
g.lang_code = request.args[
|
|
225
|
+
if "lang" in request.args:
|
|
226
|
+
g.lang_code = request.args["lang"]
|
|
218
227
|
else:
|
|
219
228
|
g.lang_code = get_locale()
|
|
220
229
|
|
|
@@ -225,17 +234,18 @@ def extract_name_from_path(path):
|
|
|
225
234
|
Useful to log requests on Piwik with categories tree structure.
|
|
226
235
|
See: http://piwik.org/faq/how-to/#faq_62
|
|
227
236
|
"""
|
|
228
|
-
base_path, query_string = path.split(
|
|
229
|
-
infos = base_path.strip(
|
|
230
|
-
if
|
|
231
|
-
|
|
237
|
+
base_path, query_string = path.split("?")
|
|
238
|
+
infos = base_path.strip("/").split("/")[2:] # Removes api/version.
|
|
239
|
+
if (
|
|
240
|
+
base_path == "/api/1/" or base_path == "/api/2/"
|
|
241
|
+
): # The API root endpoint redirects to swagger doc.
|
|
242
|
+
return safe_unicode("apidoc")
|
|
232
243
|
if len(infos) > 1: # This is an object.
|
|
233
|
-
name =
|
|
234
|
-
category=infos[0].title(),
|
|
235
|
-
name=infos[1].replace('-', ' ').title()
|
|
244
|
+
name = "{category} / {name}".format(
|
|
245
|
+
category=infos[0].title(), name=infos[1].replace("-", " ").title()
|
|
236
246
|
)
|
|
237
247
|
else: # This is a collection.
|
|
238
|
-
name =
|
|
248
|
+
name = "{category}".format(category=infos[0].title())
|
|
239
249
|
return safe_unicode(name)
|
|
240
250
|
|
|
241
251
|
|
|
@@ -243,71 +253,71 @@ def extract_name_from_path(path):
|
|
|
243
253
|
@apiv2_blueprint.after_request
|
|
244
254
|
def collect_stats(response):
|
|
245
255
|
action_name = extract_name_from_path(request.full_path)
|
|
246
|
-
blacklist = current_app.config.get(
|
|
247
|
-
if
|
|
248
|
-
request.endpoint not in blacklist):
|
|
256
|
+
blacklist = current_app.config.get("TRACKING_BLACKLIST", [])
|
|
257
|
+
if not current_app.config["TESTING"] and request.endpoint not in blacklist:
|
|
249
258
|
extras = {
|
|
250
|
-
|
|
259
|
+
"action_name": urllib.parse.quote(action_name),
|
|
251
260
|
}
|
|
252
261
|
tracking.send_signal(on_api_call, request, current_user, **extras)
|
|
253
262
|
return response
|
|
254
263
|
|
|
255
264
|
|
|
256
|
-
default_error = api.model(
|
|
257
|
-
'message': fields.String
|
|
258
|
-
})
|
|
265
|
+
default_error = api.model("Error", {"message": fields.String})
|
|
259
266
|
|
|
260
267
|
|
|
261
268
|
@api.errorhandler(PermissionDenied)
|
|
262
269
|
@api.marshal_with(default_error, code=403)
|
|
263
270
|
def handle_permission_denied(error):
|
|
264
|
-
|
|
265
|
-
message =
|
|
266
|
-
return {
|
|
271
|
+
"""Error occuring when the user does not have the required permissions"""
|
|
272
|
+
message = "You do not have the permission to modify that object."
|
|
273
|
+
return {"message": message}, 403
|
|
267
274
|
|
|
268
275
|
|
|
269
276
|
@api.errorhandler(ValueError)
|
|
270
277
|
@api.marshal_with(default_error, code=400)
|
|
271
278
|
def handle_value_error(error):
|
|
272
|
-
|
|
273
|
-
return {
|
|
279
|
+
"""A generic value error"""
|
|
280
|
+
return {"message": str(error)}, 400
|
|
274
281
|
|
|
275
282
|
|
|
276
283
|
@api.errorhandler(UnauthorizedFileType)
|
|
277
284
|
@api.marshal_with(default_error, code=400)
|
|
278
285
|
def handle_unauthorized_file_type(error):
|
|
279
|
-
|
|
280
|
-
url = url_for(
|
|
286
|
+
"""Error occuring when the user try to upload a non-allowed file type"""
|
|
287
|
+
url = url_for("api.allowed_extensions", _external=True)
|
|
281
288
|
msg = (
|
|
282
|
-
|
|
283
|
-
'The allowed file type list is available at {url}'
|
|
289
|
+
"This file type is not allowed." "The allowed file type list is available at {url}"
|
|
284
290
|
).format(url=url)
|
|
285
|
-
return {
|
|
291
|
+
return {"message": msg}, 400
|
|
292
|
+
|
|
286
293
|
|
|
294
|
+
validation_error_fields = api.model("ValidationError", {"errors": fields.Raw})
|
|
287
295
|
|
|
288
|
-
validation_error_fields = api.model('ValidationError', {
|
|
289
|
-
'errors': fields.Raw
|
|
290
|
-
})
|
|
291
296
|
|
|
292
297
|
@api.errorhandler(FieldValidationError)
|
|
293
298
|
@api.marshal_with(validation_error_fields, code=400)
|
|
294
299
|
def handle_validation_error(error: FieldValidationError):
|
|
295
|
-
|
|
300
|
+
"""A validation error"""
|
|
296
301
|
errors = {}
|
|
297
302
|
errors[error.field] = [error.message]
|
|
298
303
|
|
|
299
|
-
return {
|
|
304
|
+
return {"errors": errors}, 400
|
|
305
|
+
|
|
300
306
|
|
|
301
307
|
class API(Resource): # Avoid name collision as resource is a core model
|
|
302
308
|
pass
|
|
303
309
|
|
|
304
310
|
|
|
305
|
-
base_reference = api.model(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
+
base_reference = api.model(
|
|
312
|
+
"BaseReference",
|
|
313
|
+
{
|
|
314
|
+
"id": fields.String(description="The object unique identifier", required=True),
|
|
315
|
+
"class": fields.ClassName(
|
|
316
|
+
description="The object class", discriminator=True, required=True
|
|
317
|
+
),
|
|
318
|
+
},
|
|
319
|
+
description="Base model for reference field, aka. inline model reference",
|
|
320
|
+
)
|
|
311
321
|
|
|
312
322
|
|
|
313
323
|
def marshal_page(page, page_fields):
|
|
@@ -340,14 +350,14 @@ def init_app(app):
|
|
|
340
350
|
import udata.core.topic.api # noqa
|
|
341
351
|
import udata.core.topic.apiv2 # noqa
|
|
342
352
|
import udata.core.post.api # noqa
|
|
343
|
-
import udata.core.contact_point.api
|
|
353
|
+
import udata.core.contact_point.api # noqa
|
|
344
354
|
import udata.features.transfer.api # noqa
|
|
345
355
|
import udata.features.notifications.api # noqa
|
|
346
356
|
import udata.features.identicon.api # noqa
|
|
347
357
|
import udata.features.territories.api # noqa
|
|
348
358
|
import udata.harvest.api # noqa
|
|
349
359
|
|
|
350
|
-
for module in entrypoints.get_enabled(
|
|
360
|
+
for module in entrypoints.get_enabled("udata.apis", app).values():
|
|
351
361
|
api_module = module if inspect.ismodule(module) else import_module(module)
|
|
352
362
|
|
|
353
363
|
# api.init_app(app)
|
|
@@ -355,5 +365,6 @@ def init_app(app):
|
|
|
355
365
|
app.register_blueprint(apiv2_blueprint)
|
|
356
366
|
|
|
357
367
|
from udata.api.oauth2 import init_app as oauth2_init_app
|
|
368
|
+
|
|
358
369
|
oauth2_init_app(app)
|
|
359
370
|
cors.init_app(app)
|
udata/api/commands.py
CHANGED
|
@@ -2,79 +2,87 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
|
-
|
|
6
|
-
from werkzeug.security import gen_salt
|
|
7
|
-
from flask import json, current_app
|
|
5
|
+
from flask import current_app, json
|
|
8
6
|
from flask_restx import schemas
|
|
7
|
+
from werkzeug.security import gen_salt
|
|
9
8
|
|
|
10
9
|
from udata.api import api
|
|
11
|
-
from udata.commands import cli, success, exit_with_error
|
|
12
|
-
from udata.models import User
|
|
13
10
|
from udata.api.oauth2 import OAuth2Client
|
|
11
|
+
from udata.commands import cli, exit_with_error, success
|
|
12
|
+
from udata.models import User
|
|
14
13
|
|
|
15
14
|
log = logging.getLogger(__name__)
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
@cli.group(
|
|
17
|
+
@cli.group("api")
|
|
19
18
|
def grp():
|
|
20
|
-
|
|
19
|
+
"""API related operations"""
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
def json_to_file(data, filename, pretty=False):
|
|
24
|
-
|
|
23
|
+
"""Dump JSON data to a file"""
|
|
25
24
|
kwargs = dict(indent=4) if pretty else {}
|
|
26
25
|
dirname = os.path.dirname(filename)
|
|
27
26
|
if not os.path.exists(dirname):
|
|
28
27
|
os.makedirs(dirname)
|
|
29
28
|
dump = json.dumps(api.__schema__, **kwargs)
|
|
30
|
-
with open(filename,
|
|
31
|
-
f.write(dump.encode(
|
|
29
|
+
with open(filename, "wb") as f:
|
|
30
|
+
f.write(dump.encode("utf-8"))
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
@grp.command()
|
|
35
|
-
@click.argument(
|
|
36
|
-
@click.option(
|
|
34
|
+
@click.argument("filename")
|
|
35
|
+
@click.option("-p", "--pretty", is_flag=True, help="Pretty print")
|
|
37
36
|
def swagger(filename, pretty):
|
|
38
|
-
|
|
37
|
+
"""Dump the swagger specifications"""
|
|
39
38
|
json_to_file(api.__schema__, filename, pretty)
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
@grp.command()
|
|
43
|
-
@click.argument(
|
|
44
|
-
@click.option(
|
|
45
|
-
@click.option(
|
|
46
|
-
@click.option(
|
|
47
|
-
help='Export Swagger specifications')
|
|
42
|
+
@click.argument("filename")
|
|
43
|
+
@click.option("-p", "--pretty", is_flag=True, help="Pretty print")
|
|
44
|
+
@click.option("-u", "--urlvars", is_flag=True, help="Export query strings")
|
|
45
|
+
@click.option("-s", "--swagger", is_flag=True, help="Export Swagger specifications")
|
|
48
46
|
def postman(filename, pretty, urlvars, swagger):
|
|
49
|
-
|
|
47
|
+
"""Dump the API as a Postman collection"""
|
|
50
48
|
data = api.as_postman(urlvars=urlvars, swagger=swagger)
|
|
51
49
|
json_to_file(data, filename, pretty)
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
@grp.command()
|
|
55
53
|
def validate():
|
|
56
|
-
|
|
54
|
+
"""Validate the Swagger/OpenAPI specification with your config"""
|
|
57
55
|
with current_app.test_request_context():
|
|
58
56
|
schema = json.loads(json.dumps(api.__schema__))
|
|
59
57
|
try:
|
|
60
58
|
schemas.validate(schema)
|
|
61
|
-
success(
|
|
59
|
+
success("API specifications are valid")
|
|
62
60
|
except schemas.SchemaValidationError as e:
|
|
63
|
-
exit_with_error(
|
|
61
|
+
exit_with_error("API specifications are not valid", e)
|
|
64
62
|
|
|
65
63
|
|
|
66
64
|
@grp.command()
|
|
67
|
-
@click.option(
|
|
68
|
-
@click.option(
|
|
69
|
-
@click.option(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@click.option(
|
|
65
|
+
@click.option("-n", "--client-name", default="client-01", help="Client's name")
|
|
66
|
+
@click.option("-u", "--user-email", help="User's email")
|
|
67
|
+
@click.option(
|
|
68
|
+
"--uri", multiple=True, default=["http://localhost:8080/login"], help="Client's redirect uri"
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"-g",
|
|
72
|
+
"--grant-types",
|
|
73
|
+
multiple=True,
|
|
74
|
+
default=["authorization_code"],
|
|
75
|
+
help="Client's grant types",
|
|
76
|
+
)
|
|
77
|
+
@click.option("-s", "--scope", default="default", help="Client's scope")
|
|
78
|
+
@click.option(
|
|
79
|
+
"-r", "--response-types", multiple=True, default=["code"], help="Client's response types"
|
|
80
|
+
)
|
|
73
81
|
def create_oauth_client(client_name, user_email, uri, grant_types, scope, response_types):
|
|
74
|
-
|
|
82
|
+
"""Creates an OAuth2Client instance in DB"""
|
|
75
83
|
user = User.objects(email=user_email).first()
|
|
76
84
|
if user is None:
|
|
77
|
-
exit_with_error(
|
|
85
|
+
exit_with_error("No matching user to email")
|
|
78
86
|
|
|
79
87
|
client = OAuth2Client.objects.create(
|
|
80
88
|
name=client_name,
|
|
@@ -82,12 +90,12 @@ def create_oauth_client(client_name, user_email, uri, grant_types, scope, respon
|
|
|
82
90
|
grant_types=grant_types,
|
|
83
91
|
scope=scope,
|
|
84
92
|
response_types=response_types,
|
|
85
|
-
redirect_uris=uri
|
|
93
|
+
redirect_uris=uri,
|
|
86
94
|
)
|
|
87
95
|
|
|
88
|
-
click.echo(f
|
|
89
|
-
click.echo(f
|
|
90
|
-
click.echo(f
|
|
91
|
-
click.echo(f
|
|
92
|
-
click.echo(f
|
|
93
|
-
click.echo(f
|
|
96
|
+
click.echo(f"New OAuth client: {client.name}")
|
|
97
|
+
click.echo(f"Client's ID {client.id}")
|
|
98
|
+
click.echo(f"Client's secret {client.secret}")
|
|
99
|
+
click.echo(f"Client's grant_types {client.grant_types}")
|
|
100
|
+
click.echo(f"Client's response_types {client.response_types}")
|
|
101
|
+
click.echo(f"Client's URI {client.redirect_uris}")
|
udata/api/errors.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from udata.i18n import gettext as _
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
VALIDATION_ERROR = _(
|
|
4
|
+
"Validation error: your data cannot be updated for "
|
|
5
|
+
"now, we have been notified of the error and we will "
|
|
6
|
+
"fix it as soon as possible."
|
|
7
|
+
)
|