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
|
@@ -1,142 +1,201 @@
|
|
|
1
1
|
from flask import request
|
|
2
2
|
|
|
3
|
-
from udata.api import api,
|
|
3
|
+
from udata.api import api, base_reference, fields
|
|
4
4
|
from udata.core.badges.fields import badge_fields
|
|
5
5
|
from udata.core.organization.permissions import OrganizationPrivatePermission
|
|
6
6
|
|
|
7
|
-
from .constants import
|
|
8
|
-
|
|
9
|
-
org_ref_fields = api.inherit(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
description=
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
7
|
+
from .constants import BIGGEST_LOGO_SIZE, DEFAULT_ROLE, MEMBERSHIP_STATUS, ORG_ROLES
|
|
8
|
+
|
|
9
|
+
org_ref_fields = api.inherit(
|
|
10
|
+
"OrganizationReference",
|
|
11
|
+
base_reference,
|
|
12
|
+
{
|
|
13
|
+
"name": fields.String(description="The organization name", readonly=True),
|
|
14
|
+
"acronym": fields.String(description="The organization acronym"),
|
|
15
|
+
"uri": fields.UrlFor(
|
|
16
|
+
"api.organization",
|
|
17
|
+
lambda o: {"org": o},
|
|
18
|
+
description="The organization API URI",
|
|
19
|
+
readonly=True,
|
|
20
|
+
),
|
|
21
|
+
"slug": fields.String(
|
|
22
|
+
description="The organization string used as permalink", required=True
|
|
23
|
+
),
|
|
24
|
+
"page": fields.UrlFor(
|
|
25
|
+
"organizations.show",
|
|
26
|
+
lambda o: {"org": o},
|
|
27
|
+
description="The organization web page URL",
|
|
28
|
+
readonly=True,
|
|
29
|
+
fallback_endpoint="api.organization",
|
|
30
|
+
),
|
|
31
|
+
"logo": fields.ImageField(original=True, description="The organization logo URL"),
|
|
32
|
+
"logo_thumbnail": fields.ImageField(
|
|
33
|
+
attribute="logo",
|
|
34
|
+
size=BIGGEST_LOGO_SIZE,
|
|
35
|
+
description="The organization logo thumbnail URL. This is the square "
|
|
36
|
+
"({0}x{0}) and cropped version.".format(BIGGEST_LOGO_SIZE),
|
|
37
|
+
),
|
|
38
|
+
"badges": fields.List(
|
|
39
|
+
fields.Nested(badge_fields), description="The organization badges", readonly=True
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
)
|
|
30
43
|
|
|
31
44
|
from udata.core.user.api_fields import user_ref_fields # noqa: required
|
|
32
45
|
|
|
46
|
+
|
|
33
47
|
def check_can_access_email():
|
|
34
48
|
# This endpoint is secure, only organization member has access.
|
|
35
|
-
if request.endpoint ==
|
|
49
|
+
if request.endpoint == "api.request_membership":
|
|
36
50
|
return True
|
|
37
51
|
|
|
38
|
-
if request.endpoint !=
|
|
52
|
+
if request.endpoint != "api.organization":
|
|
39
53
|
return False
|
|
40
54
|
|
|
41
|
-
org = request.view_args.get(
|
|
55
|
+
org = request.view_args.get("org")
|
|
42
56
|
if org is None:
|
|
43
57
|
return False
|
|
44
|
-
|
|
58
|
+
|
|
45
59
|
return OrganizationPrivatePermission(org).can()
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
description=
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
fields.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
description=
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
61
|
+
|
|
62
|
+
member_user_with_email_fields = api.inherit(
|
|
63
|
+
"MemberUserWithEmail",
|
|
64
|
+
user_ref_fields,
|
|
65
|
+
{
|
|
66
|
+
"email": fields.Raw(
|
|
67
|
+
attribute=lambda o: o.email if check_can_access_email() else None,
|
|
68
|
+
description="The user email (only present on show organization endpoint if the current user has edit permission on the org)",
|
|
69
|
+
readonly=True,
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
request_fields = api.model(
|
|
75
|
+
"MembershipRequest",
|
|
76
|
+
{
|
|
77
|
+
"id": fields.String(readonly=True),
|
|
78
|
+
"user": fields.Nested(member_user_with_email_fields),
|
|
79
|
+
"created": fields.ISODateTime(description="The request creation date", readonly=True),
|
|
80
|
+
"status": fields.String(
|
|
81
|
+
description="The current request status", required=True, enum=list(MEMBERSHIP_STATUS)
|
|
82
|
+
),
|
|
83
|
+
"comment": fields.String(description="A request comment from the user", required=True),
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
member_fields = api.model(
|
|
88
|
+
"Member",
|
|
89
|
+
{
|
|
90
|
+
"user": fields.Nested(member_user_with_email_fields),
|
|
91
|
+
"role": fields.String(
|
|
92
|
+
description="The member role in the organization",
|
|
93
|
+
required=True,
|
|
94
|
+
enum=list(ORG_ROLES),
|
|
95
|
+
default=DEFAULT_ROLE,
|
|
96
|
+
),
|
|
97
|
+
"since": fields.ISODateTime(
|
|
98
|
+
description="The date the user joined the organization", readonly=True
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
org_fields = api.model(
|
|
104
|
+
"Organization",
|
|
105
|
+
{
|
|
106
|
+
"id": fields.String(description="The organization identifier", required=True),
|
|
107
|
+
"name": fields.String(description="The organization name", required=True),
|
|
108
|
+
"acronym": fields.String(description="The organization acronym"),
|
|
109
|
+
"url": fields.String(description="The organization website URL"),
|
|
110
|
+
"slug": fields.String(
|
|
111
|
+
description="The organization string used as permalink", required=True
|
|
112
|
+
),
|
|
113
|
+
"description": fields.Markdown(
|
|
114
|
+
description="The organization description in Markdown", required=True
|
|
115
|
+
),
|
|
116
|
+
"business_number_id": fields.String(
|
|
117
|
+
description="The organization's business identification number."
|
|
118
|
+
),
|
|
119
|
+
"created_at": fields.ISODateTime(
|
|
120
|
+
description="The organization creation date", readonly=True
|
|
121
|
+
),
|
|
122
|
+
"last_modified": fields.ISODateTime(
|
|
123
|
+
description="The organization last modification date", readonly=True
|
|
124
|
+
),
|
|
125
|
+
"deleted": fields.ISODateTime(
|
|
126
|
+
description="The organization deletion date if deleted", readonly=True
|
|
127
|
+
),
|
|
128
|
+
"metrics": fields.Raw(
|
|
129
|
+
attribute=lambda o: o.get_metrics(),
|
|
130
|
+
description="The organization metrics",
|
|
131
|
+
readonly=True,
|
|
132
|
+
),
|
|
133
|
+
"uri": fields.UrlFor(
|
|
134
|
+
"api.organization",
|
|
135
|
+
lambda o: {"org": o},
|
|
136
|
+
description="The organization API URI",
|
|
137
|
+
readonly=True,
|
|
138
|
+
),
|
|
139
|
+
"page": fields.UrlFor(
|
|
140
|
+
"organizations.show",
|
|
141
|
+
lambda o: {"org": o},
|
|
142
|
+
description="The organization page URL",
|
|
143
|
+
readonly=True,
|
|
144
|
+
fallback_endpoint="api.organization",
|
|
145
|
+
),
|
|
146
|
+
"logo": fields.ImageField(original=True, description="The organization logo URL"),
|
|
147
|
+
"logo_thumbnail": fields.ImageField(
|
|
148
|
+
attribute="logo",
|
|
149
|
+
size=BIGGEST_LOGO_SIZE,
|
|
150
|
+
description="The organization logo thumbnail URL. This is the square "
|
|
151
|
+
"({0}x{0}) and cropped version.".format(BIGGEST_LOGO_SIZE),
|
|
152
|
+
),
|
|
153
|
+
"members": fields.List(
|
|
154
|
+
fields.Nested(member_fields, description="The organization members")
|
|
155
|
+
),
|
|
156
|
+
"badges": fields.List(
|
|
157
|
+
fields.Nested(badge_fields), description="The organization badges", readonly=True
|
|
158
|
+
),
|
|
159
|
+
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
org_page_fields = api.model("OrganizationPage", fields.pager(org_fields))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
refuse_membership_fields = api.model(
|
|
167
|
+
"RefuseMembership",
|
|
168
|
+
{
|
|
169
|
+
"comment": fields.String(description="The refusal comment."),
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
org_role_fields = api.model(
|
|
175
|
+
"OrganizationRole",
|
|
176
|
+
{
|
|
177
|
+
"id": fields.String(description="The role identifier"),
|
|
178
|
+
"label": fields.String(description="The role label"),
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
org_suggestion_fields = api.model(
|
|
184
|
+
"OrganizationSuggestion",
|
|
185
|
+
{
|
|
186
|
+
"id": fields.String(description="The organization identifier", readonly=True),
|
|
187
|
+
"name": fields.String(description="The organization name", readonly=True),
|
|
188
|
+
"acronym": fields.String(description="The organization acronym", readonly=True),
|
|
189
|
+
"slug": fields.String(description="The organization permalink string", readonly=True),
|
|
190
|
+
"image_url": fields.ImageField(
|
|
191
|
+
size=BIGGEST_LOGO_SIZE, description="The organization logo URL", readonly=True
|
|
192
|
+
),
|
|
193
|
+
"page": fields.UrlFor(
|
|
194
|
+
"organizations.show_redirect",
|
|
195
|
+
lambda o: {"org": o["slug"]},
|
|
196
|
+
description="The organization web page URL",
|
|
197
|
+
readonly=True,
|
|
198
|
+
fallback_endpoint="api.organization",
|
|
199
|
+
),
|
|
200
|
+
},
|
|
201
|
+
)
|
udata/core/organization/apiv2.py
CHANGED
|
@@ -2,61 +2,61 @@ import mongoengine
|
|
|
2
2
|
from flask import request
|
|
3
3
|
|
|
4
4
|
from udata import search
|
|
5
|
-
from udata.api import
|
|
6
|
-
from udata.utils import multi_to_dict
|
|
5
|
+
from udata.api import API, apiv2
|
|
7
6
|
from udata.core.contact_point.api_fields import contact_point_fields
|
|
7
|
+
from udata.utils import multi_to_dict
|
|
8
|
+
|
|
9
|
+
from .api_fields import member_fields, org_fields, org_page_fields
|
|
10
|
+
from .permissions import EditOrganizationPermission, OrganizationPrivatePermission
|
|
8
11
|
from .search import OrganizationSearch
|
|
9
|
-
from .api_fields import org_page_fields, org_fields, member_fields
|
|
10
|
-
from .permissions import (
|
|
11
|
-
EditOrganizationPermission, OrganizationPrivatePermission
|
|
12
|
-
)
|
|
13
12
|
|
|
14
|
-
apiv2.inherit(
|
|
15
|
-
apiv2.inherit(
|
|
16
|
-
apiv2.inherit(
|
|
17
|
-
apiv2.inherit(
|
|
13
|
+
apiv2.inherit("OrganizationPage", org_page_fields)
|
|
14
|
+
apiv2.inherit("Organization", org_fields)
|
|
15
|
+
apiv2.inherit("Member", member_fields)
|
|
16
|
+
apiv2.inherit("ContactPoint", contact_point_fields)
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
ns = apiv2.namespace(
|
|
19
|
+
ns = apiv2.namespace("organizations", "Organization related operations")
|
|
21
20
|
search_parser = OrganizationSearch.as_request_parser()
|
|
22
21
|
|
|
23
|
-
DEFAULT_SORTING =
|
|
22
|
+
DEFAULT_SORTING = "-created_at"
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
@ns.route(
|
|
25
|
+
@ns.route("/search/", endpoint="organization_search")
|
|
27
26
|
class OrganizationSearchAPI(API):
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
"""Organizations collection search endpoint"""
|
|
28
|
+
|
|
29
|
+
@apiv2.doc("search_organizations")
|
|
30
30
|
@apiv2.expect(search_parser)
|
|
31
31
|
@apiv2.marshal_with(org_page_fields)
|
|
32
32
|
def get(self):
|
|
33
|
-
|
|
33
|
+
"""Search all organizations"""
|
|
34
34
|
search_parser.parse_args()
|
|
35
35
|
return search.query(OrganizationSearch, **multi_to_dict(request.args))
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
@ns.route(
|
|
39
|
-
@apiv2.response(400,
|
|
40
|
-
@apiv2.response(400,
|
|
41
|
-
@apiv2.response(404,
|
|
42
|
-
@apiv2.response(410,
|
|
38
|
+
@ns.route("/<org:org>/extras/", endpoint="organization_extras")
|
|
39
|
+
@apiv2.response(400, "Wrong payload format, dict expected")
|
|
40
|
+
@apiv2.response(400, "Wrong payload format, list expected")
|
|
41
|
+
@apiv2.response(404, "Organization not found")
|
|
42
|
+
@apiv2.response(410, "Organization has been deleted")
|
|
43
43
|
class OrganizationExtrasAPI(API):
|
|
44
|
-
@apiv2.doc(
|
|
44
|
+
@apiv2.doc("get_organization_extras")
|
|
45
45
|
def get(self, org):
|
|
46
|
-
|
|
46
|
+
"""Get an organization extras given its identifier"""
|
|
47
47
|
if org.deleted:
|
|
48
|
-
apiv2.abort(410,
|
|
48
|
+
apiv2.abort(410, "Organization has been deleted")
|
|
49
49
|
return org.extras
|
|
50
50
|
|
|
51
51
|
@apiv2.secure
|
|
52
|
-
@apiv2.doc(
|
|
52
|
+
@apiv2.doc("update_organization_extras")
|
|
53
53
|
def put(self, org):
|
|
54
|
-
|
|
54
|
+
"""Update a given organization extras"""
|
|
55
55
|
data = request.json
|
|
56
56
|
if not isinstance(data, dict):
|
|
57
|
-
apiv2.abort(400,
|
|
57
|
+
apiv2.abort(400, "Wrong payload format, dict expected")
|
|
58
58
|
if org.deleted:
|
|
59
|
-
apiv2.abort(410,
|
|
59
|
+
apiv2.abort(410, "Organization has been deleted")
|
|
60
60
|
EditOrganizationPermission(org).test()
|
|
61
61
|
# first remove extras key associated to a None value in payload
|
|
62
62
|
for key in [k for k in data if data[k] is None]:
|
|
@@ -72,14 +72,14 @@ class OrganizationExtrasAPI(API):
|
|
|
72
72
|
return org.extras
|
|
73
73
|
|
|
74
74
|
@apiv2.secure
|
|
75
|
-
@apiv2.doc(
|
|
75
|
+
@apiv2.doc("delete_organization_extras")
|
|
76
76
|
def delete(self, org):
|
|
77
|
-
|
|
77
|
+
"""Delete a given organization extras key on a given organization"""
|
|
78
78
|
data = request.json
|
|
79
79
|
if not isinstance(data, list):
|
|
80
|
-
apiv2.abort(400,
|
|
80
|
+
apiv2.abort(400, "Wrong payload format, list expected")
|
|
81
81
|
if org.deleted:
|
|
82
|
-
apiv2.abort(410,
|
|
82
|
+
apiv2.abort(410, "Organization has been deleted")
|
|
83
83
|
EditOrganizationPermission(org).test()
|
|
84
84
|
for key in data:
|
|
85
85
|
try:
|
|
@@ -8,43 +8,41 @@ from udata.models import GeoZone, Organization
|
|
|
8
8
|
log = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@cli.group(
|
|
11
|
+
@cli.group("organizations")
|
|
12
12
|
def grp():
|
|
13
|
-
|
|
13
|
+
"""Organizations related operations"""
|
|
14
14
|
pass
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@grp.command()
|
|
18
|
-
@click.argument(
|
|
19
|
-
@click.argument(
|
|
18
|
+
@click.argument("geoid", metavar="<geoid>")
|
|
19
|
+
@click.argument("organization_id_or_slug", metavar="<organization>")
|
|
20
20
|
def attach_zone(geoid, organization_id_or_slug):
|
|
21
|
-
|
|
22
|
-
organization = Organization.objects.get_by_id_or_slug(
|
|
23
|
-
organization_id_or_slug)
|
|
21
|
+
"""Attach a zone <geoid> restricted to level for a given <organization>."""
|
|
22
|
+
organization = Organization.objects.get_by_id_or_slug(organization_id_or_slug)
|
|
24
23
|
if not organization:
|
|
25
|
-
log.error(
|
|
24
|
+
log.error("No organization found for %s", organization_id_or_slug)
|
|
26
25
|
geozone = GeoZone.objects.get(id=geoid)
|
|
27
26
|
if not geozone:
|
|
28
|
-
log.error(
|
|
29
|
-
log.info(
|
|
30
|
-
|
|
27
|
+
log.error("No geozone found for %s", geoid)
|
|
28
|
+
log.info(
|
|
29
|
+
"Attaching {organization} with {geozone.name}".format(
|
|
30
|
+
organization=organization, geozone=geozone
|
|
31
|
+
)
|
|
32
|
+
)
|
|
31
33
|
organization.zone = geozone.id
|
|
32
34
|
organization.save()
|
|
33
|
-
log.info(
|
|
35
|
+
log.info("Done")
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
@grp.command()
|
|
37
|
-
@click.argument(
|
|
39
|
+
@click.argument("organization_id_or_slug", metavar="<organization>")
|
|
38
40
|
def detach_zone(organization_id_or_slug):
|
|
39
|
-
|
|
40
|
-
organization = Organization.objects.get_by_id_or_slug(
|
|
41
|
-
organization_id_or_slug)
|
|
41
|
+
"""Detach the zone of a given <organization>."""
|
|
42
|
+
organization = Organization.objects.get_by_id_or_slug(organization_id_or_slug)
|
|
42
43
|
if not organization:
|
|
43
|
-
exit_with_error(
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
log.info('Detaching {organization} from {organization.zone}'.format(
|
|
47
|
-
organization=organization))
|
|
44
|
+
exit_with_error("No organization found for {0}".format(organization_id_or_slug))
|
|
45
|
+
log.info("Detaching {organization} from {organization.zone}".format(organization=organization))
|
|
48
46
|
organization.zone = None
|
|
49
47
|
organization.save()
|
|
50
|
-
log.info(
|
|
48
|
+
log.info("Done")
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
from udata.i18n import lazy_gettext as _
|
|
2
2
|
|
|
3
3
|
ORG_ROLES = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
"admin": _("Administrator"),
|
|
5
|
+
"editor": _("Editor"),
|
|
6
6
|
}
|
|
7
|
-
DEFAULT_ROLE =
|
|
7
|
+
DEFAULT_ROLE = "editor"
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
MEMBERSHIP_STATUS = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
"pending": _("Pending"),
|
|
12
|
+
"accepted": _("Accepted"),
|
|
13
|
+
"refused": _("Refused"),
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
LOGO_MAX_SIZE = 500
|
|
17
17
|
LOGO_SIZES = [100, 60, 25]
|
|
18
18
|
BIGGEST_LOGO_SIZE = LOGO_SIZES[0]
|
|
19
19
|
|
|
20
|
-
PUBLIC_SERVICE =
|
|
21
|
-
CERTIFIED =
|
|
22
|
-
ASSOCIATION =
|
|
23
|
-
COMPANY =
|
|
24
|
-
LOCAL_AUTHORITY =
|
|
20
|
+
PUBLIC_SERVICE = "public-service"
|
|
21
|
+
CERTIFIED = "certified"
|
|
22
|
+
ASSOCIATION = "Association"
|
|
23
|
+
COMPANY = "Company"
|
|
24
|
+
LOCAL_AUTHORITY = "Local authority"
|
|
25
25
|
|
|
26
26
|
TITLE_SIZE_LIMIT = 350
|
|
27
27
|
DESCRIPTION_SIZE_LIMIT = 100000
|
udata/core/organization/csv.py
CHANGED
|
@@ -9,17 +9,17 @@ class OrganizationCsvAdapter(csv.Adapter):
|
|
|
9
9
|
downloads_counts = None
|
|
10
10
|
|
|
11
11
|
fields = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
(
|
|
16
|
-
|
|
17
|
-
(
|
|
18
|
-
(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(
|
|
12
|
+
"id",
|
|
13
|
+
"name",
|
|
14
|
+
"slug",
|
|
15
|
+
("url", "external_url"),
|
|
16
|
+
"description",
|
|
17
|
+
("logo", lambda o: o.logo(external=True)),
|
|
18
|
+
("badges", lambda o: ",".join([badge.kind for badge in o.badges])),
|
|
19
|
+
"created_at",
|
|
20
|
+
"last_modified",
|
|
21
|
+
"business_number_id",
|
|
22
|
+
("members_count", lambda o: len(o.members)),
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
def dynamic_fields(self):
|
|
@@ -27,12 +27,12 @@ class OrganizationCsvAdapter(csv.Adapter):
|
|
|
27
27
|
|
|
28
28
|
def get_dynamic_field_downloads(self):
|
|
29
29
|
downloads_counts = self.get_downloads_counts()
|
|
30
|
-
return [(
|
|
30
|
+
return [("downloads", lambda o: downloads_counts.get(str(o.id), 0))]
|
|
31
31
|
|
|
32
32
|
def get_downloads_counts(self):
|
|
33
|
-
|
|
33
|
+
"""
|
|
34
34
|
Prefetch all the resources' downloads for all selected organization into memory
|
|
35
|
-
|
|
35
|
+
"""
|
|
36
36
|
if self.downloads_counts is not None:
|
|
37
37
|
return self.downloads_counts
|
|
38
38
|
|
|
@@ -44,6 +44,8 @@ class OrganizationCsvAdapter(csv.Adapter):
|
|
|
44
44
|
if self.downloads_counts.get(org_id) is None:
|
|
45
45
|
self.downloads_counts[org_id] = 0
|
|
46
46
|
|
|
47
|
-
self.downloads_counts[org_id] += sum(
|
|
47
|
+
self.downloads_counts[org_id] += sum(
|
|
48
|
+
resource.metrics.get("views", 0) for resource in dataset.resources
|
|
49
|
+
)
|
|
48
50
|
|
|
49
51
|
return self.downloads_counts
|
|
@@ -2,22 +2,19 @@ import factory
|
|
|
2
2
|
|
|
3
3
|
from udata.factories import ModelFactory
|
|
4
4
|
|
|
5
|
-
from .models import Organization, Team
|
|
5
|
+
from .models import Member, Organization, Team
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class OrganizationFactory(ModelFactory):
|
|
9
9
|
class Meta:
|
|
10
10
|
model = Organization
|
|
11
11
|
|
|
12
|
-
name = factory.Faker(
|
|
13
|
-
description = factory.Faker(
|
|
14
|
-
members = factory.LazyAttribute(
|
|
15
|
-
Member(user=user, role=
|
|
16
|
-
for user in o.
|
|
17
|
-
|
|
18
|
-
Member(user=user, role='editor')
|
|
19
|
-
for user in o.editors
|
|
20
|
-
])
|
|
12
|
+
name = factory.Faker("sentence")
|
|
13
|
+
description = factory.Faker("text")
|
|
14
|
+
members = factory.LazyAttribute(
|
|
15
|
+
lambda o: [Member(user=user, role="admin") for user in o.admins]
|
|
16
|
+
+ [Member(user=user, role="editor") for user in o.editors]
|
|
17
|
+
)
|
|
21
18
|
|
|
22
19
|
class Params:
|
|
23
20
|
admins = []
|
|
@@ -28,4 +25,4 @@ class TeamFactory(ModelFactory):
|
|
|
28
25
|
class Meta:
|
|
29
26
|
model = Team
|
|
30
27
|
|
|
31
|
-
name = factory.Faker(
|
|
28
|
+
name = factory.Faker("sentence")
|