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/migrations/__init__.py
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
2
|
Data migrations logic
|
|
3
|
-
|
|
3
|
+
"""
|
|
4
|
+
|
|
4
5
|
import importlib.util
|
|
5
6
|
import inspect
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
8
9
|
import queue
|
|
9
10
|
import traceback
|
|
10
|
-
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
from logging.handlers import QueueHandler
|
|
13
|
+
|
|
13
14
|
from flask import current_app
|
|
14
15
|
from mongoengine.connection import get_db
|
|
16
|
+
from pkg_resources import (
|
|
17
|
+
resource_filename,
|
|
18
|
+
resource_isdir,
|
|
19
|
+
resource_listdir,
|
|
20
|
+
resource_string,
|
|
21
|
+
)
|
|
15
22
|
from pymongo import ReturnDocument
|
|
16
|
-
from pkg_resources import resource_isdir, resource_listdir, resource_filename, resource_string
|
|
17
23
|
|
|
18
24
|
from udata import entrypoints
|
|
19
25
|
|
|
@@ -21,13 +27,14 @@ log = logging.getLogger(__name__)
|
|
|
21
27
|
|
|
22
28
|
|
|
23
29
|
class MigrationError(Exception):
|
|
24
|
-
|
|
30
|
+
"""
|
|
25
31
|
Raised on migration execution error.
|
|
26
32
|
|
|
27
33
|
:param msg str: A human readable message (a reason)
|
|
28
34
|
:param output str: An optionnal array of logging output
|
|
29
35
|
:param exc Exception: An optionnal underlying exception
|
|
30
|
-
|
|
36
|
+
"""
|
|
37
|
+
|
|
31
38
|
def __init__(self, msg, output=None, exc=None, traceback=None):
|
|
32
39
|
super().__init__(msg)
|
|
33
40
|
self.msg = msg
|
|
@@ -37,10 +44,11 @@ class MigrationError(Exception):
|
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
class RollbackError(MigrationError):
|
|
40
|
-
|
|
47
|
+
"""
|
|
41
48
|
Raised on rollback.
|
|
42
49
|
Hold the initial migration error and rollback exception (if any)
|
|
43
|
-
|
|
50
|
+
"""
|
|
51
|
+
|
|
44
52
|
def __init__(self, msg, output=None, exc=None, migrate_exc=None):
|
|
45
53
|
super().__init__(msg)
|
|
46
54
|
self.msg = msg
|
|
@@ -54,13 +62,14 @@ class MigrationFormatter(logging.Formatter):
|
|
|
54
62
|
|
|
55
63
|
|
|
56
64
|
class Record(dict):
|
|
57
|
-
|
|
65
|
+
"""
|
|
58
66
|
A simple wrapper to migrations document
|
|
59
|
-
|
|
67
|
+
"""
|
|
68
|
+
|
|
60
69
|
__getattr__ = dict.get
|
|
61
70
|
|
|
62
71
|
def load(self):
|
|
63
|
-
specs = {
|
|
72
|
+
specs = {"plugin": self["plugin"], "filename": self["filename"]}
|
|
64
73
|
self.clear()
|
|
65
74
|
data = get_db().migrations.find_one(specs)
|
|
66
75
|
self.update(data or specs)
|
|
@@ -77,7 +86,7 @@ class Record(dict):
|
|
|
77
86
|
|
|
78
87
|
@property
|
|
79
88
|
def status(self):
|
|
80
|
-
|
|
89
|
+
"""
|
|
81
90
|
Status is the status of the last operation.
|
|
82
91
|
|
|
83
92
|
Will be `None` if the record doesn't exists.
|
|
@@ -87,69 +96,73 @@ class Record(dict):
|
|
|
87
96
|
- rollback-error
|
|
88
97
|
- error
|
|
89
98
|
- recorded
|
|
90
|
-
|
|
99
|
+
"""
|
|
91
100
|
if not self.exists():
|
|
92
101
|
return
|
|
93
102
|
op = self.ops[-1]
|
|
94
|
-
if op[
|
|
95
|
-
if op[
|
|
96
|
-
return
|
|
97
|
-
elif op[
|
|
98
|
-
return
|
|
99
|
-
elif op[
|
|
100
|
-
return
|
|
103
|
+
if op["success"]:
|
|
104
|
+
if op["type"] == "migrate":
|
|
105
|
+
return "success"
|
|
106
|
+
elif op["type"] == "rollback":
|
|
107
|
+
return "rollback"
|
|
108
|
+
elif op["type"] == "record":
|
|
109
|
+
return "recorded"
|
|
101
110
|
else:
|
|
102
|
-
return
|
|
111
|
+
return "unknown"
|
|
103
112
|
else:
|
|
104
|
-
return
|
|
113
|
+
return "rollback-error" if op["type"] == "rollback" else "error"
|
|
105
114
|
|
|
106
115
|
@property
|
|
107
116
|
def last_date(self):
|
|
108
117
|
if not self.exists():
|
|
109
118
|
return
|
|
110
119
|
op = self.ops[-1]
|
|
111
|
-
return op[
|
|
120
|
+
return op["date"]
|
|
112
121
|
|
|
113
122
|
@property
|
|
114
123
|
def ok(self):
|
|
115
|
-
|
|
124
|
+
"""
|
|
116
125
|
Is true if the migration is considered as successfully applied
|
|
117
|
-
|
|
126
|
+
"""
|
|
118
127
|
if not self.exists():
|
|
119
128
|
return False
|
|
120
129
|
op = self.ops[-1]
|
|
121
|
-
return op[
|
|
130
|
+
return op["success"] and op["type"] in ("migrate", "record")
|
|
122
131
|
|
|
123
132
|
def add(self, _type, migration, output, state, success):
|
|
124
133
|
script = inspect.getsource(migration)
|
|
125
|
-
return Record(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
return Record(
|
|
135
|
+
self.collection.find_one_and_update(
|
|
136
|
+
{"plugin": self.plugin, "filename": self.filename},
|
|
137
|
+
{
|
|
138
|
+
"$push": {
|
|
139
|
+
"ops": {
|
|
140
|
+
"date": datetime.utcnow(),
|
|
141
|
+
"type": _type,
|
|
142
|
+
"script": script,
|
|
143
|
+
"output": output,
|
|
144
|
+
"state": state,
|
|
145
|
+
"success": success,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
upsert=True,
|
|
150
|
+
return_document=ReturnDocument.AFTER,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
140
153
|
|
|
141
154
|
def delete(self):
|
|
142
|
-
return self.collection.delete_one({
|
|
155
|
+
return self.collection.delete_one({"_id": self._id})
|
|
143
156
|
|
|
144
157
|
|
|
145
158
|
class Migration:
|
|
146
159
|
def __init__(self, plugin_or_specs, filename, module_name=None):
|
|
147
|
-
if filename is None and
|
|
148
|
-
plugin, filename = plugin_or_specs.split(
|
|
160
|
+
if filename is None and ":" in plugin_or_specs:
|
|
161
|
+
plugin, filename = plugin_or_specs.split(":")
|
|
149
162
|
else:
|
|
150
163
|
plugin = plugin_or_specs
|
|
151
|
-
if not filename.endswith(
|
|
152
|
-
filename +=
|
|
164
|
+
if not filename.endswith(".py"):
|
|
165
|
+
filename += ".py"
|
|
153
166
|
|
|
154
167
|
self.plugin = plugin
|
|
155
168
|
self.filename = filename
|
|
@@ -163,16 +176,16 @@ class Migration:
|
|
|
163
176
|
|
|
164
177
|
@property
|
|
165
178
|
def db_query(self):
|
|
166
|
-
return {
|
|
179
|
+
return {"plugin": self.plugin, "filename": self.filename}
|
|
167
180
|
|
|
168
181
|
@property
|
|
169
182
|
def label(self):
|
|
170
|
-
return
|
|
183
|
+
return ":".join((self.plugin, self.filename))
|
|
171
184
|
|
|
172
185
|
@property
|
|
173
186
|
def record(self):
|
|
174
187
|
if self._record is None:
|
|
175
|
-
specs = {
|
|
188
|
+
specs = {"plugin": self.plugin, "filename": self.filename}
|
|
176
189
|
data = get_db().migrations.find_one(specs)
|
|
177
190
|
self._record = Record(data or specs)
|
|
178
191
|
return self._record
|
|
@@ -186,31 +199,31 @@ class Migration:
|
|
|
186
199
|
def __eq__(self, value):
|
|
187
200
|
return (
|
|
188
201
|
isinstance(value, Migration)
|
|
189
|
-
and getattr(value,
|
|
190
|
-
and getattr(value,
|
|
202
|
+
and getattr(value, "plugin") == self.plugin
|
|
203
|
+
and getattr(value, "filename") == self.filename
|
|
191
204
|
)
|
|
192
205
|
|
|
193
206
|
def execute(self, recordonly=False, dryrun=False):
|
|
194
|
-
|
|
207
|
+
"""
|
|
195
208
|
Execute a migration
|
|
196
209
|
|
|
197
210
|
If recordonly is True, the migration is only recorded
|
|
198
211
|
If dryrun is True, the migration is neither executed nor recorded
|
|
199
|
-
|
|
212
|
+
"""
|
|
200
213
|
q = queue.Queue(-1) # no limit on size
|
|
201
214
|
handler = QueueHandler(q)
|
|
202
215
|
handler.setFormatter(MigrationFormatter())
|
|
203
|
-
logger = getattr(self.module,
|
|
216
|
+
logger = getattr(self.module, "log", logging.getLogger(self.module.__name__))
|
|
204
217
|
logger.propagate = False
|
|
205
218
|
for h in logger.handlers:
|
|
206
219
|
logger.removeHandler(h)
|
|
207
220
|
logger.addHandler(handler)
|
|
208
221
|
|
|
209
|
-
if not hasattr(self.module,
|
|
210
|
-
error = SyntaxError(
|
|
211
|
-
raise MigrationError(
|
|
222
|
+
if not hasattr(self.module, "migrate"):
|
|
223
|
+
error = SyntaxError("A migration should at least have a migrate(db) function")
|
|
224
|
+
raise MigrationError("Error while executing migration", exc=error)
|
|
212
225
|
|
|
213
|
-
out = [[
|
|
226
|
+
out = [["info", "Recorded only"]] if recordonly else []
|
|
214
227
|
state = {}
|
|
215
228
|
|
|
216
229
|
if not recordonly and not dryrun:
|
|
@@ -222,115 +235,120 @@ class Migration:
|
|
|
222
235
|
except Exception as e:
|
|
223
236
|
out = _extract_output(q)
|
|
224
237
|
tb = traceback.format_exc()
|
|
225
|
-
self.add_record(
|
|
226
|
-
fe = MigrationError(
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
self.add_record("migrate", out, db._state, False, traceback=tb)
|
|
239
|
+
fe = MigrationError(
|
|
240
|
+
"Error while executing migration", output=out, exc=e, traceback=tb
|
|
241
|
+
)
|
|
242
|
+
if hasattr(self.module, "rollback"):
|
|
229
243
|
try:
|
|
230
244
|
self.module.rollback(db)
|
|
231
245
|
out = _extract_output(q)
|
|
232
|
-
self.add_record(
|
|
233
|
-
msg =
|
|
246
|
+
self.add_record("rollback", out, db._state, True)
|
|
247
|
+
msg = "Error while executing migration, rollback has been applied"
|
|
234
248
|
fe = RollbackError(msg, output=out, migrate_exc=fe)
|
|
235
249
|
except Exception as re:
|
|
236
250
|
out = _extract_output(q)
|
|
237
|
-
self.add_record(
|
|
238
|
-
msg =
|
|
251
|
+
self.add_record("rollback", out, db._state, False)
|
|
252
|
+
msg = "Error while executing migration rollback"
|
|
239
253
|
fe = RollbackError(msg, output=out, exc=re, migrate_exc=fe)
|
|
240
254
|
raise fe
|
|
241
255
|
|
|
242
256
|
if not dryrun:
|
|
243
|
-
self.add_record(
|
|
257
|
+
self.add_record("migrate", out, state, True)
|
|
244
258
|
|
|
245
259
|
return out
|
|
246
260
|
|
|
247
261
|
def unrecord(self):
|
|
248
|
-
|
|
262
|
+
"""Delete a migration record"""
|
|
249
263
|
if not self.record.exists():
|
|
250
264
|
return False
|
|
251
265
|
return bool(self.collection.delete_one(self.db_query).deleted_count)
|
|
252
266
|
|
|
253
267
|
def add_record(self, type, output, state, success, traceback=None):
|
|
254
268
|
script = inspect.getsource(self.module)
|
|
255
|
-
return Record(
|
|
256
|
-
self.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
return Record(
|
|
270
|
+
self.collection.find_one_and_update(
|
|
271
|
+
self.db_query,
|
|
272
|
+
{
|
|
273
|
+
"$push": {
|
|
274
|
+
"ops": {
|
|
275
|
+
"date": datetime.utcnow(),
|
|
276
|
+
"type": type,
|
|
277
|
+
"script": script,
|
|
278
|
+
"output": output,
|
|
279
|
+
"state": state,
|
|
280
|
+
"success": success,
|
|
281
|
+
"traceback": traceback,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
upsert=True,
|
|
286
|
+
return_document=ReturnDocument.AFTER,
|
|
287
|
+
)
|
|
288
|
+
)
|
|
271
289
|
|
|
272
290
|
|
|
273
291
|
def get(plugin, filename):
|
|
274
|
-
|
|
292
|
+
"""Get a migration"""
|
|
275
293
|
return Migration(plugin, filename)
|
|
276
294
|
|
|
277
295
|
|
|
278
296
|
def list_available():
|
|
279
|
-
|
|
297
|
+
"""
|
|
280
298
|
List available migrations for udata and enabled plugins
|
|
281
299
|
|
|
282
300
|
Each row is a tuple with following signature:
|
|
283
301
|
|
|
284
302
|
(plugin, package, filename)
|
|
285
|
-
|
|
303
|
+
"""
|
|
286
304
|
migrations = []
|
|
287
305
|
|
|
288
|
-
migrations.extend(_iter(
|
|
306
|
+
migrations.extend(_iter("udata", "udata"))
|
|
289
307
|
|
|
290
|
-
plugins = entrypoints.get_enabled(
|
|
308
|
+
plugins = entrypoints.get_enabled("udata.models", current_app)
|
|
291
309
|
for plugin, module in plugins.items():
|
|
292
310
|
migrations.extend(_iter(plugin, module))
|
|
293
311
|
return sorted(migrations, key=lambda m: m.filename)
|
|
294
312
|
|
|
295
313
|
|
|
296
314
|
def _iter(plugin, module):
|
|
297
|
-
|
|
315
|
+
"""
|
|
298
316
|
Iterate over migrations for a given plugin module
|
|
299
317
|
|
|
300
318
|
Yield tuples in the form (plugin_name, module_name, filename)
|
|
301
|
-
|
|
319
|
+
"""
|
|
302
320
|
module_name = module if isinstance(module, str) else module.__name__
|
|
303
|
-
if not resource_isdir(module_name,
|
|
321
|
+
if not resource_isdir(module_name, "migrations"):
|
|
304
322
|
return
|
|
305
|
-
for filename in resource_listdir(module_name,
|
|
306
|
-
if filename.endswith(
|
|
323
|
+
for filename in resource_listdir(module_name, "migrations"):
|
|
324
|
+
if filename.endswith(".py") and not filename.startswith("__"):
|
|
307
325
|
yield Migration(plugin, filename, module_name)
|
|
308
326
|
|
|
309
327
|
|
|
310
328
|
def _module_name(plugin):
|
|
311
|
-
|
|
312
|
-
if plugin ==
|
|
313
|
-
return
|
|
314
|
-
module = entrypoints.get_plugin_module(
|
|
329
|
+
"""Get the module name for a given plugin"""
|
|
330
|
+
if plugin == "udata":
|
|
331
|
+
return "udata"
|
|
332
|
+
module = entrypoints.get_plugin_module("udata.models", current_app, plugin)
|
|
315
333
|
if module is None:
|
|
316
|
-
raise MigrationError(
|
|
334
|
+
raise MigrationError("Plugin {} not found".format(plugin))
|
|
317
335
|
return module.__name__
|
|
318
336
|
|
|
319
337
|
|
|
320
338
|
def load_migration(plugin, filename, module_name=None):
|
|
321
|
-
|
|
339
|
+
"""
|
|
322
340
|
Load a migration from its python file
|
|
323
341
|
|
|
324
342
|
:returns: the loaded module
|
|
325
|
-
|
|
343
|
+
"""
|
|
326
344
|
module_name = module_name or _module_name(plugin)
|
|
327
345
|
basename = os.path.splitext(os.path.basename(filename))[0]
|
|
328
|
-
name =
|
|
329
|
-
filename = os.path.join(
|
|
346
|
+
name = ".".join((module_name, "migrations", basename))
|
|
347
|
+
filename = os.path.join("migrations", filename)
|
|
330
348
|
try:
|
|
331
349
|
script = resource_string(module_name, filename)
|
|
332
350
|
except Exception:
|
|
333
|
-
msg =
|
|
351
|
+
msg = "Unable to load file {} from module {}".format(filename, module_name)
|
|
334
352
|
raise MigrationError(msg)
|
|
335
353
|
spec = importlib.util.spec_from_loader(name, loader=None)
|
|
336
354
|
module = importlib.util.module_from_spec(spec)
|
|
@@ -340,7 +358,7 @@ def load_migration(plugin, filename, module_name=None):
|
|
|
340
358
|
|
|
341
359
|
|
|
342
360
|
def _extract_output(q):
|
|
343
|
-
|
|
361
|
+
"""Extract log output from a QueueHandler queue"""
|
|
344
362
|
out = []
|
|
345
363
|
while not q.empty():
|
|
346
364
|
record = q.get()
|
udata/models/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from mongoengine.errors import ValidationError
|
|
1
|
+
from mongoengine.errors import ValidationError # noqa
|
|
2
2
|
|
|
3
|
-
from udata import entrypoints
|
|
4
|
-
from udata.mongo import *
|
|
3
|
+
from udata import entrypoints # noqa
|
|
4
|
+
from udata.mongo import * # noqa
|
|
5
5
|
|
|
6
6
|
# Load all core models and mixins
|
|
7
7
|
from udata.core.spatial.models import * # noqa
|
|
@@ -33,4 +33,4 @@ import udata.linkchecker.models # noqa
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def init_app(app):
|
|
36
|
-
entrypoints.get_enabled(
|
|
36
|
+
entrypoints.get_enabled("udata.models", app)
|
udata/mongo/__init__.py
CHANGED
|
@@ -8,8 +8,9 @@ from .engine import db
|
|
|
8
8
|
|
|
9
9
|
log = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
|
-
MONGODB_DEPRECATED_SETTINGS =
|
|
12
|
-
MONGODB_DEPRECATED_MSG =
|
|
11
|
+
MONGODB_DEPRECATED_SETTINGS = "MONGODB_PORT", "MONGODB_DB"
|
|
12
|
+
MONGODB_DEPRECATED_MSG = "{0} is deprecated, use the MONGODB_HOST url syntax"
|
|
13
|
+
|
|
13
14
|
|
|
14
15
|
def validate_config(config):
|
|
15
16
|
for setting in MONGODB_DEPRECATED_SETTINGS:
|
|
@@ -17,26 +18,27 @@ def validate_config(config):
|
|
|
17
18
|
msg = MONGODB_DEPRECATED_MSG.format(setting)
|
|
18
19
|
log.warning(msg)
|
|
19
20
|
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
|
20
|
-
url = config[
|
|
21
|
+
url = config["MONGODB_HOST"]
|
|
21
22
|
parsed_url = urlparse(url)
|
|
22
23
|
if not all((parsed_url.scheme, parsed_url.netloc)):
|
|
23
|
-
raise ConfigError(
|
|
24
|
+
raise ConfigError("{0} is not a valid MongoDB URL".format(url))
|
|
24
25
|
if len(parsed_url.path) <= 1:
|
|
25
|
-
raise ConfigError(
|
|
26
|
+
raise ConfigError("{0} is missing the database path".format(url))
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def build_test_config(config):
|
|
29
|
-
if
|
|
30
|
-
config[
|
|
30
|
+
if "MONGODB_HOST_TEST" in config:
|
|
31
|
+
config["MONGODB_HOST"] = config["MONGODB_HOST_TEST"]
|
|
31
32
|
else:
|
|
32
33
|
# use `{database_name}-test` database for testing
|
|
33
|
-
parsed_url = urlparse(config[
|
|
34
|
-
parsed_url = parsed_url._replace(path=
|
|
35
|
-
config[
|
|
34
|
+
parsed_url = urlparse(config["MONGODB_HOST"])
|
|
35
|
+
parsed_url = parsed_url._replace(path="%s-test" % parsed_url.path)
|
|
36
|
+
config["MONGODB_HOST"] = parsed_url.geturl()
|
|
36
37
|
validate_config(config)
|
|
37
38
|
|
|
39
|
+
|
|
38
40
|
def init_app(app):
|
|
39
41
|
validate_config(app.config)
|
|
40
|
-
if app.config[
|
|
42
|
+
if app.config["TESTING"]:
|
|
41
43
|
build_test_config(app.config)
|
|
42
44
|
db.init_app(app)
|
udata/mongo/badges_field.py
CHANGED
|
@@ -20,15 +20,16 @@ class BadgesField(ListField):
|
|
|
20
20
|
|
|
21
21
|
for key in value:
|
|
22
22
|
if key not in self.registered:
|
|
23
|
-
errors[key] =
|
|
23
|
+
errors[key] = "Badge {0} is not registered".format(key)
|
|
24
24
|
|
|
25
25
|
if errors:
|
|
26
|
-
self.error(
|
|
26
|
+
self.error("Unknown badges types", errors=errors)
|
|
27
27
|
|
|
28
28
|
def __call__(self, key):
|
|
29
29
|
def inner(cls):
|
|
30
30
|
self.register(key, cls)
|
|
31
31
|
return cls
|
|
32
|
+
|
|
32
33
|
return inner
|
|
33
34
|
|
|
34
35
|
|
udata/mongo/datetime_fields.py
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
2
|
from datetime import date, datetime
|
|
4
|
-
from dateutil.parser import parse
|
|
5
3
|
|
|
4
|
+
from dateutil.parser import parse
|
|
6
5
|
from mongoengine import EmbeddedDocument
|
|
7
6
|
from mongoengine.fields import BaseField, DateTimeField
|
|
8
7
|
from mongoengine.signals import pre_save
|
|
9
8
|
|
|
10
9
|
from udata.i18n import lazy_gettext as _
|
|
11
10
|
|
|
12
|
-
|
|
13
11
|
log = logging.getLogger(__name__)
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
class DateField(BaseField):
|
|
17
|
-
|
|
15
|
+
"""
|
|
18
16
|
Store date in iso format
|
|
19
|
-
|
|
17
|
+
"""
|
|
18
|
+
|
|
20
19
|
def to_python(self, value):
|
|
21
20
|
if isinstance(value, date):
|
|
22
21
|
return value
|
|
@@ -40,7 +39,7 @@ class DateField(BaseField):
|
|
|
40
39
|
|
|
41
40
|
def validate(self, value):
|
|
42
41
|
if not isinstance(value, date):
|
|
43
|
-
self.error(
|
|
42
|
+
self.error("DateField only accepts date values")
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
class DateRange(EmbeddedDocument):
|
|
@@ -48,7 +47,7 @@ class DateRange(EmbeddedDocument):
|
|
|
48
47
|
end = DateField()
|
|
49
48
|
|
|
50
49
|
def to_dict(self):
|
|
51
|
-
return {
|
|
50
|
+
return {"start": self.start, "end": self.end}
|
|
52
51
|
|
|
53
52
|
def clean(self):
|
|
54
53
|
if self.start and self.end and self.start > self.end:
|
|
@@ -56,14 +55,16 @@ class DateRange(EmbeddedDocument):
|
|
|
56
55
|
|
|
57
56
|
|
|
58
57
|
class Datetimed(object):
|
|
59
|
-
created_at = DateTimeField(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
created_at = DateTimeField(
|
|
59
|
+
verbose_name=_("Creation date"), default=datetime.utcnow, required=True
|
|
60
|
+
)
|
|
61
|
+
last_modified = DateTimeField(
|
|
62
|
+
verbose_name=_("Last modification date"), default=datetime.utcnow, required=True
|
|
63
|
+
)
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
@pre_save.connect
|
|
66
67
|
def set_modified_datetime(sender, document, **kwargs):
|
|
67
68
|
changed = document._get_changed_fields()
|
|
68
|
-
if isinstance(document, Datetimed) and
|
|
69
|
+
if isinstance(document, Datetimed) and "last_modified" not in changed:
|
|
69
70
|
document.last_modified = datetime.utcnow()
|
udata/mongo/document.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
2
|
from collections.abc import Iterable
|
|
4
3
|
|
|
5
4
|
from flask_mongoengine import Document
|
|
@@ -10,7 +9,7 @@ log = logging.getLogger(__name__)
|
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def serialize(value):
|
|
13
|
-
if hasattr(value,
|
|
12
|
+
if hasattr(value, "to_dict"):
|
|
14
13
|
return value.to_dict()
|
|
15
14
|
elif isinstance(value, Iterable) and not isinstance(value, str):
|
|
16
15
|
return [serialize(val) for val in value]
|
|
@@ -20,30 +19,32 @@ def serialize(value):
|
|
|
20
19
|
|
|
21
20
|
class UDataDocument(Document):
|
|
22
21
|
meta = {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
"abstract": True,
|
|
23
|
+
"queryset_class": UDataQuerySet,
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
def to_dict(self, exclude=None):
|
|
28
|
-
id_field = self._meta[
|
|
27
|
+
id_field = self._meta["id_field"]
|
|
29
28
|
excluded_keys = set(exclude or [])
|
|
30
|
-
excluded_keys.add(
|
|
31
|
-
excluded_keys.add(
|
|
32
|
-
data = dict(
|
|
33
|
-
(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
excluded_keys.add("_id")
|
|
30
|
+
excluded_keys.add("_cls")
|
|
31
|
+
data = dict(
|
|
32
|
+
(
|
|
33
|
+
(key, serialize(value))
|
|
34
|
+
for key, value in self.to_mongo().items()
|
|
35
|
+
if key not in excluded_keys
|
|
36
|
+
)
|
|
37
|
+
)
|
|
37
38
|
data[id_field] = getattr(self, id_field)
|
|
38
39
|
return data
|
|
39
40
|
|
|
40
41
|
def __str__(self):
|
|
41
|
-
return
|
|
42
|
-
classname=self.__class__.__name__,
|
|
43
|
-
id=getattr(self, self._meta['id_field'])
|
|
42
|
+
return "{classname}({id})".format(
|
|
43
|
+
classname=self.__class__.__name__, id=getattr(self, self._meta["id_field"])
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class DomainModel(UDataDocument):
|
|
48
|
-
|
|
48
|
+
"""Placeholder for inheritance"""
|
|
49
|
+
|
|
49
50
|
pass
|