udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30454__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- tasks/__init__.py +109 -107
- tasks/helpers.py +18 -18
- udata/__init__.py +4 -4
- udata/admin/views.py +5 -5
- udata/api/__init__.py +111 -134
- udata/api/commands.py +45 -37
- udata/api/errors.py +5 -4
- udata/api/fields.py +23 -21
- udata/api/oauth2.py +55 -74
- udata/api/parsers.py +15 -15
- udata/api/signals.py +1 -1
- udata/api_fields.py +137 -89
- udata/app.py +58 -55
- udata/assets.py +5 -5
- udata/auth/__init__.py +37 -26
- udata/auth/forms.py +23 -15
- udata/auth/helpers.py +1 -1
- udata/auth/mails.py +3 -3
- udata/auth/password_validation.py +19 -15
- udata/auth/views.py +94 -68
- udata/commands/__init__.py +71 -69
- udata/commands/cache.py +7 -7
- udata/commands/db.py +201 -140
- udata/commands/dcat.py +36 -30
- udata/commands/fixtures.py +100 -84
- udata/commands/images.py +21 -20
- udata/commands/info.py +17 -20
- udata/commands/init.py +10 -10
- udata/commands/purge.py +12 -13
- udata/commands/serve.py +41 -29
- udata/commands/static.py +16 -18
- udata/commands/test.py +20 -20
- udata/commands/tests/fixtures.py +26 -24
- udata/commands/worker.py +31 -33
- udata/core/__init__.py +12 -12
- udata/core/activity/__init__.py +0 -1
- udata/core/activity/api.py +59 -49
- udata/core/activity/models.py +28 -26
- udata/core/activity/signals.py +1 -1
- udata/core/activity/tasks.py +16 -10
- udata/core/badges/api.py +6 -6
- udata/core/badges/commands.py +14 -13
- udata/core/badges/fields.py +8 -5
- udata/core/badges/forms.py +7 -4
- udata/core/badges/models.py +16 -31
- udata/core/badges/permissions.py +1 -3
- udata/core/badges/signals.py +2 -2
- udata/core/badges/tasks.py +3 -2
- udata/core/badges/tests/test_commands.py +10 -10
- udata/core/badges/tests/test_model.py +24 -31
- udata/core/contact_point/api.py +19 -18
- udata/core/contact_point/api_fields.py +21 -14
- udata/core/contact_point/factories.py +2 -2
- udata/core/contact_point/forms.py +7 -6
- udata/core/contact_point/models.py +3 -5
- udata/core/dataservices/api.py +26 -21
- udata/core/dataservices/factories.py +13 -11
- udata/core/dataservices/models.py +35 -40
- udata/core/dataservices/permissions.py +4 -4
- udata/core/dataservices/rdf.py +40 -17
- udata/core/dataservices/tasks.py +4 -3
- udata/core/dataset/actions.py +10 -10
- udata/core/dataset/activities.py +21 -23
- udata/core/dataset/api.py +321 -298
- udata/core/dataset/api_fields.py +443 -271
- udata/core/dataset/apiv2.py +305 -229
- udata/core/dataset/commands.py +38 -36
- udata/core/dataset/constants.py +61 -54
- udata/core/dataset/csv.py +70 -74
- udata/core/dataset/events.py +39 -32
- udata/core/dataset/exceptions.py +8 -4
- udata/core/dataset/factories.py +57 -65
- udata/core/dataset/forms.py +87 -63
- udata/core/dataset/models.py +336 -280
- udata/core/dataset/permissions.py +9 -6
- udata/core/dataset/preview.py +15 -17
- udata/core/dataset/rdf.py +156 -122
- udata/core/dataset/search.py +92 -77
- udata/core/dataset/signals.py +1 -1
- udata/core/dataset/tasks.py +63 -54
- udata/core/discussions/actions.py +5 -5
- udata/core/discussions/api.py +124 -120
- udata/core/discussions/factories.py +2 -2
- udata/core/discussions/forms.py +9 -7
- udata/core/discussions/metrics.py +1 -3
- udata/core/discussions/models.py +25 -24
- udata/core/discussions/notifications.py +18 -14
- udata/core/discussions/permissions.py +3 -3
- udata/core/discussions/signals.py +4 -4
- udata/core/discussions/tasks.py +24 -28
- udata/core/followers/api.py +32 -33
- udata/core/followers/models.py +9 -9
- udata/core/followers/signals.py +3 -3
- udata/core/jobs/actions.py +7 -7
- udata/core/jobs/api.py +99 -92
- udata/core/jobs/commands.py +48 -49
- udata/core/jobs/forms.py +11 -11
- udata/core/jobs/models.py +6 -6
- udata/core/metrics/__init__.py +2 -2
- udata/core/metrics/commands.py +34 -30
- udata/core/metrics/models.py +2 -4
- udata/core/metrics/signals.py +1 -1
- udata/core/metrics/tasks.py +3 -3
- udata/core/organization/activities.py +12 -15
- udata/core/organization/api.py +167 -174
- udata/core/organization/api_fields.py +183 -124
- udata/core/organization/apiv2.py +32 -32
- udata/core/organization/commands.py +20 -22
- udata/core/organization/constants.py +11 -11
- udata/core/organization/csv.py +17 -15
- udata/core/organization/factories.py +8 -11
- udata/core/organization/forms.py +32 -26
- udata/core/organization/metrics.py +2 -1
- udata/core/organization/models.py +87 -67
- udata/core/organization/notifications.py +18 -14
- udata/core/organization/permissions.py +10 -11
- udata/core/organization/rdf.py +14 -14
- udata/core/organization/search.py +30 -28
- udata/core/organization/signals.py +7 -7
- udata/core/organization/tasks.py +42 -61
- udata/core/owned.py +38 -27
- udata/core/post/api.py +82 -81
- udata/core/post/constants.py +8 -5
- udata/core/post/factories.py +4 -4
- udata/core/post/forms.py +13 -14
- udata/core/post/models.py +20 -22
- udata/core/post/tests/test_api.py +30 -32
- udata/core/reports/api.py +8 -7
- udata/core/reports/constants.py +1 -3
- udata/core/reports/models.py +10 -10
- udata/core/reuse/activities.py +15 -19
- udata/core/reuse/api.py +123 -126
- udata/core/reuse/api_fields.py +120 -85
- udata/core/reuse/apiv2.py +11 -10
- udata/core/reuse/constants.py +23 -23
- udata/core/reuse/csv.py +18 -18
- udata/core/reuse/factories.py +5 -9
- udata/core/reuse/forms.py +24 -21
- udata/core/reuse/models.py +55 -51
- udata/core/reuse/permissions.py +2 -2
- udata/core/reuse/search.py +49 -46
- udata/core/reuse/signals.py +1 -1
- udata/core/reuse/tasks.py +4 -5
- udata/core/site/api.py +47 -50
- udata/core/site/factories.py +2 -2
- udata/core/site/forms.py +4 -5
- udata/core/site/models.py +94 -63
- udata/core/site/rdf.py +14 -14
- udata/core/spam/api.py +16 -9
- udata/core/spam/constants.py +4 -4
- udata/core/spam/fields.py +13 -7
- udata/core/spam/models.py +27 -20
- udata/core/spam/signals.py +1 -1
- udata/core/spam/tests/test_spam.py +6 -5
- udata/core/spatial/api.py +72 -80
- udata/core/spatial/api_fields.py +73 -58
- udata/core/spatial/commands.py +67 -64
- udata/core/spatial/constants.py +3 -3
- udata/core/spatial/factories.py +37 -54
- udata/core/spatial/forms.py +27 -26
- udata/core/spatial/geoids.py +17 -17
- udata/core/spatial/models.py +43 -47
- udata/core/spatial/tasks.py +2 -1
- udata/core/spatial/tests/test_api.py +115 -130
- udata/core/spatial/tests/test_fields.py +74 -77
- udata/core/spatial/tests/test_geoid.py +22 -22
- udata/core/spatial/tests/test_models.py +5 -7
- udata/core/spatial/translations.py +16 -16
- udata/core/storages/__init__.py +16 -18
- udata/core/storages/api.py +66 -64
- udata/core/storages/tasks.py +7 -7
- udata/core/storages/utils.py +15 -15
- udata/core/storages/views.py +5 -6
- udata/core/tags/api.py +17 -14
- udata/core/tags/csv.py +4 -4
- udata/core/tags/models.py +8 -5
- udata/core/tags/tasks.py +11 -13
- udata/core/tags/views.py +4 -4
- udata/core/topic/api.py +84 -73
- udata/core/topic/apiv2.py +157 -127
- udata/core/topic/factories.py +3 -4
- udata/core/topic/forms.py +12 -14
- udata/core/topic/models.py +14 -19
- udata/core/topic/parsers.py +26 -26
- udata/core/user/activities.py +30 -29
- udata/core/user/api.py +151 -152
- udata/core/user/api_fields.py +132 -100
- udata/core/user/apiv2.py +7 -7
- udata/core/user/commands.py +38 -38
- udata/core/user/factories.py +8 -9
- udata/core/user/forms.py +14 -11
- udata/core/user/metrics.py +2 -2
- udata/core/user/models.py +68 -69
- udata/core/user/permissions.py +4 -5
- udata/core/user/rdf.py +7 -8
- udata/core/user/tasks.py +2 -2
- udata/core/user/tests/test_user_model.py +24 -16
- udata/cors.py +99 -0
- udata/db/tasks.py +2 -1
- udata/entrypoints.py +35 -31
- udata/errors.py +2 -1
- udata/event/values.py +6 -6
- udata/factories.py +2 -2
- udata/features/identicon/api.py +5 -6
- udata/features/identicon/backends.py +48 -55
- udata/features/identicon/tests/test_backends.py +4 -5
- udata/features/notifications/__init__.py +0 -1
- udata/features/notifications/actions.py +9 -9
- udata/features/notifications/api.py +17 -13
- udata/features/territories/__init__.py +12 -10
- udata/features/territories/api.py +14 -15
- udata/features/territories/models.py +23 -28
- udata/features/transfer/actions.py +8 -11
- udata/features/transfer/api.py +84 -77
- udata/features/transfer/factories.py +2 -1
- udata/features/transfer/models.py +11 -12
- udata/features/transfer/notifications.py +19 -15
- udata/features/transfer/permissions.py +5 -5
- udata/forms/__init__.py +5 -2
- udata/forms/fields.py +164 -172
- udata/forms/validators.py +19 -22
- udata/forms/widgets.py +9 -13
- udata/frontend/__init__.py +31 -26
- udata/frontend/csv.py +68 -58
- udata/frontend/markdown.py +40 -44
- udata/harvest/actions.py +89 -77
- udata/harvest/api.py +294 -238
- udata/harvest/backends/__init__.py +4 -4
- udata/harvest/backends/base.py +128 -111
- udata/harvest/backends/dcat.py +80 -66
- udata/harvest/commands.py +56 -60
- udata/harvest/csv.py +8 -8
- udata/harvest/exceptions.py +6 -3
- udata/harvest/filters.py +24 -23
- udata/harvest/forms.py +27 -28
- udata/harvest/models.py +88 -80
- udata/harvest/notifications.py +15 -10
- udata/harvest/signals.py +13 -13
- udata/harvest/tasks.py +11 -10
- udata/harvest/tests/factories.py +23 -24
- udata/harvest/tests/test_actions.py +136 -166
- udata/harvest/tests/test_api.py +220 -214
- udata/harvest/tests/test_base_backend.py +117 -112
- udata/harvest/tests/test_dcat_backend.py +380 -308
- udata/harvest/tests/test_filters.py +33 -22
- udata/harvest/tests/test_models.py +11 -14
- udata/harvest/tests/test_notifications.py +6 -7
- udata/harvest/tests/test_tasks.py +7 -6
- udata/i18n.py +237 -78
- udata/linkchecker/backends.py +5 -11
- udata/linkchecker/checker.py +23 -22
- udata/linkchecker/commands.py +4 -6
- udata/linkchecker/models.py +6 -6
- udata/linkchecker/tasks.py +18 -20
- udata/mail.py +21 -21
- udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
- udata/migrations/2020-08-24-add-fs-filename.py +9 -8
- udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
- udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
- udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
- udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
- udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
- udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
- udata/migrations/2021-08-17-follow-integrity.py +5 -4
- udata/migrations/2021-08-17-harvest-integrity.py +13 -12
- udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
- udata/migrations/2021-08-17-transfer-integrity.py +5 -4
- udata/migrations/2021-08-17-users-integrity.py +9 -8
- udata/migrations/2021-12-14-reuse-topics.py +7 -6
- udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
- udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
- udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
- udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
- udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
- udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
- udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
- udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
- udata/migrations/__init__.py +123 -105
- udata/models/__init__.py +4 -4
- udata/mongo/__init__.py +13 -11
- udata/mongo/badges_field.py +3 -2
- udata/mongo/datetime_fields.py +13 -12
- udata/mongo/document.py +17 -16
- udata/mongo/engine.py +15 -16
- udata/mongo/errors.py +2 -1
- udata/mongo/extras_fields.py +30 -20
- udata/mongo/queryset.py +12 -12
- udata/mongo/slug_fields.py +38 -28
- udata/mongo/taglist_field.py +1 -2
- udata/mongo/url_field.py +5 -5
- udata/mongo/uuid_fields.py +4 -3
- udata/notifications/__init__.py +1 -1
- udata/notifications/mattermost.py +10 -9
- udata/rdf.py +167 -188
- udata/routing.py +40 -45
- udata/search/__init__.py +18 -19
- udata/search/adapter.py +17 -16
- udata/search/commands.py +44 -51
- udata/search/fields.py +13 -20
- udata/search/query.py +23 -18
- udata/search/result.py +9 -10
- udata/sentry.py +21 -19
- udata/settings.py +262 -198
- udata/sitemap.py +8 -6
- udata/storage/s3.py +20 -13
- udata/tags.py +4 -5
- udata/tasks.py +43 -42
- udata/tests/__init__.py +9 -6
- udata/tests/api/__init__.py +8 -6
- udata/tests/api/test_auth_api.py +395 -321
- udata/tests/api/test_base_api.py +33 -35
- udata/tests/api/test_contact_points.py +7 -9
- udata/tests/api/test_dataservices_api.py +211 -158
- udata/tests/api/test_datasets_api.py +823 -812
- udata/tests/api/test_follow_api.py +13 -15
- udata/tests/api/test_me_api.py +95 -112
- udata/tests/api/test_organizations_api.py +301 -339
- udata/tests/api/test_reports_api.py +35 -25
- udata/tests/api/test_reuses_api.py +134 -139
- udata/tests/api/test_swagger.py +5 -5
- udata/tests/api/test_tags_api.py +18 -25
- udata/tests/api/test_topics_api.py +94 -94
- udata/tests/api/test_transfer_api.py +53 -48
- udata/tests/api/test_user_api.py +128 -141
- udata/tests/apiv2/test_datasets.py +290 -198
- udata/tests/apiv2/test_me_api.py +10 -11
- udata/tests/apiv2/test_organizations.py +56 -74
- udata/tests/apiv2/test_swagger.py +5 -5
- udata/tests/apiv2/test_topics.py +69 -87
- udata/tests/cli/test_cli_base.py +8 -8
- udata/tests/cli/test_db_cli.py +21 -19
- udata/tests/dataservice/test_dataservice_tasks.py +8 -12
- udata/tests/dataset/test_csv_adapter.py +44 -35
- udata/tests/dataset/test_dataset_actions.py +2 -3
- udata/tests/dataset/test_dataset_commands.py +7 -8
- udata/tests/dataset/test_dataset_events.py +36 -29
- udata/tests/dataset/test_dataset_model.py +224 -217
- udata/tests/dataset/test_dataset_rdf.py +142 -131
- udata/tests/dataset/test_dataset_tasks.py +15 -15
- udata/tests/dataset/test_resource_preview.py +10 -13
- udata/tests/features/territories/__init__.py +9 -13
- udata/tests/features/territories/test_territories_api.py +71 -91
- udata/tests/forms/test_basic_fields.py +7 -7
- udata/tests/forms/test_current_user_field.py +39 -66
- udata/tests/forms/test_daterange_field.py +31 -39
- udata/tests/forms/test_dict_field.py +28 -26
- udata/tests/forms/test_extras_fields.py +102 -76
- udata/tests/forms/test_form_field.py +8 -8
- udata/tests/forms/test_image_field.py +33 -26
- udata/tests/forms/test_model_field.py +134 -123
- udata/tests/forms/test_model_list_field.py +7 -7
- udata/tests/forms/test_nested_model_list_field.py +117 -79
- udata/tests/forms/test_publish_as_field.py +36 -65
- udata/tests/forms/test_reference_field.py +34 -53
- udata/tests/forms/test_user_forms.py +23 -21
- udata/tests/forms/test_uuid_field.py +6 -10
- udata/tests/frontend/__init__.py +9 -6
- udata/tests/frontend/test_auth.py +7 -6
- udata/tests/frontend/test_csv.py +81 -96
- udata/tests/frontend/test_hooks.py +43 -43
- udata/tests/frontend/test_markdown.py +211 -191
- udata/tests/helpers.py +32 -37
- udata/tests/models.py +2 -2
- udata/tests/organization/test_csv_adapter.py +21 -16
- udata/tests/organization/test_notifications.py +11 -18
- udata/tests/organization/test_organization_model.py +13 -13
- udata/tests/organization/test_organization_rdf.py +29 -22
- udata/tests/organization/test_organization_tasks.py +16 -17
- udata/tests/plugin.py +79 -73
- udata/tests/reuse/test_reuse_model.py +21 -21
- udata/tests/reuse/test_reuse_task.py +11 -13
- udata/tests/search/__init__.py +11 -12
- udata/tests/search/test_adapter.py +60 -70
- udata/tests/search/test_query.py +16 -16
- udata/tests/search/test_results.py +10 -7
- udata/tests/site/test_site_api.py +11 -16
- udata/tests/site/test_site_metrics.py +20 -30
- udata/tests/site/test_site_model.py +4 -5
- udata/tests/site/test_site_rdf.py +94 -78
- udata/tests/test_activity.py +17 -17
- udata/tests/test_cors.py +62 -0
- udata/tests/test_discussions.py +292 -299
- udata/tests/test_i18n.py +37 -40
- udata/tests/test_linkchecker.py +91 -85
- udata/tests/test_mail.py +13 -17
- udata/tests/test_migrations.py +219 -180
- udata/tests/test_model.py +164 -157
- udata/tests/test_notifications.py +17 -17
- udata/tests/test_owned.py +14 -14
- udata/tests/test_rdf.py +25 -23
- udata/tests/test_routing.py +89 -93
- udata/tests/test_storages.py +137 -128
- udata/tests/test_tags.py +44 -46
- udata/tests/test_topics.py +7 -7
- udata/tests/test_transfer.py +42 -49
- udata/tests/test_uris.py +160 -161
- udata/tests/test_utils.py +79 -71
- udata/tests/user/test_user_rdf.py +5 -9
- udata/tests/workers/test_jobs_commands.py +57 -58
- udata/tests/workers/test_tasks_routing.py +23 -29
- udata/tests/workers/test_workers_api.py +125 -131
- udata/tests/workers/test_workers_helpers.py +6 -6
- udata/tracking.py +4 -6
- udata/uris.py +45 -46
- udata/utils.py +68 -66
- udata/wsgi.py +1 -1
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/METADATA +7 -3
- udata-9.1.2.dev30454.dist-info/RECORD +706 -0
- udata-9.1.2.dev30355.dist-info/RECORD +0 -704
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/LICENSE +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/WHEEL +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/entry_points.txt +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/top_level.txt +0 -0
udata/core/user/api_fields.py
CHANGED
|
@@ -1,115 +1,147 @@
|
|
|
1
|
+
from udata.api import api, base_reference, fields
|
|
1
2
|
from udata.auth.helpers import current_user_is_admin_or_self
|
|
2
|
-
from udata.api import api, fields, base_reference
|
|
3
3
|
|
|
4
4
|
from .constants import BIGGEST_AVATAR_SIZE
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
description=
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
description=
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
user_ref_fields = api.inherit(
|
|
7
|
+
"UserReference",
|
|
8
|
+
base_reference,
|
|
9
|
+
{
|
|
10
|
+
"first_name": fields.String(description="The user first name", readonly=True),
|
|
11
|
+
"last_name": fields.String(description="The user larst name", readonly=True),
|
|
12
|
+
"slug": fields.String(description="The user permalink string", required=True),
|
|
13
|
+
"page": fields.UrlFor(
|
|
14
|
+
"users.show",
|
|
15
|
+
lambda u: {"user": u},
|
|
16
|
+
description="The user profile page URL",
|
|
17
|
+
readonly=True,
|
|
18
|
+
fallback_endpoint="api.user",
|
|
19
|
+
),
|
|
20
|
+
"uri": fields.UrlFor(
|
|
21
|
+
"api.user", lambda o: {"user": o}, description="The user API URI", required=True
|
|
22
|
+
),
|
|
23
|
+
"avatar": fields.ImageField(original=True, description="The user avatar URL"),
|
|
24
|
+
"avatar_thumbnail": fields.ImageField(
|
|
25
|
+
attribute="avatar",
|
|
26
|
+
size=BIGGEST_AVATAR_SIZE,
|
|
27
|
+
description="The user avatar thumbnail URL. This is the square "
|
|
28
|
+
"({0}x{0}) and cropped version.".format(BIGGEST_AVATAR_SIZE),
|
|
29
|
+
),
|
|
30
|
+
},
|
|
31
|
+
)
|
|
26
32
|
|
|
27
33
|
from udata.core.organization.api_fields import org_ref_fields # noqa
|
|
28
34
|
|
|
29
|
-
user_fields = api.model(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
description=
|
|
34
|
-
|
|
35
|
-
description=
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
fields.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
35
|
+
user_fields = api.model(
|
|
36
|
+
"User",
|
|
37
|
+
{
|
|
38
|
+
"id": fields.String(description="The user identifier", required=True),
|
|
39
|
+
"slug": fields.String(description="The user permalink string", required=True),
|
|
40
|
+
"first_name": fields.String(description="The user first name", required=True),
|
|
41
|
+
"last_name": fields.String(description="The user last name", required=True),
|
|
42
|
+
"email": fields.Raw(
|
|
43
|
+
attribute=lambda o: o.email if current_user_is_admin_or_self() else None,
|
|
44
|
+
description="The user email",
|
|
45
|
+
readonly=True,
|
|
46
|
+
),
|
|
47
|
+
"avatar": fields.ImageField(original=True, description="The user avatar URL"),
|
|
48
|
+
"avatar_thumbnail": fields.ImageField(
|
|
49
|
+
attribute="avatar",
|
|
50
|
+
size=BIGGEST_AVATAR_SIZE,
|
|
51
|
+
description="The user avatar thumbnail URL. This is the square "
|
|
52
|
+
"({0}x{0}) and cropped version.".format(BIGGEST_AVATAR_SIZE),
|
|
53
|
+
),
|
|
54
|
+
"website": fields.String(description="The user website"),
|
|
55
|
+
"about": fields.Markdown(description="The user self description"),
|
|
56
|
+
"roles": fields.List(fields.String, description="Site wide user roles"),
|
|
57
|
+
"active": fields.Boolean(),
|
|
58
|
+
"organizations": fields.List(
|
|
59
|
+
fields.Nested(org_ref_fields), description="The organization the user belongs to"
|
|
60
|
+
),
|
|
61
|
+
"since": fields.ISODateTime(
|
|
62
|
+
attribute="created_at", description="The registeration date", required=True
|
|
63
|
+
),
|
|
64
|
+
"page": fields.UrlFor(
|
|
65
|
+
"users.show",
|
|
66
|
+
lambda u: {"user": u},
|
|
67
|
+
description="The user profile page URL",
|
|
68
|
+
readonly=True,
|
|
69
|
+
fallback_endpoint="api.user",
|
|
70
|
+
),
|
|
71
|
+
"uri": fields.UrlFor(
|
|
72
|
+
"api.user", lambda o: {"user": o}, description="The user API URI", required=True
|
|
73
|
+
),
|
|
74
|
+
"metrics": fields.Raw(
|
|
75
|
+
attribute=lambda o: o.get_metrics(), description="The user metrics", readonly=True
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
)
|
|
65
79
|
|
|
66
|
-
me_fields = api.inherit(
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
me_fields = api.inherit(
|
|
81
|
+
"Me",
|
|
82
|
+
user_fields,
|
|
83
|
+
{
|
|
84
|
+
"apikey": fields.String(description="The user API Key", readonly=True),
|
|
85
|
+
},
|
|
86
|
+
)
|
|
69
87
|
|
|
70
|
-
me_metrics_fields = api.model(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
description="The user's
|
|
84
|
-
|
|
88
|
+
me_metrics_fields = api.model(
|
|
89
|
+
"MyMetrics",
|
|
90
|
+
{
|
|
91
|
+
"id": fields.String(description="The user identifier", required=True),
|
|
92
|
+
"resources_availability": fields.Float(
|
|
93
|
+
description="The user's resources availability percentage", readonly=True
|
|
94
|
+
),
|
|
95
|
+
"datasets_org_count": fields.Integer(
|
|
96
|
+
description="The user's orgs datasets number", readonly=True
|
|
97
|
+
),
|
|
98
|
+
"followers_org_count": fields.Integer(
|
|
99
|
+
description="The user's orgs followers number", readonly=True
|
|
100
|
+
),
|
|
101
|
+
"datasets_count": fields.Integer(description="The user's datasets number", readonly=True),
|
|
102
|
+
"followers_count": fields.Integer(description="The user's followers number", readonly=True),
|
|
103
|
+
},
|
|
104
|
+
)
|
|
85
105
|
|
|
86
|
-
apikey_fields = api.model(
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
apikey_fields = api.model(
|
|
107
|
+
"ApiKey",
|
|
108
|
+
{
|
|
109
|
+
"apikey": fields.String(description="The user API Key", readonly=True),
|
|
110
|
+
},
|
|
111
|
+
)
|
|
89
112
|
|
|
90
|
-
user_page_fields = api.model(
|
|
113
|
+
user_page_fields = api.model("UserPage", fields.pager(user_fields))
|
|
91
114
|
|
|
92
|
-
user_suggestion_fields = api.model(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
user_suggestion_fields = api.model(
|
|
116
|
+
"UserSuggestion",
|
|
117
|
+
{
|
|
118
|
+
"id": fields.String(description="The user identifier", readonly=True),
|
|
119
|
+
"first_name": fields.String(description="The user first name", readonly=True),
|
|
120
|
+
"last_name": fields.String(description="The user last name", readonly=True),
|
|
121
|
+
"avatar_url": fields.ImageField(
|
|
122
|
+
size=BIGGEST_AVATAR_SIZE, description="The user avatar URL", readonly=True
|
|
123
|
+
),
|
|
124
|
+
"slug": fields.String(description="The user permalink string", readonly=True),
|
|
125
|
+
},
|
|
126
|
+
)
|
|
102
127
|
|
|
103
|
-
notifications_fields = api.model(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
description=
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
notifications_fields = api.model(
|
|
129
|
+
"Notification",
|
|
130
|
+
{
|
|
131
|
+
"type": fields.String(description="The notification type", readonly=True),
|
|
132
|
+
"created_on": fields.ISODateTime(
|
|
133
|
+
description="The notification creation datetime", readonly=True
|
|
134
|
+
),
|
|
135
|
+
"details": fields.Raw(
|
|
136
|
+
description="Key-Value details depending on notification type", readonly=True
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
)
|
|
111
140
|
|
|
112
141
|
|
|
113
|
-
user_role_fields = api.model(
|
|
114
|
-
|
|
115
|
-
|
|
142
|
+
user_role_fields = api.model(
|
|
143
|
+
"UserRole",
|
|
144
|
+
{
|
|
145
|
+
"name": fields.String(description="The role name", readonly=True),
|
|
146
|
+
},
|
|
147
|
+
)
|
udata/core/user/apiv2.py
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
from flask_security import current_user
|
|
2
2
|
|
|
3
|
-
from udata.api import
|
|
3
|
+
from udata.api import API, apiv2
|
|
4
4
|
from udata.core.topic.apiv2 import topic_page_fields
|
|
5
5
|
from udata.core.topic.parsers import TopicApiParser
|
|
6
6
|
from udata.models import Topic
|
|
7
7
|
|
|
8
|
-
me = apiv2.namespace(
|
|
8
|
+
me = apiv2.namespace("me", "Connected user related operations (v2)")
|
|
9
9
|
|
|
10
10
|
# we will force include_private to True, no need for this arg
|
|
11
11
|
topic_parser = TopicApiParser(with_include_private=False)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@me.route(
|
|
14
|
+
@me.route("/org_topics/", endpoint="my_org_topics")
|
|
15
15
|
class MyOrgTopicsAPI(API):
|
|
16
16
|
@apiv2.secure
|
|
17
|
-
@apiv2.doc(
|
|
17
|
+
@apiv2.doc("my_org_topics")
|
|
18
18
|
@apiv2.expect(topic_parser.parser)
|
|
19
19
|
@apiv2.marshal_list_with(topic_page_fields)
|
|
20
20
|
def get(self):
|
|
21
|
-
|
|
21
|
+
"""List all topics related to me and my organizations."""
|
|
22
22
|
args = topic_parser.parse()
|
|
23
23
|
args["include_private"] = True
|
|
24
24
|
owners = list(current_user.organizations) + [current_user.id]
|
|
25
25
|
topics = Topic.objects.owned_by(*owners)
|
|
26
26
|
topics = topic_parser.parse_filters(topics, args)
|
|
27
|
-
sort = args[
|
|
28
|
-
return topics.order_by(sort).paginate(args[
|
|
27
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or "-last-modified"
|
|
28
|
+
return topics.order_by(sort).paginate(args["page"], args["page_size"])
|
udata/core/user/commands.py
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import click
|
|
2
1
|
import logging
|
|
3
|
-
|
|
4
2
|
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
5
|
from flask import current_app
|
|
6
6
|
from flask_security.forms import RegisterForm
|
|
7
7
|
from flask_security.utils import hash_password
|
|
8
8
|
from werkzeug.datastructures import MultiDict
|
|
9
9
|
|
|
10
|
+
from udata.commands import cli, exit_with_error, success
|
|
10
11
|
from udata.models import User, datastore
|
|
11
12
|
|
|
12
|
-
from udata.commands import cli, success, exit_with_error
|
|
13
|
-
|
|
14
13
|
log = logging.getLogger(__name__)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
@cli.group(
|
|
16
|
+
@cli.group("user")
|
|
18
17
|
def grp():
|
|
19
|
-
|
|
18
|
+
"""User related operations"""
|
|
20
19
|
pass
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@grp.command()
|
|
24
23
|
def create():
|
|
25
|
-
|
|
24
|
+
"""Create a new user"""
|
|
26
25
|
data = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
"first_name": click.prompt("First name"),
|
|
27
|
+
"last_name": click.prompt("Last name"),
|
|
28
|
+
"email": click.prompt("Email"),
|
|
29
|
+
"password": click.prompt("Password", hide_input=True),
|
|
30
|
+
"password_confirm": click.prompt("Confirm Password", hide_input=True),
|
|
32
31
|
}
|
|
33
32
|
# Until https://github.com/mattupstate/flask-security/issues/672 is fixed
|
|
34
33
|
with current_app.test_request_context():
|
|
35
|
-
form = RegisterForm(MultiDict(data), meta={
|
|
34
|
+
form = RegisterForm(MultiDict(data), meta={"csrf": False})
|
|
36
35
|
if form.validate():
|
|
37
|
-
data[
|
|
38
|
-
del data[
|
|
39
|
-
data[
|
|
36
|
+
data["password"] = hash_password(data["password"])
|
|
37
|
+
del data["password_confirm"]
|
|
38
|
+
data["confirmed_at"] = datetime.utcnow()
|
|
40
39
|
user = datastore.create_user(**data)
|
|
41
|
-
success(
|
|
40
|
+
success("User(id={u.id} email={u.email}) created".format(u=user))
|
|
42
41
|
return user
|
|
43
|
-
errors =
|
|
44
|
-
exit_with_error(
|
|
42
|
+
errors = "\n".join("\n".join([str(m) for m in e]) for e in form.errors.values())
|
|
43
|
+
exit_with_error("Error creating user", errors)
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
@grp.command()
|
|
48
47
|
def activate():
|
|
49
|
-
|
|
50
|
-
email = click.prompt(
|
|
48
|
+
"""Activate an existing user (validate their email confirmation)"""
|
|
49
|
+
email = click.prompt("Email")
|
|
51
50
|
user = User.objects(email=email).first()
|
|
52
51
|
if not user:
|
|
53
|
-
exit_with_error(
|
|
52
|
+
exit_with_error("Invalid user")
|
|
54
53
|
if user.confirmed_at is not None:
|
|
55
|
-
exit_with_error(
|
|
54
|
+
exit_with_error("User email address already confirmed")
|
|
56
55
|
return
|
|
57
56
|
user.confirmed_at = datetime.utcnow()
|
|
58
57
|
user.save()
|
|
59
|
-
success(
|
|
58
|
+
success("User activated successfully")
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
@grp.command()
|
|
63
62
|
def delete():
|
|
64
|
-
|
|
65
|
-
email = click.prompt(
|
|
63
|
+
"""Delete an existing user"""
|
|
64
|
+
email = click.prompt("Email")
|
|
66
65
|
user = User.objects(email=email).first()
|
|
67
66
|
if not user:
|
|
68
|
-
exit_with_error(
|
|
67
|
+
exit_with_error("Invalid user")
|
|
69
68
|
user.mark_as_deleted()
|
|
70
|
-
success(
|
|
69
|
+
success("User marked as deleted successfully")
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
@grp.command()
|
|
74
|
-
@click.argument(
|
|
73
|
+
@click.argument("email")
|
|
75
74
|
def set_admin(email):
|
|
76
|
-
|
|
75
|
+
"""Set an user as administrator"""
|
|
77
76
|
user = datastore.find_user(email=email)
|
|
78
|
-
log.info(
|
|
79
|
-
role = datastore.find_or_create_role(
|
|
77
|
+
log.info("Adding admin role to user %s (%s)", user.fullname, user.email)
|
|
78
|
+
role = datastore.find_or_create_role("admin")
|
|
80
79
|
datastore.add_role_to_user(user, role)
|
|
81
|
-
success(
|
|
80
|
+
success("User %s (%s) is now administrator" % (user.fullname, user.email))
|
|
82
81
|
|
|
83
82
|
|
|
84
83
|
@grp.command()
|
|
85
|
-
@click.argument(
|
|
84
|
+
@click.argument("email")
|
|
86
85
|
def password(email):
|
|
87
86
|
user = datastore.find_user(email=email)
|
|
88
|
-
password = click.prompt(
|
|
87
|
+
password = click.prompt("Enter new password", hide_input=True)
|
|
89
88
|
user.password = hash_password(password)
|
|
90
89
|
user.save()
|
|
91
90
|
|
|
91
|
+
|
|
92
92
|
@grp.command()
|
|
93
|
-
@click.argument(
|
|
93
|
+
@click.argument("email")
|
|
94
94
|
def rotate_password(email):
|
|
95
|
-
|
|
95
|
+
"""
|
|
96
96
|
Ask user for password rotation on next login and reset any current session
|
|
97
|
-
|
|
97
|
+
"""
|
|
98
98
|
user = datastore.find_user(email=email)
|
|
99
99
|
user.password_rotation_demanded = datetime.utcnow()
|
|
100
100
|
user.save()
|
udata/core/user/factories.py
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
import factory
|
|
2
|
-
|
|
3
2
|
from flask_security.utils import hash_password
|
|
4
3
|
|
|
5
4
|
from udata.factories import ModelFactory
|
|
6
5
|
|
|
7
|
-
from .models import
|
|
6
|
+
from .models import Role, User
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class UserFactory(ModelFactory):
|
|
11
10
|
class Meta:
|
|
12
11
|
model = User
|
|
13
12
|
|
|
14
|
-
first_name = factory.Faker(
|
|
15
|
-
last_name = factory.Faker(
|
|
16
|
-
email = factory.Faker(
|
|
17
|
-
fs_uniquifier = factory.Faker(
|
|
13
|
+
first_name = factory.Faker("first_name")
|
|
14
|
+
last_name = factory.Faker("last_name")
|
|
15
|
+
email = factory.Faker("email")
|
|
16
|
+
fs_uniquifier = factory.Faker("uuid4")
|
|
18
17
|
active = True
|
|
19
18
|
|
|
20
19
|
@classmethod
|
|
21
20
|
def _adjust_kwargs(cls, **kwargs):
|
|
22
|
-
if
|
|
21
|
+
if "password" in kwargs:
|
|
23
22
|
# Password is stored hashed
|
|
24
|
-
kwargs[
|
|
23
|
+
kwargs["password"] = hash_password(kwargs["password"])
|
|
25
24
|
return kwargs
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
class AdminFactory(UserFactory):
|
|
29
28
|
@factory.lazy_attribute
|
|
30
29
|
def roles(self):
|
|
31
|
-
admin_role, _ = Role.objects.get_or_create(name=
|
|
30
|
+
admin_role, _ = Role.objects.get_or_create(name="admin")
|
|
32
31
|
return [admin_role]
|
udata/core/user/forms.py
CHANGED
|
@@ -4,23 +4,26 @@ from udata.models import User
|
|
|
4
4
|
|
|
5
5
|
from .constants import AVATAR_SIZES
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
__all__ = ('UserProfileForm', 'UserProfileAdminForm')
|
|
7
|
+
__all__ = ("UserProfileForm", "UserProfileAdminForm")
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class UserProfileForm(ModelForm):
|
|
12
11
|
model_class = User
|
|
13
12
|
|
|
14
|
-
first_name = fields.StringField(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
first_name = fields.StringField(
|
|
14
|
+
_("First name"),
|
|
15
|
+
[validators.DataRequired(), validators.NoURLs(_("URLs not allowed in this field"))],
|
|
16
|
+
)
|
|
17
|
+
last_name = fields.StringField(
|
|
18
|
+
_("Last name"),
|
|
19
|
+
[validators.DataRequired(), validators.NoURLs(_("URLs not allowed in this field"))],
|
|
20
|
+
)
|
|
21
|
+
email = fields.StringField(_("Email"), [validators.DataRequired(), validators.Email()])
|
|
22
|
+
avatar = fields.ImageField(_("Avatar"), sizes=AVATAR_SIZES)
|
|
23
|
+
website = fields.URLField(_("Website"))
|
|
24
|
+
about = fields.MarkdownField(_("About"))
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class UserProfileAdminForm(UserProfileForm):
|
|
25
|
-
roles = fields.RolesField(_(
|
|
28
|
+
roles = fields.RolesField(_("Roles"))
|
|
26
29
|
active = fields.BooleanField()
|
udata/core/user/metrics.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from udata.models import db, Dataset, Reuse, User
|
|
2
1
|
from udata.core.followers.signals import on_follow, on_unfollow
|
|
3
2
|
from udata.core.owned import Owned
|
|
3
|
+
from udata.models import Dataset, Reuse, User, db
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
@Dataset.on_create.connect
|
|
6
7
|
@Dataset.on_update.connect
|
|
@@ -32,4 +33,3 @@ def update_owner_metrics(document, previous):
|
|
|
32
33
|
previous.count_datasets()
|
|
33
34
|
elif isinstance(document, Reuse):
|
|
34
35
|
previous.count_reuses()
|
|
35
|
-
|