wagtail 7.1.2__py3-none-any.whl → 7.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_page.py +1 -1
- wagtail/actions/create_alias.py +1 -1
- wagtail/actions/delete_page.py +1 -1
- wagtail/actions/publish_page_revision.py +1 -1
- wagtail/actions/publish_revision.py +1 -1
- wagtail/actions/revert_to_page_revision.py +1 -1
- wagtail/actions/unpublish.py +1 -1
- wagtail/actions/unpublish_page.py +1 -1
- wagtail/admin/auth.py +3 -1
- wagtail/admin/checks.py +2 -2
- wagtail/admin/filters.py +28 -1
- wagtail/admin/forms/collections.py +1 -1
- wagtail/admin/forms/comments.py +1 -1
- wagtail/admin/forms/models.py +1 -1
- wagtail/admin/forms/pages.py +1 -1
- wagtail/admin/forms/tags.py +1 -1
- wagtail/admin/locale/cs/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/cs/LC_MESSAGES/django.po +25 -1
- wagtail/admin/locale/en/LC_MESSAGES/django.po +278 -192
- wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +29 -15
- wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/it/LC_MESSAGES/django.po +3 -2
- wagtail/admin/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/nl/LC_MESSAGES/django.po +57 -3
- wagtail/admin/locale/nl/LC_MESSAGES/djangojs.mo +0 -0
- wagtail/admin/locale/nl/LC_MESSAGES/djangojs.po +8 -2
- wagtail/admin/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/ru/LC_MESSAGES/django.po +58 -1
- wagtail/admin/locale/tr/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/tr/LC_MESSAGES/django.po +3 -2
- 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/core.js.LICENSE.txt +2 -2
- 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/filtered-select.js +1 -1
- wagtail/admin/static/wagtailadmin/js/icons.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/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/userbar.js.LICENSE.txt +2 -2
- wagtail/admin/static/wagtailadmin/js/vendor/bootstrap-modal.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/bootstrap-transition.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/jquery-3.6.0.min.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/jquery-ui-1.13.2.min.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/jquery.datetimepicker.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/jquery.fileupload-process.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/jquery.fileupload.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/jquery.iframe-transport.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor/tag-it.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +1 -1
- wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
- wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
- wagtail/admin/templates/wagtailadmin/account/account.html +2 -0
- wagtail/admin/templates/wagtailadmin/base.html +14 -0
- wagtail/admin/templates/wagtailadmin/generic/chooser/chooser.html +2 -1
- wagtail/admin/templates/wagtailadmin/generic/chooser/creation_form.html +2 -1
- wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
- wagtail/admin/templates/wagtailadmin/panels/object_list.html +1 -1
- wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +3 -2
- wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +19 -0
- wagtail/admin/templates/wagtailadmin/shared/panel.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/set_privacy.html +15 -0
- wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +28 -1
- wagtail/admin/templates/wagtailadmin/shared/workflow_history/detail.html +2 -2
- wagtail/admin/templates/wagtailadmin/{pages/listing/_ordering_header.html → tables/ordering_header.html} +2 -2
- wagtail/admin/templates/wagtailadmin/tables/title_cell.html +1 -1
- wagtail/admin/templates/wagtailadmin/widgets/{daterange_input.html → range_input.html} +1 -1
- wagtail/admin/templates/wagtailadmin/workflows/task_chooser/chooser.html +4 -2
- wagtail/admin/templatetags/wagtailadmin_tags.py +41 -22
- wagtail/admin/tests/api/test_pages.py +7 -7
- wagtail/admin/tests/api/test_renderer_classes.py +16 -0
- wagtail/admin/tests/pages/test_create_page.py +34 -2
- wagtail/admin/tests/pages/test_edit_page.py +128 -14
- wagtail/admin/tests/pages/test_explorer_view.py +34 -7
- wagtail/admin/tests/pages/test_reorder_page.py +11 -0
- wagtail/admin/tests/test_collections_views.py +12 -0
- wagtail/admin/tests/test_edit_handlers.py +3 -3
- wagtail/admin/tests/test_filters.py +2 -2
- wagtail/admin/tests/test_keyboard_shortcuts.py +52 -2
- wagtail/admin/tests/test_menu.py +0 -2
- wagtail/admin/tests/test_privacy.py +16 -16
- wagtail/admin/tests/test_templatetags.py +137 -0
- wagtail/admin/tests/test_workflows.py +34 -0
- wagtail/admin/tests/viewsets/test_model_viewset.py +322 -0
- wagtail/admin/ui/tables/orderable.py +73 -0
- wagtail/admin/ui/tables/pages.py +3 -13
- wagtail/admin/views/collection_privacy.py +6 -2
- wagtail/admin/views/generic/__init__.py +1 -0
- wagtail/admin/views/generic/mixins.py +20 -2
- wagtail/admin/views/generic/models.py +67 -1
- wagtail/admin/views/generic/ordering.py +79 -0
- wagtail/admin/views/home.py +3 -3
- wagtail/admin/views/page_privacy.py +5 -2
- wagtail/admin/views/pages/create.py +1 -1
- wagtail/admin/views/pages/edit.py +2 -2
- wagtail/admin/views/pages/listing.py +7 -42
- wagtail/admin/views/pages/move.py +1 -1
- wagtail/admin/views/pages/ordering.py +1 -1
- wagtail/admin/viewsets/base.py +1 -1
- wagtail/admin/viewsets/model.py +49 -1
- wagtail/admin/wagtail_hooks.py +2 -1
- wagtail/admin/widgets/slug.py +10 -10
- wagtail/api/v2/serializers.py +1 -1
- wagtail/api/v2/tests/test_renderer_classes.py +32 -0
- wagtail/apps.py +2 -0
- wagtail/bin/wagtail.py +1 -1
- wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +14 -14
- wagtail/contrib/forms/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/forms/locale/nl/LC_MESSAGES/django.po +19 -2
- wagtail/contrib/forms/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/forms/locale/ru/LC_MESSAGES/django.po +18 -1
- wagtail/contrib/frontend_cache/tests.py +4 -2
- wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +4 -4
- wagtail/contrib/redirects/tests/test_tmp_storages.py +20 -0
- wagtail/contrib/redirects/tmp_storages.py +1 -1
- wagtail/contrib/redirects/views.py +3 -3
- wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +3 -3
- wagtail/contrib/search_promotions/locale/tr/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/search_promotions/locale/tr/LC_MESSAGES/django.po +43 -3
- wagtail/contrib/search_promotions/static/wagtailsearchpromotions/js/query-chooser-modal.js +1 -1
- wagtail/contrib/search_promotions/views/settings.py +2 -2
- wagtail/contrib/settings/locale/cs/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/cs/LC_MESSAGES/django.po +6 -1
- wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/settings/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/nl/LC_MESSAGES/django.po +6 -2
- wagtail/contrib/settings/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/ru/LC_MESSAGES/django.po +6 -1
- wagtail/contrib/settings/tests/site_specific/test_admin.py +40 -6
- wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/styleguide/templates/wagtailstyleguide/base.html +5 -5
- wagtail/contrib/table_block/blocks.py +1 -0
- wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +5 -1
- wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
- wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
- wagtail/coreutils.py +5 -5
- wagtail/documents/forms.py +18 -1
- wagtail/documents/locale/en/LC_MESSAGES/django.po +10 -10
- wagtail/documents/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/nl/LC_MESSAGES/django.po +9 -0
- wagtail/documents/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/ru/LC_MESSAGES/django.po +9 -0
- wagtail/documents/models.py +1 -1
- wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -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/templates/wagtaildocs/documents/add.html +0 -34
- wagtail/documents/tests/test_admin_views.py +132 -26
- wagtail/documents/tests/test_collection_privacy.py +18 -4
- wagtail/documents/tests/test_form_overrides.py +1 -1
- wagtail/documents/tests/test_search.py +21 -8
- wagtail/documents/views/documents.py +1 -1
- wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/embeds/static/wagtailembeds/js/embed-chooser-modal.js +1 -1
- wagtail/images/forms.py +16 -1
- wagtail/images/locale/cs/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/cs/LC_MESSAGES/django.po +12 -1
- wagtail/images/locale/en/LC_MESSAGES/django.po +57 -46
- wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/nl/LC_MESSAGES/django.po +37 -14
- wagtail/images/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/ru/LC_MESSAGES/django.po +20 -1
- wagtail/images/models.py +1 -1
- wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
- wagtail/images/static/wagtailimages/js/focal-point-chooser.js +1 -1
- wagtail/images/static/wagtailimages/js/image-block.js +1 -1
- 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/static/wagtailimages/js/image-url-generator.js +1 -1
- wagtail/images/static/wagtailimages/js/vendor/jquery.Jcrop.min.js +1 -1
- wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
- wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-validate.js +1 -1
- wagtail/images/static/wagtailimages/js/vendor/load-image.min.js +1 -1
- wagtail/images/templates/wagtailimages/chooser/chooser.html +22 -13
- wagtail/images/templates/wagtailimages/chooser/image_preview_column_cell.html +10 -0
- wagtail/images/templates/wagtailimages/chooser/results.html +24 -20
- wagtail/images/templates/wagtailimages/chooser/title_column_cell.html +15 -0
- wagtail/images/templates/wagtailimages/images/add.html +0 -34
- wagtail/images/templates/wagtailimages/images/index.html +3 -3
- wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
- wagtail/images/templates/wagtailimages/images/layout_toggle_button.html +8 -7
- wagtail/images/templatetags/wagtailimages_tags.py +2 -2
- wagtail/images/tests/test_admin_views.py +87 -0
- wagtail/images/tests/test_form_overrides.py +1 -1
- wagtail/images/tests/test_models.py +48 -9
- wagtail/images/views/chooser.py +66 -2
- wagtail/locale/en/LC_MESSAGES/django.po +55 -55
- wagtail/locale/is_IS/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/is_IS/LC_MESSAGES/django.po +3 -3
- wagtail/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/nl/LC_MESSAGES/django.po +11 -2
- wagtail/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/ru/LC_MESSAGES/django.po +11 -1
- wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/locales/locale/nl/LC_MESSAGES/django.po +12 -1
- wagtail/locales/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/locales/locale/ru/LC_MESSAGES/django.po +10 -1
- wagtail/locales/views.py +2 -2
- wagtail/models/orderable.py +10 -0
- wagtail/models/pages.py +9 -11
- wagtail/models/sites.py +1 -1
- wagtail/models/workflows.py +8 -5
- wagtail/project_template/home/tests.py +6 -7
- wagtail/project_template/project_name/settings/base.py +9 -9
- wagtail/project_template/requirements.txt +1 -1
- wagtail/query.py +7 -2
- wagtail/rich_text/rewriters.py +1 -1
- wagtail/search/apps.py +4 -49
- wagtail/search/backends/__init__.py +1 -113
- wagtail/search/backends/base.py +1 -547
- wagtail/search/backends/database/__init__.py +1 -50
- wagtail/search/backends/database/fallback.py +1 -253
- wagtail/search/backends/database/mysql/mysql.py +1 -700
- wagtail/search/backends/database/mysql/query.py +1 -258
- wagtail/search/backends/database/postgres/postgres.py +1 -749
- wagtail/search/backends/database/postgres/query.py +1 -83
- wagtail/search/backends/database/postgres/weights.py +1 -63
- wagtail/search/backends/database/sqlite/query.py +1 -294
- wagtail/search/backends/database/sqlite/sqlite.py +1 -719
- wagtail/search/backends/database/sqlite/utils.py +1 -35
- wagtail/search/backends/deprecation.py +45 -0
- wagtail/search/backends/elasticsearch7.py +18 -1260
- wagtail/search/backends/elasticsearch8.py +21 -96
- wagtail/search/backends/elasticsearch9.py +35 -0
- wagtail/search/backends/opensearch2.py +35 -0
- wagtail/search/backends/opensearch3.py +35 -0
- wagtail/search/index.py +1 -358
- wagtail/search/locale/en/LC_MESSAGES/django.po +2 -10
- wagtail/search/management/commands/update_index.py +1 -205
- wagtail/search/management/commands/wagtail_update_index.py +1 -4
- wagtail/search/models.py +32 -158
- wagtail/search/query.py +1 -114
- wagtail/search/queryset.py +1 -43
- wagtail/search/signal_handlers.py +1 -24
- wagtail/search/tasks.py +1 -10
- wagtail/search/tests/test_elasticsearch.py +22 -0
- wagtail/search/utils.py +1 -206
- wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -3
- wagtail/snippets/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/ru/LC_MESSAGES/django.po +8 -1
- wagtail/snippets/locale/tr/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/tr/LC_MESSAGES/django.po +8 -1
- wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
- wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
- wagtail/snippets/tests/test_reordering.py +319 -0
- wagtail/snippets/tests/test_snippets.py +65 -12
- wagtail/snippets/views/snippets.py +16 -0
- wagtail/test/numberformat.py +30 -0
- wagtail/test/settings.py +35 -12
- wagtail/test/testapp/fields.py +12 -0
- wagtail/test/testapp/migrations/0056_commentablejsonpage.py +50 -0
- wagtail/test/testapp/migrations/0057_featurecompletetoy_sort_order.py +23 -0
- wagtail/test/testapp/migrations/0058_customlocktask.py +31 -0
- wagtail/test/testapp/models.py +27 -0
- wagtail/test/testapp/views.py +3 -1
- wagtail/test/utils/page_tests.py +17 -17
- wagtail/test/utils/template_tests.py +4 -6
- wagtail/test/utils/wagtail_tests.py +1 -2
- wagtail/tests/test_page_model.py +15 -0
- wagtail/{search/tests → tests}/test_page_search.py +29 -2
- wagtail/tests/test_search_fields.py +69 -0
- wagtail/tests/test_tests.py +62 -6
- wagtail/tests/test_workflow.py +25 -1
- wagtail/users/locale/cs/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/cs/LC_MESSAGES/django.po +3 -0
- wagtail/users/locale/en/LC_MESSAGES/django.po +2 -2
- wagtail/users/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/nl/LC_MESSAGES/django.po +6 -3
- wagtail/users/locale/ru/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/ru/LC_MESSAGES/django.po +5 -1
- wagtail/users/locale/tr/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/tr/LC_MESSAGES/django.po +78 -4
- wagtail/users/templates/wagtailusers/users/create.html +2 -0
- wagtail/users/templates/wagtailusers/users/edit.html +2 -0
- wagtail/users/tests/test_admin_views.py +4 -0
- wagtail/users/views/users.py +1 -1
- {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/METADATA +7 -6
- {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/RECORD +309 -315
- wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +0 -13
- wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +0 -13
- wagtail/search/tests/__init__.py +0 -0
- wagtail/search/tests/elasticsearch_common_tests.py +0 -251
- wagtail/search/tests/test_backends.py +0 -1215
- wagtail/search/tests/test_db_backend.py +0 -62
- wagtail/search/tests/test_elasticsearch7_backend.py +0 -1452
- wagtail/search/tests/test_elasticsearch8_backend.py +0 -15
- wagtail/search/tests/test_index_functions.py +0 -256
- wagtail/search/tests/test_indexed_class.py +0 -157
- wagtail/search/tests/test_mysql_backend.py +0 -192
- wagtail/search/tests/test_postgres_backend.py +0 -210
- wagtail/search/tests/test_queries.py +0 -332
- wagtail/search/tests/test_related_fields.py +0 -102
- wagtail/search/tests/test_sqlite_backend.py +0 -52
- wagtail/test/search/__init__.py +0 -0
- wagtail/test/search/apps.py +0 -9
- wagtail/test/search/fixtures/search.json +0 -545
- wagtail/test/search/migrations/0001_initial.py +0 -146
- wagtail/test/search/migrations/0002_bookunindexed.py +0 -43
- wagtail/test/search/migrations/0003_book_summary.py +0 -18
- wagtail/test/search/migrations/__init__.py +0 -0
- wagtail/test/search/models.py +0 -137
- /wagtail/admin/templates/wagtailadmin/{pages/listing/_ordering_cell.html → tables/ordering_cell.html} +0 -0
- /wagtail/{search/checks.py → checks.py} +0 -0
- {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/WHEEL +0 -0
- {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/entry_points.txt +0 -0
- {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/licenses/LICENSE +0 -0
- {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/top_level.txt +0 -0
|
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
|
|
4
4
|
from datetime import timezone as dt_timezone
|
|
5
5
|
from unittest import mock
|
|
6
6
|
|
|
7
|
+
from django import forms
|
|
7
8
|
from django.conf import settings
|
|
8
9
|
from django.template import Context, Template, TemplateSyntaxError
|
|
9
10
|
from django.test import SimpleTestCase, TestCase
|
|
@@ -1084,3 +1085,139 @@ class ThemeColorSchemeTest(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
|
|
1084
1085
|
meta_tag = soup.find("meta", {"name": "color-scheme"})
|
|
1085
1086
|
self.assertIsNotNone(meta_tag)
|
|
1086
1087
|
self.assertEqual(meta_tag["content"], "light")
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
class FormattedfieldTagTestCase(WagtailTestUtils, SimpleTestCase):
|
|
1091
|
+
def render_template(self, template, field_name="title", **context):
|
|
1092
|
+
class BasicForm(forms.Form):
|
|
1093
|
+
title = forms.CharField()
|
|
1094
|
+
|
|
1095
|
+
form = BasicForm(data={})
|
|
1096
|
+
|
|
1097
|
+
html = Template("{% load wagtailadmin_tags %}" + template).render(
|
|
1098
|
+
Context(
|
|
1099
|
+
{
|
|
1100
|
+
"field": form[field_name],
|
|
1101
|
+
**context,
|
|
1102
|
+
}
|
|
1103
|
+
)
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1106
|
+
# Parse the rendered HTML for the first DOM element in the output
|
|
1107
|
+
return self.get_soup(html).find()
|
|
1108
|
+
|
|
1109
|
+
def test_basic_usage_with_full_html(self):
|
|
1110
|
+
soup = self.render_template("{% formattedfield field=field %}")
|
|
1111
|
+
|
|
1112
|
+
# check the outer wrapper attributes
|
|
1113
|
+
self.assertEqual(
|
|
1114
|
+
{"class": ["w-field__wrapper"], "data-field-wrapper": ""},
|
|
1115
|
+
soup.attrs,
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
# check the label is correct
|
|
1119
|
+
label = soup.find("label")
|
|
1120
|
+
self.assertIsNotNone(label)
|
|
1121
|
+
self.assertEqual(label["for"], "id_title")
|
|
1122
|
+
self.assertEqual(label["id"], "id_title-label")
|
|
1123
|
+
self.assertEqual(label.get_text().strip(), "Title*")
|
|
1124
|
+
|
|
1125
|
+
# check there is an error container
|
|
1126
|
+
errors = soup.find("div", {"data-field-errors": ""})
|
|
1127
|
+
self.assertIsNotNone(errors)
|
|
1128
|
+
self.assertEqual(errors["class"], ["w-field__errors"])
|
|
1129
|
+
self.assertEqual(errors.get_text().strip(), "This field is required.")
|
|
1130
|
+
|
|
1131
|
+
# check there is a help container that is empty
|
|
1132
|
+
help_text = soup.find("div", {"data-field-help": ""})
|
|
1133
|
+
self.assertIsNotNone(help_text)
|
|
1134
|
+
self.assertEqual(help_text["class"], ["w-field__help"])
|
|
1135
|
+
self.assertEqual(help_text.get_text().strip(), "")
|
|
1136
|
+
|
|
1137
|
+
# check there is an input
|
|
1138
|
+
input = soup.find("input")
|
|
1139
|
+
self.assertIsNotNone(input)
|
|
1140
|
+
self.assertEqual(input["id"], "id_title")
|
|
1141
|
+
self.assertEqual(input["name"], "title")
|
|
1142
|
+
self.assertEqual(input["type"], "text")
|
|
1143
|
+
self.assertEqual(input["required"], "")
|
|
1144
|
+
|
|
1145
|
+
# check the input container & input siblings
|
|
1146
|
+
input_container = input.parent
|
|
1147
|
+
self.assertIsNone(input.find("svg")) # there should be no icon
|
|
1148
|
+
self.assertEqual(
|
|
1149
|
+
input_container.attrs, {"class": ["w-field__input"], "data-field-input": ""}
|
|
1150
|
+
) # validate input container
|
|
1151
|
+
|
|
1152
|
+
def test_complex_usage_with_full_html(self):
|
|
1153
|
+
soup = self.render_template(
|
|
1154
|
+
"""{% formattedfield field=field wrapper_id="__CUSTOM_ID__" classname="extra-custom-class" sr_only_label=True icon='search' help_text='This is a help text.' show_add_comment_button=True %}"""
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
# check the outer wrapper attributes
|
|
1158
|
+
self.assertEqual(
|
|
1159
|
+
{
|
|
1160
|
+
"class": ["w-field__wrapper", "extra-custom-class"],
|
|
1161
|
+
"id": "__CUSTOM_ID__",
|
|
1162
|
+
"data-field-wrapper": "",
|
|
1163
|
+
},
|
|
1164
|
+
soup.attrs,
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
# check the label is correct
|
|
1168
|
+
label = soup.find("label")
|
|
1169
|
+
self.assertIsNotNone(label)
|
|
1170
|
+
self.assertEqual(label["for"], "id_title")
|
|
1171
|
+
self.assertEqual(label["id"], "id_title-label")
|
|
1172
|
+
self.assertEqual(label.get_text().strip(), "Title*")
|
|
1173
|
+
|
|
1174
|
+
# check there is an error container
|
|
1175
|
+
errors = soup.find("div", {"data-field-errors": ""})
|
|
1176
|
+
self.assertIsNotNone(errors)
|
|
1177
|
+
self.assertEqual(errors["class"], ["w-field__errors"])
|
|
1178
|
+
self.assertEqual(errors.get_text().strip(), "This field is required.")
|
|
1179
|
+
|
|
1180
|
+
# check there is a help container that is empty
|
|
1181
|
+
help_text = soup.find("div", {"data-field-help": ""})
|
|
1182
|
+
self.assertIsNotNone(help_text)
|
|
1183
|
+
self.assertEqual(help_text["class"], ["w-field__help"])
|
|
1184
|
+
self.assertEqual(help_text.get_text().strip(), "This is a help text.")
|
|
1185
|
+
|
|
1186
|
+
# check there is an input
|
|
1187
|
+
input = soup.find("input")
|
|
1188
|
+
self.assertIsNotNone(input)
|
|
1189
|
+
self.assertEqual(input["id"], "id_title")
|
|
1190
|
+
self.assertEqual(input["name"], "title")
|
|
1191
|
+
self.assertEqual(input["type"], "text")
|
|
1192
|
+
self.assertEqual(input["required"], "")
|
|
1193
|
+
|
|
1194
|
+
# check the input container & input siblings
|
|
1195
|
+
input_container = input.parent
|
|
1196
|
+
self.assertEqual(
|
|
1197
|
+
input_container.find("svg").find("use")["href"], "#icon-search"
|
|
1198
|
+
) # there should be an icon
|
|
1199
|
+
self.assertEqual(
|
|
1200
|
+
input_container.attrs, {"class": ["w-field__input"], "data-field-input": ""}
|
|
1201
|
+
) # validate input container
|
|
1202
|
+
|
|
1203
|
+
def test_attrs_rendering(self):
|
|
1204
|
+
soup = self.render_template(
|
|
1205
|
+
"""{% formattedfield field=field wrapper_id="__CUSTOM_ID__" classname="extra-custom-class" attrs=attrs %}""",
|
|
1206
|
+
attrs={
|
|
1207
|
+
"data-custom-attr": "custom-value",
|
|
1208
|
+
"data-controller": "w-example w-other",
|
|
1209
|
+
"data-field-wrapper": "__CANNOT_OVERRIDE_DEFAULT__",
|
|
1210
|
+
},
|
|
1211
|
+
)
|
|
1212
|
+
|
|
1213
|
+
self.assertEqual(
|
|
1214
|
+
{
|
|
1215
|
+
"class": ["w-field__wrapper", "extra-custom-class"],
|
|
1216
|
+
"id": "__CUSTOM_ID__",
|
|
1217
|
+
"data-custom-attr": "custom-value",
|
|
1218
|
+
"data-controller": "w-example w-other",
|
|
1219
|
+
# wrapper attribute should be preserved
|
|
1220
|
+
"data-field-wrapper": "",
|
|
1221
|
+
},
|
|
1222
|
+
soup.attrs,
|
|
1223
|
+
)
|
|
@@ -26,6 +26,7 @@ from wagtail.admin.utils import (
|
|
|
26
26
|
get_latest_str,
|
|
27
27
|
get_user_display_name,
|
|
28
28
|
)
|
|
29
|
+
from wagtail.locks import BasicLock
|
|
29
30
|
from wagtail.models import (
|
|
30
31
|
GroupApprovalTask,
|
|
31
32
|
GroupPagePermission,
|
|
@@ -41,6 +42,8 @@ from wagtail.models import (
|
|
|
41
42
|
)
|
|
42
43
|
from wagtail.signals import page_published, published
|
|
43
44
|
from wagtail.test.testapp.models import (
|
|
45
|
+
CustomLockTask,
|
|
46
|
+
CustomWorkflowLock,
|
|
44
47
|
FullFeaturedSnippet,
|
|
45
48
|
ModeratedModel,
|
|
46
49
|
MultiPreviewModesPage,
|
|
@@ -4851,3 +4854,34 @@ class TestWorkflowStateEmailNotifier(BasePageWorkflowTests):
|
|
|
4851
4854
|
with self.subTest(f"Testing with {notification}_notifications"):
|
|
4852
4855
|
notifier.notification = notification
|
|
4853
4856
|
self.assertSetEqual(notifier.get_valid_recipients(self.object), set())
|
|
4857
|
+
|
|
4858
|
+
|
|
4859
|
+
class TestCustomWorkflowLockOnTask(BasePageWorkflowTests):
|
|
4860
|
+
def setup_workflow_and_tasks(self):
|
|
4861
|
+
self.workflow = Workflow.objects.create(name="test_workflow")
|
|
4862
|
+
self.task_1 = CustomLockTask.objects.create(name="test_task_1")
|
|
4863
|
+
WorkflowTask.objects.create(
|
|
4864
|
+
workflow=self.workflow, task=self.task_1, sort_order=1
|
|
4865
|
+
)
|
|
4866
|
+
|
|
4867
|
+
def test_custom_lock_class(self):
|
|
4868
|
+
self.post("submit")
|
|
4869
|
+
response = self.client.get(self.get_url("edit"))
|
|
4870
|
+
self.assertContains(response, "If there is a door, there must be a key")
|
|
4871
|
+
self.assertIsInstance(self.object.get_lock(), CustomWorkflowLock)
|
|
4872
|
+
|
|
4873
|
+
@mock.patch.object(CustomLockTask, "lock_class", new_callable=mock.PropertyMock)
|
|
4874
|
+
def test_typeerror_if_custom_lock_class_inherits_basic_locks(self, mock_property):
|
|
4875
|
+
mock_property.return_value = BasicLock
|
|
4876
|
+
|
|
4877
|
+
self.post("submit")
|
|
4878
|
+
|
|
4879
|
+
with self.assertRaises(TypeError):
|
|
4880
|
+
self.client.get(self.get_url("edit"))
|
|
4881
|
+
|
|
4882
|
+
|
|
4883
|
+
class TestCustomWorkflowLockOnTaskWithSnippets(
|
|
4884
|
+
TestCustomWorkflowLockOnTask,
|
|
4885
|
+
BaseSnippetWorkflowTests,
|
|
4886
|
+
):
|
|
4887
|
+
pass
|
|
@@ -1828,3 +1828,325 @@ class TestHeaderButtons(WagtailTestUtils, TestCase):
|
|
|
1828
1828
|
[(a.text.strip(), a.get("href")) for a in header_buttons],
|
|
1829
1829
|
expected_buttons,
|
|
1830
1830
|
)
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
class TestIndexViewReordering(WagtailTestUtils, TestCase):
|
|
1834
|
+
def setUp(self):
|
|
1835
|
+
self.user = self.login()
|
|
1836
|
+
self.obj1 = FeatureCompleteToy.objects.create(name="Toy 1", sort_order=0)
|
|
1837
|
+
self.obj2 = FeatureCompleteToy.objects.create(name="Toy 2", sort_order=1)
|
|
1838
|
+
self.obj3 = FeatureCompleteToy.objects.create(name="Toy 3", sort_order=2)
|
|
1839
|
+
|
|
1840
|
+
def test_header_button_rendered(self):
|
|
1841
|
+
index_url = reverse("feature_complete_toy:index")
|
|
1842
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
1843
|
+
response = self.client.get(index_url)
|
|
1844
|
+
self.assertEqual(response.status_code, 200)
|
|
1845
|
+
soup = self.get_soup(response.content)
|
|
1846
|
+
button = soup.select_one(
|
|
1847
|
+
f".w-slim-header .w-dropdown a[href='{custom_ordering_url}']"
|
|
1848
|
+
)
|
|
1849
|
+
self.assertIsNotNone(button)
|
|
1850
|
+
self.assertEqual(button.text.strip(), "Sort item order")
|
|
1851
|
+
|
|
1852
|
+
# Reordering feature disabled when not sorting by sort_order
|
|
1853
|
+
table = soup.select_one("main table")
|
|
1854
|
+
self.assertIsNotNone(table)
|
|
1855
|
+
self.assertFalse(table.get("data-controller"))
|
|
1856
|
+
first_th = soup.select_one("main thead th:first-child")
|
|
1857
|
+
self.assertIsNotNone(first_th)
|
|
1858
|
+
self.assertEqual(first_th.text.strip(), "Name")
|
|
1859
|
+
|
|
1860
|
+
def test_show_ordering_column(self):
|
|
1861
|
+
index_url = reverse("feature_complete_toy:index")
|
|
1862
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
1863
|
+
response = self.client.get(custom_ordering_url)
|
|
1864
|
+
self.assertEqual(response.status_code, 200)
|
|
1865
|
+
soup = self.get_soup(response.content)
|
|
1866
|
+
|
|
1867
|
+
# The table should have the w-orderable controller
|
|
1868
|
+
table = soup.select_one("main table")
|
|
1869
|
+
self.assertIsNotNone(table)
|
|
1870
|
+
self.assertEqual(table.get("data-controller"), "w-orderable")
|
|
1871
|
+
self.assertEqual(
|
|
1872
|
+
table.get("data-w-orderable-message-value"),
|
|
1873
|
+
"'__LABEL__' has been moved successfully.",
|
|
1874
|
+
)
|
|
1875
|
+
self.assertEqual(
|
|
1876
|
+
table.get("data-w-orderable-url-value"),
|
|
1877
|
+
reverse("feature_complete_toy:reorder", args=[999999]),
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1880
|
+
# The ordering column added as the first column
|
|
1881
|
+
first_th = table.select_one("thead th:first-child")
|
|
1882
|
+
self.assertIsNotNone(first_th)
|
|
1883
|
+
self.assertEqual(first_th.text.strip(), "Sort")
|
|
1884
|
+
|
|
1885
|
+
# All rows have the corresponding attributes for reordering
|
|
1886
|
+
rows = table.select("tbody tr")
|
|
1887
|
+
self.assertEqual(len(rows), 3)
|
|
1888
|
+
expected = [
|
|
1889
|
+
{
|
|
1890
|
+
"id": f"item_{quote(toy.pk)}",
|
|
1891
|
+
"data-w-orderable-item-id": quote(toy.pk),
|
|
1892
|
+
"data-w-orderable-item-label": str(toy),
|
|
1893
|
+
"data-w-orderable-target": "item",
|
|
1894
|
+
}
|
|
1895
|
+
for toy in [self.obj1, self.obj2, self.obj3]
|
|
1896
|
+
]
|
|
1897
|
+
for row, expected_attrs in zip(rows, expected):
|
|
1898
|
+
for attr, value in expected_attrs.items():
|
|
1899
|
+
self.assertEqual(row.get(attr), value)
|
|
1900
|
+
handle = row.select_one("td button[data-w-orderable-target='handle']")
|
|
1901
|
+
self.assertIsNotNone(handle)
|
|
1902
|
+
|
|
1903
|
+
def test_reordering_disabled_with_explicit_sort_order_field_none(self):
|
|
1904
|
+
# The model has a sort_order_field, but the viewset explicitly sets
|
|
1905
|
+
# sort_order_field = None
|
|
1906
|
+
index_url = reverse("fctoy-alt2:index")
|
|
1907
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
1908
|
+
response = self.client.get(custom_ordering_url)
|
|
1909
|
+
self.assertEqual(response.status_code, 200)
|
|
1910
|
+
soup = self.get_soup(response.content)
|
|
1911
|
+
|
|
1912
|
+
# Header button for enabling reordering should not be rendered
|
|
1913
|
+
button = soup.select_one(
|
|
1914
|
+
f".w-slim-header .w-dropdown a[href='{custom_ordering_url}']"
|
|
1915
|
+
)
|
|
1916
|
+
self.assertIsNone(button)
|
|
1917
|
+
|
|
1918
|
+
# Reordering feature not enabled
|
|
1919
|
+
table = soup.select_one("main table")
|
|
1920
|
+
self.assertIsNotNone(table)
|
|
1921
|
+
self.assertFalse(table.get("data-controller"))
|
|
1922
|
+
first_th = soup.select_one("main thead th:first-child")
|
|
1923
|
+
self.assertIsNotNone(first_th)
|
|
1924
|
+
self.assertEqual(first_th.text.strip(), "Feature complete toy")
|
|
1925
|
+
|
|
1926
|
+
def test_reordering_disabled_with_no_sort_order_field(self):
|
|
1927
|
+
# This model has no sort_order_field defined on the model nor the viewset
|
|
1928
|
+
JSONStreamModel.objects.create()
|
|
1929
|
+
index_url = reverse("streammodel:index")
|
|
1930
|
+
custom_ordering_url = index_url + "?ordering="
|
|
1931
|
+
response = self.client.get(custom_ordering_url)
|
|
1932
|
+
self.assertEqual(response.status_code, 200)
|
|
1933
|
+
soup = self.get_soup(response.content)
|
|
1934
|
+
|
|
1935
|
+
# Header button for enabling reordering should not be rendered
|
|
1936
|
+
button = soup.select_one(
|
|
1937
|
+
f".w-slim-header .w-dropdown a[href^='{custom_ordering_url}']"
|
|
1938
|
+
)
|
|
1939
|
+
self.assertIsNone(button)
|
|
1940
|
+
|
|
1941
|
+
# Reordering feature not enabled
|
|
1942
|
+
table = soup.select_one("main table")
|
|
1943
|
+
self.assertIsNotNone(table)
|
|
1944
|
+
self.assertFalse(table.get("data-controller"))
|
|
1945
|
+
first_th = soup.select_one("main thead th:first-child")
|
|
1946
|
+
self.assertIsNotNone(first_th)
|
|
1947
|
+
self.assertEqual(first_th.text.strip(), "JSON stream model")
|
|
1948
|
+
|
|
1949
|
+
def test_reordering_disabled_with_insufficient_permission(self):
|
|
1950
|
+
self.user.is_superuser = False
|
|
1951
|
+
self.user.save()
|
|
1952
|
+
admin_permission = Permission.objects.get(
|
|
1953
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
1954
|
+
)
|
|
1955
|
+
view_permission = Permission.objects.get(
|
|
1956
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
1957
|
+
codename=get_permission_codename("view", self.obj1._meta),
|
|
1958
|
+
)
|
|
1959
|
+
self.user.user_permissions.add(admin_permission, view_permission)
|
|
1960
|
+
|
|
1961
|
+
index_url = reverse("feature_complete_toy:index")
|
|
1962
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
1963
|
+
response = self.client.get(custom_ordering_url)
|
|
1964
|
+
self.assertEqual(response.status_code, 200)
|
|
1965
|
+
soup = self.get_soup(response.content)
|
|
1966
|
+
|
|
1967
|
+
# Header button for enabling reordering should not be rendered
|
|
1968
|
+
button = soup.select_one(
|
|
1969
|
+
f".w-slim-header .w-dropdown a[href='{custom_ordering_url}']"
|
|
1970
|
+
)
|
|
1971
|
+
self.assertIsNone(button)
|
|
1972
|
+
|
|
1973
|
+
# Reordering feature not enabled
|
|
1974
|
+
table = soup.select_one("main table")
|
|
1975
|
+
self.assertIsNotNone(table)
|
|
1976
|
+
self.assertFalse(table.get("data-controller"))
|
|
1977
|
+
first_th = soup.select_one("main thead th:first-child")
|
|
1978
|
+
self.assertIsNotNone(first_th)
|
|
1979
|
+
self.assertEqual(first_th.text.strip(), "Name")
|
|
1980
|
+
|
|
1981
|
+
def test_minimal_permission(self):
|
|
1982
|
+
self.user.is_superuser = False
|
|
1983
|
+
self.user.save()
|
|
1984
|
+
admin_permission = Permission.objects.get(
|
|
1985
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
1986
|
+
)
|
|
1987
|
+
change_permission = Permission.objects.get(
|
|
1988
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
1989
|
+
codename=get_permission_codename("change", self.obj1._meta),
|
|
1990
|
+
)
|
|
1991
|
+
self.user.user_permissions.add(admin_permission, change_permission)
|
|
1992
|
+
|
|
1993
|
+
self.test_header_button_rendered()
|
|
1994
|
+
self.test_show_ordering_column()
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
class TestCreateViewReordering(WagtailTestUtils, TestCase):
|
|
1998
|
+
def setUp(self):
|
|
1999
|
+
self.user = self.login()
|
|
2000
|
+
FeatureCompleteToy.objects.bulk_create(
|
|
2001
|
+
[
|
|
2002
|
+
FeatureCompleteToy(name="Toy 1", sort_order=0),
|
|
2003
|
+
FeatureCompleteToy(name="Toy 2", sort_order=1),
|
|
2004
|
+
FeatureCompleteToy(name="Toy 3", sort_order=2),
|
|
2005
|
+
]
|
|
2006
|
+
)
|
|
2007
|
+
|
|
2008
|
+
def test_create_sets_max_sort_order(self):
|
|
2009
|
+
response = self.client.post(
|
|
2010
|
+
reverse("feature_complete_toy:add"),
|
|
2011
|
+
data={"name": "New Toy", "release_date": "2025-08-17"},
|
|
2012
|
+
)
|
|
2013
|
+
self.assertRedirects(response, reverse("feature_complete_toy:index"))
|
|
2014
|
+
new_toy = FeatureCompleteToy.objects.get(name="New Toy")
|
|
2015
|
+
self.assertEqual(new_toy.sort_order, 3)
|
|
2016
|
+
|
|
2017
|
+
def test_create_does_not_set_max_sort_order_without_sort_order_field(self):
|
|
2018
|
+
response = self.client.post(
|
|
2019
|
+
reverse("fctoy-alt2:add"),
|
|
2020
|
+
data={"name": "New Toy", "release_date": "2025-08-17", "strid": "foo"},
|
|
2021
|
+
)
|
|
2022
|
+
self.assertRedirects(response, reverse("fctoy-alt2:index"))
|
|
2023
|
+
new_toy = FeatureCompleteToy.objects.get(name="New Toy")
|
|
2024
|
+
self.assertIsNone(new_toy.sort_order)
|
|
2025
|
+
|
|
2026
|
+
|
|
2027
|
+
class TestReorderView(WagtailTestUtils, TestCase):
|
|
2028
|
+
def setUp(self):
|
|
2029
|
+
self.user = self.login()
|
|
2030
|
+
# We don't do any normalization, so the sort_order values may not be
|
|
2031
|
+
# consecutive integers (e.g. after an item is deleted), and the update
|
|
2032
|
+
# logic may cause the sort_order values to be negative or larger than
|
|
2033
|
+
# the number of items in the queryset.
|
|
2034
|
+
self.obj1 = FeatureCompleteToy.objects.create(name="Toy 1", sort_order=-3)
|
|
2035
|
+
self.obj2 = FeatureCompleteToy.objects.create(name="Toy 2", sort_order=4)
|
|
2036
|
+
self.obj3 = FeatureCompleteToy.objects.create(name="Toy 3", sort_order=9)
|
|
2037
|
+
|
|
2038
|
+
def get_url(self, obj):
|
|
2039
|
+
return reverse("feature_complete_toy:reorder", args=(quote(obj.pk),))
|
|
2040
|
+
|
|
2041
|
+
def assertOrder(self, objs):
|
|
2042
|
+
self.assertSequenceEqual(
|
|
2043
|
+
[
|
|
2044
|
+
(obj, obj.sort_order)
|
|
2045
|
+
for obj in FeatureCompleteToy.objects.order_by("sort_order")
|
|
2046
|
+
],
|
|
2047
|
+
objs,
|
|
2048
|
+
)
|
|
2049
|
+
|
|
2050
|
+
def test_reorder_view_disabled_without_sort_order_field(self):
|
|
2051
|
+
# The alt2 viewset explicitly sets sort_order_field to `None`,
|
|
2052
|
+
# so the reorder view should not be available
|
|
2053
|
+
with self.assertRaises(NoReverseMatch):
|
|
2054
|
+
reverse("fctoy-alt2:reorder", args=(quote(self.obj1.pk),))
|
|
2055
|
+
|
|
2056
|
+
# This model has no sort_order_field on the model nor the viewset
|
|
2057
|
+
obj = JSONStreamModel.objects.create()
|
|
2058
|
+
with self.assertRaises(NoReverseMatch):
|
|
2059
|
+
reverse("streammodel:reorder", args=(quote(obj.pk),))
|
|
2060
|
+
|
|
2061
|
+
def test_get_request_does_not_alter_order(self):
|
|
2062
|
+
response = self.client.get(self.get_url(self.obj1))
|
|
2063
|
+
self.assertEqual(response.status_code, 405)
|
|
2064
|
+
|
|
2065
|
+
# Ensure item order does not change
|
|
2066
|
+
self.assertOrder([(self.obj1, -3), (self.obj2, 4), (self.obj3, 9)])
|
|
2067
|
+
|
|
2068
|
+
def test_post_request_without_position_argument_moves_to_the_end(self):
|
|
2069
|
+
response = self.client.post(self.get_url(self.obj1))
|
|
2070
|
+
self.assertEqual(response.status_code, 200)
|
|
2071
|
+
|
|
2072
|
+
# The item will be moved to the last position by taking the sort_order
|
|
2073
|
+
# of the last item, and the sort_order of the other items updated by -1
|
|
2074
|
+
self.assertOrder([(self.obj2, 3), (self.obj3, 8), (self.obj1, 9)])
|
|
2075
|
+
|
|
2076
|
+
def test_post_request_with_non_integer_position_moves_to_the_end(self):
|
|
2077
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=good")
|
|
2078
|
+
self.assertEqual(response.status_code, 200)
|
|
2079
|
+
|
|
2080
|
+
# The item will be moved to the last position by taking the sort_order
|
|
2081
|
+
# of the last item, and the sort_order of the other items updated by -1
|
|
2082
|
+
self.assertOrder([(self.obj2, 3), (self.obj3, 8), (self.obj1, 9)])
|
|
2083
|
+
|
|
2084
|
+
def test_move_position_up(self):
|
|
2085
|
+
# Move obj3 to the first position
|
|
2086
|
+
response = self.client.post(self.get_url(self.obj3) + "?position=0")
|
|
2087
|
+
self.assertEqual(response.status_code, 200)
|
|
2088
|
+
|
|
2089
|
+
# Check if obj3 is now the first item by taking obj1's sort_order and
|
|
2090
|
+
# incrementing sort_order of the other items after it by 1
|
|
2091
|
+
self.assertOrder([(self.obj3, -3), (self.obj1, -2), (self.obj2, 5)])
|
|
2092
|
+
|
|
2093
|
+
def test_move_position_down(self):
|
|
2094
|
+
# Move obj1 to the second position
|
|
2095
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
2096
|
+
self.assertEqual(response.status_code, 200)
|
|
2097
|
+
|
|
2098
|
+
# Check if obj1 is now the second item by taking obj2's sort_order
|
|
2099
|
+
# and decrementing sort_order of the other items before it by 1
|
|
2100
|
+
self.assertOrder([(self.obj2, 3), (self.obj1, 4), (self.obj3, 9)])
|
|
2101
|
+
|
|
2102
|
+
def test_move_position_to_same_position(self):
|
|
2103
|
+
# Move obj1 to position 0 (where it already is)
|
|
2104
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=0")
|
|
2105
|
+
self.assertEqual(response.status_code, 200)
|
|
2106
|
+
|
|
2107
|
+
# Ensure item order does not change
|
|
2108
|
+
self.assertOrder([(self.obj1, -3), (self.obj2, 4), (self.obj3, 9)])
|
|
2109
|
+
|
|
2110
|
+
def test_move_position_with_invalid_target_position(self):
|
|
2111
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=99")
|
|
2112
|
+
self.assertEqual(response.status_code, 200)
|
|
2113
|
+
|
|
2114
|
+
# The item will be moved to the last position by taking the sort_order
|
|
2115
|
+
# of the last item, and the sort_order of the other items updated by -1
|
|
2116
|
+
self.assertOrder([(self.obj2, 3), (self.obj3, 8), (self.obj1, 9)])
|
|
2117
|
+
|
|
2118
|
+
def test_insufficient_permission(self):
|
|
2119
|
+
self.user.is_superuser = False
|
|
2120
|
+
self.user.save()
|
|
2121
|
+
admin_permission = Permission.objects.get(
|
|
2122
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
2123
|
+
)
|
|
2124
|
+
view_permission = Permission.objects.get(
|
|
2125
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
2126
|
+
codename=get_permission_codename("view", self.obj1._meta),
|
|
2127
|
+
)
|
|
2128
|
+
self.user.user_permissions.add(admin_permission, view_permission)
|
|
2129
|
+
|
|
2130
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
2131
|
+
self.assertEqual(response.status_code, 302)
|
|
2132
|
+
self.assertRedirects(response, reverse("wagtailadmin_home"))
|
|
2133
|
+
self.assertOrder([(self.obj1, -3), (self.obj2, 4), (self.obj3, 9)])
|
|
2134
|
+
|
|
2135
|
+
def test_minimal_permission(self):
|
|
2136
|
+
self.user.is_superuser = False
|
|
2137
|
+
self.user.save()
|
|
2138
|
+
admin_permission = Permission.objects.get(
|
|
2139
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
2140
|
+
)
|
|
2141
|
+
change_permission = Permission.objects.get(
|
|
2142
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
2143
|
+
codename=get_permission_codename("change", self.obj1._meta),
|
|
2144
|
+
)
|
|
2145
|
+
self.user.user_permissions.add(admin_permission, change_permission)
|
|
2146
|
+
|
|
2147
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
2148
|
+
self.assertEqual(response.status_code, 200)
|
|
2149
|
+
|
|
2150
|
+
# Check if obj1 is now the second item by taking obj2's sort_order
|
|
2151
|
+
# and decrementing sort_order of the other items before it by 1
|
|
2152
|
+
self.assertOrder([(self.obj2, 3), (self.obj1, 4), (self.obj3, 9)])
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
|
|
3
|
+
from django.contrib.admin.utils import quote
|
|
4
|
+
from django.utils.functional import cached_property
|
|
5
|
+
from django.utils.translation import gettext, gettext_lazy
|
|
6
|
+
|
|
7
|
+
from wagtail.admin.ui.tables import BaseColumn, BulkActionsCheckboxColumn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrderingColumn(BaseColumn):
|
|
11
|
+
header_template_name = "wagtailadmin/tables/ordering_header.html"
|
|
12
|
+
cell_template_name = "wagtailadmin/tables/ordering_cell.html"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OrderableTableMixin:
|
|
16
|
+
success_message = gettext_lazy("'%(page_title)s' has been moved successfully.")
|
|
17
|
+
|
|
18
|
+
def __init__(self, *args, sort_order_field=None, reorder_url=None, **kwargs):
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
self.sort_order_field = sort_order_field
|
|
21
|
+
self.reorder_url = reorder_url
|
|
22
|
+
if self.reorder_url:
|
|
23
|
+
self._add_ordering_column()
|
|
24
|
+
|
|
25
|
+
@cached_property
|
|
26
|
+
def ordering_column(self):
|
|
27
|
+
return OrderingColumn("ordering", width="80px", sort_key=self.sort_order_field)
|
|
28
|
+
|
|
29
|
+
def _add_ordering_column(self):
|
|
30
|
+
self.columns = OrderedDict(
|
|
31
|
+
[(self.ordering_column.name, self.ordering_column)]
|
|
32
|
+
+ [
|
|
33
|
+
(column_name, column)
|
|
34
|
+
for column_name, column in self.columns.items()
|
|
35
|
+
# Replace bulk actions column with the ordering column if it exists
|
|
36
|
+
if not isinstance(column, BulkActionsCheckboxColumn)
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def attrs(self):
|
|
42
|
+
attrs = super().attrs
|
|
43
|
+
if self.reorder_url:
|
|
44
|
+
attrs = {
|
|
45
|
+
**attrs,
|
|
46
|
+
"data-controller": "w-orderable",
|
|
47
|
+
"data-w-orderable-active-class": "w-orderable--active",
|
|
48
|
+
"data-w-orderable-chosen-class": "w-orderable__item--active",
|
|
49
|
+
"data-w-orderable-container-value": "tbody",
|
|
50
|
+
"data-w-orderable-message-value": self.get_success_message(),
|
|
51
|
+
"data-w-orderable-url-value": self.reorder_url,
|
|
52
|
+
}
|
|
53
|
+
return attrs
|
|
54
|
+
|
|
55
|
+
def get_success_message(self):
|
|
56
|
+
return self.success_message % {"page_title": "__LABEL__"}
|
|
57
|
+
|
|
58
|
+
def get_row_attrs(self, instance):
|
|
59
|
+
attrs = super().get_row_attrs(instance)
|
|
60
|
+
if self.reorder_url:
|
|
61
|
+
attrs["id"] = "item_%s" % quote(instance.pk)
|
|
62
|
+
attrs["data-w-orderable-item-id"] = quote(instance.pk)
|
|
63
|
+
attrs["data-w-orderable-item-label"] = str(instance)
|
|
64
|
+
attrs["data-w-orderable-target"] = "item"
|
|
65
|
+
return attrs
|
|
66
|
+
|
|
67
|
+
def get_caption(self):
|
|
68
|
+
caption = super().get_caption()
|
|
69
|
+
if not caption and self.reorder_url:
|
|
70
|
+
return gettext(
|
|
71
|
+
"Focus on the drag button and press up or down arrows to move the item, then press enter to submit the change."
|
|
72
|
+
)
|
|
73
|
+
return caption
|
wagtail/admin/ui/tables/pages.py
CHANGED
|
@@ -2,6 +2,7 @@ from django.utils.safestring import mark_safe
|
|
|
2
2
|
from django.utils.translation import gettext
|
|
3
3
|
|
|
4
4
|
from wagtail.admin.ui.tables import BaseColumn, BulkActionsCheckboxColumn, Column, Table
|
|
5
|
+
from wagtail.admin.ui.tables.orderable import OrderableTableMixin
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class PageTitleColumn(BaseColumn):
|
|
@@ -77,11 +78,6 @@ class BulkActionsColumn(BulkActionsCheckboxColumn):
|
|
|
77
78
|
return context
|
|
78
79
|
|
|
79
80
|
|
|
80
|
-
class OrderingColumn(BaseColumn):
|
|
81
|
-
header_template_name = "wagtailadmin/pages/listing/_ordering_header.html"
|
|
82
|
-
cell_template_name = "wagtailadmin/pages/listing/_ordering_cell.html"
|
|
83
|
-
|
|
84
|
-
|
|
85
81
|
class PageTypeColumn(Column):
|
|
86
82
|
def get_header_context_data(self, parent_context):
|
|
87
83
|
context = super().get_header_context_data(parent_context)
|
|
@@ -109,11 +105,10 @@ class NavigateToChildrenColumn(BaseColumn):
|
|
|
109
105
|
return mark_safe("<td></td>")
|
|
110
106
|
|
|
111
107
|
|
|
112
|
-
class PageTable(Table):
|
|
108
|
+
class PageTable(OrderableTableMixin, Table):
|
|
113
109
|
def __init__(
|
|
114
110
|
self,
|
|
115
111
|
*args,
|
|
116
|
-
use_row_ordering_attributes=False,
|
|
117
112
|
parent_page=None,
|
|
118
113
|
show_locale_labels=False,
|
|
119
114
|
actions_next_url=None,
|
|
@@ -121,9 +116,6 @@ class PageTable(Table):
|
|
|
121
116
|
):
|
|
122
117
|
super().__init__(*args, **kwargs)
|
|
123
118
|
|
|
124
|
-
# If true, attributes will be added on the <tr> element to support reordering
|
|
125
|
-
self.use_row_ordering_attributes = use_row_ordering_attributes
|
|
126
|
-
|
|
127
119
|
# The parent page of the pages being listed - used to add extra context to the title text
|
|
128
120
|
# of the reordering links. Leave this undefined if the pages being listed do not share a
|
|
129
121
|
# common parent.
|
|
@@ -163,11 +155,9 @@ class PageTable(Table):
|
|
|
163
155
|
|
|
164
156
|
def get_row_attrs(self, instance):
|
|
165
157
|
attrs = super().get_row_attrs(instance)
|
|
166
|
-
if self.
|
|
158
|
+
if self.reorder_url:
|
|
167
159
|
attrs["id"] = "page_%d" % instance.id
|
|
168
|
-
attrs["data-w-orderable-item-id"] = instance.id
|
|
169
160
|
attrs["data-w-orderable-item-label"] = instance.get_admin_display_title()
|
|
170
|
-
attrs["data-w-orderable-target"] = "item"
|
|
171
161
|
return attrs
|
|
172
162
|
|
|
173
163
|
def get_context_data(self, parent_context):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from django.core.exceptions import PermissionDenied
|
|
2
2
|
from django.shortcuts import get_object_or_404
|
|
3
|
+
from django.urls import reverse
|
|
3
4
|
|
|
4
5
|
from wagtail.admin.forms.collections import CollectionViewRestrictionForm
|
|
5
6
|
from wagtail.admin.modal_workflow import render_modal_workflow
|
|
@@ -69,10 +70,13 @@ def set_privacy(request, collection_id):
|
|
|
69
70
|
# no restriction set at ancestor level - can set restrictions here
|
|
70
71
|
return render_modal_workflow(
|
|
71
72
|
request,
|
|
72
|
-
"wagtailadmin/
|
|
73
|
+
"wagtailadmin/shared/set_privacy.html",
|
|
73
74
|
None,
|
|
74
75
|
{
|
|
75
|
-
"
|
|
76
|
+
"action_url": reverse(
|
|
77
|
+
"wagtailadmin_collections:set_privacy", args=(collection.pk,)
|
|
78
|
+
),
|
|
79
|
+
"object": collection,
|
|
76
80
|
"form": form,
|
|
77
81
|
},
|
|
78
82
|
json_data={"step": "set_privacy"},
|