udata 14.0.0__py3-none-any.whl → 14.5.1.dev6__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.
- udata/api/__init__.py +2 -0
- udata/api_fields.py +35 -4
- udata/app.py +18 -20
- udata/auth/__init__.py +29 -6
- udata/auth/forms.py +2 -2
- udata/auth/views.py +13 -6
- udata/commands/dcat.py +1 -1
- udata/commands/serve.py +3 -11
- udata/commands/tests/test_fixtures.py +9 -9
- udata/core/access_type/api.py +1 -1
- udata/core/access_type/constants.py +12 -8
- udata/core/activity/api.py +5 -6
- udata/core/badges/tests/test_commands.py +6 -6
- udata/core/csv.py +5 -0
- udata/core/dataservices/api.py +8 -1
- udata/core/dataservices/apiv2.py +2 -5
- udata/core/dataservices/models.py +5 -2
- udata/core/dataservices/rdf.py +2 -1
- udata/core/dataservices/tasks.py +13 -2
- udata/core/dataset/api.py +10 -0
- udata/core/dataset/models.py +6 -6
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/rdf.py +8 -2
- udata/core/dataset/tasks.py +23 -7
- udata/core/discussions/api.py +15 -1
- udata/core/discussions/models.py +6 -0
- udata/core/legal/__init__.py +0 -0
- udata/core/legal/mails.py +128 -0
- udata/core/organization/api.py +16 -5
- udata/core/organization/apiv2.py +2 -3
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +15 -2
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/tests/test_api.py +32 -0
- udata/core/post/api.py +24 -69
- udata/core/post/models.py +84 -16
- udata/core/post/tests/test_api.py +24 -1
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/api.py +8 -0
- udata/core/reuse/apiv2.py +2 -5
- udata/core/reuse/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/spatial/forms.py +2 -2
- udata/core/topic/models.py +8 -2
- udata/core/user/api.py +10 -3
- udata/core/user/models.py +12 -2
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +56 -0
- udata/features/notifications/tasks.py +25 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/flask_mongoengine/pagination.py +1 -1
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +21 -1
- udata/harvest/api.py +25 -8
- udata/harvest/backends/base.py +27 -1
- udata/harvest/backends/ckan/harvesters.py +11 -2
- udata/harvest/backends/dcat.py +4 -1
- udata/harvest/commands.py +33 -0
- udata/harvest/filters.py +17 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tests/ckan/test_ckan_backend.py +33 -0
- udata/harvest/tests/test_actions.py +58 -5
- udata/harvest/tests/test_api.py +276 -122
- udata/harvest/tests/test_base_backend.py +86 -1
- udata/harvest/tests/test_dcat_backend.py +81 -10
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +1 -4
- udata/mail.py +19 -1
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +58 -10
- udata/routing.py +2 -2
- udata/settings.py +11 -0
- udata/tasks.py +1 -0
- udata/templates/mail/message.html +5 -31
- udata/tests/__init__.py +27 -2
- udata/tests/api/__init__.py +108 -21
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_auth_api.py +121 -95
- udata/tests/api/test_base_api.py +7 -4
- udata/tests/api/test_datasets_api.py +50 -19
- udata/tests/api/test_organizations_api.py +192 -197
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_reuses_api.py +147 -147
- udata/tests/api/test_security_api.py +12 -12
- udata/tests/api/test_swagger.py +4 -4
- udata/tests/api/test_tags_api.py +8 -8
- udata/tests/api/test_user_api.py +1 -1
- udata/tests/apiv2/test_search.py +30 -0
- udata/tests/apiv2/test_swagger.py +4 -4
- udata/tests/cli/test_cli_base.py +8 -9
- udata/tests/dataservice/test_dataservice_tasks.py +29 -0
- udata/tests/dataset/test_dataset_commands.py +4 -4
- udata/tests/dataset/test_dataset_model.py +66 -26
- udata/tests/dataset/test_dataset_rdf.py +99 -5
- udata/tests/dataset/test_dataset_tasks.py +25 -0
- udata/tests/frontend/test_auth.py +58 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +31 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/search/test_search_integration.py +33 -0
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_api_fields.py +10 -0
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_legal_mails.py +359 -0
- udata/tests/test_migrations.py +21 -21
- udata/tests/test_notifications.py +15 -57
- udata/tests/test_notifications_task.py +43 -0
- udata/tests/test_owned.py +81 -1
- udata/tests/test_storages.py +25 -19
- udata/tests/test_topics.py +77 -61
- udata/tests/test_uris.py +33 -0
- udata/tests/workers/test_jobs_commands.py +23 -23
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +187 -108
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +187 -108
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +187 -108
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +188 -109
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +187 -108
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +187 -108
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +187 -108
- udata/translations/udata.pot +215 -106
- udata/uris.py +0 -2
- udata-14.5.1.dev6.dist-info/METADATA +109 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/RECORD +143 -140
- udata/core/post/forms.py +0 -30
- udata/flask_mongoengine/json.py +0 -38
- udata/templates/mail/base.html +0 -105
- udata/templates/mail/base.txt +0 -6
- udata/templates/mail/button.html +0 -3
- udata/templates/mail/layouts/1-column.html +0 -19
- udata/templates/mail/layouts/2-columns.html +0 -20
- udata/templates/mail/layouts/center-panel.html +0 -16
- udata-14.0.0.dist-info/METADATA +0 -132
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/WHEEL +0 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/entry_points.txt +0 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/licenses/LICENSE +0 -0
- {udata-14.0.0.dist-info → udata-14.5.1.dev6.dist-info}/top_level.txt +0 -0
|
@@ -6,51 +6,25 @@
|
|
|
6
6
|
<title>{{ message.subject }}</title>
|
|
7
7
|
</head>
|
|
8
8
|
<body style="font-family: Marianne, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; background-color: #ffffff; max-width: 600px; margin: 0 auto; padding: 20px;">
|
|
9
|
+
{% if config.MAIL_LOGO_URL %}
|
|
9
10
|
<div style="margin-bottom: 30px;">
|
|
10
|
-
<
|
|
11
|
-
<path d="M143.469 29.5086H145.86L152.728 46.4699L159.558 29.5086H161.986L154.208 48.6328H151.248L143.469 29.5086Z" fill="#3558A2"/>
|
|
12
|
-
<path d="M138.218 40.5126V29.5086H140.419V40.4746C140.419 46.1664 137.155 49.3917 132.26 49.3917C127.328 49.3917 124.026 46.1664 124.026 40.4746V29.5086H126.265V40.5126C126.265 44.8383 128.656 47.2668 132.26 47.2668C135.789 47.2668 138.218 44.8383 138.218 40.5126Z" fill="#3558A2"/>
|
|
13
|
-
<path d="M109.662 28.7498C115.809 28.7498 119.945 33.6067 119.945 39.0708C119.945 44.5348 115.809 49.3918 109.662 49.3918C103.553 49.3918 99.4167 44.5348 99.4167 39.0708C99.4167 33.6067 103.553 28.7498 109.662 28.7498ZM109.7 47.2669C114.215 47.2669 117.554 43.3965 117.554 39.0708C117.554 34.6692 114.215 30.8747 109.7 30.8747C105.033 30.8747 101.731 34.7071 101.731 39.0708C101.731 43.4344 105.033 47.2669 109.7 47.2669Z" fill="#3558A2"/>
|
|
14
|
-
<path d="M79.7158 52.1617C79.7158 50.1886 80.7024 48.6328 82.6755 47.1909C81.7269 46.5459 81.2716 45.5214 81.2716 44.4968C81.2716 43.017 82.1822 41.7269 83.6621 40.8541C81.9546 39.602 80.968 37.6288 80.968 35.5039C80.968 31.8991 83.8139 28.7118 88.2155 28.7118C89.5435 28.7118 90.7578 29.0153 91.7823 29.5086H98.9539V31.4817H94.1728C95.0076 32.6201 95.5009 34.024 95.5009 35.5039C95.5009 39.1087 92.655 42.296 88.2155 42.296C87.0771 42.296 86.0526 42.0684 85.1419 41.7269C84.0036 42.3719 83.3965 43.3205 83.3965 44.1553C83.3965 45.0281 83.8139 45.7111 85.2178 45.7111H91.0613C96.2598 45.7111 98.4227 48.1016 98.4227 51.4028C98.4227 55.2732 94.4764 58.1191 88.8605 58.1191C83.4724 58.1191 79.7158 55.8424 79.7158 52.1617ZM88.2534 40.3988C91.4787 40.3988 93.2621 38.0842 93.2621 35.5039C93.2621 32.8478 91.4787 30.609 88.2534 30.609C84.9522 30.609 83.1688 32.8478 83.1688 35.5039C83.1688 38.1221 84.9522 40.3988 88.2534 40.3988ZM81.9546 51.972C81.9546 54.5522 84.7625 56.1839 88.8226 56.1839C93.2242 56.1839 96.1839 54.4005 96.1839 51.5166C96.1839 49.4297 95.0456 47.7601 90.9854 47.7601H84.6487C82.9032 48.9364 81.9546 50.2645 81.9546 51.972Z" fill="#3558A2"/>
|
|
15
|
-
<path d="M65.9257 49.2021C62.0933 49.2021 59.4751 47.0013 59.4751 43.4724C59.4751 40.5886 61.7138 38.4257 65.8119 37.7427L71.6554 36.7561V36.2628C71.6554 34.2518 70.1376 32.9616 67.9368 32.9616C66.0775 32.9616 64.6356 33.8344 63.6111 35.2383L60.0822 32.5442C61.7897 30.1917 64.5977 28.7498 68.0886 28.7498C73.6285 28.7498 76.4744 32.051 76.4744 36.2628V48.6329H71.6554V46.7736C70.4412 48.2534 68.1645 49.2021 65.9257 49.2021ZM64.2562 43.2447C64.2562 44.5348 65.2807 45.3696 66.9123 45.3696C69.1131 45.3696 70.7068 44.3451 71.6554 42.8273V40.1332L67.102 40.8921C65.0909 41.2336 64.2562 42.0684 64.2562 43.2447Z" fill="#3558A2"/>
|
|
16
|
-
<path d="M46.6593 41.6509V33.8343H43.0925V29.5086H46.6593V24.7275H51.5163V29.5086H57.3598V33.8343H51.5163V41.6509C51.5163 43.7759 52.6546 44.6106 54.5519 44.6106C55.88 44.6106 56.7527 44.4589 57.3978 44.1932V48.4051C56.4491 48.8225 55.3108 49.0123 53.7171 49.0123C48.936 49.0123 46.6593 46.3182 46.6593 41.6509Z" fill="#3558A2"/>
|
|
17
|
-
<path d="M30.5161 49.2021C26.6836 49.2021 24.0654 47.0013 24.0654 43.4724C24.0654 40.5886 26.3042 38.4257 30.4022 37.7427L36.2457 36.7561V36.2628C36.2457 34.2518 34.7279 32.9616 32.5271 32.9616C30.6678 32.9616 29.2259 33.8344 28.2014 35.2383L24.6725 32.5442C26.3801 30.1917 29.188 28.7498 32.6789 28.7498C38.2189 28.7498 41.0647 32.051 41.0647 36.2628V48.6329H36.2457V46.7736C35.0315 48.2534 32.7548 49.2021 30.5161 49.2021ZM28.8465 43.2447C28.8465 44.5348 29.871 45.3696 31.5026 45.3696C33.7034 45.3696 35.2971 44.3451 36.2457 42.8273V40.1332L31.6924 40.8921C29.6813 41.2336 28.8465 42.0684 28.8465 43.2447Z" fill="#3558A2"/>
|
|
18
|
-
<path d="M0 39.0706C0 33.4927 3.68066 28.7496 9.71389 28.7496C12.2941 28.7496 14.1534 29.5465 15.6333 31.0263V20.1741H20.4902V48.6327H15.6333V47.115C14.1534 48.5948 12.2941 49.3916 9.71389 49.3916C3.68066 49.3916 0 44.6485 0 39.0706ZM5.04667 39.0706C5.04667 42.4098 7.13364 44.8383 10.3969 44.8383C12.5598 44.8383 14.3432 43.9276 15.6333 42.1821V35.9591C14.3432 34.2137 12.5598 33.303 10.3969 33.303C7.13364 33.303 5.04667 35.7315 5.04667 39.0706Z" fill="#3558A2"/>
|
|
19
|
-
<path d="M179.017 11.7431C179.017 11.7431 179.017 11.7297 179.017 11.7207C179.008 10.9944 178.619 10.3218 177.994 9.96316L174.864 8.1518C174.573 7.98142 174.238 7.89175 173.894 7.88278C173.845 7.88278 173.8 7.88278 173.751 7.88727C173.509 7.90072 173.281 7.94555 173.067 8.03074C172.982 8.06661 172.901 8.10696 172.821 8.1518L170.916 9.25476L169.731 9.94074C169.66 9.98109 169.593 10.0259 169.53 10.0752C169.53 10.0752 169.503 10.0932 169.499 10.0932C169.248 10.277 169.052 10.5146 168.913 10.7792C168.904 10.7971 168.891 10.815 168.882 10.833C168.873 10.8509 168.864 10.8688 168.859 10.8868C168.734 11.1603 168.667 11.4696 168.667 11.7969V15.2672V15.321C168.667 15.3838 168.672 15.4511 168.681 15.5318C168.681 15.5721 168.69 15.6169 168.699 15.6663C168.734 15.9084 168.815 16.137 168.927 16.3433C169.11 16.684 169.383 16.9665 169.718 17.1593L172.794 18.9393C172.906 19.002 172.991 19.0424 173.076 19.0783C173.29 19.1679 173.518 19.2173 173.751 19.2262C173.76 19.2262 173.769 19.2262 173.777 19.2262C173.997 19.2307 174.22 19.2083 174.435 19.141C174.582 19.0962 174.73 19.0334 174.864 18.9527L177.971 17.1548C178.289 16.971 178.552 16.7065 178.736 16.3881C178.919 16.0698 179.017 15.7066 179.017 15.3434V11.7431Z" fill="#3558A2"/>
|
|
20
|
-
<path d="M168.667 18.4043V22.8378V24.1148L173.602 26.9722V21.2617L172.451 20.5965L168.667 18.4043Z" fill="#3558A2"/>
|
|
21
|
-
<path d="M178.359 9.68272C178.359 9.68272 178.4 9.71397 178.418 9.72736C178.418 9.72736 178.427 9.73183 178.431 9.73629C178.462 9.75862 178.494 9.78541 178.525 9.80773C178.525 9.80773 178.53 9.8122 178.534 9.81666C178.539 9.82112 178.548 9.82559 178.552 9.83005C178.588 9.86131 178.628 9.89256 178.66 9.92382C178.669 9.93275 178.678 9.94167 178.687 9.9506C178.709 9.97293 178.732 9.99525 178.754 10.0176C178.763 10.0265 178.772 10.0354 178.776 10.0399C178.808 10.0756 178.839 10.1113 178.871 10.1471C178.889 10.1649 178.902 10.1872 178.915 10.2051C178.924 10.2185 178.938 10.2319 178.947 10.2453C178.956 10.2542 178.965 10.2676 178.974 10.2765C179.005 10.3167 179.032 10.3614 179.059 10.406C179.086 10.4507 179.113 10.4908 179.14 10.5355C179.167 10.5846 179.189 10.6337 179.216 10.6828C179.238 10.7275 179.256 10.7677 179.274 10.8123C179.292 10.8525 179.306 10.8971 179.319 10.9373C179.319 10.9463 179.324 10.9507 179.328 10.9597C179.328 10.9686 179.333 10.9775 179.337 10.982C179.351 11.0222 179.364 11.0623 179.377 11.1025C179.395 11.1695 179.409 11.2365 179.418 11.3079C179.418 11.3079 179.418 11.3124 179.418 11.3168C179.418 11.3258 179.422 11.3392 179.422 11.3481C179.422 11.3704 179.431 11.3883 179.436 11.4106C179.449 11.5133 179.458 11.6204 179.458 11.7276V15.1968L180.149 15.5941V9.88363L175.179 7.02615V7.84321L178.207 9.58449C178.256 9.61128 178.306 9.64253 178.355 9.67825L178.359 9.68272Z" fill="#3558A2"/>
|
|
22
|
-
<path d="M161.984 7.02615V12.7595L166.954 15.6284V9.89506L161.984 7.02615Z" fill="#3558A2"/>
|
|
23
|
-
<path d="M178.969 16.8342C178.929 16.8925 178.884 16.9419 178.84 16.9958C178.809 17.0317 178.777 17.0676 178.746 17.1035C178.697 17.1573 178.639 17.2022 178.586 17.2516C178.559 17.274 178.532 17.3009 178.505 17.3279L179.352 17.8215C179.419 17.8619 179.464 17.9337 179.464 18.0145V22.8032L180.15 23.2026V17.4625L179.005 16.7938C179.005 16.7938 178.982 16.8207 178.973 16.8342H178.969Z" fill="#3558A2"/>
|
|
24
|
-
<path d="M172.779 6.64593V7.67751C172.779 7.67751 172.811 7.66858 172.828 7.65965C172.917 7.61946 173.015 7.5882 173.109 7.5614C173.185 7.53908 173.26 7.52121 173.341 7.50782C173.416 7.49442 173.488 7.48102 173.563 7.47209C173.59 7.47209 173.612 7.46316 173.639 7.45869V6.11451L168.704 3.25644V4.15405L172.668 6.44944C172.735 6.48963 172.779 6.56108 172.779 6.64146V6.64593Z" fill="#3558A2"/>
|
|
25
|
-
<path d="M161.984 20.3141L166.954 23.1686V17.4551L161.984 14.6006V20.3141Z" fill="#3558A2"/>
|
|
26
|
-
<path d="M185.53 20.314V14.6005L180.594 17.455V23.1685L185.53 20.314Z" fill="#3558A2"/>
|
|
27
|
-
<path d="M174.048 26.9723L179.018 24.1148V22.9138V18.4043L174.048 21.2618V26.9723Z" fill="#3558A2"/>
|
|
28
|
-
<path d="M167.4 15.6284L168.215 15.1533V11.7554C168.215 10.9754 168.585 10.2313 169.209 9.76061C169.209 9.76061 169.217 9.75613 169.222 9.75165C169.222 9.75165 169.226 9.75165 169.231 9.74716C169.24 9.74268 169.249 9.73372 169.253 9.73372C169.333 9.67544 169.391 9.6351 169.458 9.59475L172.335 7.92272V7.02618L167.4 9.89509V15.6284Z" fill="#3558A2"/>
|
|
29
|
-
<path d="M185.53 12.7595V7.02618L180.594 9.89509V15.6284L185.53 12.7595Z" fill="#3558A2"/>
|
|
30
|
-
<path d="M174.169 7.46644C174.218 7.47089 174.262 7.48426 174.311 7.48871C174.369 7.49762 174.431 7.51099 174.489 7.52435C174.543 7.53772 174.592 7.55554 174.641 7.57336C174.681 7.58672 174.721 7.59563 174.761 7.609V6.63336C174.761 6.55317 174.806 6.48189 174.872 6.44179L179.055 4.02718V3.25647L174.12 6.10767V7.45753C174.12 7.45753 174.155 7.46644 174.173 7.46644H174.169Z" fill="#3558A2"/>
|
|
31
|
-
<path d="M168.26 17.9037C168.273 17.8815 168.296 17.8637 168.318 17.8459C168.323 17.8459 168.327 17.837 168.332 17.8325L169.182 17.343C169.182 17.343 169.146 17.3074 169.124 17.2896C169.052 17.2273 168.981 17.165 168.914 17.0937C168.882 17.0626 168.855 17.027 168.829 16.9958C168.775 16.9335 168.726 16.8712 168.681 16.8045C168.672 16.7911 168.658 16.7778 168.645 16.7599L168.623 16.7733L167.4 17.4765V23.1688L168.22 22.697V18.0239C168.22 17.9883 168.233 17.9527 168.246 17.9216C168.246 17.9127 168.255 17.9082 168.26 17.8993V17.9037Z" fill="#3558A2"/>
|
|
32
|
-
<path d="M172.132 6.629L167.182 3.76953L162.227 6.629L167.182 9.49294L172.132 6.629Z" fill="#3558A2"/>
|
|
33
|
-
<path d="M178.847 2.86394L173.878 0L168.908 2.86394L173.878 5.72341L178.847 2.86394Z" fill="#3558A2"/>
|
|
34
|
-
<path d="M180.045 9.30052L180.371 9.49294L185.326 6.629L180.371 3.76953L179.396 4.33337L175.421 6.629L177.514 7.8417L180.045 9.30052Z" fill="#3558A2"/>
|
|
35
|
-
<path d="M180.264 16.1917L179.438 15.7158C179.433 15.7469 179.42 15.7825 179.415 15.8136C179.402 15.8892 179.384 15.9648 179.366 16.036C179.353 16.0805 179.335 16.125 179.321 16.165C179.299 16.2317 179.277 16.294 179.245 16.3607C179.236 16.3785 179.232 16.3918 179.228 16.4096L180.376 17.0679L185.328 14.2213L184.617 13.8121L180.492 16.1828C180.457 16.2006 180.421 16.2139 180.381 16.2139C180.34 16.2139 180.305 16.205 180.269 16.1828L180.264 16.1917Z" fill="#3558A2"/>
|
|
36
|
-
<path d="M175.068 19.3326C174.902 19.426 174.723 19.5016 174.544 19.5594C174.419 19.5994 174.29 19.6217 174.16 19.6395C174.115 19.6439 174.071 19.6439 174.026 19.6484C173.959 19.6528 173.896 19.6617 173.829 19.6617C173.793 19.6617 173.757 19.6617 173.722 19.6617C173.722 19.6617 173.717 19.6617 173.713 19.6617C173.57 19.6573 173.431 19.6395 173.292 19.6083C173.154 19.5772 173.02 19.5372 172.89 19.4838C172.787 19.4393 172.684 19.3904 172.581 19.337L169.585 17.6157L169.349 17.7536L168.875 18.0249L173.829 20.8715L174.366 20.5602L174.871 20.2711L175.881 19.6884L178.779 18.0249L178.305 17.7536L178.068 17.6157L175.072 19.337L175.068 19.3326Z" fill="#3558A2"/>
|
|
37
|
-
<path d="M168.256 15.7246C168.256 15.6935 168.247 15.6668 168.242 15.6357L167.29 16.1828C167.254 16.2006 167.218 16.2139 167.178 16.2139C167.138 16.2139 167.102 16.205 167.066 16.1828L162.938 13.8121L162.227 14.2213L167.182 17.0679L168.43 16.3518C168.354 16.1739 168.296 15.9693 168.26 15.7424C168.26 15.7424 168.26 15.7335 168.26 15.7291L168.256 15.7246Z" fill="#3558A2"/>
|
|
38
|
-
</svg>
|
|
11
|
+
<img width="186" src="{{ config.MAIL_LOGO_URL }}" alt="Logo">
|
|
39
12
|
</div>
|
|
13
|
+
{% endif %}
|
|
40
14
|
|
|
41
15
|
<p style="margin-bottom: 20px;">{{ _('Hi %(user)s', user=recipient.first_name) }},</p>
|
|
42
16
|
|
|
43
17
|
{% for item in message.paragraphs %}
|
|
44
18
|
{% if item.__class__.__name__ == 'MailCTA' %}
|
|
45
19
|
<p style="margin: 25px 0;">
|
|
46
|
-
<a href="{{ item.link }}" style="color: #
|
|
20
|
+
<a href="{{ item.link }}" style="color: #000091; text-decoration: underline; font-weight: 700;">{{ item.label }}</a>
|
|
47
21
|
</p>
|
|
48
22
|
{% elif item.__class__.__name__ == 'LabelledContent' %}
|
|
49
23
|
{% if item.inline %}
|
|
50
24
|
<p style="margin-bottom: 15px;"><strong>{{ item.label }}</strong> {{ item.truncated_content }}</p>
|
|
51
25
|
{% else %}
|
|
52
26
|
<p style="margin-bottom: 5px; font-weight: bold;">{{ item.label }}</p>
|
|
53
|
-
<p style="margin-bottom: 15px; margin-top: 0;">{{ item.truncated_content }}</p>
|
|
27
|
+
<p style="margin-bottom: 15px; margin-top: 0; font-style: italic;">{{ item.truncated_content }}</p>
|
|
54
28
|
{% endif %}
|
|
55
29
|
{% elif item.__class__.__name__ == 'ParagraphWithLinks' %}
|
|
56
30
|
<p style="margin-bottom: 15px;">{{ item.html|safe }}</p>
|
udata/tests/__init__.py
CHANGED
|
@@ -5,7 +5,6 @@ from werkzeug import Response
|
|
|
5
5
|
|
|
6
6
|
from udata import settings
|
|
7
7
|
from udata.app import UDataApp, create_app
|
|
8
|
-
from udata.tests.plugin import TestClient
|
|
9
8
|
|
|
10
9
|
from . import helpers
|
|
11
10
|
|
|
@@ -33,7 +32,6 @@ class TestCaseMixin:
|
|
|
33
32
|
def _app(self, request):
|
|
34
33
|
test_settings = self.get_settings(request)
|
|
35
34
|
self.app = create_app(settings.Defaults, override=test_settings)
|
|
36
|
-
self.app.test_client_class = TestClient
|
|
37
35
|
return self.app
|
|
38
36
|
|
|
39
37
|
def assertEqualDates(self, datetime1, datetime2, limit=1): # Seconds.
|
|
@@ -47,6 +45,33 @@ class TestCaseMixin:
|
|
|
47
45
|
stream2 = list(response2.iter_encoded())
|
|
48
46
|
assert stream1 == stream2
|
|
49
47
|
|
|
48
|
+
def cli(self, *args, **kwargs):
|
|
49
|
+
"""
|
|
50
|
+
Execute a CLI command.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
self.cli("command", "arg1", "arg2")
|
|
54
|
+
self.cli("command arg1 arg2") # Auto-split on spaces
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
*args: Command and arguments (can be a single string with spaces or multiple args)
|
|
58
|
+
**kwargs: Additional arguments for the CLI runner (e.g., expect_error=True)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The CLI result object
|
|
62
|
+
"""
|
|
63
|
+
import shlex
|
|
64
|
+
|
|
65
|
+
from udata.commands import cli as cli_cmd
|
|
66
|
+
|
|
67
|
+
if len(args) == 1 and " " in args[0]:
|
|
68
|
+
args = shlex.split(args[0])
|
|
69
|
+
|
|
70
|
+
result = self.app.test_cli_runner().invoke(cli_cmd, args, **kwargs)
|
|
71
|
+
if result.exit_code != 0 and kwargs.get("expect_error") is not True:
|
|
72
|
+
helpers.assert_command_ok(result)
|
|
73
|
+
return result
|
|
74
|
+
|
|
50
75
|
|
|
51
76
|
class TestCase(TestCaseMixin, unittest.TestCase):
|
|
52
77
|
pass
|
udata/tests/api/__init__.py
CHANGED
|
@@ -2,7 +2,10 @@ from contextlib import contextmanager
|
|
|
2
2
|
from urllib.parse import urlparse
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from flask import json
|
|
6
|
+
from flask_security.utils import login_user, logout_user, set_request_attr
|
|
5
7
|
|
|
8
|
+
from udata.core.user.factories import UserFactory
|
|
6
9
|
from udata.mongo import db
|
|
7
10
|
from udata.mongo.document import get_all_models
|
|
8
11
|
from udata.tests import PytestOnlyTestCase, TestCase, helpers
|
|
@@ -11,12 +14,15 @@ from udata.tests import PytestOnlyTestCase, TestCase, helpers
|
|
|
11
14
|
@pytest.mark.usefixtures("instance_path")
|
|
12
15
|
class APITestCaseMixin:
|
|
13
16
|
"""
|
|
14
|
-
|
|
17
|
+
API Test Case Mixin with integrated API client functionality.
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
This mixin provides API testing methods with automatic JSON handling.
|
|
20
|
+
The `get`, `post`, `put`, `patch`, `delete`, and `options` methods
|
|
21
|
+
default to JSON content-type and automatic serialization.
|
|
17
22
|
"""
|
|
18
23
|
|
|
19
24
|
user = None
|
|
25
|
+
_user = None # For API key authentication context
|
|
20
26
|
|
|
21
27
|
@pytest.fixture(autouse=True)
|
|
22
28
|
def load_api_routes(self, app):
|
|
@@ -26,45 +32,126 @@ class APITestCaseMixin:
|
|
|
26
32
|
frontend.init_app(app)
|
|
27
33
|
|
|
28
34
|
@pytest.fixture(autouse=True)
|
|
29
|
-
def
|
|
35
|
+
def inject_client(self, app):
|
|
30
36
|
"""
|
|
31
|
-
Inject
|
|
37
|
+
Inject test client for Flask testing.
|
|
32
38
|
"""
|
|
33
|
-
self.
|
|
34
|
-
|
|
35
|
-
@pytest.fixture(autouse=True)
|
|
36
|
-
def inject_client(self, client):
|
|
37
|
-
"""
|
|
38
|
-
Inject test client for compatibility with Flask-Testing.
|
|
39
|
-
"""
|
|
40
|
-
self.client = client
|
|
39
|
+
self.client = app.test_client()
|
|
41
40
|
|
|
42
41
|
@contextmanager
|
|
43
42
|
def api_user(self, user=None):
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
"""
|
|
44
|
+
Context manager for API key authentication.
|
|
45
|
+
|
|
46
|
+
Usage:
|
|
47
|
+
with self.api_user(user) as user:
|
|
48
|
+
response = self.get(url)
|
|
49
|
+
"""
|
|
50
|
+
self._user = user or UserFactory()
|
|
51
|
+
if not self._user.apikey:
|
|
52
|
+
self._user.generate_api_key()
|
|
53
|
+
self._user.save()
|
|
54
|
+
yield self._user
|
|
55
|
+
self._user = None
|
|
46
56
|
|
|
47
57
|
def login(self, user=None):
|
|
48
|
-
|
|
58
|
+
"""Login a user via session authentication."""
|
|
59
|
+
self.user = user or UserFactory()
|
|
60
|
+
|
|
61
|
+
login_user(self.user)
|
|
62
|
+
set_request_attr("fs_authn_via", "session")
|
|
63
|
+
|
|
49
64
|
return self.user
|
|
50
65
|
|
|
66
|
+
def logout(self):
|
|
67
|
+
"""Logout the current user."""
|
|
68
|
+
logout_user()
|
|
69
|
+
self.user = None
|
|
70
|
+
|
|
71
|
+
def perform(self, verb, url, **kwargs):
|
|
72
|
+
"""
|
|
73
|
+
Perform an HTTP request with JSON handling.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
verb: HTTP verb (get, post, put, patch, delete, options)
|
|
77
|
+
url: URL to request
|
|
78
|
+
**kwargs: Additional arguments for the request
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Flask test response
|
|
82
|
+
"""
|
|
83
|
+
headers = kwargs.pop("headers", {})
|
|
84
|
+
|
|
85
|
+
# Only set Content-Type for methods that have a body
|
|
86
|
+
if verb in ("post", "put", "patch", "delete"):
|
|
87
|
+
headers["Content-Type"] = "application/json"
|
|
88
|
+
|
|
89
|
+
data = kwargs.get("data")
|
|
90
|
+
if data is not None:
|
|
91
|
+
data = json.dumps(data)
|
|
92
|
+
headers["Content-Length"] = len(data)
|
|
93
|
+
kwargs["data"] = data
|
|
94
|
+
|
|
95
|
+
if self._user:
|
|
96
|
+
headers["X-API-KEY"] = kwargs.get("X-API-KEY", self._user.apikey)
|
|
97
|
+
|
|
98
|
+
kwargs["headers"] = headers
|
|
99
|
+
method = getattr(self.client, verb)
|
|
100
|
+
return method(url, **kwargs)
|
|
101
|
+
|
|
51
102
|
def get(self, url, *args, **kwargs):
|
|
52
|
-
|
|
103
|
+
"""Perform a GET request with JSON handling."""
|
|
104
|
+
return self.perform("get", url, *args, **kwargs)
|
|
53
105
|
|
|
54
106
|
def post(self, url, data=None, json=True, *args, **kwargs):
|
|
55
|
-
|
|
107
|
+
"""
|
|
108
|
+
Perform a POST request.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
url: URL to request
|
|
112
|
+
data: Data to send (will be JSON-encoded if json=True)
|
|
113
|
+
json: If True, send as JSON (default: True)
|
|
114
|
+
*args, **kwargs: Additional arguments
|
|
115
|
+
"""
|
|
116
|
+
if not json:
|
|
117
|
+
return self.client.post(url, data=data or {}, *args, **kwargs)
|
|
118
|
+
return self.perform("post", url, data=data or {}, *args, **kwargs)
|
|
56
119
|
|
|
57
120
|
def put(self, url, data=None, json=True, *args, **kwargs):
|
|
58
|
-
|
|
121
|
+
"""
|
|
122
|
+
Perform a PUT request.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
url: URL to request
|
|
126
|
+
data: Data to send (will be JSON-encoded if json=True)
|
|
127
|
+
json: If True, send as JSON (default: True)
|
|
128
|
+
*args, **kwargs: Additional arguments
|
|
129
|
+
"""
|
|
130
|
+
if not json:
|
|
131
|
+
return self.client.put(url, data=data or {}, *args, **kwargs)
|
|
132
|
+
return self.perform("put", url, data=data or {}, *args, **kwargs)
|
|
59
133
|
|
|
60
134
|
def patch(self, url, data=None, json=True, *args, **kwargs):
|
|
61
|
-
|
|
135
|
+
"""
|
|
136
|
+
Perform a PATCH request.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
url: URL to request
|
|
140
|
+
data: Data to send (will be JSON-encoded if json=True)
|
|
141
|
+
json: If True, send as JSON (default: True)
|
|
142
|
+
*args, **kwargs: Additional arguments
|
|
143
|
+
"""
|
|
144
|
+
if not json:
|
|
145
|
+
return self.client.patch(url, data=data or {}, *args, **kwargs)
|
|
146
|
+
return self.perform("patch", url, data=data or {}, *args, **kwargs)
|
|
62
147
|
|
|
63
148
|
def delete(self, url, data=None, *args, **kwargs):
|
|
64
|
-
|
|
149
|
+
"""Perform a DELETE request with JSON handling."""
|
|
150
|
+
return self.perform("delete", url, data=data or {}, *args, **kwargs)
|
|
65
151
|
|
|
66
152
|
def options(self, url, data=None, *args, **kwargs):
|
|
67
|
-
|
|
153
|
+
"""Perform an OPTIONS request with JSON handling."""
|
|
154
|
+
return self.perform("options", url, data=data or {}, *args, **kwargs)
|
|
68
155
|
|
|
69
156
|
def assertStatus(self, response, status_code, message=None):
|
|
70
157
|
__tracebackhide__ = True
|
|
@@ -4,6 +4,7 @@ from werkzeug.test import TestResponse
|
|
|
4
4
|
from udata.core.activity.models import Activity
|
|
5
5
|
from udata.core.dataset.factories import DatasetFactory
|
|
6
6
|
from udata.core.dataset.models import Dataset
|
|
7
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
7
8
|
from udata.core.reuse.factories import ReuseFactory
|
|
8
9
|
from udata.core.reuse.models import Reuse
|
|
9
10
|
from udata.core.topic.factories import TopicFactory
|
|
@@ -111,3 +112,38 @@ class ActivityAPITest(APITestCase):
|
|
|
111
112
|
assert activity_data["related_to_id"] == str(topic.id)
|
|
112
113
|
assert activity_data["related_to_kind"] == "Topic"
|
|
113
114
|
assert activity_data["related_to_url"] == topic.self_api_url()
|
|
115
|
+
|
|
116
|
+
def test_activity_api_list_with_private_visible_to_owner(self) -> None:
|
|
117
|
+
"""Owner should see activities about their own private objects."""
|
|
118
|
+
owner = UserFactory()
|
|
119
|
+
dataset = DatasetFactory(private=True, owner=owner)
|
|
120
|
+
FakeDatasetActivity.objects.create(actor=UserFactory(), related_to=dataset)
|
|
121
|
+
|
|
122
|
+
# Anonymous user won't see it
|
|
123
|
+
response = self.get(url_for("api.activity"))
|
|
124
|
+
assert200(response)
|
|
125
|
+
assert len(response.json["data"]) == 0
|
|
126
|
+
|
|
127
|
+
# Owner should see their own private dataset activity
|
|
128
|
+
self.login(owner)
|
|
129
|
+
response = self.get(url_for("api.activity"))
|
|
130
|
+
assert200(response)
|
|
131
|
+
assert len(response.json["data"]) == 1
|
|
132
|
+
|
|
133
|
+
def test_activity_api_list_with_private_visible_to_org_member(self) -> None:
|
|
134
|
+
"""Organization members should see activities about their org's private objects."""
|
|
135
|
+
member = UserFactory()
|
|
136
|
+
org = OrganizationFactory(admins=[member])
|
|
137
|
+
dataset = DatasetFactory(private=True, organization=org)
|
|
138
|
+
FakeDatasetActivity.objects.create(actor=UserFactory(), related_to=dataset)
|
|
139
|
+
|
|
140
|
+
# Anonymous user won't see it
|
|
141
|
+
response = self.get(url_for("api.activity"))
|
|
142
|
+
assert200(response)
|
|
143
|
+
assert len(response.json["data"]) == 0
|
|
144
|
+
|
|
145
|
+
# Org member should see the private dataset activity
|
|
146
|
+
self.login(member)
|
|
147
|
+
response = self.get(url_for("api.activity"))
|
|
148
|
+
assert200(response)
|
|
149
|
+
assert len(response.json["data"]) == 1
|