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/forms/fields.py
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
|
|
3
3
|
from dateutil.parser import parse
|
|
4
|
-
|
|
5
4
|
from flask import url_for
|
|
6
5
|
from flask_mongoengine.wtf import fields as mefields
|
|
7
6
|
from flask_storage.mongo import ImageReference
|
|
8
7
|
from speaklater import is_lazy_string
|
|
9
|
-
from wtforms import
|
|
8
|
+
from wtforms import Field as WTField
|
|
9
|
+
from wtforms import Form as WTForm
|
|
10
|
+
from wtforms import SubmitField, fields, validators # noqa
|
|
10
11
|
from wtforms.utils import unset_value
|
|
11
12
|
from wtforms.widgets import TextInput
|
|
12
13
|
from wtforms_json import flatten_json
|
|
13
14
|
|
|
14
|
-
from . import widgets
|
|
15
|
-
|
|
16
|
-
from udata.auth import current_user, admin_permission
|
|
17
|
-
from udata.models import db, User, Organization, Dataset, Reuse, datastore, ContactPoint
|
|
18
|
-
from udata.core.storages import tmp
|
|
19
|
-
from udata.core.organization.permissions import OrganizationPrivatePermission
|
|
20
|
-
from udata.i18n import lazy_gettext as _
|
|
21
15
|
from udata import tags, uris
|
|
16
|
+
from udata.auth import admin_permission, current_user
|
|
17
|
+
from udata.core.organization.permissions import OrganizationPrivatePermission
|
|
18
|
+
from udata.core.storages import tmp
|
|
22
19
|
from udata.forms import ModelForm
|
|
23
|
-
from udata.
|
|
20
|
+
from udata.i18n import lazy_gettext as _
|
|
21
|
+
from udata.models import ContactPoint, Dataset, Organization, Reuse, User, datastore, db
|
|
22
|
+
from udata.utils import get_by, to_iso_date
|
|
23
|
+
|
|
24
|
+
from . import widgets
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class FieldHelper(object):
|
|
27
28
|
def __init__(self, *args, **kwargs):
|
|
28
|
-
self._preprocessors = kwargs.pop(
|
|
29
|
+
self._preprocessors = kwargs.pop("preprocessors", [])
|
|
29
30
|
super(FieldHelper, self).__init__(*args, **kwargs)
|
|
30
|
-
self._form = kwargs.get(
|
|
31
|
+
self._form = kwargs.get("_form", None)
|
|
31
32
|
|
|
32
33
|
@property
|
|
33
34
|
def id(self):
|
|
34
|
-
return
|
|
35
|
+
return "{0}-id".format(self.name)
|
|
35
36
|
|
|
36
37
|
@id.setter
|
|
37
38
|
def id(self, value):
|
|
@@ -42,16 +43,16 @@ class FieldHelper(object):
|
|
|
42
43
|
return False
|
|
43
44
|
|
|
44
45
|
def __call__(self, **kwargs):
|
|
45
|
-
placeholder = kwargs.pop(
|
|
46
|
+
placeholder = kwargs.pop("placeholder", self.label.text)
|
|
46
47
|
if placeholder:
|
|
47
|
-
kwargs[
|
|
48
|
-
required = kwargs.pop(
|
|
48
|
+
kwargs["placeholder"] = placeholder
|
|
49
|
+
required = kwargs.pop("required", self.flags.required)
|
|
49
50
|
if required is True:
|
|
50
|
-
kwargs[
|
|
51
|
+
kwargs["required"] = required
|
|
51
52
|
return super(FieldHelper, self).__call__(**kwargs)
|
|
52
53
|
|
|
53
54
|
def pre_validate(self, form):
|
|
54
|
-
|
|
55
|
+
"""Calls preprocessors before pre_validation"""
|
|
55
56
|
for preprocessor in self._preprocessors:
|
|
56
57
|
preprocessor(form, self)
|
|
57
58
|
super(FieldHelper, self).pre_validate(form)
|
|
@@ -63,7 +64,7 @@ class Field(FieldHelper, WTField):
|
|
|
63
64
|
|
|
64
65
|
class EmptyNone(object):
|
|
65
66
|
def process_formdata(self, valuelist):
|
|
66
|
-
|
|
67
|
+
"""Replace empty values by None"""
|
|
67
68
|
super(EmptyNone, self).process_formdata(valuelist)
|
|
68
69
|
self.data = self.data or None
|
|
69
70
|
|
|
@@ -95,10 +96,17 @@ class WTFDateTimeField(WTField):
|
|
|
95
96
|
:param display_format:
|
|
96
97
|
A format string to pass to strftime() to format dates for display.
|
|
97
98
|
"""
|
|
99
|
+
|
|
98
100
|
widget = TextInput()
|
|
99
101
|
|
|
100
|
-
def __init__(
|
|
101
|
-
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
label=None,
|
|
105
|
+
validators=None,
|
|
106
|
+
parse_kwargs=None,
|
|
107
|
+
display_format="%Y-%m-%d %H:%M",
|
|
108
|
+
**kwargs,
|
|
109
|
+
):
|
|
102
110
|
super(WTFDateTimeField, self).__init__(label, validators, **kwargs)
|
|
103
111
|
if parse_kwargs is None:
|
|
104
112
|
parse_kwargs = {}
|
|
@@ -107,42 +115,45 @@ class WTFDateTimeField(WTField):
|
|
|
107
115
|
|
|
108
116
|
def _value(self):
|
|
109
117
|
if self.raw_data:
|
|
110
|
-
return
|
|
118
|
+
return " ".join(self.raw_data)
|
|
111
119
|
else:
|
|
112
|
-
return self.data and self.data.strftime(self.display_format) or
|
|
120
|
+
return self.data and self.data.strftime(self.display_format) or ""
|
|
113
121
|
|
|
114
122
|
def process_formdata(self, valuelist):
|
|
115
123
|
if valuelist:
|
|
116
|
-
date_str =
|
|
124
|
+
date_str = " ".join(valuelist)
|
|
117
125
|
if not date_str:
|
|
118
126
|
self.data = None
|
|
119
|
-
raise validators.ValidationError(_(
|
|
127
|
+
raise validators.ValidationError(_("Please input a date/time value"))
|
|
120
128
|
|
|
121
129
|
parse_kwargs = self.parse_kwargs.copy()
|
|
122
|
-
if
|
|
130
|
+
if "default" not in parse_kwargs:
|
|
123
131
|
try:
|
|
124
|
-
parse_kwargs[
|
|
132
|
+
parse_kwargs["default"] = self.default()
|
|
125
133
|
except TypeError:
|
|
126
|
-
parse_kwargs[
|
|
134
|
+
parse_kwargs["default"] = self.default
|
|
127
135
|
try:
|
|
128
136
|
self.data = parse(date_str, **parse_kwargs)
|
|
129
137
|
except ValueError:
|
|
130
138
|
self.data = None
|
|
131
|
-
raise validators.ValidationError(_(
|
|
139
|
+
raise validators.ValidationError(_("Invalid date/time input"))
|
|
132
140
|
|
|
133
141
|
|
|
134
142
|
class DateField(WTFDateTimeField):
|
|
135
143
|
"""
|
|
136
144
|
Same as the DateTimeField, but stores only the date portion.
|
|
137
145
|
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
|
|
147
|
+
def __init__(
|
|
148
|
+
self, label=None, validators=None, parse_kwargs=None, display_format="%Y-%m-%d", **kwargs
|
|
149
|
+
):
|
|
150
|
+
super(DateField, self).__init__(
|
|
151
|
+
label, validators, parse_kwargs=parse_kwargs, display_format=display_format, **kwargs
|
|
152
|
+
)
|
|
142
153
|
|
|
143
154
|
def process_formdata(self, valuelist):
|
|
144
155
|
super(DateField, self).process_formdata(valuelist)
|
|
145
|
-
if self.data is not None and hasattr(self.data,
|
|
156
|
+
if self.data is not None and hasattr(self.data, "date"):
|
|
146
157
|
self.data = self.data.date()
|
|
147
158
|
|
|
148
159
|
|
|
@@ -155,7 +166,8 @@ class RolesField(Field):
|
|
|
155
166
|
self.data.append(role)
|
|
156
167
|
else:
|
|
157
168
|
raise validators.ValidationError(
|
|
158
|
-
_(
|
|
169
|
+
_("The role {role} does not exist").format(role=name)
|
|
170
|
+
)
|
|
159
171
|
|
|
160
172
|
|
|
161
173
|
class DateTimeField(Field, WTFDateTimeField):
|
|
@@ -172,28 +184,28 @@ class UUIDField(Field):
|
|
|
172
184
|
self.data = uuid.UUID(valuelist[0])
|
|
173
185
|
except ValueError:
|
|
174
186
|
self.data = None
|
|
175
|
-
raise ValueError(self.gettext(
|
|
187
|
+
raise ValueError(self.gettext("Not a valid UUID"))
|
|
176
188
|
|
|
177
189
|
|
|
178
190
|
class BooleanField(FieldHelper, fields.BooleanField):
|
|
179
191
|
def __init__(self, *args, **kwargs):
|
|
180
|
-
self.stacked = kwargs.pop(
|
|
192
|
+
self.stacked = kwargs.pop("stacked", False)
|
|
181
193
|
super(BooleanField, self).__init__(*args, **kwargs)
|
|
182
194
|
|
|
183
195
|
def process_formdata(self, valuelist):
|
|
184
196
|
# We override this so that when no value is provided
|
|
185
|
-
# the form doesn't think the value is `False` instead
|
|
197
|
+
# the form doesn't think the value is `False` instead
|
|
186
198
|
# the value is not present and the model can keep the
|
|
187
199
|
# existing value
|
|
188
200
|
if not valuelist:
|
|
189
|
-
return
|
|
201
|
+
return
|
|
190
202
|
|
|
191
203
|
super().process_formdata(valuelist)
|
|
192
204
|
|
|
193
205
|
|
|
194
206
|
class RadioField(FieldHelper, fields.RadioField):
|
|
195
207
|
def __init__(self, *args, **kwargs):
|
|
196
|
-
self.stacked = kwargs.pop(
|
|
208
|
+
self.stacked = kwargs.pop("stacked", False)
|
|
197
209
|
super(RadioField, self).__init__(*args, **kwargs)
|
|
198
210
|
|
|
199
211
|
|
|
@@ -218,8 +230,8 @@ class URLField(EmptyNone, Field):
|
|
|
218
230
|
|
|
219
231
|
class UploadableURLField(URLField):
|
|
220
232
|
def __init__(self, *args, **kwargs):
|
|
221
|
-
storage = kwargs.pop(
|
|
222
|
-
self.endpoint = url_for(
|
|
233
|
+
storage = kwargs.pop("storage")
|
|
234
|
+
self.endpoint = url_for("storage.upload", name=storage.name)
|
|
223
235
|
super(UploadableURLField, self).__init__(*args, **kwargs)
|
|
224
236
|
|
|
225
237
|
|
|
@@ -228,17 +240,18 @@ class TextAreaField(FieldHelper, EmptyNone, fields.TextAreaField):
|
|
|
228
240
|
|
|
229
241
|
|
|
230
242
|
class FormWrapper(object):
|
|
231
|
-
|
|
243
|
+
"""
|
|
232
244
|
Wrap FormField nested form class to handle both
|
|
233
245
|
JSON provisionning from wtforms-json
|
|
234
246
|
and CSRF disabled from flask-wtf
|
|
235
|
-
|
|
247
|
+
"""
|
|
248
|
+
|
|
236
249
|
def __init__(self, cls):
|
|
237
250
|
self.cls = cls
|
|
238
251
|
|
|
239
252
|
def __call__(self, *args, **kwargs):
|
|
240
|
-
meta = kwargs[
|
|
241
|
-
meta[
|
|
253
|
+
meta = kwargs["meta"] = kwargs.get("meta", {})
|
|
254
|
+
meta["csrf"] = False
|
|
242
255
|
return self.cls(*args, **kwargs)
|
|
243
256
|
|
|
244
257
|
def __getattr__(self, name):
|
|
@@ -247,9 +260,8 @@ class FormWrapper(object):
|
|
|
247
260
|
|
|
248
261
|
class FormField(FieldHelper, fields.FormField):
|
|
249
262
|
def __init__(self, form_class, *args, **kwargs):
|
|
250
|
-
super(FormField, self).__init__(FormWrapper(form_class),
|
|
251
|
-
|
|
252
|
-
self.prefix = '{0}-'.format(self.name)
|
|
263
|
+
super(FormField, self).__init__(FormWrapper(form_class), *args, **kwargs)
|
|
264
|
+
self.prefix = "{0}-".format(self.name)
|
|
253
265
|
self._formdata = None
|
|
254
266
|
|
|
255
267
|
def process(self, formdata, data=unset_value, **kwargs):
|
|
@@ -258,8 +270,10 @@ class FormField(FieldHelper, fields.FormField):
|
|
|
258
270
|
|
|
259
271
|
def validate(self, form, extra_validators=tuple()):
|
|
260
272
|
if extra_validators:
|
|
261
|
-
raise TypeError(
|
|
262
|
-
|
|
273
|
+
raise TypeError(
|
|
274
|
+
"FormField does not accept in-line validators, "
|
|
275
|
+
"as it gets errors from the enclosed form."
|
|
276
|
+
)
|
|
263
277
|
|
|
264
278
|
# Run normal validation only if there is data for this form
|
|
265
279
|
if self.has_data:
|
|
@@ -269,7 +283,7 @@ class FormField(FieldHelper, fields.FormField):
|
|
|
269
283
|
def populate_obj(self, obj, name):
|
|
270
284
|
if not self.has_data:
|
|
271
285
|
return
|
|
272
|
-
if getattr(self.form_class,
|
|
286
|
+
if getattr(self.form_class, "model_class", None) and not self._obj:
|
|
273
287
|
self._obj = self.form_class.model_class()
|
|
274
288
|
super(FormField, self).populate_obj(obj, name)
|
|
275
289
|
|
|
@@ -279,26 +293,24 @@ class FormField(FieldHelper, fields.FormField):
|
|
|
279
293
|
|
|
280
294
|
@property
|
|
281
295
|
def has_data(self):
|
|
282
|
-
return self._formdata and any(
|
|
283
|
-
k.startswith(self.prefix) for k in self._formdata
|
|
284
|
-
)
|
|
296
|
+
return self._formdata and any(k.startswith(self.prefix) for k in self._formdata)
|
|
285
297
|
|
|
286
298
|
|
|
287
299
|
class TmpFilename(Field):
|
|
288
300
|
def _value(self):
|
|
289
|
-
return
|
|
301
|
+
return ""
|
|
290
302
|
|
|
291
303
|
|
|
292
304
|
class BBoxField(Field):
|
|
293
305
|
def _value(self):
|
|
294
306
|
if self.data:
|
|
295
|
-
return
|
|
307
|
+
return ",".join([str(x) for x in self.data])
|
|
296
308
|
else:
|
|
297
|
-
return
|
|
309
|
+
return ""
|
|
298
310
|
|
|
299
311
|
def process_formdata(self, valuelist):
|
|
300
312
|
if valuelist:
|
|
301
|
-
self.data = [int(float(x)) for x in valuelist[0].split(
|
|
313
|
+
self.data = [int(float(x)) for x in valuelist[0].split(",")]
|
|
302
314
|
else:
|
|
303
315
|
self.data = None
|
|
304
316
|
|
|
@@ -310,10 +322,9 @@ class ImageForm(WTForm):
|
|
|
310
322
|
|
|
311
323
|
class ImageField(FormField):
|
|
312
324
|
def __init__(self, label=None, validators=None, **kwargs):
|
|
313
|
-
self.sizes = kwargs.pop(
|
|
314
|
-
self.placeholder = kwargs.pop(
|
|
315
|
-
super(ImageField, self).__init__(ImageForm, label, validators,
|
|
316
|
-
**kwargs)
|
|
325
|
+
self.sizes = kwargs.pop("sizes", [100])
|
|
326
|
+
self.placeholder = kwargs.pop("placeholder", "default")
|
|
327
|
+
super(ImageField, self).__init__(ImageForm, label, validators, **kwargs)
|
|
317
328
|
|
|
318
329
|
def process(self, formdata, data=unset_value, **kwargs):
|
|
319
330
|
self.src = data(100) if isinstance(data, ImageReference) else None
|
|
@@ -324,29 +335,28 @@ class ImageField(FormField):
|
|
|
324
335
|
bbox = self.form.bbox.data or None
|
|
325
336
|
filename = self.form.filename.data or None
|
|
326
337
|
if filename and filename in tmp:
|
|
327
|
-
with tmp.open(filename,
|
|
338
|
+
with tmp.open(filename, "rb") as infile:
|
|
328
339
|
field.save(infile, filename, bbox=bbox)
|
|
329
340
|
tmp.delete(filename)
|
|
330
341
|
|
|
331
342
|
@property
|
|
332
343
|
def endpoint(self):
|
|
333
|
-
return url_for(
|
|
344
|
+
return url_for("storage.upload", name="tmp")
|
|
334
345
|
|
|
335
346
|
|
|
336
347
|
def nullable_text(value):
|
|
337
|
-
return None if value ==
|
|
348
|
+
return None if value == "None" else str(value)
|
|
338
349
|
|
|
339
350
|
|
|
340
351
|
class SelectField(FieldHelper, fields.SelectField):
|
|
341
|
-
def __init__(self, label=None, validators=None, coerce=nullable_text,
|
|
342
|
-
**kwargs):
|
|
352
|
+
def __init__(self, label=None, validators=None, coerce=nullable_text, **kwargs):
|
|
343
353
|
# self._choices = kwargs.pop('choices')
|
|
344
354
|
super(SelectField, self).__init__(label, validators, coerce, **kwargs)
|
|
345
355
|
|
|
346
356
|
@staticmethod
|
|
347
357
|
def localize_label(label):
|
|
348
358
|
if not label:
|
|
349
|
-
return
|
|
359
|
+
return ""
|
|
350
360
|
if is_lazy_string(label):
|
|
351
361
|
return label
|
|
352
362
|
return _(label)
|
|
@@ -354,11 +364,9 @@ class SelectField(FieldHelper, fields.SelectField):
|
|
|
354
364
|
def iter_choices(self):
|
|
355
365
|
localized_choices = [
|
|
356
366
|
(value, self.localize_label(label), selected)
|
|
357
|
-
for value, label, selected
|
|
358
|
-
in super(SelectField, self).iter_choices()
|
|
367
|
+
for value, label, selected in super(SelectField, self).iter_choices()
|
|
359
368
|
]
|
|
360
|
-
for value, label, selected in sorted(localized_choices,
|
|
361
|
-
key=lambda c: c[1]):
|
|
369
|
+
for value, label, selected in sorted(localized_choices, key=lambda c: c[1]):
|
|
362
370
|
yield (value, label, selected)
|
|
363
371
|
|
|
364
372
|
@property
|
|
@@ -382,21 +390,19 @@ class SelectMultipleField(FieldHelper, fields.SelectMultipleField):
|
|
|
382
390
|
|
|
383
391
|
def iter_choices(self):
|
|
384
392
|
localized_choices = [
|
|
385
|
-
(value, self._(label) if label else
|
|
386
|
-
for value, label, selected
|
|
387
|
-
in super(SelectMultipleField, self).iter_choices()
|
|
393
|
+
(value, self._(label) if label else "", selected)
|
|
394
|
+
for value, label, selected in super(SelectMultipleField, self).iter_choices()
|
|
388
395
|
]
|
|
389
|
-
for value, label, selected in sorted(localized_choices,
|
|
390
|
-
key=lambda c: c[1]):
|
|
396
|
+
for value, label, selected in sorted(localized_choices, key=lambda c: c[1]):
|
|
391
397
|
yield (value, label, selected)
|
|
392
398
|
|
|
393
399
|
|
|
394
400
|
class TagField(Field):
|
|
395
401
|
def _value(self):
|
|
396
402
|
if self.data:
|
|
397
|
-
return
|
|
403
|
+
return ",".join(self.data)
|
|
398
404
|
else:
|
|
399
|
-
return
|
|
405
|
+
return ""
|
|
400
406
|
|
|
401
407
|
def process_formdata(self, valuelist):
|
|
402
408
|
if valuelist and len(valuelist) > 1:
|
|
@@ -412,16 +418,17 @@ class TagField(Field):
|
|
|
412
418
|
for tag in self.data:
|
|
413
419
|
if not tags.MIN_TAG_LENGTH <= len(tag) <= tags.MAX_TAG_LENGTH:
|
|
414
420
|
message = _(
|
|
415
|
-
'Tag "%(tag)s" must be between %(min)d '
|
|
416
|
-
'and %(max)d characters long.',
|
|
421
|
+
'Tag "%(tag)s" must be between %(min)d ' "and %(max)d characters long.",
|
|
417
422
|
min=tags.MIN_TAG_LENGTH,
|
|
418
|
-
max=tags.MAX_TAG_LENGTH,
|
|
423
|
+
max=tags.MAX_TAG_LENGTH,
|
|
424
|
+
tag=tag,
|
|
425
|
+
)
|
|
419
426
|
raise validators.ValidationError(message)
|
|
420
427
|
|
|
421
428
|
|
|
422
429
|
def clean_oid(oid, model):
|
|
423
|
-
if
|
|
424
|
-
return clean_oid(oid[
|
|
430
|
+
if isinstance(oid, dict) and "id" in oid:
|
|
431
|
+
return clean_oid(oid["id"], model)
|
|
425
432
|
else:
|
|
426
433
|
try:
|
|
427
434
|
# Prevalidation is required as to_python is failsafe
|
|
@@ -429,21 +436,21 @@ def clean_oid(oid, model):
|
|
|
429
436
|
model.id.validate(oid)
|
|
430
437
|
return model.id.to_python(oid)
|
|
431
438
|
except Exception: # Catch all exceptions as model.type is not predefined
|
|
432
|
-
raise ValueError(
|
|
439
|
+
raise ValueError("Unsupported identifier: {0}".format(oid))
|
|
433
440
|
|
|
434
441
|
|
|
435
442
|
class ModelFieldMixin(object):
|
|
436
443
|
model = None
|
|
437
444
|
|
|
438
445
|
def __init__(self, *args, **kwargs):
|
|
439
|
-
self.model = kwargs.pop(
|
|
446
|
+
self.model = kwargs.pop("model", self.model)
|
|
440
447
|
super(ModelFieldMixin, self).__init__(*args, **kwargs)
|
|
441
448
|
|
|
442
449
|
def _value(self):
|
|
443
450
|
if self.data:
|
|
444
451
|
return str(self.data.id)
|
|
445
452
|
else:
|
|
446
|
-
return
|
|
453
|
+
return ""
|
|
447
454
|
|
|
448
455
|
def process_formdata(self, valuelist):
|
|
449
456
|
if valuelist and len(valuelist) == 1 and valuelist[0]:
|
|
@@ -451,7 +458,7 @@ class ModelFieldMixin(object):
|
|
|
451
458
|
id = clean_oid(valuelist[0], self.model)
|
|
452
459
|
self.data = self.model.objects.get(id=id)
|
|
453
460
|
except self.model.DoesNotExist:
|
|
454
|
-
message = _(
|
|
461
|
+
message = _("{0} does not exists").format(self.model.__name__)
|
|
455
462
|
raise validators.ValidationError(message)
|
|
456
463
|
|
|
457
464
|
|
|
@@ -460,11 +467,11 @@ class ModelField(Field):
|
|
|
460
467
|
if formdata:
|
|
461
468
|
# Process prefixed values as in FormField
|
|
462
469
|
newdata = {}
|
|
463
|
-
prefix = self.short_name +
|
|
470
|
+
prefix = self.short_name + "-"
|
|
464
471
|
for key in list(formdata.keys()):
|
|
465
472
|
if key.startswith(prefix):
|
|
466
473
|
value = formdata.pop(key)
|
|
467
|
-
newdata[key.replace(prefix,
|
|
474
|
+
newdata[key.replace(prefix, "")] = value
|
|
468
475
|
if newdata:
|
|
469
476
|
formdata.add(self.short_name, newdata)
|
|
470
477
|
super(ModelField, self).process(formdata, data, **kwargs)
|
|
@@ -475,35 +482,35 @@ class ModelField(Field):
|
|
|
475
482
|
specs = valuelist[0]
|
|
476
483
|
model_field = getattr(self._form.model_class, self.name)
|
|
477
484
|
if isinstance(specs, str):
|
|
478
|
-
specs = {
|
|
479
|
-
elif not specs.get(
|
|
485
|
+
specs = {"id": specs}
|
|
486
|
+
elif not specs.get("id", None):
|
|
480
487
|
raise validators.ValidationError(_('Missing "id" field'))
|
|
481
488
|
|
|
482
489
|
if isinstance(model_field, db.ReferenceField):
|
|
483
490
|
expected_model = str(model_field.document_type.__name__)
|
|
484
|
-
if
|
|
485
|
-
specs[
|
|
486
|
-
elif specs[
|
|
491
|
+
if "class" not in specs:
|
|
492
|
+
specs["class"] = expected_model
|
|
493
|
+
elif specs["class"] != expected_model:
|
|
487
494
|
msg = _('Expect a "{0}" class but "{1}" was found').format(
|
|
488
|
-
expected_model, specs[
|
|
495
|
+
expected_model, specs["class"]
|
|
489
496
|
)
|
|
490
497
|
raise validators.ValidationError(msg)
|
|
491
498
|
elif isinstance(model_field, db.GenericReferenceField):
|
|
492
|
-
if
|
|
493
|
-
msg = _(
|
|
499
|
+
if "class" not in specs:
|
|
500
|
+
msg = _("Expect both class and identifier")
|
|
494
501
|
raise validators.ValidationError(msg)
|
|
495
502
|
|
|
496
503
|
# No try/except required
|
|
497
504
|
# In case of error, ValueError is raised
|
|
498
505
|
# and is properly handled as form validation error
|
|
499
|
-
model = db.resolve_model(specs[
|
|
506
|
+
model = db.resolve_model(specs["class"])
|
|
500
507
|
oid = clean_oid(specs, model)
|
|
501
508
|
|
|
502
509
|
try:
|
|
503
|
-
self.data = model.objects.only(
|
|
510
|
+
self.data = model.objects.only("id").get(id=oid)
|
|
504
511
|
except db.DoesNotExist:
|
|
505
|
-
label =
|
|
506
|
-
msg = _(
|
|
512
|
+
label = "{0}({1})".format(model.__name__, oid)
|
|
513
|
+
msg = _("{0} does not exists").format(label)
|
|
507
514
|
raise validators.ValidationError(msg)
|
|
508
515
|
|
|
509
516
|
def pre_validate(self, form):
|
|
@@ -518,25 +525,24 @@ class ModelChoiceField(StringField):
|
|
|
518
525
|
models = None
|
|
519
526
|
|
|
520
527
|
def __init__(self, *args, **kwargs):
|
|
521
|
-
self.models = kwargs.pop(
|
|
528
|
+
self.models = kwargs.pop("models", self.models)
|
|
522
529
|
super(ModelChoiceField, self).__init__(*args, **kwargs)
|
|
523
530
|
|
|
524
531
|
def _value(self):
|
|
525
532
|
if self.data:
|
|
526
533
|
return str(self.data.id)
|
|
527
534
|
else:
|
|
528
|
-
return
|
|
535
|
+
return ""
|
|
529
536
|
|
|
530
537
|
def process_formdata(self, valuelist):
|
|
531
538
|
if valuelist and len(valuelist) == 1 and valuelist[0]:
|
|
532
539
|
for model in self.models:
|
|
533
540
|
try:
|
|
534
|
-
self.data = model.objects.get(id=clean_oid(valuelist[0],
|
|
535
|
-
model))
|
|
541
|
+
self.data = model.objects.get(id=clean_oid(valuelist[0], model))
|
|
536
542
|
except model.DoesNotExist:
|
|
537
543
|
pass
|
|
538
544
|
if not self.data:
|
|
539
|
-
message = _(
|
|
545
|
+
message = _("Model for {0} not found").format(valuelist[0])
|
|
540
546
|
raise validators.ValidationError(message)
|
|
541
547
|
|
|
542
548
|
|
|
@@ -545,34 +551,34 @@ class ModelList(object):
|
|
|
545
551
|
|
|
546
552
|
def _value(self):
|
|
547
553
|
if self.data:
|
|
548
|
-
return
|
|
554
|
+
return ",".join([str(o.id) for o in self.data])
|
|
549
555
|
else:
|
|
550
|
-
return
|
|
556
|
+
return ""
|
|
551
557
|
|
|
552
558
|
def process_formdata(self, valuelist):
|
|
553
559
|
if not valuelist:
|
|
554
560
|
return []
|
|
555
561
|
if len(valuelist) == 1 and isinstance(valuelist[0], str):
|
|
556
|
-
oids = [clean_oid(id, self.model)
|
|
557
|
-
for id in valuelist[0].split(',') if id]
|
|
562
|
+
oids = [clean_oid(id, self.model) for id in valuelist[0].split(",") if id]
|
|
558
563
|
else:
|
|
559
564
|
oids = [clean_oid(id, self.model) for id in valuelist]
|
|
560
565
|
self.data = self.fetch_objects(oids)
|
|
561
566
|
|
|
562
567
|
def fetch_objects(self, oids):
|
|
563
|
-
|
|
568
|
+
"""
|
|
564
569
|
This methods is used to fetch models
|
|
565
570
|
from a list of identifiers.
|
|
566
571
|
|
|
567
572
|
Default implementation performs a bulk query on identifiers.
|
|
568
573
|
|
|
569
574
|
Override this method to customize the objects retrieval.
|
|
570
|
-
|
|
575
|
+
"""
|
|
571
576
|
objects = self.model.objects.in_bulk(oids)
|
|
572
577
|
if len(objects.keys()) != len(oids):
|
|
573
578
|
non_existants = set(oids) - set(objects.keys())
|
|
574
|
-
msg = _(
|
|
575
|
-
identifiers=
|
|
579
|
+
msg = _("Unknown identifiers: {identifiers}").format(
|
|
580
|
+
identifiers=", ".join(str(ne) for ne in non_existants)
|
|
581
|
+
)
|
|
576
582
|
raise validators.ValidationError(msg)
|
|
577
583
|
|
|
578
584
|
return [objects[id] for id in oids]
|
|
@@ -580,22 +586,18 @@ class ModelList(object):
|
|
|
580
586
|
|
|
581
587
|
class NestedModelList(fields.FieldList):
|
|
582
588
|
def __init__(self, model_form, *args, **kwargs):
|
|
583
|
-
super(NestedModelList, self).__init__(FormField(model_form),
|
|
584
|
-
*args,
|
|
585
|
-
**kwargs)
|
|
589
|
+
super(NestedModelList, self).__init__(FormField(model_form), *args, **kwargs)
|
|
586
590
|
self.nested_form = model_form
|
|
587
591
|
self.nested_model = model_form.model_class
|
|
588
592
|
self.data_submitted = False
|
|
589
593
|
self.initial_data = []
|
|
590
|
-
self.prefix =
|
|
594
|
+
self.prefix = "{0}-".format(self.name)
|
|
591
595
|
|
|
592
596
|
def process(self, formdata, data=unset_value, **kwargs):
|
|
593
597
|
self._formdata = formdata
|
|
594
598
|
self.initial_data = data
|
|
595
599
|
self.is_list_data = formdata and self.name in formdata
|
|
596
|
-
self.is_dict_data = formdata and any(
|
|
597
|
-
k.startswith(self.prefix) for k in formdata
|
|
598
|
-
)
|
|
600
|
+
self.is_dict_data = formdata and any(k.startswith(self.prefix) for k in formdata)
|
|
599
601
|
self.has_data = self.is_list_data or self.is_dict_data
|
|
600
602
|
if self.has_data:
|
|
601
603
|
super(NestedModelList, self).process(formdata, data, **kwargs)
|
|
@@ -604,7 +606,7 @@ class NestedModelList(fields.FieldList):
|
|
|
604
606
|
# super(NestedModelList, self).process(None, data, **kwargs)
|
|
605
607
|
|
|
606
608
|
def validate(self, form, extra_validators=tuple()):
|
|
607
|
-
|
|
609
|
+
"""Perform validation only if data has been submitted"""
|
|
608
610
|
if not self.has_data:
|
|
609
611
|
return True
|
|
610
612
|
if self.is_list_data:
|
|
@@ -626,33 +628,31 @@ class NestedModelList(fields.FieldList):
|
|
|
626
628
|
|
|
627
629
|
for idx, field in enumerate(self):
|
|
628
630
|
initial = None
|
|
629
|
-
if hasattr(self.nested_model,
|
|
630
|
-
id = self.nested_model.id.to_python(field.data[
|
|
631
|
-
initial = get_by(initial_values,
|
|
631
|
+
if hasattr(self.nested_model, "id") and "id" in field.data:
|
|
632
|
+
id = self.nested_model.id.to_python(field.data["id"])
|
|
633
|
+
initial = get_by(initial_values, "id", id)
|
|
632
634
|
|
|
633
635
|
holder.nested = initial or self.nested_model()
|
|
634
|
-
field.populate_obj(holder,
|
|
636
|
+
field.populate_obj(holder, "nested")
|
|
635
637
|
new_values.append(holder.nested)
|
|
636
638
|
|
|
637
639
|
setattr(obj, name, new_values)
|
|
638
640
|
|
|
639
641
|
def _add_entry(self, formdata=None, data=unset_value, index=None):
|
|
640
|
-
|
|
642
|
+
"""
|
|
641
643
|
Fill the form with previous data if necessary to handle partial update
|
|
642
|
-
|
|
644
|
+
"""
|
|
643
645
|
if formdata:
|
|
644
|
-
prefix =
|
|
645
|
-
basekey =
|
|
646
|
-
idkey = basekey.format(
|
|
646
|
+
prefix = "-".join((self.name, str(index)))
|
|
647
|
+
basekey = "-".join((prefix, "{0}"))
|
|
648
|
+
idkey = basekey.format("id")
|
|
647
649
|
if prefix in formdata:
|
|
648
650
|
formdata[idkey] = formdata.pop(prefix)
|
|
649
|
-
if hasattr(self.nested_model,
|
|
651
|
+
if hasattr(self.nested_model, "id") and idkey in formdata:
|
|
650
652
|
id = self.nested_model.id.to_python(formdata[idkey])
|
|
651
|
-
data = get_by(self.initial_data,
|
|
653
|
+
data = get_by(self.initial_data, "id", id)
|
|
652
654
|
|
|
653
|
-
initial = flatten_json(self.nested_form,
|
|
654
|
-
data.to_mongo(),
|
|
655
|
-
prefix)
|
|
655
|
+
initial = flatten_json(self.nested_form, data.to_mongo(), prefix)
|
|
656
656
|
|
|
657
657
|
for key, value in initial.items():
|
|
658
658
|
if key not in formdata:
|
|
@@ -685,34 +685,32 @@ class MarkdownField(FieldHelper, fields.TextAreaField):
|
|
|
685
685
|
class DateRangeField(Field):
|
|
686
686
|
def _value(self):
|
|
687
687
|
if self.data:
|
|
688
|
-
return
|
|
689
|
-
to_iso_date(self.data.end)])
|
|
688
|
+
return " - ".join([to_iso_date(self.data.start), to_iso_date(self.data.end)])
|
|
690
689
|
else:
|
|
691
|
-
return
|
|
690
|
+
return ""
|
|
692
691
|
|
|
693
692
|
def process_formdata(self, valuelist):
|
|
694
693
|
if valuelist and valuelist[0]:
|
|
695
694
|
value = valuelist[0]
|
|
696
695
|
if isinstance(value, str):
|
|
697
|
-
start, end = value.split(
|
|
696
|
+
start, end = value.split(" - ")
|
|
698
697
|
self.data = db.DateRange(
|
|
699
698
|
start=parse(start, yearfirst=True).date(),
|
|
700
699
|
end=parse(end, yearfirst=True).date(),
|
|
701
700
|
)
|
|
702
|
-
elif
|
|
701
|
+
elif "start" in value and "end" in value:
|
|
703
702
|
self.data = db.DateRange(
|
|
704
|
-
start=parse(value[
|
|
705
|
-
end=parse(value[
|
|
703
|
+
start=parse(value["start"], yearfirst=True).date(),
|
|
704
|
+
end=parse(value["end"], yearfirst=True).date(),
|
|
706
705
|
)
|
|
707
706
|
else:
|
|
708
|
-
raise validators.ValidationError(
|
|
709
|
-
_('Unable to parse date range'))
|
|
707
|
+
raise validators.ValidationError(_("Unable to parse date range"))
|
|
710
708
|
else:
|
|
711
709
|
self.data = None
|
|
712
710
|
|
|
713
711
|
|
|
714
712
|
def default_owner():
|
|
715
|
-
|
|
713
|
+
"""Default to current_user if authenticated"""
|
|
716
714
|
if current_user.is_authenticated:
|
|
717
715
|
return current_user._get_current_object()
|
|
718
716
|
|
|
@@ -721,7 +719,7 @@ class CurrentUserField(ModelFieldMixin, Field):
|
|
|
721
719
|
model = User
|
|
722
720
|
|
|
723
721
|
def __init__(self, *args, **kwargs):
|
|
724
|
-
kwargs[
|
|
722
|
+
kwargs["default"] = kwargs.pop("default", default_owner)
|
|
725
723
|
super(CurrentUserField, self).__init__(*args, **kwargs)
|
|
726
724
|
|
|
727
725
|
def process(self, formdata, data=unset_value, **kwargs):
|
|
@@ -732,20 +730,18 @@ class CurrentUserField(ModelFieldMixin, Field):
|
|
|
732
730
|
def pre_validate(self, form):
|
|
733
731
|
if self.data:
|
|
734
732
|
if current_user.is_anonymous:
|
|
735
|
-
raise validators.ValidationError(
|
|
736
|
-
_('You must be authenticated'))
|
|
733
|
+
raise validators.ValidationError(_("You must be authenticated"))
|
|
737
734
|
elif not admin_permission and current_user.id != self.data.id:
|
|
738
|
-
raise validators.ValidationError(
|
|
739
|
-
_('You can only set yourself as owner'))
|
|
735
|
+
raise validators.ValidationError(_("You can only set yourself as owner"))
|
|
740
736
|
return True
|
|
741
737
|
|
|
742
738
|
|
|
743
739
|
class PublishAsField(ModelFieldMixin, Field):
|
|
744
740
|
model = Organization
|
|
745
|
-
owner_field =
|
|
741
|
+
owner_field = "owner"
|
|
746
742
|
|
|
747
743
|
def __init__(self, *args, **kwargs):
|
|
748
|
-
self.owner_field = kwargs.pop(
|
|
744
|
+
self.owner_field = kwargs.pop("owner_field", self.owner_field)
|
|
749
745
|
super(PublishAsField, self).__init__(*args, **kwargs)
|
|
750
746
|
|
|
751
747
|
@property
|
|
@@ -755,11 +751,9 @@ class PublishAsField(ModelFieldMixin, Field):
|
|
|
755
751
|
def pre_validate(self, form):
|
|
756
752
|
if self.data:
|
|
757
753
|
if not current_user.is_authenticated:
|
|
758
|
-
raise validators.ValidationError(
|
|
759
|
-
_('You must be authenticated'))
|
|
754
|
+
raise validators.ValidationError(_("You must be authenticated"))
|
|
760
755
|
elif not OrganizationPrivatePermission(self.data).can():
|
|
761
|
-
raise validators.ValidationError(
|
|
762
|
-
_("Permission denied for this organization"))
|
|
756
|
+
raise validators.ValidationError(_("Permission denied for this organization"))
|
|
763
757
|
# Ensure either owner field or this field value is unset
|
|
764
758
|
owner_field = form._fields[self.owner_field]
|
|
765
759
|
if self.raw_data:
|
|
@@ -779,8 +773,8 @@ class ContactPointField(ModelFieldMixin, Field):
|
|
|
779
773
|
|
|
780
774
|
|
|
781
775
|
def field_parse(cls, value, *args, **kwargs):
|
|
782
|
-
kwargs[
|
|
783
|
-
kwargs[
|
|
776
|
+
kwargs["_form"] = WTForm()
|
|
777
|
+
kwargs["name"] = "extra"
|
|
784
778
|
field = cls(*args, **kwargs)
|
|
785
779
|
field.process_formdata([value])
|
|
786
780
|
return field.data
|
|
@@ -801,23 +795,21 @@ class ExtrasField(Field):
|
|
|
801
795
|
def __init__(self, *args, **kwargs):
|
|
802
796
|
super(ExtrasField, self).__init__(*args, **kwargs)
|
|
803
797
|
if not isinstance(self._form, ModelForm):
|
|
804
|
-
raise ValueError(
|
|
798
|
+
raise ValueError("ExtrasField can only be used within a ModelForm")
|
|
805
799
|
model_field = getattr(self._form.model_class, self.short_name, None)
|
|
806
800
|
if not model_field or not isinstance(model_field, db.ExtrasField):
|
|
807
|
-
msg =
|
|
801
|
+
msg = "Form ExtrasField can only be mapped to a model ExtraField"
|
|
808
802
|
raise ValueError(msg)
|
|
809
803
|
|
|
810
804
|
@property
|
|
811
805
|
def extras(self):
|
|
812
|
-
|
|
806
|
+
"""Getter to the model extras field"""
|
|
813
807
|
return getattr(self._form.model_class, self.short_name)
|
|
814
808
|
|
|
815
809
|
def parse(self, data):
|
|
816
|
-
|
|
810
|
+
"""Parse fields and store individual errors"""
|
|
817
811
|
self.field_errors = {}
|
|
818
|
-
return dict(
|
|
819
|
-
(k, self._parse_value(k, v)) for k, v in data.items()
|
|
820
|
-
)
|
|
812
|
+
return dict((k, self._parse_value(k, v)) for k, v in data.items())
|
|
821
813
|
|
|
822
814
|
def _parse_value(self, key, value):
|
|
823
815
|
if key not in self.extras.registered:
|
|
@@ -827,7 +819,7 @@ class ExtrasField(Field):
|
|
|
827
819
|
try:
|
|
828
820
|
return field_parse(self.KNOWN_TYPES[expected], value)
|
|
829
821
|
except (validators.ValidationError, ValueError) as e:
|
|
830
|
-
self.field_errors[key] = getattr(e,
|
|
822
|
+
self.field_errors[key] = getattr(e, "message", str(e))
|
|
831
823
|
else:
|
|
832
824
|
return value
|
|
833
825
|
|
|
@@ -837,14 +829,14 @@ class ExtrasField(Field):
|
|
|
837
829
|
if isinstance(data, dict):
|
|
838
830
|
self.data = self.parse(data)
|
|
839
831
|
else:
|
|
840
|
-
raise ValueError(
|
|
832
|
+
raise ValueError("Unsupported data type")
|
|
841
833
|
else:
|
|
842
834
|
self.data = self.parse(self.data or {})
|
|
843
835
|
|
|
844
836
|
def validate(self, form, extra_validators=tuple()):
|
|
845
837
|
if self.process_errors:
|
|
846
838
|
self.errors = list(self.process_errors)
|
|
847
|
-
elif getattr(self,
|
|
839
|
+
elif getattr(self, "field_errors", None):
|
|
848
840
|
self.errors = self.field_errors
|
|
849
841
|
elif self.data:
|
|
850
842
|
try:
|
|
@@ -864,4 +856,4 @@ class DictField(Field):
|
|
|
864
856
|
if isinstance(data, dict):
|
|
865
857
|
self.data = data
|
|
866
858
|
else:
|
|
867
|
-
raise ValueError(
|
|
859
|
+
raise ValueError("Unsupported data type")
|