wagtail 6.1.3__py3-none-any.whl → 6.2rc1__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.
- wagtail/__init__.py +1 -1
- wagtail/actions/copy_for_translation.py +15 -1
- wagtail/admin/checks.py +20 -30
- wagtail/admin/forms/pages.py +10 -0
- wagtail/admin/icons.py +43 -0
- wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
- wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
- wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
- wagtail/admin/menu.py +2 -2
- wagtail/admin/migrations/0004_editingsession.py +57 -0
- wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
- wagtail/admin/models.py +36 -3
- wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
- wagtail/admin/static/wagtailadmin/css/core.css +1 -1
- wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
- wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
- wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
- wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
- wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
- wagtail/admin/static/wagtailadmin/js/core.js +1 -1
- wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
- wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
- wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
- wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
- wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
- wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
- wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
- wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
- wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
- wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
- wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
- wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
- wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
- wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
- wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
- wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
- wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
- wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
- wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
- wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
- wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
- wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
- wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
- wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
- wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
- wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
- wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
- wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
- wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
- wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
- wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
- wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
- wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
- wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
- wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
- wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
- wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
- wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
- wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
- wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
- wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
- wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
- wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
- wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
- wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
- wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
- wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
- wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
- wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
- wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
- wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
- wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
- wagtail/admin/tests/formats/en/__init__.py +0 -0
- wagtail/admin/tests/formats/en/formats.py +1 -0
- wagtail/admin/tests/pages/test_create_page.py +47 -0
- wagtail/admin/tests/pages/test_edit_page.py +10 -8
- wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
- wagtail/admin/tests/test_checks.py +53 -3
- wagtail/admin/tests/test_collections_views.py +62 -1
- wagtail/admin/tests/test_edit_handlers.py +37 -0
- wagtail/admin/tests/test_editing_sessions.py +1336 -0
- wagtail/admin/tests/test_icon_sprite.py +12 -21
- wagtail/admin/tests/test_page_chooser.py +309 -7
- wagtail/admin/tests/test_privacy.py +82 -0
- wagtail/admin/tests/test_reports_views.py +464 -70
- wagtail/admin/tests/test_userbar.py +93 -6
- wagtail/admin/tests/test_workflows.py +223 -33
- wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
- wagtail/admin/ui/editing_sessions.py +57 -0
- wagtail/admin/urls/__init__.py +9 -15
- wagtail/admin/urls/editing_sessions.py +17 -0
- wagtail/admin/urls/reports.py +33 -1
- wagtail/admin/userbar.py +77 -20
- wagtail/admin/views/chooser.py +49 -22
- wagtail/admin/views/collections.py +0 -11
- wagtail/admin/views/editing_sessions.py +193 -0
- wagtail/admin/views/generic/__init__.py +1 -0
- wagtail/admin/views/generic/history.py +9 -3
- wagtail/admin/views/generic/mixins.py +44 -3
- wagtail/admin/views/generic/models.py +46 -72
- wagtail/admin/views/generic/permissions.py +20 -10
- wagtail/admin/views/home.py +2 -31
- wagtail/admin/views/page_privacy.py +20 -5
- wagtail/admin/views/pages/choose_parent.py +62 -0
- wagtail/admin/views/pages/edit.py +28 -0
- wagtail/admin/views/reports/aging_pages.py +6 -10
- wagtail/admin/views/reports/audit_logging.py +13 -42
- wagtail/admin/views/reports/base.py +31 -4
- wagtail/admin/views/reports/locked_pages.py +5 -8
- wagtail/admin/views/reports/page_types_usage.py +6 -10
- wagtail/admin/views/reports/workflows.py +36 -12
- wagtail/admin/viewsets/base.py +8 -3
- wagtail/admin/viewsets/chooser.py +1 -1
- wagtail/admin/viewsets/model.py +26 -1
- wagtail/admin/wagtail_hooks.py +2 -1
- wagtail/api/v2/filters.py +6 -0
- wagtail/api/v2/tests/test_documents.py +1 -1
- wagtail/api/v2/tests/test_images.py +1 -1
- wagtail/api/v2/tests/test_pages.py +11 -1
- wagtail/api/v2/utils.py +2 -2
- wagtail/blocks/base.py +35 -12
- wagtail/blocks/definition_lookup.py +85 -0
- wagtail/blocks/list_block.py +12 -0
- wagtail/blocks/migrations/migrate_operation.py +2 -0
- wagtail/blocks/stream_block.py +19 -0
- wagtail/blocks/struct_block.py +19 -0
- wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
- wagtail/contrib/frontend_cache/backends/azure.py +179 -0
- wagtail/contrib/frontend_cache/backends/base.py +28 -0
- wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
- wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
- wagtail/contrib/frontend_cache/backends/http.py +64 -0
- wagtail/contrib/frontend_cache/tests.py +59 -17
- wagtail/contrib/frontend_cache/utils.py +26 -8
- wagtail/contrib/redirects/filters.py +15 -1
- wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
- wagtail/contrib/redirects/models.py +6 -5
- wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
- wagtail/contrib/redirects/tests/test_redirects.py +141 -1
- wagtail/contrib/redirects/urls.py +1 -2
- wagtail/contrib/redirects/views.py +39 -80
- wagtail/contrib/routable_page/models.py +6 -4
- wagtail/contrib/routable_page/tests.py +11 -0
- wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
- wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
- wagtail/contrib/simple_translation/models.py +2 -1
- wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
- wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
- wagtail/contrib/typed_table_block/blocks.py +19 -0
- wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
- wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
- wagtail/contrib/typed_table_block/tests.py +38 -0
- wagtail/coreutils.py +1 -1
- wagtail/documents/__init__.py +1 -1
- wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
- wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
- wagtail/documents/models.py +5 -1
- wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
- wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
- wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
- wagtail/documents/tests/test_models.py +5 -1
- wagtail/embeds/apps.py +2 -0
- wagtail/embeds/embeds.py +12 -14
- wagtail/embeds/finders/__init__.py +2 -0
- wagtail/embeds/finders/facebook.py +17 -33
- wagtail/embeds/finders/instagram.py +19 -16
- wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/embeds/signal_handlers.py +13 -0
- wagtail/embeds/tests/test_embeds.py +7 -7
- wagtail/fields.py +58 -14
- wagtail/images/__init__.py +1 -1
- wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
- wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
- wagtail/images/models.py +2 -0
- wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
- wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
- wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
- wagtail/images/templates/wagtailimages/images/edit.html +4 -4
- wagtail/images/tests/test_admin_views.py +26 -2
- wagtail/images/views/chooser.py +6 -1
- wagtail/locale/en/LC_MESSAGES/django.po +84 -80
- wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
- wagtail/locales/tests.py +16 -0
- wagtail/locales/wagtail_hooks.py +0 -9
- wagtail/migrations/0094_alter_page_locale.py +19 -0
- wagtail/models/__init__.py +11 -5
- wagtail/models/i18n.py +6 -1
- wagtail/project_template/requirements.txt +1 -1
- wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/signals.py +4 -0
- wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
- wagtail/sites/tests.py +15 -0
- wagtail/sites/wagtail_hooks.py +0 -9
- wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
- wagtail/snippets/permissions.py +5 -3
- wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
- wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
- wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
- wagtail/snippets/tests/test_snippets.py +78 -12
- wagtail/snippets/tests/test_viewset.py +22 -0
- wagtail/snippets/views/snippets.py +19 -14
- wagtail/snippets/wagtail_hooks.py +2 -10
- wagtail/templatetags/wagtailcore_tags.py +3 -0
- wagtail/test/dummy_external_storage.py +1 -1
- wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
- wagtail/test/routablepage/models.py +4 -0
- wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
- wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
- wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
- wagtail/test/testapp/models.py +24 -0
- wagtail/test/testapp/views.py +1 -0
- wagtail/test/testapp/wagtail_hooks.py +9 -0
- wagtail/test/urls_multilang.py +6 -1
- wagtail/test/urls_multilang_non_root.py +11 -0
- wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
- wagtail/tests/test_audit_log.py +72 -2
- wagtail/tests/test_blocks.py +103 -0
- wagtail/tests/test_signals.py +49 -2
- wagtail/tests/test_streamfield.py +153 -0
- wagtail/tests/test_utils.py +14 -0
- wagtail/tests/tests.py +5 -0
- wagtail/users/apps.py +1 -0
- wagtail/users/forms.py +7 -0
- wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
- wagtail/users/models.py +1 -0
- wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
- wagtail/users/templatetags/wagtailusers_tags.py +9 -0
- wagtail/users/tests/__init__.py +7 -1
- wagtail/users/tests/test_admin_views.py +117 -32
- wagtail/users/views/groups.py +4 -0
- wagtail/users/views/users.py +58 -14
- wagtail/users/wagtail_hooks.py +7 -123
- wagtail/utils/utils.py +1 -0
- wagtail/utils/version.py +5 -2
- {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
- {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
- wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
- wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
- wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
- wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
- wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
- wagtail/contrib/frontend_cache/backends.py +0 -400
- wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
- wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
- wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
- {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
- {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
- {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
- {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
|
@@ -13,6 +13,7 @@ from django.utils.html import escape
|
|
|
13
13
|
from django.utils.timezone import make_aware
|
|
14
14
|
from openpyxl import load_workbook
|
|
15
15
|
|
|
16
|
+
from wagtail import hooks
|
|
16
17
|
from wagtail.admin.admin_url_finder import AdminURLFinder
|
|
17
18
|
from wagtail.log_actions import log
|
|
18
19
|
from wagtail.models import ModelLogEntry
|
|
@@ -58,6 +59,38 @@ class TestModelViewSetGroup(WagtailTestUtils, TestCase):
|
|
|
58
59
|
"/admin/blockcounts/streammodel/",
|
|
59
60
|
)
|
|
60
61
|
|
|
62
|
+
def test_menu_item_with_only_view_permission(self):
|
|
63
|
+
self.user.is_superuser = False
|
|
64
|
+
self.user.save()
|
|
65
|
+
admin_permission = Permission.objects.get(
|
|
66
|
+
content_type__app_label="wagtailadmin",
|
|
67
|
+
codename="access_admin",
|
|
68
|
+
)
|
|
69
|
+
view_permission = Permission.objects.get(
|
|
70
|
+
content_type=ContentType.objects.get_for_model(JSONStreamModel),
|
|
71
|
+
codename=get_permission_codename("view", JSONStreamModel._meta),
|
|
72
|
+
)
|
|
73
|
+
self.user.user_permissions.add(admin_permission, view_permission)
|
|
74
|
+
|
|
75
|
+
response = self.client.get(reverse("wagtailadmin_home"))
|
|
76
|
+
|
|
77
|
+
# The group menu item is still shown
|
|
78
|
+
self.assertContains(
|
|
79
|
+
response,
|
|
80
|
+
'"name": "tests", "label": "Tests", "icon_name": "folder-open-inverse"',
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# The menu item for the model is shown
|
|
84
|
+
self.assertContains(response, "Json Stream Models")
|
|
85
|
+
self.assertContains(response, reverse("streammodel:index"))
|
|
86
|
+
self.assertEqual(reverse("streammodel:index"), "/admin/streammodel/")
|
|
87
|
+
|
|
88
|
+
# The other items in the group are not shown as the user doesn't have permission
|
|
89
|
+
self.assertNotContains(response, "JSON MinMaxCount StreamModel")
|
|
90
|
+
self.assertNotContains(response, reverse("minmaxcount_streammodel:index"))
|
|
91
|
+
self.assertNotContains(response, "JSON BlockCounts StreamModel")
|
|
92
|
+
self.assertNotContains(response, reverse("blockcounts_streammodel:index"))
|
|
93
|
+
|
|
61
94
|
|
|
62
95
|
class TestTemplateConfiguration(WagtailTestUtils, TestCase):
|
|
63
96
|
def setUp(self):
|
|
@@ -1358,6 +1391,25 @@ class TestInspectView(WagtailTestUtils, TestCase):
|
|
|
1358
1391
|
self.assertEqual(fields, expected_fields)
|
|
1359
1392
|
self.assertEqual(values, expected_values)
|
|
1360
1393
|
|
|
1394
|
+
def test_view_permission_registered(self):
|
|
1395
|
+
content_type = ContentType.objects.get_for_model(FeatureCompleteToy)
|
|
1396
|
+
qs = Permission.objects.none()
|
|
1397
|
+
for fn in hooks.get_hooks("register_permissions"):
|
|
1398
|
+
qs |= fn()
|
|
1399
|
+
registered_user_permissions = qs.filter(content_type=content_type)
|
|
1400
|
+
self.assertEqual(
|
|
1401
|
+
set(registered_user_permissions.values_list("codename", flat=True)),
|
|
1402
|
+
{
|
|
1403
|
+
"add_featurecompletetoy",
|
|
1404
|
+
"change_featurecompletetoy",
|
|
1405
|
+
"delete_featurecompletetoy",
|
|
1406
|
+
# The "view" permission should be registered if inspect view is enabled
|
|
1407
|
+
"view_featurecompletetoy",
|
|
1408
|
+
# Any custom permissions should be registered too
|
|
1409
|
+
"can_set_release_date",
|
|
1410
|
+
},
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1361
1413
|
def test_disabled(self):
|
|
1362
1414
|
# An alternate viewset for the same model without inspect_view_enabled = True
|
|
1363
1415
|
with self.assertRaises(NoReverseMatch):
|
|
@@ -1375,7 +1427,7 @@ class TestInspectView(WagtailTestUtils, TestCase):
|
|
|
1375
1427
|
self.assertEqual(response.status_code, 302)
|
|
1376
1428
|
self.assertRedirects(response, reverse("wagtailadmin_home"))
|
|
1377
1429
|
|
|
1378
|
-
def
|
|
1430
|
+
def assert_minimal_permission(self, permission):
|
|
1379
1431
|
self.user.is_superuser = False
|
|
1380
1432
|
self.user.user_permissions.add(
|
|
1381
1433
|
Permission.objects.get(
|
|
@@ -1383,7 +1435,7 @@ class TestInspectView(WagtailTestUtils, TestCase):
|
|
|
1383
1435
|
),
|
|
1384
1436
|
Permission.objects.get(
|
|
1385
1437
|
content_type__app_label=self.object._meta.app_label,
|
|
1386
|
-
codename=get_permission_codename(
|
|
1438
|
+
codename=get_permission_codename(permission, self.object._meta),
|
|
1387
1439
|
),
|
|
1388
1440
|
)
|
|
1389
1441
|
self.user.save()
|
|
@@ -1406,6 +1458,12 @@ class TestInspectView(WagtailTestUtils, TestCase):
|
|
|
1406
1458
|
self.assertEqual(len(soup.find_all("a", attrs={"href": self.edit_url})), 0)
|
|
1407
1459
|
self.assertEqual(len(soup.find_all("a", attrs={"href": self.delete_url})), 0)
|
|
1408
1460
|
|
|
1461
|
+
def test_only_add_permission(self):
|
|
1462
|
+
self.assert_minimal_permission("add")
|
|
1463
|
+
|
|
1464
|
+
def test_only_view_permission(self):
|
|
1465
|
+
self.assert_minimal_permission("view")
|
|
1466
|
+
|
|
1409
1467
|
|
|
1410
1468
|
class TestListingButtons(WagtailTestUtils, TestCase):
|
|
1411
1469
|
def setUp(self):
|
|
@@ -1464,6 +1522,76 @@ class TestListingButtons(WagtailTestUtils, TestCase):
|
|
|
1464
1522
|
self.assertEqual(rendered_button.attrs.get("aria-label"), aria_label)
|
|
1465
1523
|
self.assertEqual(rendered_button.attrs.get("href"), url)
|
|
1466
1524
|
|
|
1525
|
+
def test_title_cell_not_link_to_edit_view_when_no_edit_permission(self):
|
|
1526
|
+
self.user.is_superuser = False
|
|
1527
|
+
self.user.save()
|
|
1528
|
+
admin_permission = Permission.objects.get(
|
|
1529
|
+
content_type__app_label="wagtailadmin",
|
|
1530
|
+
codename="access_admin",
|
|
1531
|
+
)
|
|
1532
|
+
add_permission = Permission.objects.get(
|
|
1533
|
+
content_type__app_label=self.object._meta.app_label,
|
|
1534
|
+
codename=get_permission_codename("add", self.object._meta),
|
|
1535
|
+
)
|
|
1536
|
+
self.user.user_permissions.add(admin_permission, add_permission)
|
|
1537
|
+
|
|
1538
|
+
response = self.client.get(reverse("fctoy-alt2:index"))
|
|
1539
|
+
self.assertEqual(response.status_code, 200)
|
|
1540
|
+
soup = self.get_soup(response.content)
|
|
1541
|
+
title_wrapper = soup.select_one("#listing-results td.title .title-wrapper")
|
|
1542
|
+
self.assertIsNotNone(title_wrapper)
|
|
1543
|
+
|
|
1544
|
+
# fctoy-alt2 doesn't have inspect view enabled, so the title cell should
|
|
1545
|
+
# not link anywhere
|
|
1546
|
+
self.assertIsNone(title_wrapper.select_one("a"))
|
|
1547
|
+
self.assertEqual(title_wrapper.text.strip(), str(self.object))
|
|
1548
|
+
|
|
1549
|
+
# There should be no edit link at all on the page
|
|
1550
|
+
self.assertNotContains(
|
|
1551
|
+
response,
|
|
1552
|
+
reverse("fctoy-alt2:edit", args=[quote(self.object.pk)]),
|
|
1553
|
+
)
|
|
1554
|
+
|
|
1555
|
+
def test_title_cell_links_to_inspect_view_when_no_edit_permission(self):
|
|
1556
|
+
self.user.is_superuser = False
|
|
1557
|
+
self.user.save()
|
|
1558
|
+
admin_permission = Permission.objects.get(
|
|
1559
|
+
content_type__app_label="wagtailadmin",
|
|
1560
|
+
codename="access_admin",
|
|
1561
|
+
)
|
|
1562
|
+
view_permission = Permission.objects.get(
|
|
1563
|
+
content_type__app_label=self.object._meta.app_label,
|
|
1564
|
+
codename=get_permission_codename("view", self.object._meta),
|
|
1565
|
+
)
|
|
1566
|
+
self.user.user_permissions.add(admin_permission, view_permission)
|
|
1567
|
+
|
|
1568
|
+
response = self.client.get(reverse("feature_complete_toy:index"))
|
|
1569
|
+
self.assertEqual(response.status_code, 200)
|
|
1570
|
+
soup = self.get_soup(response.content)
|
|
1571
|
+
title_wrapper = soup.select_one("#listing-results td.title .title-wrapper")
|
|
1572
|
+
self.assertIsNotNone(title_wrapper)
|
|
1573
|
+
link = title_wrapper.select_one("a")
|
|
1574
|
+
self.assertIsNotNone(link)
|
|
1575
|
+
self.assertEqual(link.text.strip(), self.object.name)
|
|
1576
|
+
self.assertEqual(
|
|
1577
|
+
link.get("href"),
|
|
1578
|
+
reverse("feature_complete_toy:inspect", args=[quote(self.object.pk)]),
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
# Should contain the inspect link twice:
|
|
1582
|
+
# once in the title cell and once in the dropdown
|
|
1583
|
+
self.assertContains(
|
|
1584
|
+
response,
|
|
1585
|
+
reverse("feature_complete_toy:inspect", args=[quote(self.object.pk)]),
|
|
1586
|
+
count=2,
|
|
1587
|
+
)
|
|
1588
|
+
|
|
1589
|
+
# There should be no edit link at all on the page
|
|
1590
|
+
self.assertNotContains(
|
|
1591
|
+
response,
|
|
1592
|
+
reverse("feature_complete_toy:edit", args=[quote(self.object.pk)]),
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1467
1595
|
def test_copy_disabled(self):
|
|
1468
1596
|
response = self.client.get(reverse("fctoy_alt1:index"))
|
|
1469
1597
|
|
|
@@ -1508,6 +1636,27 @@ class TestListingButtons(WagtailTestUtils, TestCase):
|
|
|
1508
1636
|
self.assertEqual(rendered_button.attrs.get("aria-label"), aria_label)
|
|
1509
1637
|
self.assertEqual(rendered_button.attrs.get("href"), url)
|
|
1510
1638
|
|
|
1639
|
+
def test_dropdown_not_rendered_when_no_child_buttons_exist(self):
|
|
1640
|
+
self.user.is_superuser = False
|
|
1641
|
+
self.user.save()
|
|
1642
|
+
admin_permission = Permission.objects.get(
|
|
1643
|
+
content_type__app_label="wagtailadmin",
|
|
1644
|
+
codename="access_admin",
|
|
1645
|
+
)
|
|
1646
|
+
add_permission = Permission.objects.get(
|
|
1647
|
+
content_type__app_label=self.object._meta.app_label,
|
|
1648
|
+
codename=get_permission_codename("add", self.object._meta),
|
|
1649
|
+
)
|
|
1650
|
+
self.user.user_permissions.add(admin_permission, add_permission)
|
|
1651
|
+
|
|
1652
|
+
# The alt3 viewset doesn't have "copy" and "inspect" views enabled,
|
|
1653
|
+
# so when only "add" permission is granted, the dropdown should have
|
|
1654
|
+
# no items and thus not be rendered at all
|
|
1655
|
+
response = self.client.get(reverse("fctoy-alt3:index"))
|
|
1656
|
+
soup = self.get_soup(response.content)
|
|
1657
|
+
actions = soup.select_one("tbody tr td ul.actions")
|
|
1658
|
+
self.assertIsNone(actions)
|
|
1659
|
+
|
|
1511
1660
|
|
|
1512
1661
|
class TestCopyView(WagtailTestUtils, TestCase):
|
|
1513
1662
|
def setUp(self):
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
|
|
3
|
+
from wagtail.admin.ui.components import Component
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EditingSessionsModule(Component):
|
|
7
|
+
template_name = "wagtailadmin/shared/editing_sessions/module.html"
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
current_session,
|
|
12
|
+
ping_url,
|
|
13
|
+
release_url,
|
|
14
|
+
other_sessions,
|
|
15
|
+
content_type,
|
|
16
|
+
revision_id=None,
|
|
17
|
+
):
|
|
18
|
+
self.current_session = current_session
|
|
19
|
+
self.ping_url = ping_url
|
|
20
|
+
self.release_url = release_url
|
|
21
|
+
self.sessions_list = EditingSessionsList(
|
|
22
|
+
current_session, other_sessions, content_type
|
|
23
|
+
)
|
|
24
|
+
self.content_type = content_type
|
|
25
|
+
self.revision_id = revision_id
|
|
26
|
+
|
|
27
|
+
def get_context_data(self, parent_context):
|
|
28
|
+
ping_interval = getattr(
|
|
29
|
+
settings,
|
|
30
|
+
"WAGTAIL_EDITING_SESSION_PING_INTERVAL",
|
|
31
|
+
10000,
|
|
32
|
+
)
|
|
33
|
+
return {
|
|
34
|
+
"current_session": self.current_session,
|
|
35
|
+
"ping_url": self.ping_url,
|
|
36
|
+
"release_url": self.release_url,
|
|
37
|
+
"ping_interval": str(ping_interval), # avoid the need to | unlocalize
|
|
38
|
+
"sessions_list": self.sessions_list,
|
|
39
|
+
"content_type": self.content_type,
|
|
40
|
+
"revision_id": self.revision_id,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class EditingSessionsList(Component):
|
|
45
|
+
template_name = "wagtailadmin/shared/editing_sessions/list.html"
|
|
46
|
+
|
|
47
|
+
def __init__(self, current_session, other_sessions, content_type):
|
|
48
|
+
self.current_session = current_session
|
|
49
|
+
self.sessions = other_sessions
|
|
50
|
+
self.content_type = content_type
|
|
51
|
+
|
|
52
|
+
def get_context_data(self, parent_context):
|
|
53
|
+
return {
|
|
54
|
+
"current_session": self.current_session,
|
|
55
|
+
"sessions": self.sessions,
|
|
56
|
+
"content_type": self.content_type,
|
|
57
|
+
}
|
wagtail/admin/urls/__init__.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import functools
|
|
2
|
-
import hashlib
|
|
3
2
|
|
|
4
3
|
from django.conf import settings
|
|
5
4
|
from django.http import Http404
|
|
@@ -13,6 +12,7 @@ from wagtail import hooks
|
|
|
13
12
|
from wagtail.admin.api import urls as api_urls
|
|
14
13
|
from wagtail.admin.auth import require_admin_access
|
|
15
14
|
from wagtail.admin.urls import collections as wagtailadmin_collections_urls
|
|
15
|
+
from wagtail.admin.urls import editing_sessions as wagtailadmin_editing_sessions_urls
|
|
16
16
|
from wagtail.admin.urls import pages as wagtailadmin_pages_urls
|
|
17
17
|
from wagtail.admin.urls import password_reset as wagtailadmin_password_reset_urls
|
|
18
18
|
from wagtail.admin.urls import reports as wagtailadmin_reports_urls
|
|
@@ -111,6 +111,13 @@ urlpatterns = [
|
|
|
111
111
|
dismissibles.DismissiblesView.as_view(),
|
|
112
112
|
name="wagtailadmin_dismissibles",
|
|
113
113
|
),
|
|
114
|
+
path(
|
|
115
|
+
"editing-sessions/",
|
|
116
|
+
include(
|
|
117
|
+
wagtailadmin_editing_sessions_urls,
|
|
118
|
+
namespace="wagtailadmin_editing_sessions",
|
|
119
|
+
),
|
|
120
|
+
),
|
|
114
121
|
]
|
|
115
122
|
|
|
116
123
|
|
|
@@ -124,23 +131,10 @@ for fn in hooks.get_hooks("register_admin_urls"):
|
|
|
124
131
|
# Add "wagtailadmin.access_admin" permission check
|
|
125
132
|
urlpatterns = decorate_urlpatterns(urlpatterns, require_admin_access)
|
|
126
133
|
|
|
127
|
-
sprite_hash = None
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def get_sprite_hash():
|
|
131
|
-
global sprite_hash
|
|
132
|
-
if not sprite_hash:
|
|
133
|
-
content = str(home.sprite(None).content, "utf-8")
|
|
134
|
-
# SECRET_KEY is used to prevent exposing the Wagtail version
|
|
135
|
-
sprite_hash = hashlib.sha1(
|
|
136
|
-
(content + settings.SECRET_KEY).encode("utf-8")
|
|
137
|
-
).hexdigest()[:8]
|
|
138
|
-
return sprite_hash
|
|
139
|
-
|
|
140
134
|
|
|
141
135
|
# These url patterns do not require an authenticated admin user
|
|
142
136
|
urlpatterns += [
|
|
143
|
-
path(
|
|
137
|
+
path("sprite/", home.sprite, name="wagtailadmin_sprite"),
|
|
144
138
|
path("login/", account.LoginView.as_view(), name="wagtailadmin_login"),
|
|
145
139
|
# Password reset
|
|
146
140
|
path("password_reset/", include(wagtailadmin_password_reset_urls)),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.urls import path
|
|
2
|
+
|
|
3
|
+
from wagtail.admin.views.editing_sessions import ping, release
|
|
4
|
+
|
|
5
|
+
app_name = "wagtailadmin_editing_sessions"
|
|
6
|
+
urlpatterns = [
|
|
7
|
+
path(
|
|
8
|
+
"ping/<str:app_label>/<str:model_name>/<str:object_id>/<int:session_id>/",
|
|
9
|
+
ping,
|
|
10
|
+
name="ping",
|
|
11
|
+
),
|
|
12
|
+
path(
|
|
13
|
+
"release/<int:session_id>/",
|
|
14
|
+
release,
|
|
15
|
+
name="release",
|
|
16
|
+
),
|
|
17
|
+
]
|
wagtail/admin/urls/reports.py
CHANGED
|
@@ -11,11 +11,43 @@ from wagtail.admin.views.reports.workflows import WorkflowTasksView, WorkflowVie
|
|
|
11
11
|
app_name = "wagtailadmin_reports"
|
|
12
12
|
urlpatterns = [
|
|
13
13
|
path("locked/", LockedPagesView.as_view(), name="locked_pages"),
|
|
14
|
+
path(
|
|
15
|
+
"locked/results/",
|
|
16
|
+
LockedPagesView.as_view(results_only=True),
|
|
17
|
+
name="locked_pages_results",
|
|
18
|
+
),
|
|
14
19
|
path("workflow/", WorkflowView.as_view(), name="workflow"),
|
|
20
|
+
path(
|
|
21
|
+
"workflow/results/",
|
|
22
|
+
WorkflowView.as_view(results_only=True),
|
|
23
|
+
name="workflow_results",
|
|
24
|
+
),
|
|
15
25
|
path("workflow_tasks/", WorkflowTasksView.as_view(), name="workflow_tasks"),
|
|
26
|
+
path(
|
|
27
|
+
"workflow_tasks/results/",
|
|
28
|
+
WorkflowTasksView.as_view(results_only=True),
|
|
29
|
+
name="workflow_tasks_results",
|
|
30
|
+
),
|
|
16
31
|
path("site-history/", LogEntriesView.as_view(), name="site_history"),
|
|
32
|
+
path(
|
|
33
|
+
"site-history/results/",
|
|
34
|
+
LogEntriesView.as_view(results_only=True),
|
|
35
|
+
name="site_history_results",
|
|
36
|
+
),
|
|
17
37
|
path("aging-pages/", AgingPagesView.as_view(), name="aging_pages"),
|
|
18
38
|
path(
|
|
19
|
-
"
|
|
39
|
+
"aging-pages/results/",
|
|
40
|
+
AgingPagesView.as_view(results_only=True),
|
|
41
|
+
name="aging_pages_results",
|
|
42
|
+
),
|
|
43
|
+
path(
|
|
44
|
+
"page-types-usage/",
|
|
45
|
+
PageTypesUsageReportView.as_view(),
|
|
46
|
+
name="page_types_usage",
|
|
47
|
+
),
|
|
48
|
+
path(
|
|
49
|
+
"page-types-usage/results/",
|
|
50
|
+
PageTypesUsageReportView.as_view(results_only=True),
|
|
51
|
+
name="page_types_usage_results",
|
|
20
52
|
),
|
|
21
53
|
]
|
wagtail/admin/userbar.py
CHANGED
|
@@ -63,30 +63,71 @@ class AccessibilityItem(BaseItem):
|
|
|
63
63
|
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter-examples>`__.
|
|
64
64
|
axe_rules = {}
|
|
65
65
|
|
|
66
|
+
#: A list to add custom Axe rules or override their properties,
|
|
67
|
+
#: alongside with ``axe_custom_checks``. Includes Wagtail’s custom rules.
|
|
68
|
+
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure>`_.
|
|
69
|
+
axe_custom_rules = [
|
|
70
|
+
{
|
|
71
|
+
"id": "alt-text-quality",
|
|
72
|
+
"impact": "serious",
|
|
73
|
+
"selector": "img[alt]",
|
|
74
|
+
"tags": ["best-practice"],
|
|
75
|
+
"any": ["check-image-alt-text"],
|
|
76
|
+
# If omitted, defaults to True and overrides configs in `axe_run_only`.
|
|
77
|
+
"enabled": True,
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
#: A list to add custom Axe checks or override their properties.
|
|
82
|
+
#: Should be used in conjunction with ``axe_custom_rules``.
|
|
83
|
+
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure>`_.
|
|
84
|
+
axe_custom_checks = [
|
|
85
|
+
{
|
|
86
|
+
"id": "check-image-alt-text",
|
|
87
|
+
"options": {"pattern": "\\.(avif|gif|jpg|jpeg|png|svg|webp)$|_"},
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
|
|
66
91
|
#: A dictionary that maps axe-core rule IDs to custom translatable strings
|
|
67
92
|
#: to use as the error messages. If an enabled rule does not exist in this
|
|
68
93
|
#: dictionary, Axe's error message for the rule will be used as fallback.
|
|
69
94
|
axe_messages = {
|
|
70
|
-
"button-name":
|
|
71
|
-
"Button text is empty
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
"button-name": {
|
|
96
|
+
"error_name": _("Button text is empty"),
|
|
97
|
+
"help_text": _("Use meaningful text for screen reader users"),
|
|
98
|
+
},
|
|
99
|
+
"empty-heading": {
|
|
100
|
+
"error_name": _("Empty heading found"),
|
|
101
|
+
"help_text": _("Use meaningful text for screen reader users"),
|
|
102
|
+
},
|
|
103
|
+
"empty-table-header": {
|
|
104
|
+
"error_name": _("Table header text is empty"),
|
|
105
|
+
"help_text": _("Use meaningful text for screen reader users"),
|
|
106
|
+
},
|
|
107
|
+
"frame-title": {
|
|
108
|
+
"error_name": _("Empty frame title found"),
|
|
109
|
+
"help_text": _("Use a meaningful title for screen reader users"),
|
|
110
|
+
},
|
|
111
|
+
"heading-order": {
|
|
112
|
+
"error_name": _("Incorrect heading hierarchy"),
|
|
113
|
+
"help_text": _("Avoid skipping levels"),
|
|
114
|
+
},
|
|
115
|
+
"input-button-name": {
|
|
116
|
+
"error_name": _("Input button text is empty"),
|
|
117
|
+
"help_text": _("Use meaningful text for screen reader users"),
|
|
118
|
+
},
|
|
119
|
+
"link-name": {
|
|
120
|
+
"error_name": _("Link text is empty"),
|
|
121
|
+
"help_text": _("Use meaningful text for screen reader users"),
|
|
122
|
+
},
|
|
123
|
+
"p-as-heading": {
|
|
124
|
+
"error_name": _("Misusing paragraphs as headings"),
|
|
125
|
+
"help_text": _("Use proper heading tags"),
|
|
126
|
+
},
|
|
127
|
+
"alt-text-quality": {
|
|
128
|
+
"error_name": _("Image alt text has inappropriate pattern"),
|
|
129
|
+
"help_text": _("Use meaningful text"),
|
|
130
|
+
},
|
|
90
131
|
}
|
|
91
132
|
|
|
92
133
|
def get_axe_include(self, request):
|
|
@@ -105,6 +146,14 @@ class AccessibilityItem(BaseItem):
|
|
|
105
146
|
"""Returns a dictionary that maps axe-core rule IDs to a dictionary of rule options."""
|
|
106
147
|
return self.axe_rules
|
|
107
148
|
|
|
149
|
+
def get_axe_custom_rules(self, request):
|
|
150
|
+
"""List of rule objects per axe.run API."""
|
|
151
|
+
return self.axe_custom_rules
|
|
152
|
+
|
|
153
|
+
def get_axe_custom_checks(self, request):
|
|
154
|
+
"""List of check objects per axe.run API, without evaluate function."""
|
|
155
|
+
return self.axe_custom_checks
|
|
156
|
+
|
|
108
157
|
def get_axe_messages(self, request):
|
|
109
158
|
"""Returns a dictionary that maps axe-core rule IDs to custom translatable strings."""
|
|
110
159
|
return self.axe_messages
|
|
@@ -139,11 +188,19 @@ class AccessibilityItem(BaseItem):
|
|
|
139
188
|
options.pop("runOnly")
|
|
140
189
|
return options
|
|
141
190
|
|
|
191
|
+
def get_axe_spec(self, request):
|
|
192
|
+
"""Returns spec for Axe, including custom rules and custom checks"""
|
|
193
|
+
return {
|
|
194
|
+
"rules": self.get_axe_custom_rules(request),
|
|
195
|
+
"checks": self.get_axe_custom_checks(request),
|
|
196
|
+
}
|
|
197
|
+
|
|
142
198
|
def get_axe_configuration(self, request):
|
|
143
199
|
return {
|
|
144
200
|
"context": self.get_axe_context(request),
|
|
145
201
|
"options": self.get_axe_options(request),
|
|
146
202
|
"messages": self.get_axe_messages(request),
|
|
203
|
+
"spec": self.get_axe_spec(request),
|
|
147
204
|
}
|
|
148
205
|
|
|
149
206
|
def get_context_data(self, request):
|
wagtail/admin/views/chooser.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import re
|
|
2
|
-
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from urllib.parse import parse_qs, quote, urlencode, urlsplit
|
|
3
4
|
|
|
4
5
|
from django.conf import settings
|
|
5
6
|
from django.core.paginator import InvalidPage, Paginator
|
|
6
7
|
from django.http import Http404
|
|
7
8
|
from django.shortcuts import get_object_or_404
|
|
8
9
|
from django.template.response import TemplateResponse
|
|
10
|
+
from django.urls import NoReverseMatch
|
|
9
11
|
from django.urls.base import reverse
|
|
10
12
|
from django.utils.translation import gettext_lazy as _
|
|
11
13
|
from django.views.generic.base import View
|
|
@@ -629,29 +631,57 @@ class ExternalLinkView(BaseLinkFormView):
|
|
|
629
631
|
if sites is None:
|
|
630
632
|
sites = Site.get_site_root_paths()
|
|
631
633
|
|
|
634
|
+
try:
|
|
635
|
+
# The serve view might not be routed to the root path of the domain,
|
|
636
|
+
# e.g. /pages/, so we need to account for the path to the serve view
|
|
637
|
+
serve_path = reverse("wagtail_serve", args=("",))
|
|
638
|
+
except NoReverseMatch:
|
|
639
|
+
serve_path = None
|
|
640
|
+
|
|
632
641
|
match_relative_paths = submitted_url.startswith("/") and len(sites) == 1
|
|
633
642
|
# We should only match relative urls if there's only a single site
|
|
634
643
|
# Otherwise this could get very annoying accidentally matching coincidentally
|
|
635
644
|
# named pages on different sites
|
|
636
645
|
|
|
646
|
+
possible_sites = defaultdict(list)
|
|
647
|
+
|
|
637
648
|
if match_relative_paths:
|
|
638
|
-
|
|
639
|
-
(
|
|
640
|
-
|
|
649
|
+
for pk, path, url, language_code in sites:
|
|
650
|
+
possible_sites[pk].append(url_without_query)
|
|
651
|
+
|
|
652
|
+
# If the submitted URL is prefixed with the serve path,
|
|
653
|
+
# also consider it without the serve path so we can match
|
|
654
|
+
# the page using Page.route()
|
|
655
|
+
if serve_path and url_without_query.startswith(serve_path):
|
|
656
|
+
possible_sites[pk].append(
|
|
657
|
+
url_without_query[len(serve_path) - 1 :]
|
|
658
|
+
)
|
|
641
659
|
else:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
660
|
+
for pk, path, url, language_code in sites:
|
|
661
|
+
if not submitted_url.startswith(url):
|
|
662
|
+
continue
|
|
663
|
+
possible_sites[pk].append(url_without_query[len(url) :])
|
|
664
|
+
|
|
665
|
+
# If the submitted URL is prefixed with the serve path,
|
|
666
|
+
# also consider it without the serve path so we can match
|
|
667
|
+
# the page using Page.route()
|
|
668
|
+
if serve_path and url_without_query.startswith(url + serve_path):
|
|
669
|
+
possible_sites[pk].append(
|
|
670
|
+
url_without_query[len(url) + len(serve_path) - 1 :]
|
|
671
|
+
)
|
|
647
672
|
|
|
648
673
|
# Loop over possible sites to identify a page match
|
|
649
|
-
for pk,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
674
|
+
for pk, possible_urls in possible_sites.items():
|
|
675
|
+
site = Site.objects.select_related("root_page").get(pk=pk)
|
|
676
|
+
root_page = site.root_page.specific
|
|
677
|
+
for url in possible_urls:
|
|
678
|
+
try:
|
|
679
|
+
route = root_page.route(
|
|
680
|
+
request,
|
|
681
|
+
[component for component in url.split("/") if component],
|
|
682
|
+
)
|
|
683
|
+
except Http404:
|
|
684
|
+
continue
|
|
655
685
|
|
|
656
686
|
matched_page = route.page.specific
|
|
657
687
|
|
|
@@ -704,9 +734,6 @@ class ExternalLinkView(BaseLinkFormView):
|
|
|
704
734
|
},
|
|
705
735
|
)
|
|
706
736
|
|
|
707
|
-
except Http404:
|
|
708
|
-
continue
|
|
709
|
-
|
|
710
737
|
# Otherwise, with no internal matches, fall back to an external url
|
|
711
738
|
return self.render_chosen_response(result)
|
|
712
739
|
else: # form invalid
|
|
@@ -748,9 +775,9 @@ class EmailLinkView(BaseLinkFormView):
|
|
|
748
775
|
"subject": self.form.cleaned_data["subject"],
|
|
749
776
|
"body": self.form.cleaned_data["body"],
|
|
750
777
|
}
|
|
751
|
-
encoded_params =
|
|
778
|
+
encoded_params = urlencode(
|
|
752
779
|
{k: v for k, v in params.items() if v is not None and v != ""},
|
|
753
|
-
quote_via=
|
|
780
|
+
quote_via=quote,
|
|
754
781
|
)
|
|
755
782
|
|
|
756
783
|
url = "mailto:" + self.form.cleaned_data["email_address"]
|
|
@@ -781,11 +808,11 @@ class EmailLinkView(BaseLinkFormView):
|
|
|
781
808
|
def parse_email_link(self, mailto):
|
|
782
809
|
result = {}
|
|
783
810
|
|
|
784
|
-
mail_result =
|
|
811
|
+
mail_result = urlsplit(mailto)
|
|
785
812
|
|
|
786
813
|
result["email"] = mail_result.path
|
|
787
814
|
|
|
788
|
-
query =
|
|
815
|
+
query = parse_qs(mail_result.query)
|
|
789
816
|
result["subject"] = query["subject"][0] if "subject" in query else ""
|
|
790
817
|
result["body"] = query["body"][0] if "body" in query else ""
|
|
791
818
|
|
|
@@ -138,17 +138,6 @@ class Edit(EditView):
|
|
|
138
138
|
instance.move(self.form.cleaned_data["parent"], "sorted-child")
|
|
139
139
|
return instance
|
|
140
140
|
|
|
141
|
-
def get_context_data(self, **kwargs):
|
|
142
|
-
context = super().get_context_data(**kwargs)
|
|
143
|
-
context["can_delete"] = (
|
|
144
|
-
self.permission_policy.instances_user_has_permission_for(
|
|
145
|
-
self.request.user, "delete"
|
|
146
|
-
)
|
|
147
|
-
.filter(pk=self.object.pk)
|
|
148
|
-
.first()
|
|
149
|
-
)
|
|
150
|
-
return context
|
|
151
|
-
|
|
152
141
|
|
|
153
142
|
class Delete(DeleteView):
|
|
154
143
|
permission_policy = collection_permission_policy
|