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
|
@@ -2,137 +2,130 @@ import hashlib
|
|
|
2
2
|
import io
|
|
3
3
|
|
|
4
4
|
import pydenticon
|
|
5
|
-
|
|
6
|
-
from flask import redirect, send_file, current_app
|
|
5
|
+
from flask import current_app, redirect, send_file
|
|
7
6
|
|
|
8
7
|
from udata import entrypoints
|
|
9
8
|
from udata.app import cache
|
|
10
9
|
|
|
11
|
-
ADORABLE_AVATARS_URL =
|
|
12
|
-
ROBOHASH_URL =
|
|
10
|
+
ADORABLE_AVATARS_URL = "https://api.adorable.io/avatars/{size}/{identifier}.png" # noqa
|
|
11
|
+
ROBOHASH_URL = "https://robohash.org/{identifier}.png?size={size}x{size}&set={skin}&bgset={bg}" # noqa
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
# Default values overriden by theme default and local config
|
|
16
15
|
DEFAULTS = {
|
|
17
|
-
|
|
16
|
+
"AVATAR_PROVIDER": "internal",
|
|
18
17
|
# Internal provider
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
"AVATAR_INTERNAL_SIZE": 7,
|
|
19
|
+
"AVATAR_INTERNAL_FOREGROUND": [
|
|
20
|
+
"rgb(45,79,255)",
|
|
21
|
+
"rgb(254,180,44)",
|
|
22
|
+
"rgb(226,121,234)",
|
|
23
|
+
"rgb(30,179,253)",
|
|
24
|
+
"rgb(232,77,65)",
|
|
25
|
+
"rgb(49,203,115)",
|
|
26
|
+
"rgb(141,69,170)",
|
|
28
27
|
],
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
"AVATAR_INTERNAL_BACKGROUND": "rgb(224,224,224)",
|
|
29
|
+
"AVATAR_INTERNAL_PADDING": 10,
|
|
32
30
|
# robohash prodiver
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
"AVATAR_ROBOHASH_SKIN": "set1",
|
|
32
|
+
"AVATAR_ROBOHASH_BACKGROUND": "bg1",
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
|
|
38
36
|
def get_config(key):
|
|
39
|
-
|
|
37
|
+
"""
|
|
40
38
|
Get an identicon configuration parameter.
|
|
41
39
|
|
|
42
40
|
Precedance order is:
|
|
43
41
|
- application config (`udata.cfg`)
|
|
44
42
|
- default
|
|
45
|
-
|
|
46
|
-
key =
|
|
43
|
+
"""
|
|
44
|
+
key = "AVATAR_{0}".format(key.upper())
|
|
47
45
|
local_config = current_app.config.get(key)
|
|
48
46
|
return local_config or DEFAULTS[key]
|
|
49
47
|
|
|
50
48
|
|
|
51
49
|
def get_internal_config(key):
|
|
52
|
-
return get_config(
|
|
50
|
+
return get_config("internal_{0}".format(key))
|
|
53
51
|
|
|
54
52
|
|
|
55
53
|
def get_provider():
|
|
56
|
-
|
|
57
|
-
name = get_config(
|
|
58
|
-
available = entrypoints.get_all(
|
|
54
|
+
"""Get the current provider from config"""
|
|
55
|
+
name = get_config("provider")
|
|
56
|
+
available = entrypoints.get_all("udata.avatars")
|
|
59
57
|
if name not in available:
|
|
60
|
-
raise ValueError(
|
|
58
|
+
raise ValueError("Unknown avatar provider: {0}".format(name))
|
|
61
59
|
return available[name]
|
|
62
60
|
|
|
63
61
|
|
|
64
62
|
def get_identicon(identifier, size):
|
|
65
|
-
|
|
63
|
+
"""
|
|
66
64
|
Get an identicon for a given identifier at a given size.
|
|
67
65
|
|
|
68
66
|
Automatically select the provider from `AVATAR_PROVIDER`
|
|
69
67
|
|
|
70
68
|
:returns: a HTTP response, either an image or a redirect
|
|
71
|
-
|
|
69
|
+
"""
|
|
72
70
|
return get_provider()(identifier, size)
|
|
73
71
|
|
|
74
72
|
|
|
75
73
|
@cache.memoize()
|
|
76
74
|
def generate_pydenticon(identifier, size):
|
|
77
|
-
|
|
75
|
+
"""
|
|
78
76
|
Use pydenticon to generate an identicon image.
|
|
79
77
|
All parameters are extracted from configuration.
|
|
80
|
-
|
|
81
|
-
blocks_size = get_internal_config(
|
|
82
|
-
foreground = get_internal_config(
|
|
83
|
-
background = get_internal_config(
|
|
84
|
-
generator = pydenticon.Generator(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
background=background)
|
|
78
|
+
"""
|
|
79
|
+
blocks_size = get_internal_config("size")
|
|
80
|
+
foreground = get_internal_config("foreground")
|
|
81
|
+
background = get_internal_config("background")
|
|
82
|
+
generator = pydenticon.Generator(
|
|
83
|
+
blocks_size, blocks_size, digest=hashlib.sha1, foreground=foreground, background=background
|
|
84
|
+
)
|
|
88
85
|
|
|
89
86
|
# Pydenticon adds padding to the size and as a consequence
|
|
90
87
|
# we need to compute the size without the padding
|
|
91
|
-
padding = int(round(get_internal_config(
|
|
88
|
+
padding = int(round(get_internal_config("padding") * size / 100.0))
|
|
92
89
|
size = size - 2 * padding
|
|
93
|
-
padding = (padding,
|
|
94
|
-
return generator.generate(identifier, size, size,
|
|
95
|
-
padding=padding,
|
|
96
|
-
output_format='png')
|
|
90
|
+
padding = (padding,) * 4
|
|
91
|
+
return generator.generate(identifier, size, size, padding=padding, output_format="png")
|
|
97
92
|
|
|
98
93
|
|
|
99
94
|
def internal(identifier, size):
|
|
100
|
-
|
|
95
|
+
"""
|
|
101
96
|
Internal provider
|
|
102
97
|
|
|
103
98
|
Use pydenticon to generate an identicon.
|
|
104
|
-
|
|
99
|
+
"""
|
|
105
100
|
identicon = generate_pydenticon(identifier, size)
|
|
106
|
-
response = send_file(io.BytesIO(identicon), mimetype=
|
|
101
|
+
response = send_file(io.BytesIO(identicon), mimetype="image/png")
|
|
107
102
|
etag = hashlib.sha1(identicon).hexdigest()
|
|
108
103
|
response.set_etag(etag)
|
|
109
104
|
return response
|
|
110
105
|
|
|
111
106
|
|
|
112
107
|
def adorable(identifier, size):
|
|
113
|
-
|
|
108
|
+
"""
|
|
114
109
|
Adorable Avatars provider
|
|
115
110
|
|
|
116
111
|
Simply redirect to the external API.
|
|
117
112
|
|
|
118
113
|
See: http://avatars.adorable.io/
|
|
119
|
-
|
|
114
|
+
"""
|
|
120
115
|
url = ADORABLE_AVATARS_URL.format(identifier=identifier, size=size)
|
|
121
116
|
return redirect(url)
|
|
122
117
|
|
|
123
118
|
|
|
124
119
|
def robohash(identifier, size):
|
|
125
|
-
|
|
120
|
+
"""
|
|
126
121
|
Robohash provider
|
|
127
122
|
|
|
128
123
|
Redirect to the Robohash API
|
|
129
124
|
with parameters extracted from configuration.
|
|
130
125
|
|
|
131
126
|
See: https://robohash.org/
|
|
132
|
-
|
|
133
|
-
skin = get_config(
|
|
134
|
-
background = get_config(
|
|
135
|
-
url = ROBOHASH_URL.format(
|
|
136
|
-
identifier=identifier, size=size, skin=skin, bg=background
|
|
137
|
-
)
|
|
127
|
+
"""
|
|
128
|
+
skin = get_config("robohash_skin")
|
|
129
|
+
background = get_config("robohash_background")
|
|
130
|
+
url = ROBOHASH_URL.format(identifier=identifier, size=size, skin=skin, bg=background)
|
|
138
131
|
return redirect(url)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from udata.features.identicon.backends import internal
|
|
4
|
-
from udata.utils import faker
|
|
5
4
|
from udata.tests.helpers import assert200
|
|
5
|
+
from udata.utils import faker
|
|
6
6
|
|
|
7
|
-
pytestmark = pytest.mark.usefixtures(
|
|
7
|
+
pytestmark = pytest.mark.usefixtures("app")
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def assert_stream_equal(response1, response2):
|
|
@@ -18,12 +18,11 @@ class InternalBackendTest:
|
|
|
18
18
|
def test_base_rendering(self):
|
|
19
19
|
response = internal(faker.word(), 32)
|
|
20
20
|
assert200(response)
|
|
21
|
-
assert response.mimetype ==
|
|
21
|
+
assert response.mimetype == "image/png"
|
|
22
22
|
assert response.is_streamed
|
|
23
23
|
etag, weak = response.get_etag()
|
|
24
24
|
assert etag is not None
|
|
25
25
|
|
|
26
26
|
def test_render_twice_the_same(self):
|
|
27
27
|
identifier = faker.word()
|
|
28
|
-
assert_stream_equal(internal(identifier, 32),
|
|
29
|
-
internal(identifier, 32))
|
|
28
|
+
assert_stream_equal(internal(identifier, 32), internal(identifier, 32))
|
|
@@ -6,32 +6,32 @@ _providers = {}
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def register_provider(name, func):
|
|
9
|
-
|
|
9
|
+
"""Register a notification provder"""
|
|
10
10
|
_providers[name] = func
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def list_providers():
|
|
14
|
-
|
|
14
|
+
"""List registere notifcation provider"""
|
|
15
15
|
return _providers.keys()
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def notifier(name):
|
|
19
|
-
|
|
19
|
+
"""A decorator registering a function as provider"""
|
|
20
|
+
|
|
20
21
|
def wrapper(func):
|
|
21
22
|
register_provider(name, func)
|
|
22
23
|
return func
|
|
24
|
+
|
|
23
25
|
return wrapper
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
def get_notifications(user):
|
|
27
|
-
|
|
29
|
+
"""List notification for a given user"""
|
|
28
30
|
notifications = []
|
|
29
31
|
|
|
30
32
|
for name, func in _providers.items():
|
|
31
|
-
notifications.extend(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
'details': details
|
|
35
|
-
} for dt, details in func(user)])
|
|
33
|
+
notifications.extend(
|
|
34
|
+
[{"type": name, "created_on": dt, "details": details} for dt, details in func(user)]
|
|
35
|
+
)
|
|
36
36
|
|
|
37
37
|
return notifications
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
from udata.api import api, fields
|
|
1
|
+
from udata.api import API, api, fields
|
|
2
2
|
from udata.auth import current_user
|
|
3
3
|
|
|
4
4
|
from .actions import get_notifications
|
|
5
5
|
|
|
6
|
-
notifs = api.namespace(
|
|
6
|
+
notifs = api.namespace("notifications", "Notifications API")
|
|
7
7
|
|
|
8
|
-
notifications_fields = api.model(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
description=
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
notifications_fields = api.model(
|
|
9
|
+
"Notification",
|
|
10
|
+
{
|
|
11
|
+
"type": fields.String(description="The notification type", readonly=True),
|
|
12
|
+
"created_on": fields.ISODateTime(
|
|
13
|
+
description="The notification creation datetime", readonly=True
|
|
14
|
+
),
|
|
15
|
+
"details": fields.Raw(
|
|
16
|
+
description="Key-Value details depending on notification type", readonly=True
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
)
|
|
16
20
|
|
|
17
21
|
|
|
18
|
-
@notifs.route(
|
|
22
|
+
@notifs.route("/", endpoint="notifications")
|
|
19
23
|
class NotificationsAPI(API):
|
|
20
24
|
@api.secure
|
|
21
|
-
@api.doc(
|
|
25
|
+
@api.doc("get_notifications")
|
|
22
26
|
@api.marshal_list_with(notifications_fields)
|
|
23
27
|
def get(self):
|
|
24
|
-
|
|
28
|
+
"""List all current user pending notifications"""
|
|
25
29
|
user = current_user._get_current_object()
|
|
26
30
|
return get_notifications(user)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from flask import current_app
|
|
2
2
|
|
|
3
|
-
from udata.models import
|
|
3
|
+
from udata.models import GeoZone, db
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def check_for_territories(query):
|
|
@@ -9,26 +9,28 @@ def check_for_territories(query):
|
|
|
9
9
|
|
|
10
10
|
Results are sorted by population and area (biggest first).
|
|
11
11
|
"""
|
|
12
|
-
if not query or not current_app.config.get(
|
|
12
|
+
if not query or not current_app.config.get("ACTIVATE_TERRITORIES"):
|
|
13
13
|
return []
|
|
14
14
|
|
|
15
15
|
dbqs = db.Q()
|
|
16
16
|
query = query.lower()
|
|
17
17
|
is_digit = query.isdigit()
|
|
18
18
|
query_length = len(query)
|
|
19
|
-
for level in current_app.config.get(
|
|
20
|
-
if level ==
|
|
19
|
+
for level in current_app.config.get("HANDLED_LEVELS"):
|
|
20
|
+
if level == "country":
|
|
21
21
|
continue # Level not fully handled yet.
|
|
22
22
|
q = db.Q(level=level)
|
|
23
|
-
if
|
|
24
|
-
(is_digit or query in ('2a', '2b'))):
|
|
23
|
+
if query_length == 2 and level == "fr:departement" and (is_digit or query in ("2a", "2b")):
|
|
25
24
|
# Counties + Corsica.
|
|
26
25
|
q &= db.Q(code=query)
|
|
27
|
-
elif query_length == 3 and level ==
|
|
26
|
+
elif query_length == 3 and level == "fr:departement" and is_digit:
|
|
28
27
|
# French DROM-COM.
|
|
29
28
|
q &= db.Q(code=query)
|
|
30
|
-
elif
|
|
31
|
-
|
|
29
|
+
elif (
|
|
30
|
+
query_length == 5
|
|
31
|
+
and level == "fr:commune"
|
|
32
|
+
and (is_digit or query.startswith("2a") or query.startswith("2b"))
|
|
33
|
+
):
|
|
32
34
|
# INSEE code then postal codes with Corsica exceptions.
|
|
33
35
|
q &= db.Q(code=query)
|
|
34
36
|
elif query_length >= 4:
|
|
@@ -44,4 +46,4 @@ def check_for_territories(query):
|
|
|
44
46
|
return []
|
|
45
47
|
|
|
46
48
|
# Sort matching results by population and area.
|
|
47
|
-
return GeoZone.objects(dbqs).order_by(
|
|
49
|
+
return GeoZone.objects(dbqs).order_by("-population", "-area")
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
from udata.api import
|
|
1
|
+
from udata.api import API, api
|
|
2
2
|
from udata.features.territories import check_for_territories
|
|
3
3
|
|
|
4
4
|
suggest_parser = api.parser()
|
|
5
5
|
suggest_parser.add_argument(
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
"q", type=str, help="The string to autocomplete/suggest", location="args", required=True
|
|
7
|
+
)
|
|
8
8
|
suggest_parser.add_argument(
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
"size", type=int, help="The maximum result size", location="args", required=False
|
|
10
|
+
)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@api.route(
|
|
13
|
+
@api.route("/territory/suggest/", endpoint="suggest_territory")
|
|
14
14
|
class SuggestTerritoriesAPI(API):
|
|
15
|
-
@api.doc(
|
|
15
|
+
@api.doc("suggest_territory")
|
|
16
16
|
@api.expect(suggest_parser)
|
|
17
17
|
def get(self):
|
|
18
18
|
args = suggest_parser.parse_args()
|
|
19
|
-
territories = check_for_territories(args[
|
|
20
|
-
if args[
|
|
21
|
-
territories = territories[:args[
|
|
22
|
-
return [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} for territory in territories]
|
|
19
|
+
territories = check_for_territories(args["q"])
|
|
20
|
+
if args["size"]:
|
|
21
|
+
territories = territories[: args["size"]]
|
|
22
|
+
return [
|
|
23
|
+
{"id": territory.id, "title": territory.name, "page": territory.external_url}
|
|
24
|
+
for territory in territories
|
|
25
|
+
]
|
|
@@ -2,27 +2,20 @@ from flask import url_for
|
|
|
2
2
|
|
|
3
3
|
from udata.models import License, Organization
|
|
4
4
|
|
|
5
|
-
__all__ = (
|
|
6
|
-
'TerritoryDataset', 'ResourceBasedTerritoryDataset', 'TERRITORY_DATASETS'
|
|
7
|
-
)
|
|
5
|
+
__all__ = ("TerritoryDataset", "ResourceBasedTerritoryDataset", "TERRITORY_DATASETS")
|
|
8
6
|
|
|
9
7
|
|
|
10
|
-
TERRITORY_DATASETS = {
|
|
11
|
-
'commune': {},
|
|
12
|
-
'departement': {},
|
|
13
|
-
'region': {},
|
|
14
|
-
'country': {}
|
|
15
|
-
}
|
|
8
|
+
TERRITORY_DATASETS = {"commune": {}, "departement": {}, "region": {}, "country": {}}
|
|
16
9
|
|
|
17
10
|
|
|
18
11
|
class TerritoryDataset(object):
|
|
19
12
|
order = 0
|
|
20
|
-
id =
|
|
21
|
-
title =
|
|
22
|
-
organization_id =
|
|
23
|
-
url_template =
|
|
24
|
-
description =
|
|
25
|
-
license_id =
|
|
13
|
+
id = ""
|
|
14
|
+
title = ""
|
|
15
|
+
organization_id = ""
|
|
16
|
+
url_template = ""
|
|
17
|
+
description = ""
|
|
18
|
+
license_id = "fr-lo"
|
|
26
19
|
|
|
27
20
|
def __init__(self, territory):
|
|
28
21
|
self.territory = territory
|
|
@@ -33,8 +26,7 @@ class TerritoryDataset(object):
|
|
|
33
26
|
|
|
34
27
|
@property
|
|
35
28
|
def slug(self):
|
|
36
|
-
return
|
|
37
|
-
territory_id=self.territory.id, id=self.id)
|
|
29
|
+
return "{territory_id}:{id}".format(territory_id=self.territory.id, id=self.id)
|
|
38
30
|
|
|
39
31
|
@property
|
|
40
32
|
def organization(self):
|
|
@@ -46,19 +38,22 @@ class TerritoryDataset(object):
|
|
|
46
38
|
|
|
47
39
|
|
|
48
40
|
class ResourceBasedTerritoryDataset(TerritoryDataset):
|
|
49
|
-
dataset_id =
|
|
50
|
-
resource_id =
|
|
51
|
-
territory_attr =
|
|
52
|
-
csv_column =
|
|
41
|
+
dataset_id = ""
|
|
42
|
+
resource_id = ""
|
|
43
|
+
territory_attr = ""
|
|
44
|
+
csv_column = ""
|
|
53
45
|
|
|
54
46
|
def url_for(self, external=False):
|
|
55
|
-
return url_for(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
return url_for(
|
|
48
|
+
"territories.territory_dataset_resource",
|
|
49
|
+
territory=self.territory,
|
|
50
|
+
dataset=self.dataset_id,
|
|
51
|
+
resource_id=self.resource_id,
|
|
52
|
+
territory_attr=self.territory_attr,
|
|
53
|
+
csv_column=self.csv_column,
|
|
54
|
+
_external=external,
|
|
55
|
+
)
|
|
56
|
+
|
|
62
57
|
url = property(url_for)
|
|
63
58
|
|
|
64
59
|
@property
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
from udata.auth import login_required, current_user
|
|
4
|
+
from udata.auth import current_user, login_required
|
|
7
5
|
from udata.models import Organization, User
|
|
8
6
|
|
|
9
7
|
from .models import Transfer
|
|
@@ -14,28 +12,27 @@ log = logging.getLogger(__name__)
|
|
|
14
12
|
|
|
15
13
|
@login_required
|
|
16
14
|
def request_transfer(subject, recipient, comment):
|
|
17
|
-
|
|
15
|
+
"""Initiate a transfer request"""
|
|
18
16
|
TransferPermission(subject).test()
|
|
19
17
|
if recipient == (subject.organization or subject.owner):
|
|
20
|
-
raise ValueError(
|
|
21
|
-
'Recipient should be different than the current owner')
|
|
18
|
+
raise ValueError("Recipient should be different than the current owner")
|
|
22
19
|
transfer = Transfer.objects.create(
|
|
23
20
|
owner=subject.organization or subject.owner,
|
|
24
21
|
recipient=recipient,
|
|
25
22
|
subject=subject,
|
|
26
|
-
comment=comment
|
|
23
|
+
comment=comment,
|
|
27
24
|
)
|
|
28
25
|
return transfer
|
|
29
26
|
|
|
30
27
|
|
|
31
28
|
@login_required
|
|
32
29
|
def accept_transfer(transfer, comment=None):
|
|
33
|
-
|
|
30
|
+
"""Accept an incoming a transfer request"""
|
|
34
31
|
TransferResponsePermission(transfer).test()
|
|
35
32
|
|
|
36
33
|
transfer.responded = datetime.utcnow()
|
|
37
34
|
transfer.responder = current_user._get_current_object()
|
|
38
|
-
transfer.status =
|
|
35
|
+
transfer.status = "accepted"
|
|
39
36
|
transfer.response_comment = comment
|
|
40
37
|
transfer.save()
|
|
41
38
|
|
|
@@ -53,12 +50,12 @@ def accept_transfer(transfer, comment=None):
|
|
|
53
50
|
|
|
54
51
|
@login_required
|
|
55
52
|
def refuse_transfer(transfer, comment=None):
|
|
56
|
-
|
|
53
|
+
"""Refuse an incoming a transfer request"""
|
|
57
54
|
TransferResponsePermission(transfer).test()
|
|
58
55
|
|
|
59
56
|
transfer.responded = datetime.utcnow()
|
|
60
57
|
transfer.responder = current_user._get_current_object()
|
|
61
|
-
transfer.status =
|
|
58
|
+
transfer.status = "refused"
|
|
62
59
|
transfer.response_comment = comment
|
|
63
60
|
transfer.save()
|
|
64
61
|
|