wagtail 6.0.1__py3-none-any.whl → 6.1rc1__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/admin/checks.py +51 -0
- wagtail/admin/compare.py +1 -1
- wagtail/admin/filters.py +70 -1
- wagtail/admin/forms/account.py +1 -1
- wagtail/admin/forms/collections.py +15 -0
- wagtail/admin/forms/pages.py +49 -0
- wagtail/admin/locale/ca/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/ca/LC_MESSAGES/django.po +122 -0
- wagtail/admin/locale/de/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/de/LC_MESSAGES/django.po +5 -5
- wagtail/admin/locale/en/LC_MESSAGES/django.po +474 -385
- wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +3 -3
- wagtail/admin/locale/es/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/es/LC_MESSAGES/django.po +6 -6
- wagtail/admin/locale/fr/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/fr/LC_MESSAGES/django.po +70 -3
- wagtail/admin/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/he_IL/LC_MESSAGES/django.po +2 -6
- wagtail/admin/locale/he_IL/LC_MESSAGES/djangojs.mo +0 -0
- wagtail/admin/locale/he_IL/LC_MESSAGES/djangojs.po +2 -2
- wagtail/admin/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/hr_HR/LC_MESSAGES/django.po +4 -0
- wagtail/admin/locale/hu/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/hu/LC_MESSAGES/django.po +142 -2
- wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/it/LC_MESSAGES/django.po +80 -8
- wagtail/admin/locale/it/LC_MESSAGES/djangojs.mo +0 -0
- wagtail/admin/locale/it/LC_MESSAGES/djangojs.po +14 -2
- wagtail/admin/locale/lv/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/lv/LC_MESSAGES/django.po +154 -1
- wagtail/admin/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/pt_PT/LC_MESSAGES/django.po +73 -2
- wagtail/admin/locale/ro/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/ro/LC_MESSAGES/django.po +3 -3
- wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/sl/LC_MESSAGES/django.po +145 -2
- wagtail/admin/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/sv/LC_MESSAGES/django.po +77 -3
- wagtail/admin/locale/zh_Hant/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/zh_Hant/LC_MESSAGES/django.po +17 -1
- wagtail/admin/panels/comment_panel.py +1 -1
- wagtail/admin/panels/field_panel.py +1 -1
- wagtail/admin/rich_text/converters/editor_html.py +3 -1
- wagtail/admin/rich_text/editors/draftail/__init__.py +28 -2
- wagtail/admin/static/wagtailadmin/css/core.css +1 -1
- wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
- wagtail/admin/static/wagtailadmin/images/favicon.ico +0 -0
- 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/core.js.LICENSE.txt +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 +1 -1
- 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/telepath.js +1 -1
- wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
- wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +4 -4
- wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
- wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
- wagtail/admin/staticfiles.py +1 -0
- wagtail/admin/templates/wagtailadmin/admin_base.html +1 -0
- wagtail/admin/templates/wagtailadmin/base.html +1 -0
- wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +3 -1
- wagtail/admin/templates/wagtailadmin/collections/edit.html +0 -1
- wagtail/admin/templates/wagtailadmin/collections/index_results.html +10 -0
- wagtail/admin/templates/wagtailadmin/generic/base.html +1 -9
- wagtail/admin/templates/wagtailadmin/generic/form.html +4 -2
- wagtail/admin/templates/wagtailadmin/generic/history/action_cell.html +27 -0
- wagtail/admin/templates/wagtailadmin/generic/index_results.html +8 -0
- wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +3 -4
- wagtail/admin/templates/wagtailadmin/icons/keyboard.svg +1 -0
- wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +3 -1
- wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -15
- wagtail/admin/templates/wagtailadmin/pages/action_menu/save_draft.html +3 -1
- wagtail/admin/templates/wagtailadmin/pages/choose_parent.html +17 -0
- wagtail/admin/templates/wagtailadmin/pages/explorable_index.html +8 -0
- wagtail/admin/templates/wagtailadmin/pages/history.html +1 -61
- wagtail/admin/templates/wagtailadmin/pages/index.html +1 -5
- wagtail/admin/templates/wagtailadmin/pages/listing/_locked_indicator.html +2 -2
- wagtail/admin/templates/wagtailadmin/pages/listing/_page_title_column_header.html +25 -27
- wagtail/admin/templates/wagtailadmin/pages/page_listing_header.html +2 -1
- wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
- wagtail/admin/templates/wagtailadmin/panels/publishing/schedule_publishing_panel.html +3 -1
- wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/active_filters.html +2 -1
- wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +8 -0
- wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/headers/page_edit_header.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +21 -9
- wagtail/admin/templates/wagtailadmin/shared/human_readable_date.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +29 -0
- wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +2 -1
- wagtail/admin/templates/wagtailadmin/skeleton.html +2 -1
- wagtail/admin/templates/wagtailadmin/tables/related_objects_cell.html +9 -0
- wagtail/admin/templates/wagtailadmin/tables/title_cell.html +9 -7
- wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html +1 -1
- wagtail/admin/templates/wagtailadmin/workflows/create.html +6 -23
- wagtail/admin/templates/wagtailadmin/workflows/create_task.html +6 -15
- wagtail/admin/templates/wagtailadmin/workflows/edit.html +6 -23
- wagtail/admin/templates/wagtailadmin/workflows/edit_task.html +6 -13
- wagtail/admin/templates/wagtailadmin/workflows/includes/task_usage_cell.html +4 -4
- wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_tasks_cell.html +18 -0
- wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_title_cell.html +7 -0
- wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_used_by_cell.html +25 -0
- wagtail/admin/templates/wagtailadmin/workflows/index.html +0 -99
- wagtail/admin/templates/wagtailadmin/workflows/index_results.html +10 -0
- wagtail/admin/templates/wagtailadmin/workflows/task_index.html +0 -30
- wagtail/admin/templates/wagtailadmin/workflows/task_index_results.html +10 -0
- wagtail/admin/templates/wagtailadmin/workflows/usage.html +1 -1
- wagtail/admin/templatetags/wagtailadmin_tags.py +116 -39
- wagtail/admin/tests/api/test_pages.py +26 -10
- wagtail/admin/tests/pages/test_create_page.py +10 -4
- wagtail/admin/tests/pages/test_custom_listing.py +37 -0
- wagtail/admin/tests/pages/test_edit_page.py +6 -6
- wagtail/admin/tests/pages/test_explorer_view.py +19 -18
- wagtail/admin/tests/pages/test_move_page.py +1 -1
- wagtail/admin/tests/pages/test_page_usage.py +50 -2
- wagtail/admin/tests/pages/test_parent_page_chooser_view.py +119 -0
- wagtail/admin/tests/pages/test_preview.py +18 -4
- wagtail/admin/tests/test_account_management.py +20 -1
- wagtail/admin/tests/test_audit_log.py +172 -5
- wagtail/admin/tests/test_checks.py +92 -0
- wagtail/admin/tests/test_collections_views.py +19 -5
- wagtail/admin/tests/test_compare.py +6 -6
- wagtail/admin/tests/test_dashboard.py +404 -0
- wagtail/admin/tests/test_dbwhitelister.py +4 -5
- wagtail/admin/tests/test_edit_handlers.py +2 -2
- wagtail/admin/tests/test_keyboard_shortcuts.py +84 -0
- wagtail/admin/tests/test_page_chooser.py +31 -18
- wagtail/admin/tests/test_privacy.py +36 -2
- wagtail/admin/tests/test_rich_text.py +168 -23
- wagtail/admin/tests/test_templatetags.py +411 -43
- wagtail/admin/tests/test_views.py +4 -2
- wagtail/admin/tests/test_workflows.py +531 -9
- wagtail/admin/tests/tests.py +3 -1
- wagtail/admin/tests/ui/test_tables.py +48 -1
- wagtail/admin/tests/viewsets/test_model_viewset.py +130 -23
- wagtail/admin/ui/side_panels.py +3 -1
- wagtail/admin/ui/tables/__init__.py +13 -1
- wagtail/admin/ui/tables/pages.py +17 -6
- wagtail/admin/urls/__init__.py +8 -3
- wagtail/admin/urls/pages.py +5 -0
- wagtail/admin/urls/workflows.py +10 -0
- wagtail/admin/views/chooser.py +20 -24
- wagtail/admin/views/collections.py +17 -1
- wagtail/admin/views/generic/base.py +34 -4
- wagtail/admin/views/generic/history.py +220 -51
- wagtail/admin/views/generic/mixins.py +7 -4
- wagtail/admin/views/generic/models.py +54 -47
- wagtail/admin/views/generic/multiple_upload.py +17 -8
- wagtail/admin/views/generic/usage.py +17 -11
- wagtail/admin/views/home.py +15 -12
- wagtail/admin/views/mixins.py +30 -0
- wagtail/admin/views/pages/choose_parent.py +73 -0
- wagtail/admin/views/pages/history.py +54 -66
- wagtail/admin/views/pages/listing.py +187 -106
- wagtail/admin/views/pages/usage.py +6 -1
- wagtail/admin/views/pages/utils.py +70 -1
- wagtail/admin/views/workflows.py +150 -21
- wagtail/admin/viewsets/model.py +2 -2
- wagtail/admin/viewsets/pages.py +77 -0
- wagtail/admin/wagtail_hooks.py +40 -2
- wagtail/admin/widgets/button.py +10 -10
- wagtail/api/v2/filters.py +1 -1
- wagtail/api/v2/tests/test_pages.py +1 -1
- wagtail/blocks/base.py +18 -9
- wagtail/blocks/field_block.py +9 -7
- wagtail/blocks/list_block.py +16 -6
- wagtail/blocks/static_block.py +3 -0
- wagtail/blocks/stream_block.py +58 -23
- wagtail/blocks/struct_block.py +15 -9
- wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +39 -47
- wagtail/contrib/forms/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/forms/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/forms/models.py +5 -5
- wagtail/contrib/forms/templates/wagtailforms/list_submissions.html +44 -33
- wagtail/contrib/forms/templates/wagtailforms/submissions_index.html +2 -63
- wagtail/contrib/forms/tests/test_models.py +26 -0
- wagtail/contrib/forms/urls.py +6 -0
- wagtail/contrib/forms/views.py +52 -49
- wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.po +3 -3
- wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +34 -42
- wagtail/contrib/redirects/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/redirects/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/redirects/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/redirects/signal_handlers.py +1 -1
- wagtail/contrib/redirects/templates/wagtailredirects/index.html +1 -36
- wagtail/contrib/redirects/templates/wagtailredirects/index_results.html +18 -0
- wagtail/contrib/redirects/templates/wagtailredirects/redirect_target_cell.html +8 -0
- wagtail/contrib/redirects/tests/test_import_command.py +1 -1
- wagtail/contrib/redirects/tests/test_redirects.py +79 -8
- wagtail/contrib/redirects/urls.py +2 -1
- wagtail/contrib/redirects/views.py +85 -55
- wagtail/contrib/search_promotions/admin_urls.py +2 -1
- wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +41 -64
- wagtail/contrib/search_promotions/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/search_promotions/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/search_promotions/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/search_promotions/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/search_promotions/locale/hr_HR/LC_MESSAGES/django.po +41 -2
- wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.po +9 -3
- wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +1 -16
- wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index_results.html +11 -0
- wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/list.html +0 -51
- wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/results.html +3 -16
- wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/search_promotion_column.html +15 -0
- wagtail/contrib/search_promotions/tests.py +122 -9
- wagtail/contrib/search_promotions/views.py +66 -65
- wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
- wagtail/contrib/settings/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/settings/locale/tr/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/tr/LC_MESSAGES/django.po +6 -2
- wagtail/contrib/settings/registry.py +10 -5
- wagtail/contrib/settings/tests/generic/test_admin.py +9 -0
- wagtail/contrib/settings/tests/site_specific/test_admin.py +10 -1
- wagtail/contrib/settings/tests/site_specific/test_model.py +3 -3
- wagtail/contrib/settings/tests/site_specific/test_templates.py +1 -1
- wagtail/contrib/settings/views.py +3 -1
- wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/simple_translation/tests/test_wagtail_hooks.py +2 -2
- wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
- wagtail/contrib/styleguide/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/styleguide/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/table_block/blocks.py +2 -2
- wagtail/contrib/table_block/locale/ca/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/table_block/locale/ca/LC_MESSAGES/django.po +27 -2
- wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/table_block/locale/hu/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/table_block/locale/hu/LC_MESSAGES/django.po +27 -2
- wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.po +27 -2
- wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
- wagtail/contrib/table_block/tests.py +6 -0
- wagtail/contrib/typed_table_block/locale/ca/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/typed_table_block/locale/ca/LC_MESSAGES/django.po +12 -2
- wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/typed_table_block/locale/hu/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/typed_table_block/locale/hu/LC_MESSAGES/django.po +12 -2
- wagtail/contrib/typed_table_block/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/typed_table_block/locale/it/LC_MESSAGES/django.po +12 -2
- wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
- wagtail/coreutils.py +3 -2
- wagtail/documents/admin_urls.py +2 -2
- wagtail/documents/locale/en/LC_MESSAGES/django.po +22 -22
- wagtail/documents/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/documents/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/documents/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/hr_HR/LC_MESSAGES/django.po +19 -2
- wagtail/documents/locale/hu/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/hu/LC_MESSAGES/django.po +16 -2
- wagtail/documents/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/it/LC_MESSAGES/django.po +19 -2
- wagtail/documents/migrations/0013_delete_uploadeddocument.py +16 -0
- wagtail/documents/models.py +1 -20
- wagtail/documents/rich_text/__init__.py +11 -7
- 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/templates/wagtaildocs/documents/index.html +0 -16
- wagtail/documents/tests/test_admin_views.py +155 -23
- wagtail/documents/tests/test_collection_privacy.py +55 -1
- wagtail/documents/tests/test_rich_text.py +14 -0
- wagtail/documents/views/documents.py +25 -22
- wagtail/documents/views/multiple.py +6 -7
- wagtail/documents/views/serve.py +16 -1
- wagtail/documents/wagtail_hooks.py +20 -15
- wagtail/embeds/blocks.py +5 -0
- wagtail/embeds/locale/en/LC_MESSAGES/django.po +2 -2
- wagtail/embeds/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/embeds/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/embeds/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/embeds/rich_text/__init__.py +1 -1
- wagtail/embeds/tests/test_rich_text.py +14 -0
- wagtail/embeds/wagtail_hooks.py +4 -14
- wagtail/fields.py +3 -48
- wagtail/images/admin_urls.py +2 -2
- wagtail/images/check_files/wagtail.jpg +0 -0
- wagtail/images/check_files/wagtail.png +0 -0
- wagtail/images/fields.py +2 -0
- wagtail/images/image_operations.py +1 -1
- wagtail/images/locale/ca/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/ca/LC_MESSAGES/django.po +12 -0
- wagtail/images/locale/en/LC_MESSAGES/django.po +33 -45
- wagtail/images/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/images/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/images/locale/hu/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/hu/LC_MESSAGES/django.po +28 -2
- wagtail/images/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/it/LC_MESSAGES/django.po +14 -2
- wagtail/images/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/pt_PT/LC_MESSAGES/django.po +4 -0
- wagtail/images/migrations/0026_delete_uploadedimage.py +16 -0
- wagtail/images/models.py +49 -43
- wagtail/images/rich_text/__init__.py +18 -8
- 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/image_listing_header.html +6 -0
- wagtail/images/templates/wagtailimages/images/index.html +11 -51
- wagtail/images/tests/test_admin_views.py +119 -62
- wagtail/images/tests/test_image_operations.py +10 -0
- wagtail/images/tests/test_models.py +35 -33
- wagtail/images/tests/test_rich_text.py +14 -0
- wagtail/images/tests/utils.py +1 -1
- wagtail/images/views/images.py +35 -64
- wagtail/images/views/multiple.py +6 -7
- wagtail/images/wagtail_hooks.py +4 -14
- wagtail/locale/en/LC_MESSAGES/django.po +150 -136
- wagtail/locale/es/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/es/LC_MESSAGES/django.po +3 -2
- wagtail/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/it/LC_MESSAGES/django.po +5 -5
- wagtail/locale/sl/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/sl/LC_MESSAGES/django.po +27 -2
- wagtail/locales/locale/ar/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/be/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/bg/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/ca/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/cs/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/cy/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/da/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/de/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/el/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/es/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/et/LC_MESSAGES/django.po +2 -2
- wagtail/locales/locale/fa/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/fi/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/fr/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/gl/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/locales/locale/he_IL/LC_MESSAGES/django.po +3 -3
- wagtail/locales/locale/hr_HR/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/hu/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/id_ID/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/is_IS/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/it/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/ja/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/ko/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/lt/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/lv/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/mi/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/mn/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/my/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/nb/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/nl/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/pl/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/pt_BR/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/pt_PT/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/ro/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/ru/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/sk_SK/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/sl/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/sv/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/tet/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/th/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/tr/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/tr_TR/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/uk/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/vi/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/zh/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/zh_Hans/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/zh_Hant/LC_MESSAGES/django.po +1 -1
- wagtail/locales/tests.py +18 -3
- wagtail/locales/views.py +0 -1
- wagtail/management/commands/rebuild_references_index.py +3 -1
- wagtail/migrations/0092_alter_collectionviewrestriction_password_and_more.py +33 -0
- wagtail/migrations/0093_uploadedfile.py +53 -0
- wagtail/models/__init__.py +147 -32
- wagtail/models/i18n.py +1 -1
- wagtail/models/{collections.py → media.py} +33 -2
- wagtail/models/reference_index.py +1 -1
- wagtail/models/view_restrictions.py +10 -3
- wagtail/project_template/project_name/settings/base.py +6 -0
- wagtail/project_template/requirements.txt +1 -1
- wagtail/rich_text/__init__.py +25 -8
- wagtail/rich_text/pages.py +19 -8
- wagtail/rich_text/rewriters.py +140 -68
- wagtail/search/backends/database/mysql/mysql.py +3 -3
- wagtail/search/backends/database/postgres/postgres.py +3 -3
- wagtail/search/backends/database/sqlite/sqlite.py +2 -2
- wagtail/search/backends/elasticsearch7.py +4 -0
- wagtail/search/locale/en/LC_MESSAGES/django.po +3 -3
- wagtail/search/tests/test_postgres_backend.py +50 -0
- wagtail/sites/locale/en/LC_MESSAGES/django.po +8 -8
- wagtail/sites/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/sites/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/sites/locale/ro/LC_MESSAGES/django.mo +0 -0
- wagtail/sites/locale/ro/LC_MESSAGES/django.po +3 -2
- wagtail/sites/tests.py +35 -9
- wagtail/sites/views.py +3 -1
- wagtail/snippets/locale/de/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/de/LC_MESSAGES/django.po +7 -8
- wagtail/snippets/locale/en/LC_MESSAGES/django.po +16 -56
- wagtail/snippets/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/snippets/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/snippets/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/hr_HR/LC_MESSAGES/django.po +6 -2
- wagtail/snippets/locale/lv/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/lv/LC_MESSAGES/django.po +12 -0
- wagtail/snippets/locale/zh_Hant/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/zh_Hant/LC_MESSAGES/django.po +4 -0
- 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/publish.html +3 -1
- wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/save.html +3 -1
- wagtail/snippets/templates/wagtailsnippets/snippets/create.html +2 -3
- wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +2 -3
- wagtail/snippets/tests/test_preview.py +13 -2
- wagtail/snippets/tests/test_snippets.py +41 -16
- wagtail/snippets/tests/test_viewset.py +95 -18
- wagtail/snippets/tests/test_workflows.py +12 -0
- wagtail/snippets/views/snippets.py +1 -40
- wagtail/templatetags/wagtailcore_tags.py +1 -1
- wagtail/test/demosite/models.py +1 -1
- wagtail/test/middleware.py +14 -1
- wagtail/test/testapp/fixtures/test.json +20 -0
- wagtail/test/testapp/migrations/0001_squashed_0073_revisablechildmodel_secret_text.py +8 -8
- wagtail/test/testapp/migrations/0023_snippetchoosermodel_full_featured.py +1 -0
- wagtail/test/testapp/migrations/0034_custompermissionmodel.py +44 -0
- wagtail/test/testapp/migrations/0035_modelwithcustommanager.py +30 -0
- wagtail/test/testapp/migrations/0036_complexdefaultstreampage.py +28 -0
- wagtail/test/testapp/models.py +79 -2
- wagtail/test/testapp/templates/tests/custom_docs_password_required.html +10 -0
- wagtail/test/testapp/templates/tests/custom_page_password_required.html +10 -0
- wagtail/test/testapp/views.py +24 -2
- wagtail/test/testapp/wagtail_hooks.py +19 -0
- wagtail/test/utils/wagtail_tests.py +2 -2
- wagtail/tests/test_blocks.py +262 -1
- wagtail/tests/test_migrations.py +1 -1
- wagtail/tests/test_page_model.py +77 -0
- wagtail/tests/test_page_privacy.py +18 -1
- wagtail/tests/test_rich_text.py +95 -5
- wagtail/tests/test_streamfield.py +43 -0
- wagtail/tests/test_utils.py +8 -2
- wagtail/tests/test_views.py +52 -1
- wagtail/tests/test_whitelist.py +7 -7
- wagtail/users/forms.py +3 -1
- wagtail/users/locale/en/LC_MESSAGES/django.po +124 -96
- wagtail/users/locale/fr/LC_MESSAGES/django.po +2 -2
- wagtail/users/locale/he_IL/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/he_IL/LC_MESSAGES/django.po +2 -2
- wagtail/users/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/hr_HR/LC_MESSAGES/django.po +13 -2
- wagtail/users/migrations/0013_userprofile_density.py +23 -0
- wagtail/users/models.py +14 -3
- wagtail/users/templates/wagtailusers/groups/create.html +1 -7
- wagtail/users/templates/wagtailusers/groups/edit.html +1 -13
- wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +46 -2
- wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -3
- wagtail/users/templates/wagtailusers/users/create.html +1 -14
- wagtail/users/templates/wagtailusers/users/edit.html +1 -14
- wagtail/users/templates/wagtailusers/users/index.html +2 -5
- wagtail/users/templates/wagtailusers/users/index_results.html +3 -13
- wagtail/users/templates/wagtailusers/users/user_cell.html +9 -0
- wagtail/users/templatetags/wagtailusers_tags.py +107 -20
- wagtail/users/tests/test_admin_views.py +669 -90
- wagtail/users/views/groups.py +58 -61
- wagtail/users/views/users.py +211 -92
- wagtail/users/wagtail_hooks.py +6 -38
- wagtail/users/widgets.py +3 -5
- wagtail/utils/text.py +1 -1
- wagtail/views.py +5 -9
- wagtail/whitelist.py +1 -1
- {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/METADATA +5 -6
- {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/RECORD +496 -477
- wagtail/admin/static/wagtailadmin/js/page-editor.js +0 -1
- wagtail/admin/static/wagtailadmin/js/vendor/mousetrap.min.js +0 -1
- wagtail/admin/static/wagtailadmin/js/vendor/urlify.js +0 -1
- wagtail/admin/static/wagtailadmin/js/vendor/xregexp.min.js +0 -1
- wagtail/admin/templates/wagtailadmin/collections/index.html +0 -34
- wagtail/admin/templates/wagtailadmin/pages/revisions/_actions.html +0 -22
- wagtail/admin/templates/wagtailadmin/shared/page_breadcrumbs.html +0 -55
- wagtail/admin/tests/pages/test_dashboard.py +0 -172
- wagtail/contrib/redirects/templates/wagtailredirects/results.html +0 -23
- wagtail/documents/templates/wagtaildocs/documents/list.html +0 -2
- wagtail/search/tests/test_postgres_stemming.py +0 -40
- wagtail/sites/templates/wagtailsites/create.html +0 -7
- wagtail/sites/templates/wagtailsites/edit.html +0 -7
- wagtail/snippets/templates/wagtailsnippets/snippets/revisions/_actions.html +0 -36
- wagtail/users/templates/wagtailusers/users/list.html +0 -62
- wagtail/users/urls/users.py +0 -12
- {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/LICENSE +0 -0
- {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/WHEEL +0 -0
- {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/entry_points.txt +0 -0
- {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/top_level.txt +0 -0
|
@@ -3,18 +3,28 @@ import unittest.mock
|
|
|
3
3
|
from django import forms
|
|
4
4
|
from django.apps import apps
|
|
5
5
|
from django.conf import settings
|
|
6
|
+
from django.contrib.admin.utils import quote
|
|
6
7
|
from django.contrib.auth import get_user_model
|
|
7
8
|
from django.contrib.auth.models import Group, Permission
|
|
9
|
+
from django.contrib.contenttypes.models import ContentType
|
|
8
10
|
from django.core.exceptions import ImproperlyConfigured
|
|
9
11
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
10
12
|
from django.db.models import Q
|
|
11
13
|
from django.http import HttpRequest, HttpResponse
|
|
14
|
+
from django.template import RequestContext, Template
|
|
12
15
|
from django.test import TestCase, override_settings
|
|
13
16
|
from django.urls import reverse
|
|
17
|
+
from django.utils import timezone
|
|
18
|
+
from django.utils.text import capfirst
|
|
14
19
|
|
|
15
20
|
from wagtail import hooks
|
|
16
21
|
from wagtail.admin.admin_url_finder import AdminURLFinder
|
|
22
|
+
from wagtail.admin.models import Admin
|
|
23
|
+
from wagtail.admin.staticfiles import versioned_static
|
|
24
|
+
from wagtail.admin.widgets.button import ButtonWithDropdown
|
|
17
25
|
from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
|
|
26
|
+
from wagtail.coreutils import get_dummy_request
|
|
27
|
+
from wagtail.log_actions import log
|
|
18
28
|
from wagtail.models import (
|
|
19
29
|
Collection,
|
|
20
30
|
GroupCollectionPermission,
|
|
@@ -29,7 +39,10 @@ from wagtail.users.permission_order import register as register_permission_order
|
|
|
29
39
|
from wagtail.users.views.groups import GroupViewSet
|
|
30
40
|
from wagtail.users.views.users import get_user_creation_form, get_user_edit_form
|
|
31
41
|
from wagtail.users.wagtail_hooks import get_group_viewset_cls
|
|
42
|
+
from wagtail.users.widgets import UserListingButton
|
|
43
|
+
from wagtail.utils.deprecation import RemovedInWagtail70Warning
|
|
32
44
|
|
|
45
|
+
add_user_perm_codename = f"add_{AUTH_USER_MODEL_NAME.lower()}"
|
|
33
46
|
delete_user_perm_codename = f"delete_{AUTH_USER_MODEL_NAME.lower()}"
|
|
34
47
|
change_user_perm_codename = f"change_{AUTH_USER_MODEL_NAME.lower()}"
|
|
35
48
|
|
|
@@ -111,75 +124,23 @@ class TestGroupUsersView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
111
124
|
)
|
|
112
125
|
|
|
113
126
|
def test_simple(self):
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
with self.assertWarnsMessage(
|
|
128
|
+
RemovedInWagtail70Warning,
|
|
129
|
+
"Accessing the list of users in a group via "
|
|
130
|
+
f"/admin/groups/{self.test_group.pk}/users/ is deprecated, use "
|
|
131
|
+
f"/admin/users/?group={self.test_group.pk} instead.",
|
|
132
|
+
):
|
|
133
|
+
response = self.get()
|
|
134
|
+
|
|
135
|
+
self.assertRedirects(
|
|
136
|
+
response,
|
|
137
|
+
reverse("wagtailusers_users:index") + f"?group={self.test_group.pk}",
|
|
138
|
+
)
|
|
121
139
|
|
|
122
140
|
def test_inexisting_group(self):
|
|
123
141
|
response = self.get(group_id=9999)
|
|
124
142
|
self.assertEqual(response.status_code, 404)
|
|
125
143
|
|
|
126
|
-
def test_search(self):
|
|
127
|
-
response = self.get({"q": "Hello"})
|
|
128
|
-
self.assertEqual(response.status_code, 200)
|
|
129
|
-
self.assertEqual(response.context["query_string"], "Hello")
|
|
130
|
-
|
|
131
|
-
def test_search_query_one_field(self):
|
|
132
|
-
response = self.get({"q": "first name"})
|
|
133
|
-
self.assertEqual(response.status_code, 200)
|
|
134
|
-
results = response.context["users"]
|
|
135
|
-
self.assertIn(self.test_user, results)
|
|
136
|
-
|
|
137
|
-
def test_search_query_multiple_fields(self):
|
|
138
|
-
response = self.get({"q": "first name last name"})
|
|
139
|
-
self.assertEqual(response.status_code, 200)
|
|
140
|
-
results = response.context["users"]
|
|
141
|
-
self.assertIn(self.test_user, results)
|
|
142
|
-
|
|
143
|
-
def test_pagination(self):
|
|
144
|
-
# page numbers in range should be accepted
|
|
145
|
-
response = self.get({"p": 1})
|
|
146
|
-
self.assertEqual(response.status_code, 200)
|
|
147
|
-
# page numbers out of range should return 404
|
|
148
|
-
response = self.get({"p": 9999})
|
|
149
|
-
self.assertEqual(response.status_code, 404)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
class TestGroupUsersResultsView(WagtailTestUtils, TestCase):
|
|
153
|
-
def setUp(self):
|
|
154
|
-
# create a user that should be visible in the listing
|
|
155
|
-
self.test_user = self.create_user(
|
|
156
|
-
username="testuser",
|
|
157
|
-
email="testuser@email.com",
|
|
158
|
-
password="password",
|
|
159
|
-
first_name="First Name",
|
|
160
|
-
last_name="Last Name",
|
|
161
|
-
)
|
|
162
|
-
self.test_group = Group.objects.create(name="Test Group")
|
|
163
|
-
self.test_user.groups.add(self.test_group)
|
|
164
|
-
self.login()
|
|
165
|
-
|
|
166
|
-
def get(self, params={}, group_id=None):
|
|
167
|
-
return self.client.get(
|
|
168
|
-
reverse(
|
|
169
|
-
"wagtailusers_groups:users_results",
|
|
170
|
-
args=(group_id or self.test_group.pk,),
|
|
171
|
-
),
|
|
172
|
-
params,
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
def test_simple(self):
|
|
176
|
-
response = self.get()
|
|
177
|
-
self.assertEqual(response.status_code, 200)
|
|
178
|
-
self.assertTemplateUsed(response, "wagtailusers/users/index_results.html")
|
|
179
|
-
self.assertContains(response, "testuser")
|
|
180
|
-
# response should contain not page furniture
|
|
181
|
-
self.assertNotContains(response, "Add a user")
|
|
182
|
-
|
|
183
144
|
|
|
184
145
|
class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
185
146
|
def setUp(self):
|
|
@@ -191,7 +152,7 @@ class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
191
152
|
first_name="First Name",
|
|
192
153
|
last_name="Last Name",
|
|
193
154
|
)
|
|
194
|
-
self.login()
|
|
155
|
+
self.user = self.login()
|
|
195
156
|
|
|
196
157
|
def get(self, params={}):
|
|
197
158
|
return self.client.get(reverse("wagtailusers_users:index"), params)
|
|
@@ -203,7 +164,10 @@ class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
203
164
|
self.assertContains(response, "testuser")
|
|
204
165
|
# response should contain page furniture, including the "Add a user" button
|
|
205
166
|
self.assertContains(response, "Add a user")
|
|
206
|
-
self.
|
|
167
|
+
self.assertBreadcrumbsItemsRendered(
|
|
168
|
+
[{"url": "", "label": capfirst(User._meta.verbose_name_plural)}],
|
|
169
|
+
response.content,
|
|
170
|
+
)
|
|
207
171
|
|
|
208
172
|
@unittest.skipIf(
|
|
209
173
|
settings.AUTH_USER_MODEL == "emailuser.EmailUser", "Negative UUID not possible"
|
|
@@ -241,21 +205,106 @@ class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
241
205
|
response = self.get({"p": 9999})
|
|
242
206
|
self.assertEqual(response.status_code, 404)
|
|
243
207
|
|
|
244
|
-
def
|
|
208
|
+
def test_ordering(self):
|
|
245
209
|
# checking that only valid ordering used, in case of `IndexView` the valid
|
|
246
|
-
# ordering fields are
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
#
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
210
|
+
# ordering fields are:
|
|
211
|
+
# - `name`: maps to `User.last_name` and `User.first_name` fields if available
|
|
212
|
+
# - `User.USERNAME_FIELD`: dynamically maps to User.USERNAME_FIELD
|
|
213
|
+
# - `is_superuser`: maps to User.is_superuser (from PermissionsMixin)
|
|
214
|
+
# - `is_active`: maps to User.is_active if available
|
|
215
|
+
# - `last_login`: maps to User.last_login (from AbstractBaseUser)
|
|
216
|
+
cases = {
|
|
217
|
+
"name": ("last_name", "first_name"),
|
|
218
|
+
"-name": ("-last_name", "-first_name"),
|
|
219
|
+
User.USERNAME_FIELD: (User.USERNAME_FIELD,),
|
|
220
|
+
f"-{User.USERNAME_FIELD}": (f"-{User.USERNAME_FIELD}",),
|
|
221
|
+
"is_superuser": ("is_superuser",),
|
|
222
|
+
"-is_superuser": ("-is_superuser",),
|
|
223
|
+
"is_active": ("is_active",),
|
|
224
|
+
"-is_active": ("-is_active",),
|
|
225
|
+
"last_login": ("last_login",),
|
|
226
|
+
"-last_login": ("-last_login",),
|
|
227
|
+
}
|
|
228
|
+
for param, order_by in cases.items():
|
|
229
|
+
with self.subTest(param=param):
|
|
230
|
+
response = self.get({"ordering": param})
|
|
231
|
+
self.assertEqual(
|
|
232
|
+
response.context_data["object_list"].query.order_by,
|
|
233
|
+
order_by,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def test_filters(self):
|
|
237
|
+
response = self.get()
|
|
238
|
+
self.assertEqual(response.status_code, 200)
|
|
239
|
+
self.assertCountEqual(
|
|
240
|
+
response.context["object_list"],
|
|
241
|
+
[self.test_user, self.user],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
response = self.get({"is_superuser": True})
|
|
245
|
+
self.assertEqual(response.status_code, 200)
|
|
246
|
+
self.assertCountEqual(response.context["object_list"], [self.user])
|
|
247
|
+
|
|
248
|
+
response = self.get({"is_superuser": False})
|
|
249
|
+
self.assertEqual(response.status_code, 200)
|
|
250
|
+
self.assertCountEqual(response.context["object_list"], [self.test_user])
|
|
251
|
+
|
|
252
|
+
self.test_user.is_active = False
|
|
253
|
+
self.test_user.save()
|
|
254
|
+
|
|
255
|
+
response = self.get({"is_active": True})
|
|
256
|
+
self.assertEqual(response.status_code, 200)
|
|
257
|
+
self.assertCountEqual(response.context["object_list"], [self.user])
|
|
258
|
+
|
|
259
|
+
response = self.get({"is_active": False})
|
|
260
|
+
self.assertEqual(response.status_code, 200)
|
|
261
|
+
self.assertCountEqual(response.context["object_list"], [self.test_user])
|
|
262
|
+
|
|
263
|
+
now = timezone.now()
|
|
264
|
+
if timezone.is_aware(now):
|
|
265
|
+
today = timezone.localtime(now).date()
|
|
266
|
+
else:
|
|
267
|
+
today = now.date()
|
|
268
|
+
tomorrow = today + timezone.timedelta(days=1)
|
|
269
|
+
yesterday = today - timezone.timedelta(days=1)
|
|
270
|
+
|
|
271
|
+
response = self.get({"last_login_from": str(today)})
|
|
272
|
+
self.assertEqual(response.status_code, 200)
|
|
273
|
+
self.assertCountEqual(response.context["object_list"], [self.user])
|
|
274
|
+
|
|
275
|
+
response = self.get({"last_login_from": str(tomorrow)})
|
|
276
|
+
self.assertEqual(response.status_code, 200)
|
|
277
|
+
self.assertCountEqual(response.context["object_list"], [])
|
|
278
|
+
|
|
279
|
+
response = self.get({"last_login_to": str(today)})
|
|
280
|
+
self.assertEqual(response.status_code, 200)
|
|
281
|
+
self.assertCountEqual(response.context["object_list"], [self.user])
|
|
282
|
+
|
|
283
|
+
response = self.get({"last_login_to": str(yesterday)})
|
|
284
|
+
self.assertEqual(response.status_code, 200)
|
|
285
|
+
self.assertCountEqual(response.context["object_list"], [])
|
|
286
|
+
|
|
287
|
+
musicians = Group.objects.create(name="Musicians")
|
|
288
|
+
songwriters = Group.objects.create(name="Songwriters")
|
|
289
|
+
self.test_user.groups.add(musicians)
|
|
290
|
+
self.user.groups.add(songwriters)
|
|
291
|
+
|
|
292
|
+
response = self.get({"group": musicians.pk})
|
|
293
|
+
self.assertEqual(response.status_code, 200)
|
|
294
|
+
self.assertCountEqual(response.context["object_list"], [self.test_user])
|
|
295
|
+
|
|
296
|
+
response = self.get({"group": [musicians.pk, songwriters.pk]})
|
|
297
|
+
self.assertEqual(response.status_code, 200)
|
|
298
|
+
self.assertCountEqual(
|
|
299
|
+
response.context["object_list"],
|
|
300
|
+
[self.test_user, self.user],
|
|
301
|
+
)
|
|
253
302
|
|
|
254
303
|
def test_num_queries(self):
|
|
255
304
|
# Warm up
|
|
256
305
|
self.get()
|
|
257
306
|
|
|
258
|
-
num_queries =
|
|
307
|
+
num_queries = 10
|
|
259
308
|
with self.assertNumQueries(num_queries):
|
|
260
309
|
self.get()
|
|
261
310
|
|
|
@@ -264,8 +313,77 @@ class TestUserIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
264
313
|
with self.assertNumQueries(num_queries):
|
|
265
314
|
self.get()
|
|
266
315
|
|
|
316
|
+
def test_default_buttons(self):
|
|
317
|
+
response = self.get()
|
|
318
|
+
soup = self.get_soup(response.content)
|
|
319
|
+
dropdown_buttons = soup.select("li [data-controller='w-dropdown'] a")
|
|
320
|
+
expected_urls = [
|
|
321
|
+
reverse("wagtailusers_users:edit", args=(self.user.pk,)),
|
|
322
|
+
reverse("wagtailusers_users:copy", args=(self.user.pk,)),
|
|
323
|
+
# Should not link to delete page for the current user
|
|
324
|
+
reverse("wagtailusers_users:edit", args=(self.test_user.pk,)),
|
|
325
|
+
reverse("wagtailusers_users:copy", args=(self.test_user.pk,)),
|
|
326
|
+
reverse("wagtailusers_users:delete", args=(self.test_user.pk,)),
|
|
327
|
+
]
|
|
328
|
+
urls = [button.attrs.get("href") for button in dropdown_buttons]
|
|
329
|
+
self.assertSequenceEqual(urls, expected_urls)
|
|
330
|
+
|
|
331
|
+
def test_buttons_hook(self):
|
|
332
|
+
def hook(user, request_user):
|
|
333
|
+
self.assertEqual(request_user, self.user)
|
|
334
|
+
yield UserListingButton(
|
|
335
|
+
"Show profile",
|
|
336
|
+
f"/goes/to/a/url/{user.pk}",
|
|
337
|
+
priority=30,
|
|
338
|
+
)
|
|
339
|
+
yield ButtonWithDropdown(
|
|
340
|
+
label="Moar pls!",
|
|
341
|
+
buttons=[UserListingButton("Alrighty", "/cheers", priority=10)],
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
with self.register_hook("register_user_listing_buttons", hook):
|
|
345
|
+
response = self.get()
|
|
267
346
|
|
|
268
|
-
|
|
347
|
+
self.assertEqual(response.status_code, 200)
|
|
348
|
+
self.assertTemplateUsed(response, "wagtailadmin/shared/buttons.html")
|
|
349
|
+
|
|
350
|
+
soup = self.get_soup(response.content)
|
|
351
|
+
row = soup.select_one(f"tbody tr:has([data-object-id='{self.test_user.pk}'])")
|
|
352
|
+
self.assertIsNotNone(row)
|
|
353
|
+
|
|
354
|
+
profile_url = f"/goes/to/a/url/{self.test_user.pk}"
|
|
355
|
+
actions = row.select_one("td ul.actions")
|
|
356
|
+
top_level_custom_button = actions.select_one(f"li > a[href='{profile_url}']")
|
|
357
|
+
self.assertIsNone(top_level_custom_button)
|
|
358
|
+
custom_button = actions.select_one(
|
|
359
|
+
f"li [data-controller='w-dropdown'] a[href='{profile_url}']"
|
|
360
|
+
)
|
|
361
|
+
self.assertIsNotNone(custom_button)
|
|
362
|
+
self.assertEqual(
|
|
363
|
+
custom_button.text.strip(),
|
|
364
|
+
"Show profile",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
nested_dropdown = actions.select_one(
|
|
368
|
+
"li [data-controller='w-dropdown'] [data-controller='w-dropdown']"
|
|
369
|
+
)
|
|
370
|
+
self.assertIsNone(nested_dropdown)
|
|
371
|
+
dropdown_buttons = actions.select("li > [data-controller='w-dropdown']")
|
|
372
|
+
# Default "More" button and the custom "Moar pls!" button
|
|
373
|
+
self.assertEqual(len(dropdown_buttons), 2)
|
|
374
|
+
custom_dropdown = None
|
|
375
|
+
for button in dropdown_buttons:
|
|
376
|
+
if "Moar pls!" in button.text.strip():
|
|
377
|
+
custom_dropdown = button
|
|
378
|
+
self.assertIsNotNone(custom_dropdown)
|
|
379
|
+
self.assertEqual(custom_dropdown.select_one("button").text.strip(), "Moar pls!")
|
|
380
|
+
# Should contain the custom button inside the custom dropdown
|
|
381
|
+
custom_button = custom_dropdown.find("a", attrs={"href": "/cheers"})
|
|
382
|
+
self.assertIsNotNone(custom_button)
|
|
383
|
+
self.assertEqual(custom_button.text.strip(), "Alrighty")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class TestUserIndexResultsView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
269
387
|
def setUp(self):
|
|
270
388
|
# create a user that should be visible in the listing
|
|
271
389
|
self.test_user = self.create_user(
|
|
@@ -286,7 +404,7 @@ class TestUserIndexResultsView(WagtailTestUtils, TestCase):
|
|
|
286
404
|
self.assertTemplateUsed(response, "wagtailusers/users/index_results.html")
|
|
287
405
|
self.assertContains(response, "testuser")
|
|
288
406
|
# response should not contain page furniture
|
|
289
|
-
self.
|
|
407
|
+
self.assertBreadcrumbsNotRendered(response.content)
|
|
290
408
|
|
|
291
409
|
|
|
292
410
|
class TestUserCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
@@ -307,7 +425,16 @@ class TestUserCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
307
425
|
self.assertTemplateUsed(response, "wagtailusers/users/create.html")
|
|
308
426
|
self.assertContains(response, "Password")
|
|
309
427
|
self.assertContains(response, "Password confirmation")
|
|
310
|
-
self.
|
|
428
|
+
self.assertBreadcrumbsItemsRendered(
|
|
429
|
+
[
|
|
430
|
+
{
|
|
431
|
+
"url": "/admin/users/",
|
|
432
|
+
"label": capfirst(User._meta.verbose_name_plural),
|
|
433
|
+
},
|
|
434
|
+
{"url": "", "label": f"New: {capfirst(User._meta.verbose_name)}"},
|
|
435
|
+
],
|
|
436
|
+
response.content,
|
|
437
|
+
)
|
|
311
438
|
|
|
312
439
|
def test_create(self):
|
|
313
440
|
response = self.post(
|
|
@@ -846,13 +973,44 @@ class TestUserEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
846
973
|
self.assertTemplateUsed(response, "wagtailusers/users/edit.html")
|
|
847
974
|
self.assertContains(response, "Password")
|
|
848
975
|
self.assertContains(response, "Password confirmation")
|
|
849
|
-
self.
|
|
976
|
+
self.assertBreadcrumbsItemsRendered(
|
|
977
|
+
[
|
|
978
|
+
{
|
|
979
|
+
"url": "/admin/users/",
|
|
980
|
+
"label": capfirst(User._meta.verbose_name_plural),
|
|
981
|
+
},
|
|
982
|
+
{"url": "", "label": "Original User"},
|
|
983
|
+
],
|
|
984
|
+
response.content,
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
soup = self.get_soup(response.content)
|
|
988
|
+
header = soup.select_one(".w-slim-header")
|
|
989
|
+
history_url = reverse("wagtailusers_users:history", args=(self.test_user.pk,))
|
|
990
|
+
history_link = header.find("a", attrs={"href": history_url})
|
|
991
|
+
self.assertIsNotNone(history_link)
|
|
850
992
|
|
|
851
993
|
url_finder = AdminURLFinder(self.current_user)
|
|
852
|
-
expected_url = "/admin/users
|
|
994
|
+
expected_url = f"/admin/users/edit/{self.test_user.pk}/"
|
|
853
995
|
self.assertEqual(url_finder.get_edit_url(self.test_user), expected_url)
|
|
854
996
|
|
|
855
|
-
def
|
|
997
|
+
def test_legacy_url_redirect(self):
|
|
998
|
+
with self.assertWarnsMessage(
|
|
999
|
+
RemovedInWagtail70Warning,
|
|
1000
|
+
(
|
|
1001
|
+
"UserViewSet's `/<pk>/` edit view URL pattern has been "
|
|
1002
|
+
"deprecated in favour of /edit/<pk>/."
|
|
1003
|
+
),
|
|
1004
|
+
):
|
|
1005
|
+
response = self.client.get(f"/admin/users/{self.test_user.pk}/")
|
|
1006
|
+
|
|
1007
|
+
self.assertRedirects(
|
|
1008
|
+
response,
|
|
1009
|
+
f"/admin/users/edit/{self.test_user.pk}/",
|
|
1010
|
+
status_code=301,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
def test_nonexistent_redirect(self):
|
|
856
1014
|
invalid_id = (
|
|
857
1015
|
"99999999-9999-9999-9999-999999999999"
|
|
858
1016
|
if settings.AUTH_USER_MODEL == "emailuser.EmailUser"
|
|
@@ -885,6 +1043,23 @@ class TestUserEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
885
1043
|
else:
|
|
886
1044
|
self.assertContains(response, "User 'testuser' updated.")
|
|
887
1045
|
|
|
1046
|
+
# On next load of the edit view,
|
|
1047
|
+
# should render the status panel with the last updated time
|
|
1048
|
+
response = self.get()
|
|
1049
|
+
self.assertContains(response, "Edited User")
|
|
1050
|
+
soup = self.get_soup(response.content)
|
|
1051
|
+
status_panel = soup.select_one('[data-side-panel="status"]')
|
|
1052
|
+
self.assertIsNotNone(status_panel)
|
|
1053
|
+
last_updated = status_panel.select_one(".w-help-text")
|
|
1054
|
+
self.assertIsNotNone(last_updated)
|
|
1055
|
+
self.assertRegex(
|
|
1056
|
+
last_updated.get_text(strip=True),
|
|
1057
|
+
f"[0-9][0-9]:[0-9][0-9] by {self.current_user.get_username()}",
|
|
1058
|
+
)
|
|
1059
|
+
history_url = reverse("wagtailusers_users:history", args=(self.test_user.pk,))
|
|
1060
|
+
history_link = status_panel.select_one(f'a[href="{history_url}"]')
|
|
1061
|
+
self.assertIsNotNone(history_link)
|
|
1062
|
+
|
|
888
1063
|
def test_password_optional(self):
|
|
889
1064
|
"""Leaving password fields blank should leave it unchanged"""
|
|
890
1065
|
response = self.post(
|
|
@@ -1248,6 +1423,61 @@ class TestUserEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1248
1423
|
self.assertEqual(response.content, b"Overridden!")
|
|
1249
1424
|
|
|
1250
1425
|
|
|
1426
|
+
class TestUserCopyView(WagtailTestUtils, TestCase):
|
|
1427
|
+
def setUp(self):
|
|
1428
|
+
self.user = self.login()
|
|
1429
|
+
|
|
1430
|
+
@classmethod
|
|
1431
|
+
def setUpTestData(cls):
|
|
1432
|
+
cls.test_user = cls.create_user(
|
|
1433
|
+
username="testuser",
|
|
1434
|
+
email="testuser@email.com",
|
|
1435
|
+
first_name="Original",
|
|
1436
|
+
last_name="User",
|
|
1437
|
+
password="password",
|
|
1438
|
+
)
|
|
1439
|
+
cls.url = reverse("wagtailusers_users:copy", args=[quote(cls.test_user.pk)])
|
|
1440
|
+
|
|
1441
|
+
def test_without_permission(self):
|
|
1442
|
+
self.user.is_superuser = False
|
|
1443
|
+
self.user.save()
|
|
1444
|
+
admin_permission = Permission.objects.get(
|
|
1445
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
1446
|
+
)
|
|
1447
|
+
self.user.user_permissions.add(admin_permission)
|
|
1448
|
+
|
|
1449
|
+
response = self.client.get(self.url)
|
|
1450
|
+
self.assertEqual(response.status_code, 302)
|
|
1451
|
+
self.assertRedirects(response, reverse("wagtailadmin_home"))
|
|
1452
|
+
|
|
1453
|
+
def test_with_minimal_permission(self):
|
|
1454
|
+
self.user.is_superuser = False
|
|
1455
|
+
self.user.save()
|
|
1456
|
+
self.user.user_permissions.add(
|
|
1457
|
+
Permission.objects.get(
|
|
1458
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
1459
|
+
),
|
|
1460
|
+
Permission.objects.get(
|
|
1461
|
+
content_type__app_label=AUTH_USER_APP_LABEL,
|
|
1462
|
+
codename=add_user_perm_codename,
|
|
1463
|
+
),
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
# Form should be prefilled
|
|
1467
|
+
response = self.client.get(self.url)
|
|
1468
|
+
self.assertEqual(response.status_code, 200)
|
|
1469
|
+
soup = self.get_soup(response.content)
|
|
1470
|
+
first_name = soup.select_one('input[name="first_name"]')
|
|
1471
|
+
self.assertEqual(first_name.attrs.get("value"), "Original")
|
|
1472
|
+
last_name = soup.select_one('input[name="last_name"]')
|
|
1473
|
+
self.assertEqual(last_name.attrs.get("value"), "User")
|
|
1474
|
+
# Password fields should be empty
|
|
1475
|
+
password1 = soup.select_one('input[name="password1"]')
|
|
1476
|
+
password2 = soup.select_one('input[name="password2"]')
|
|
1477
|
+
self.assertIsNone(password1.attrs.get("value"))
|
|
1478
|
+
self.assertIsNone(password2.attrs.get("value"))
|
|
1479
|
+
|
|
1480
|
+
|
|
1251
1481
|
class TestUserProfileCreation(WagtailTestUtils, TestCase):
|
|
1252
1482
|
def setUp(self):
|
|
1253
1483
|
# Create a user
|
|
@@ -1326,6 +1556,32 @@ class TestUserEditViewForNonSuperuser(WagtailTestUtils, TestCase):
|
|
|
1326
1556
|
self.assertIs(user.is_superuser, False)
|
|
1327
1557
|
|
|
1328
1558
|
|
|
1559
|
+
class TestUserHistoryView(WagtailTestUtils, TestCase):
|
|
1560
|
+
# More thorough tests are in test_model_viewset
|
|
1561
|
+
|
|
1562
|
+
@classmethod
|
|
1563
|
+
def setUpTestData(cls):
|
|
1564
|
+
cls.test_user = cls.create_user(
|
|
1565
|
+
username="testuser",
|
|
1566
|
+
email="testuser@email.com",
|
|
1567
|
+
first_name="Original",
|
|
1568
|
+
last_name="User",
|
|
1569
|
+
password="password",
|
|
1570
|
+
)
|
|
1571
|
+
cls.url = reverse("wagtailusers_users:history", args=(cls.test_user.pk,))
|
|
1572
|
+
|
|
1573
|
+
def setUp(self):
|
|
1574
|
+
self.user = self.login()
|
|
1575
|
+
|
|
1576
|
+
def test_simple(self):
|
|
1577
|
+
log(self.test_user, "wagtail.create", user=self.user)
|
|
1578
|
+
log(self.test_user, "wagtail.edit", user=self.user)
|
|
1579
|
+
response = self.client.get(self.url)
|
|
1580
|
+
self.assertTemplateUsed("wagtailadmin/generic/listing.html")
|
|
1581
|
+
self.assertContains(response, "Created")
|
|
1582
|
+
self.assertContains(response, "Edited")
|
|
1583
|
+
|
|
1584
|
+
|
|
1329
1585
|
class TestGroupIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
1330
1586
|
def setUp(self):
|
|
1331
1587
|
self.login()
|
|
@@ -1340,7 +1596,9 @@ class TestGroupIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1340
1596
|
self.assertTemplateUsed(response, "wagtailadmin/generic/index.html")
|
|
1341
1597
|
# response should contain page furniture, including the "Add a group" button
|
|
1342
1598
|
self.assertContains(response, "Add a group")
|
|
1343
|
-
self.
|
|
1599
|
+
self.assertBreadcrumbsItemsRendered(
|
|
1600
|
+
[{"url": "", "label": "Groups"}], response.content
|
|
1601
|
+
)
|
|
1344
1602
|
|
|
1345
1603
|
def test_search(self):
|
|
1346
1604
|
response = self.get({"q": "Hello"})
|
|
@@ -1413,7 +1671,24 @@ class TestGroupCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1413
1671
|
response = self.get()
|
|
1414
1672
|
self.assertEqual(response.status_code, 200)
|
|
1415
1673
|
self.assertTemplateUsed(response, "wagtailusers/groups/create.html")
|
|
1416
|
-
self.
|
|
1674
|
+
self.assertBreadcrumbsItemsRendered(
|
|
1675
|
+
[
|
|
1676
|
+
{"url": "/admin/groups/", "label": "Groups"},
|
|
1677
|
+
{"url": "", "label": "New: Group"},
|
|
1678
|
+
],
|
|
1679
|
+
response.content,
|
|
1680
|
+
)
|
|
1681
|
+
# Should contain the JS from the form and the template include
|
|
1682
|
+
page_chooser_js = versioned_static("wagtailadmin/js/page-chooser.js")
|
|
1683
|
+
group_form_js = versioned_static("wagtailusers/js/group-form.js")
|
|
1684
|
+
self.assertContains(response, page_chooser_js)
|
|
1685
|
+
self.assertContains(response, group_form_js)
|
|
1686
|
+
|
|
1687
|
+
def test_num_queries(self):
|
|
1688
|
+
# Warm up the cache
|
|
1689
|
+
self.get()
|
|
1690
|
+
with self.assertNumQueries(20):
|
|
1691
|
+
self.get()
|
|
1417
1692
|
|
|
1418
1693
|
def test_create_group(self):
|
|
1419
1694
|
response = self.post({"name": "test group"})
|
|
@@ -1527,6 +1802,13 @@ class TestGroupCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1527
1802
|
| Q(codename__startswith="publish")
|
|
1528
1803
|
).delete()
|
|
1529
1804
|
|
|
1805
|
+
# A custom permission that happens to also start with "change"
|
|
1806
|
+
Permission.objects.filter(
|
|
1807
|
+
codename="change_text",
|
|
1808
|
+
content_type__app_label="tests",
|
|
1809
|
+
content_type__model="custompermissionmodel",
|
|
1810
|
+
).delete()
|
|
1811
|
+
|
|
1530
1812
|
response = self.get()
|
|
1531
1813
|
|
|
1532
1814
|
self.assertInHTML("Custom permissions", response.content.decode(), count=0)
|
|
@@ -1567,6 +1849,102 @@ class TestGroupCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1567
1849
|
# Should not show inputs for publish permissions on models without DraftStateMixin
|
|
1568
1850
|
self.assertNotInHTML("Can publish advert", html)
|
|
1569
1851
|
|
|
1852
|
+
def test_strip_model_name_from_custom_permissions(self):
|
|
1853
|
+
"""
|
|
1854
|
+
https://github.com/wagtail/wagtail/issues/10982
|
|
1855
|
+
Ensure model name or verbose name is stripped from permissions' labels
|
|
1856
|
+
for consistency with built-in permissions.
|
|
1857
|
+
"""
|
|
1858
|
+
response = self.get()
|
|
1859
|
+
|
|
1860
|
+
self.assertContains(response, "Can bulk update")
|
|
1861
|
+
self.assertContains(response, "Can start trouble")
|
|
1862
|
+
self.assertContains(response, "Cause chaos for")
|
|
1863
|
+
self.assertContains(response, "Change text")
|
|
1864
|
+
self.assertContains(response, "Manage")
|
|
1865
|
+
self.assertNotContains(response, "Can bulk_update")
|
|
1866
|
+
self.assertNotContains(response, "Can bulk update ADVANCED permission model")
|
|
1867
|
+
self.assertNotContains(response, "Cause chaos for advanced permission model")
|
|
1868
|
+
self.assertNotContains(response, "Manage custom permission model")
|
|
1869
|
+
|
|
1870
|
+
def test_permission_with_same_action(self):
|
|
1871
|
+
"""
|
|
1872
|
+
https://github.com/wagtail/wagtail/issues/11650
|
|
1873
|
+
Ensure that permissions with the same action (part before the first _ in
|
|
1874
|
+
the codename) are not hidden.
|
|
1875
|
+
"""
|
|
1876
|
+
response = self.get()
|
|
1877
|
+
soup = self.get_soup(response.content)
|
|
1878
|
+
main_change_permission = Permission.objects.get(
|
|
1879
|
+
codename="change_custompermissionmodel",
|
|
1880
|
+
content_type__app_label="tests",
|
|
1881
|
+
content_type__model="custompermissionmodel",
|
|
1882
|
+
)
|
|
1883
|
+
custom_change_permission = Permission.objects.get(
|
|
1884
|
+
codename="change_text",
|
|
1885
|
+
content_type__app_label="tests",
|
|
1886
|
+
content_type__model="custompermissionmodel",
|
|
1887
|
+
)
|
|
1888
|
+
|
|
1889
|
+
# Main change permission is in the dedicated column, so it's directly
|
|
1890
|
+
# inside a <td>, not inside a <fieldset>"
|
|
1891
|
+
self.assertIsNotNone(
|
|
1892
|
+
soup.select_one(f'td > input[value="{main_change_permission.pk}"]')
|
|
1893
|
+
)
|
|
1894
|
+
self.assertIsNone(
|
|
1895
|
+
soup.select_one(f'td > fieldset input[value="{main_change_permission.pk}"]')
|
|
1896
|
+
)
|
|
1897
|
+
|
|
1898
|
+
# Custom "change_text" permission is in the custom permissions column,
|
|
1899
|
+
# so it's inside a <fieldset> and not directly inside a <td>
|
|
1900
|
+
self.assertIsNone(
|
|
1901
|
+
soup.select_one(f'td > input[value="{custom_change_permission.pk}"]')
|
|
1902
|
+
)
|
|
1903
|
+
self.assertIsNotNone(
|
|
1904
|
+
soup.select_one(
|
|
1905
|
+
f'td > fieldset input[value="{custom_change_permission.pk}"]'
|
|
1906
|
+
)
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
def test_custom_other_permissions_with_wagtail_admin_content_type(self):
|
|
1910
|
+
"""
|
|
1911
|
+
https://github.com/wagtail/wagtail/issues/8086
|
|
1912
|
+
Allow custom permissions using Wagtail's Admin content type to be
|
|
1913
|
+
displayed in the "Other permissions" section.
|
|
1914
|
+
"""
|
|
1915
|
+
admin_ct = ContentType.objects.get_for_model(Admin)
|
|
1916
|
+
custom_permission = Permission.objects.create(
|
|
1917
|
+
codename="roadmap_sync",
|
|
1918
|
+
name="Can sync roadmap items from GitHub",
|
|
1919
|
+
content_type=admin_ct,
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
with self.register_hook(
|
|
1923
|
+
"register_permissions",
|
|
1924
|
+
lambda: Permission.objects.filter(
|
|
1925
|
+
codename="roadmap_sync", content_type=admin_ct
|
|
1926
|
+
),
|
|
1927
|
+
):
|
|
1928
|
+
response = self.get()
|
|
1929
|
+
|
|
1930
|
+
soup = self.get_soup(response.content)
|
|
1931
|
+
|
|
1932
|
+
other_permissions = soup.select_one("#other-permissions-section")
|
|
1933
|
+
self.assertIsNotNone(other_permissions)
|
|
1934
|
+
|
|
1935
|
+
custom_checkbox = other_permissions.select_one(
|
|
1936
|
+
f'input[value="{custom_permission.pk}"]'
|
|
1937
|
+
)
|
|
1938
|
+
self.assertIsNotNone(custom_checkbox)
|
|
1939
|
+
|
|
1940
|
+
custom_label = other_permissions.select_one(
|
|
1941
|
+
f'label[for="{custom_checkbox.attrs.get("id")}"]'
|
|
1942
|
+
)
|
|
1943
|
+
self.assertIsNotNone(custom_label)
|
|
1944
|
+
self.assertEqual(
|
|
1945
|
+
custom_label.get_text(strip=True), "Can sync roadmap items from GitHub"
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1570
1948
|
|
|
1571
1949
|
class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
1572
1950
|
def setUp(self):
|
|
@@ -1655,13 +2033,39 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1655
2033
|
response = self.get()
|
|
1656
2034
|
self.assertEqual(response.status_code, 200)
|
|
1657
2035
|
self.assertTemplateUsed(response, "wagtailusers/groups/edit.html")
|
|
1658
|
-
self.
|
|
2036
|
+
self.assertBreadcrumbsItemsRendered(
|
|
2037
|
+
[
|
|
2038
|
+
{
|
|
2039
|
+
"url": "/admin/groups/",
|
|
2040
|
+
"label": "Groups",
|
|
2041
|
+
},
|
|
2042
|
+
{"url": "", "label": str(self.test_group)},
|
|
2043
|
+
],
|
|
2044
|
+
response.content,
|
|
2045
|
+
)
|
|
2046
|
+
# Should contain the JS from the form and the template include
|
|
2047
|
+
page_chooser_js = versioned_static("wagtailadmin/js/page-chooser.js")
|
|
2048
|
+
group_form_js = versioned_static("wagtailusers/js/group-form.js")
|
|
2049
|
+
self.assertContains(response, page_chooser_js)
|
|
2050
|
+
self.assertContains(response, group_form_js)
|
|
2051
|
+
|
|
2052
|
+
soup = self.get_soup(response.content)
|
|
2053
|
+
header = soup.select_one(".w-slim-header")
|
|
2054
|
+
history_url = reverse("wagtailusers_groups:history", args=(self.test_group.pk,))
|
|
2055
|
+
history_link = header.find("a", attrs={"href": history_url})
|
|
2056
|
+
self.assertIsNotNone(history_link)
|
|
1659
2057
|
|
|
1660
2058
|
url_finder = AdminURLFinder(self.user)
|
|
1661
2059
|
expected_url = "/admin/groups/edit/%d/" % self.test_group.id
|
|
1662
2060
|
self.assertEqual(url_finder.get_edit_url(self.test_group), expected_url)
|
|
1663
2061
|
|
|
1664
|
-
def
|
|
2062
|
+
def test_num_queries(self):
|
|
2063
|
+
# Warm up the cache
|
|
2064
|
+
self.get()
|
|
2065
|
+
with self.assertNumQueries(32):
|
|
2066
|
+
self.get()
|
|
2067
|
+
|
|
2068
|
+
def test_nonexistent_group_redirect(self):
|
|
1665
2069
|
self.assertEqual(self.get(group_id=100000).status_code, 404)
|
|
1666
2070
|
|
|
1667
2071
|
def test_group_edit(self):
|
|
@@ -1674,6 +2078,23 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1674
2078
|
group = Group.objects.get(pk=self.test_group.pk)
|
|
1675
2079
|
self.assertEqual(group.name, "test group edited")
|
|
1676
2080
|
|
|
2081
|
+
# On next load of the edit view,
|
|
2082
|
+
# should render the status panel with the last updated time
|
|
2083
|
+
response = self.get()
|
|
2084
|
+
self.assertContains(response, "test group edited")
|
|
2085
|
+
soup = self.get_soup(response.content)
|
|
2086
|
+
status_panel = soup.select_one('[data-side-panel="status"]')
|
|
2087
|
+
self.assertIsNotNone(status_panel)
|
|
2088
|
+
last_updated = status_panel.select_one(".w-help-text")
|
|
2089
|
+
self.assertIsNotNone(last_updated)
|
|
2090
|
+
self.assertRegex(
|
|
2091
|
+
last_updated.get_text(strip=True),
|
|
2092
|
+
f"[0-9][0-9]:[0-9][0-9] by {self.user.get_username()}",
|
|
2093
|
+
)
|
|
2094
|
+
history_url = reverse("wagtailusers_groups:history", args=(self.test_group.pk,))
|
|
2095
|
+
history_link = status_panel.select_one(f'a[href="{history_url}"]')
|
|
2096
|
+
self.assertIsNotNone(history_link)
|
|
2097
|
+
|
|
1677
2098
|
def test_group_edit_validation_error(self):
|
|
1678
2099
|
# Leave "name" field blank. This should give a validation error
|
|
1679
2100
|
response = self.post({"name": ""})
|
|
@@ -1971,12 +2392,21 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1971
2392
|
|
|
1972
2393
|
response = self.get()
|
|
1973
2394
|
|
|
1974
|
-
self.
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2395
|
+
soup = self.get_soup(response.content)
|
|
2396
|
+
checkbox = soup.find_all(
|
|
2397
|
+
"input",
|
|
2398
|
+
attrs={
|
|
2399
|
+
"name": "permissions",
|
|
2400
|
+
"checked": True,
|
|
2401
|
+
"value": custom_permission.id,
|
|
2402
|
+
"data-action": "w-bulk#toggle",
|
|
2403
|
+
"data-w-bulk-group-param": "custom",
|
|
2404
|
+
"data-w-bulk-target": "item",
|
|
2405
|
+
},
|
|
1978
2406
|
)
|
|
1979
2407
|
|
|
2408
|
+
self.assertEqual(len(checkbox), 1)
|
|
2409
|
+
|
|
1980
2410
|
def test_show_publish_permissions(self):
|
|
1981
2411
|
response = self.get()
|
|
1982
2412
|
html = response.content.decode()
|
|
@@ -2029,7 +2459,13 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
2029
2459
|
perm.content_type.model,
|
|
2030
2460
|
)
|
|
2031
2461
|
for perm_set in object_perms
|
|
2032
|
-
for perm in [
|
|
2462
|
+
for perm in [
|
|
2463
|
+
next(
|
|
2464
|
+
v
|
|
2465
|
+
for v in flatten(perm_set)
|
|
2466
|
+
if isinstance(v, dict) and "perm" in v
|
|
2467
|
+
)["perm"]
|
|
2468
|
+
]
|
|
2033
2469
|
]
|
|
2034
2470
|
|
|
2035
2471
|
# Set order on two objects, should appear first and second
|
|
@@ -2072,6 +2508,52 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
2072
2508
|
msg="Default object permission order is incorrect",
|
|
2073
2509
|
)
|
|
2074
2510
|
|
|
2511
|
+
def test_data_attributes_for_bulk_selection(self):
|
|
2512
|
+
response = self.get()
|
|
2513
|
+
soup = self.get_soup(response.content)
|
|
2514
|
+
|
|
2515
|
+
table = soup.find("table", "listing")
|
|
2516
|
+
self.assertIn(table["data-controller"], "w-bulk")
|
|
2517
|
+
|
|
2518
|
+
# confirm there is a single select all checkbox for all items
|
|
2519
|
+
toggle_all = table.select('tfoot th input[data-w-bulk-target="all"]')
|
|
2520
|
+
self.assertEqual(len(toggle_all), 1)
|
|
2521
|
+
self.assertEqual(toggle_all[0]["data-action"], "w-bulk#toggleAll")
|
|
2522
|
+
|
|
2523
|
+
# confirm there is one 'add' select all checkbox
|
|
2524
|
+
toggle_all_add = table.select(
|
|
2525
|
+
'tfoot td input[data-w-bulk-target="all"][data-w-bulk-group-param="add"]'
|
|
2526
|
+
)
|
|
2527
|
+
self.assertEqual(len(toggle_all_add), 1)
|
|
2528
|
+
self.assertEqual(toggle_all_add[0]["data-action"], "w-bulk#toggleAll")
|
|
2529
|
+
|
|
2530
|
+
# confirm that the individual object permissions have the correct attributes
|
|
2531
|
+
toggle_add_items = table.select(
|
|
2532
|
+
'tbody td input[data-w-bulk-target="item"][data-w-bulk-group-param="add"]'
|
|
2533
|
+
)
|
|
2534
|
+
self.assertGreaterEqual(len(toggle_add_items), 30)
|
|
2535
|
+
self.assertEqual(toggle_add_items[0]["data-action"], "w-bulk#toggle")
|
|
2536
|
+
|
|
2537
|
+
|
|
2538
|
+
class TestGroupHistoryView(WagtailTestUtils, TestCase):
|
|
2539
|
+
# More thorough tests are in test_model_viewset
|
|
2540
|
+
|
|
2541
|
+
@classmethod
|
|
2542
|
+
def setUpTestData(cls):
|
|
2543
|
+
cls.test_group = Group.objects.create(name="test group")
|
|
2544
|
+
cls.url = reverse("wagtailusers_groups:history", args=(cls.test_group.pk,))
|
|
2545
|
+
|
|
2546
|
+
def setUp(self):
|
|
2547
|
+
self.user = self.login()
|
|
2548
|
+
|
|
2549
|
+
def test_simple(self):
|
|
2550
|
+
log(self.test_group, "wagtail.create", user=self.user)
|
|
2551
|
+
log(self.test_group, "wagtail.edit", user=self.user)
|
|
2552
|
+
response = self.client.get(self.url)
|
|
2553
|
+
self.assertTemplateUsed("wagtailadmin/generic/listing.html")
|
|
2554
|
+
self.assertContains(response, "Created")
|
|
2555
|
+
self.assertContains(response, "Edited")
|
|
2556
|
+
|
|
2075
2557
|
|
|
2076
2558
|
class TestGroupViewSet(TestCase):
|
|
2077
2559
|
def setUp(self):
|
|
@@ -2368,3 +2850,100 @@ class TestAuthorisationDeleteView(WagtailTestUtils, TestCase):
|
|
|
2368
2850
|
self.assertRedirects(response, reverse("wagtailusers_users:index"))
|
|
2369
2851
|
user = get_user_model().objects.filter(email="test_user@email.com")
|
|
2370
2852
|
self.assertFalse(user.exists())
|
|
2853
|
+
|
|
2854
|
+
|
|
2855
|
+
class TestTemplateTags(WagtailTestUtils, TestCase):
|
|
2856
|
+
@classmethod
|
|
2857
|
+
def setUpTestData(cls):
|
|
2858
|
+
cls.user = cls.create_superuser("admin")
|
|
2859
|
+
cls.request = get_dummy_request()
|
|
2860
|
+
cls.request.user = cls.user
|
|
2861
|
+
cls.test_user = cls.create_user(
|
|
2862
|
+
username="testuser",
|
|
2863
|
+
email="testuser@email.com",
|
|
2864
|
+
password="password",
|
|
2865
|
+
)
|
|
2866
|
+
|
|
2867
|
+
def test_user_listing_buttons(self):
|
|
2868
|
+
template = """
|
|
2869
|
+
{% load wagtailusers_tags %}
|
|
2870
|
+
{% for user in users %}
|
|
2871
|
+
<ul class="actions">
|
|
2872
|
+
{% user_listing_buttons user %}
|
|
2873
|
+
</ul>
|
|
2874
|
+
{% endfor %}
|
|
2875
|
+
"""
|
|
2876
|
+
|
|
2877
|
+
def hook(user, request_user):
|
|
2878
|
+
self.assertEqual(user, self.test_user)
|
|
2879
|
+
self.assertEqual(request_user, self.user)
|
|
2880
|
+
yield UserListingButton(
|
|
2881
|
+
"Show profile",
|
|
2882
|
+
f"/goes/to/a/url/{user.pk}",
|
|
2883
|
+
priority=30,
|
|
2884
|
+
)
|
|
2885
|
+
|
|
2886
|
+
with self.register_hook("register_user_listing_buttons", hook):
|
|
2887
|
+
with self.assertWarnsMessage(
|
|
2888
|
+
RemovedInWagtail70Warning,
|
|
2889
|
+
"`user_listing_buttons` template tag is deprecated.",
|
|
2890
|
+
):
|
|
2891
|
+
html = Template(template).render(
|
|
2892
|
+
RequestContext(self.request, {"users": [self.test_user]})
|
|
2893
|
+
)
|
|
2894
|
+
|
|
2895
|
+
soup = self.get_soup(html)
|
|
2896
|
+
|
|
2897
|
+
profile_url = f"/goes/to/a/url/{self.test_user.pk}"
|
|
2898
|
+
top_level_custom_button = soup.select_one(f"li > a[href='{profile_url}']")
|
|
2899
|
+
self.assertIsNotNone(top_level_custom_button)
|
|
2900
|
+
self.assertEqual(
|
|
2901
|
+
top_level_custom_button.text.strip(),
|
|
2902
|
+
"Show profile",
|
|
2903
|
+
)
|
|
2904
|
+
|
|
2905
|
+
def test_user_listing_buttons_with_deprecated_hook(self):
|
|
2906
|
+
template = """
|
|
2907
|
+
{% load wagtailusers_tags %}
|
|
2908
|
+
{% for user in users %}
|
|
2909
|
+
<ul class="actions">
|
|
2910
|
+
{% user_listing_buttons user %}
|
|
2911
|
+
</ul>
|
|
2912
|
+
{% endfor %}
|
|
2913
|
+
"""
|
|
2914
|
+
|
|
2915
|
+
def deprecated_hook(context, user):
|
|
2916
|
+
self.assertEqual(user, self.test_user)
|
|
2917
|
+
self.assertEqual(context.request.user, self.user)
|
|
2918
|
+
yield UserListingButton(
|
|
2919
|
+
"Show profile",
|
|
2920
|
+
f"/goes/to/a/url/{user.pk}",
|
|
2921
|
+
priority=30,
|
|
2922
|
+
)
|
|
2923
|
+
|
|
2924
|
+
with self.register_hook("register_user_listing_buttons", deprecated_hook):
|
|
2925
|
+
with self.assertWarns(RemovedInWagtail70Warning) as warning_manager:
|
|
2926
|
+
html = Template(template).render(
|
|
2927
|
+
RequestContext(self.request, {"users": [self.test_user]})
|
|
2928
|
+
)
|
|
2929
|
+
|
|
2930
|
+
self.assertEqual(
|
|
2931
|
+
[str(w.message) for w in warning_manager.warnings],
|
|
2932
|
+
[
|
|
2933
|
+
# Deprecation of the template tag
|
|
2934
|
+
"`user_listing_buttons` template tag is deprecated.",
|
|
2935
|
+
# Deprecation of the hook signature
|
|
2936
|
+
"`register_user_listing_buttons` hook functions should accept a "
|
|
2937
|
+
"`request_user` argument instead of `context` - "
|
|
2938
|
+
"wagtail.users.tests.test_admin_views.deprecated_hook needs to be updated",
|
|
2939
|
+
],
|
|
2940
|
+
)
|
|
2941
|
+
|
|
2942
|
+
soup = self.get_soup(html)
|
|
2943
|
+
profile_url = f"/goes/to/a/url/{self.test_user.pk}"
|
|
2944
|
+
top_level_custom_button = soup.select_one(f"li > a[href='{profile_url}']")
|
|
2945
|
+
self.assertIsNotNone(top_level_custom_button)
|
|
2946
|
+
self.assertEqual(
|
|
2947
|
+
top_level_custom_button.text.strip(),
|
|
2948
|
+
"Show profile",
|
|
2949
|
+
)
|