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
wagtail/search/utils.py
CHANGED
|
@@ -1,206 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import re
|
|
3
|
-
from functools import partial
|
|
4
|
-
|
|
5
|
-
from django.apps import apps
|
|
6
|
-
from django.db import connections
|
|
7
|
-
from django.http import QueryDict
|
|
8
|
-
|
|
9
|
-
from wagtail.search.index import RelatedFields, SearchField
|
|
10
|
-
|
|
11
|
-
from .query import MATCH_NONE, Phrase, PlainText
|
|
12
|
-
|
|
13
|
-
NOT_SET = object()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def balanced_reduce(operator, seq, initializer=NOT_SET):
|
|
17
|
-
"""
|
|
18
|
-
Has the same result as Python's reduce function, but performs the calculations in a different order.
|
|
19
|
-
|
|
20
|
-
This is important when the operator is constructing data structures such as search query classes.
|
|
21
|
-
This method will make the resulting data structures flatter, so operations that need to traverse
|
|
22
|
-
them don't end up crashing with recursion errors.
|
|
23
|
-
|
|
24
|
-
For example:
|
|
25
|
-
|
|
26
|
-
Python's builtin reduce() function will do the following calculation:
|
|
27
|
-
|
|
28
|
-
reduce(add, [1, 2, 3, 4, 5, 6, 7, 8])
|
|
29
|
-
(1 + (2 + (3 + (4 + (5 + (6 + (7 + 8)))))))
|
|
30
|
-
|
|
31
|
-
When using this with query classes, it would create a large data structure with a depth of 7
|
|
32
|
-
Whereas balanced_reduce will execute this like so:
|
|
33
|
-
|
|
34
|
-
balanced_reduce(add, [1, 2, 3, 4, 5, 6, 7, 8])
|
|
35
|
-
((1 + 2) + (3 + 4)) + ((5 + 6) + (7 + 8))
|
|
36
|
-
|
|
37
|
-
Which only has a depth of 2
|
|
38
|
-
"""
|
|
39
|
-
# Casting all iterables to list makes the implementation simpler
|
|
40
|
-
if not isinstance(seq, list):
|
|
41
|
-
seq = list(seq)
|
|
42
|
-
|
|
43
|
-
# Note, it needs to be possible to use None as an initial value
|
|
44
|
-
if initializer is not NOT_SET:
|
|
45
|
-
if len(seq) == 0:
|
|
46
|
-
return initializer
|
|
47
|
-
else:
|
|
48
|
-
return operator(initializer, balanced_reduce(operator, seq))
|
|
49
|
-
|
|
50
|
-
if len(seq) == 0:
|
|
51
|
-
raise TypeError("reduce() of empty sequence with no initial value")
|
|
52
|
-
elif len(seq) == 1:
|
|
53
|
-
return seq[0]
|
|
54
|
-
else:
|
|
55
|
-
break_point = len(seq) // 2
|
|
56
|
-
first_set = balanced_reduce(operator, seq[:break_point])
|
|
57
|
-
second_set = balanced_reduce(operator, seq[break_point:])
|
|
58
|
-
return operator(first_set, second_set)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# Reduce any iterable to a single value using a logical OR e.g. (a | b | ...)
|
|
62
|
-
OR = partial(balanced_reduce, operator.or_)
|
|
63
|
-
# Reduce any iterable to a single value using a logical AND e.g. (a & b & ...)
|
|
64
|
-
AND = partial(balanced_reduce, operator.and_)
|
|
65
|
-
# Reduce any iterable to a single value using an addition
|
|
66
|
-
ADD = partial(balanced_reduce, operator.add)
|
|
67
|
-
# Reduce any iterable to a single value using a multiplication
|
|
68
|
-
MUL = partial(balanced_reduce, operator.mul)
|
|
69
|
-
|
|
70
|
-
MAX_QUERY_STRING_LENGTH = 255
|
|
71
|
-
|
|
72
|
-
filters_regexp = re.compile(r'\b(\w+):(\w+|"[^"]+"|\'[^\']+\')')
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def normalise_query_string(query_string):
|
|
76
|
-
# Truncate query string
|
|
77
|
-
query_string = query_string[:MAX_QUERY_STRING_LENGTH]
|
|
78
|
-
# Convert query_string to lowercase
|
|
79
|
-
query_string = query_string.lower()
|
|
80
|
-
|
|
81
|
-
# Remove leading, trailing and multiple spaces
|
|
82
|
-
query_string = re.sub(" +", " ", query_string).strip()
|
|
83
|
-
|
|
84
|
-
return query_string
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def separate_filters_from_query(query_string):
|
|
88
|
-
filters = QueryDict(mutable=True)
|
|
89
|
-
for match_object in filters_regexp.finditer(query_string):
|
|
90
|
-
key, value = match_object.groups()
|
|
91
|
-
filters.update({key: value.strip("\"'")})
|
|
92
|
-
|
|
93
|
-
query_string = filters_regexp.sub("", query_string).strip()
|
|
94
|
-
|
|
95
|
-
return filters, query_string
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def parse_query_string(query_string, operator=None, zero_terms=MATCH_NONE):
|
|
99
|
-
"""
|
|
100
|
-
This takes a query string typed in by a user and extracts the following:
|
|
101
|
-
|
|
102
|
-
- Quoted terms (for phrase search)
|
|
103
|
-
- Filters
|
|
104
|
-
|
|
105
|
-
For example, the following query:
|
|
106
|
-
|
|
107
|
-
`hello "this is a phrase" live:true` would be parsed into:
|
|
108
|
-
|
|
109
|
-
filters: {'live': 'true'}
|
|
110
|
-
tokens: And([PlainText('hello'), Phrase('this is a phrase')])
|
|
111
|
-
"""
|
|
112
|
-
filters, query_string = separate_filters_from_query(query_string)
|
|
113
|
-
|
|
114
|
-
is_phrase = False
|
|
115
|
-
tokens = []
|
|
116
|
-
if '"' in query_string:
|
|
117
|
-
parts = query_string.split('"')
|
|
118
|
-
else:
|
|
119
|
-
parts = query_string.split("'")
|
|
120
|
-
|
|
121
|
-
for part in parts:
|
|
122
|
-
part = part.strip()
|
|
123
|
-
|
|
124
|
-
if part:
|
|
125
|
-
if is_phrase:
|
|
126
|
-
tokens.append(Phrase(part))
|
|
127
|
-
else:
|
|
128
|
-
tokens.append(
|
|
129
|
-
PlainText(part, operator=operator or PlainText.DEFAULT_OPERATOR)
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
is_phrase = not is_phrase
|
|
133
|
-
|
|
134
|
-
if tokens:
|
|
135
|
-
if operator == "or":
|
|
136
|
-
search_query = OR(tokens)
|
|
137
|
-
else:
|
|
138
|
-
search_query = AND(tokens)
|
|
139
|
-
else:
|
|
140
|
-
search_query = zero_terms
|
|
141
|
-
|
|
142
|
-
return filters, search_query
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def get_descendant_models(model):
|
|
146
|
-
"""
|
|
147
|
-
Returns all descendants of a model, including the model itself.
|
|
148
|
-
"""
|
|
149
|
-
descendant_models = {
|
|
150
|
-
other_model
|
|
151
|
-
for other_model in apps.get_models()
|
|
152
|
-
if issubclass(other_model, model)
|
|
153
|
-
}
|
|
154
|
-
descendant_models.add(model)
|
|
155
|
-
return descendant_models
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def get_content_type_pk(model):
|
|
159
|
-
# We import it locally because this file is loaded before apps are ready.
|
|
160
|
-
from django.contrib.contenttypes.models import ContentType
|
|
161
|
-
|
|
162
|
-
return ContentType.objects.get_for_model(model).pk
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def get_ancestors_content_types_pks(model):
|
|
166
|
-
"""
|
|
167
|
-
Returns content types ids for the ancestors of this model, excluding it.
|
|
168
|
-
"""
|
|
169
|
-
from django.contrib.contenttypes.models import ContentType
|
|
170
|
-
|
|
171
|
-
return [
|
|
172
|
-
ct.pk
|
|
173
|
-
for ct in ContentType.objects.get_for_models(
|
|
174
|
-
*model._meta.get_parent_list()
|
|
175
|
-
).values()
|
|
176
|
-
]
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def get_descendants_content_types_pks(model):
|
|
180
|
-
"""
|
|
181
|
-
Returns content types ids for the descendants of this model, including it.
|
|
182
|
-
"""
|
|
183
|
-
from django.contrib.contenttypes.models import ContentType
|
|
184
|
-
|
|
185
|
-
return [
|
|
186
|
-
ct.pk
|
|
187
|
-
for ct in ContentType.objects.get_for_models(
|
|
188
|
-
*get_descendant_models(model)
|
|
189
|
-
).values()
|
|
190
|
-
]
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def get_search_fields(search_fields):
|
|
194
|
-
for search_field in search_fields:
|
|
195
|
-
if isinstance(search_field, SearchField):
|
|
196
|
-
yield search_field
|
|
197
|
-
elif isinstance(search_field, RelatedFields):
|
|
198
|
-
yield from get_search_fields(search_field.fields)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def get_postgresql_connections():
|
|
202
|
-
return [
|
|
203
|
-
connection
|
|
204
|
-
for connection in connections.all()
|
|
205
|
-
if connection.vendor == "postgresql"
|
|
206
|
-
]
|
|
1
|
+
from wagtailmodelsearch.utils import * # noqa: F403
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: \n"
|
|
11
|
-
"POT-Creation-Date: 2025-
|
|
11
|
+
"POT-Creation-Date: 2025-10-23 16:45+0100\n"
|
|
12
12
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
13
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
14
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: \n"
|
|
11
|
-
"POT-Creation-Date: 2025-
|
|
11
|
+
"POT-Creation-Date: 2025-10-23 16:45+0100\n"
|
|
12
12
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
13
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
14
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
@@ -178,7 +178,7 @@ msgstr ""
|
|
|
178
178
|
msgid "Choose"
|
|
179
179
|
msgstr ""
|
|
180
180
|
|
|
181
|
-
#: views/snippets.py:87 views/snippets.py:
|
|
181
|
+
#: views/snippets.py:87 views/snippets.py:875 wagtail_hooks.py:38
|
|
182
182
|
msgid "Snippets"
|
|
183
183
|
msgstr ""
|
|
184
184
|
|
|
@@ -195,7 +195,7 @@ msgstr ""
|
|
|
195
195
|
msgid "More options for '%(title)s'"
|
|
196
196
|
msgstr ""
|
|
197
197
|
|
|
198
|
-
#: views/snippets.py:
|
|
198
|
+
#: views/snippets.py:867
|
|
199
199
|
msgid "Home"
|
|
200
200
|
msgstr ""
|
|
201
201
|
|
|
Binary file
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# sergeybe <sergeybe@gmail.com>, 2020
|
|
13
13
|
# sergeybe <sergeybe@gmail.com>, 2020
|
|
14
14
|
# Tatsiana Tsygan <art.tatsiana@gmail.com>, 2018
|
|
15
|
+
# Twixxik, 2025
|
|
15
16
|
# Vassily <zuber.kg@gmail.com>, 2017
|
|
16
17
|
# Vassily <zuber.kg@gmail.com>, 2017
|
|
17
18
|
# Виктор Виктор <spam.vitek@gmail.com>, 2020-2021
|
|
@@ -22,7 +23,7 @@ msgstr ""
|
|
|
22
23
|
"Report-Msgid-Bugs-To: \n"
|
|
23
24
|
"POT-Creation-Date: 2025-07-24 16:20+0200\n"
|
|
24
25
|
"PO-Revision-Date: 2014-02-19 19:01+0000\n"
|
|
25
|
-
"Last-Translator:
|
|
26
|
+
"Last-Translator: Twixxik, 2025\n"
|
|
26
27
|
"Language-Team: Russian (http://app.transifex.com/torchbox/wagtail/language/"
|
|
27
28
|
"ru/)\n"
|
|
28
29
|
"MIME-Version: 1.0\n"
|
|
@@ -132,9 +133,15 @@ msgstr ""
|
|
|
132
133
|
"href=\"%(wagtailsnippets_create_snippet_url)s\" target=\"_blank\" "
|
|
133
134
|
"rel=\"noopener noreferrer\">создать сейчас</a>?,"
|
|
134
135
|
|
|
136
|
+
msgid "Scheduling…"
|
|
137
|
+
msgstr "Планирование…"
|
|
138
|
+
|
|
135
139
|
msgid "Publishing…"
|
|
136
140
|
msgstr "Публикуется..."
|
|
137
141
|
|
|
142
|
+
msgid "Schedule to publish"
|
|
143
|
+
msgstr "Запланировать публикацию"
|
|
144
|
+
|
|
138
145
|
msgid "Publish this version"
|
|
139
146
|
msgstr "Опубликовать эту версию"
|
|
140
147
|
|
|
Binary file
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# Basitlik İyidir, 2020
|
|
7
7
|
# Basitlik İyidir, 2020
|
|
8
8
|
# Cihad GÜNDOĞDU <cihadgundogdu@gmail.com>, 2016,2025
|
|
9
|
+
# Ertuğrul Keremoğlu, 2025
|
|
9
10
|
# Py Data Geek <pydatageek@gmail.com>, 2019
|
|
10
11
|
# Py Data Geek <pydatageek@gmail.com>, 2019
|
|
11
12
|
# Ragıp Ünal <ragip@ragipunal.com>, 2016
|
|
@@ -15,7 +16,7 @@ msgstr ""
|
|
|
15
16
|
"Report-Msgid-Bugs-To: \n"
|
|
16
17
|
"POT-Creation-Date: 2025-07-24 16:20+0200\n"
|
|
17
18
|
"PO-Revision-Date: 2014-02-19 19:01+0000\n"
|
|
18
|
-
"Last-Translator:
|
|
19
|
+
"Last-Translator: Ertuğrul Keremoğlu, 2025\n"
|
|
19
20
|
"Language-Team: Turkish (http://app.transifex.com/torchbox/wagtail/language/"
|
|
20
21
|
"tr/)\n"
|
|
21
22
|
"MIME-Version: 1.0\n"
|
|
@@ -70,6 +71,12 @@ msgstr "Seçilen blokları sil"
|
|
|
70
71
|
msgid "%(model_name)s '%(object)s' deleted."
|
|
71
72
|
msgstr "%(model_name)s '%(object)s' silindi."
|
|
72
73
|
|
|
74
|
+
#, python-format
|
|
75
|
+
msgid "%(count)d %(model_name)s deleted."
|
|
76
|
+
msgid_plural "%(count)d %(model_name)s deleted."
|
|
77
|
+
msgstr[0] "%(count)d %(model_name)s silindi."
|
|
78
|
+
msgstr[1] "%(count)d %(model_name)s silindi."
|
|
79
|
+
|
|
73
80
|
#, python-format
|
|
74
81
|
msgid "Delete %(snippet_type_name)s"
|
|
75
82
|
msgstr "%(snippet_type_name)s sil"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";var e,r={8039:(e,r,t)=>{var o=t(2614),a=t(9465);class n extends o.ZZ{}class i extends a.y{titleStateKey="string";chooserModalClass=n}class s extends a._{widgetClass=i;chooserModalClass=n}window.telepath.register("wagtail.snippets.widgets.SnippetChooser",s)}
|
|
1
|
+
(()=>{"use strict";var e,r={1669:e=>{e.exports=jQuery},8039:(e,r,t)=>{var o=t(2614),a=t(9465);class n extends o.ZZ{}class i extends a.y{titleStateKey="string";chooserModalClass=n}class s extends a._{widgetClass=i;chooserModalClass=n}window.telepath.register("wagtail.snippets.widgets.SnippetChooser",s)}},t={};function o(e){var a=t[e];if(void 0!==a)return a.exports;var n=t[e]={exports:{}};return r[e](n,n.exports,o),n.exports}o.m=r,e=[],o.O=(r,t,a,n)=>{if(!t){var i=1/0;for(d=0;d<e.length;d++){for(var[t,a,n]=e[d],s=!0,l=0;l<t.length;l++)(!1&n||i>=n)&&Object.keys(o.O).every(e=>o.O[e](t[l]))?t.splice(l--,1):(s=!1,n<i&&(i=n));if(s){e.splice(d--,1);var u=a();void 0!==u&&(r=u)}}return r}n=n||0;for(var d=e.length;d>0&&e[d-1][2]>n;d--)e[d]=e[d-1];e[d]=[t,a,n]},o.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return o.d(r,{a:r}),r},o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.j=474,(()=>{var e={474:0};o.O.j=r=>0===e[r];var r=(r,t)=>{var a,n,[i,s,l]=t,u=0;if(i.some(r=>0!==e[r])){for(a in s)o.o(s,a)&&(o.m[a]=s[a]);if(l)var d=l(o)}for(r&&r(t);u<i.length;u++)n=i[u],o.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return o.O(d)},t=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var a=o.O(void 0,[321],()=>o(8039));a=o.O(a)})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";var e,r={9987:(e,r,t)=>{var o=t(2614),n=t(9465);class a extends o.ZZ{}class i extends n.y{titleStateKey="string";chooserModalClass=a}window.SnippetChooser=i}
|
|
1
|
+
(()=>{"use strict";var e,r={1669:e=>{e.exports=jQuery},9987:(e,r,t)=>{var o=t(2614),n=t(9465);class a extends o.ZZ{}class i extends n.y{titleStateKey="string";chooserModalClass=a}window.SnippetChooser=i}},t={};function o(e){var n=t[e];if(void 0!==n)return n.exports;var a=t[e]={exports:{}};return r[e](a,a.exports,o),a.exports}o.m=r,e=[],o.O=(r,t,n,a)=>{if(!t){var i=1/0;for(f=0;f<e.length;f++){for(var[t,n,a]=e[f],l=!0,s=0;s<t.length;s++)(!1&a||i>=a)&&Object.keys(o.O).every(e=>o.O[e](t[s]))?t.splice(s--,1):(l=!1,a<i&&(i=a));if(l){e.splice(f--,1);var u=n();void 0!==u&&(r=u)}}return r}a=a||0;for(var f=e.length;f>0&&e[f-1][2]>a;f--)e[f]=e[f-1];e[f]=[t,n,a]},o.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return o.d(r,{a:r}),r},o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.j=686,(()=>{var e={686:0};o.O.j=r=>0===e[r];var r=(r,t)=>{var n,a,[i,l,s]=t,u=0;if(i.some(r=>0!==e[r])){for(n in l)o.o(l,n)&&(o.m[n]=l[n]);if(s)var f=s(o)}for(r&&r(t);u<i.length;u++)a=i[u],o.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return o.O(f)},t=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var n=o.O(void 0,[321],()=>o(9987));n=o.O(n)})();
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
from django.contrib.admin.utils import quote
|
|
2
|
+
from django.contrib.auth import get_permission_codename
|
|
3
|
+
from django.contrib.auth.models import Permission
|
|
4
|
+
from django.test import TestCase
|
|
5
|
+
from django.urls import reverse
|
|
6
|
+
|
|
7
|
+
from wagtail.test.testapp.models import FullFeaturedSnippet
|
|
8
|
+
from wagtail.test.utils import WagtailTestUtils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestIndexViewReordering(WagtailTestUtils, TestCase):
|
|
12
|
+
def setUp(self):
|
|
13
|
+
self.user = self.login()
|
|
14
|
+
# This model extends Orderable, thus it has a sort_order_field on the model
|
|
15
|
+
# and we don't need to set it on the viewset.
|
|
16
|
+
self.obj1 = FullFeaturedSnippet.objects.create(text="Toy 1", sort_order=0)
|
|
17
|
+
self.obj2 = FullFeaturedSnippet.objects.create(text="Toy 2", sort_order=1)
|
|
18
|
+
self.obj3 = FullFeaturedSnippet.objects.create(text="Toy 3", sort_order=2)
|
|
19
|
+
|
|
20
|
+
def get_url_name(self, name):
|
|
21
|
+
return FullFeaturedSnippet.snippet_viewset.get_url_name(name)
|
|
22
|
+
|
|
23
|
+
def test_header_button_rendered(self):
|
|
24
|
+
index_url = reverse(self.get_url_name("list"))
|
|
25
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
26
|
+
response = self.client.get(index_url)
|
|
27
|
+
self.assertEqual(response.status_code, 200)
|
|
28
|
+
soup = self.get_soup(response.content)
|
|
29
|
+
button = soup.select_one(
|
|
30
|
+
f".w-slim-header .w-dropdown a[href='{custom_ordering_url}']"
|
|
31
|
+
)
|
|
32
|
+
self.assertIsNotNone(button)
|
|
33
|
+
self.assertEqual(button.text.strip(), "Sort item order")
|
|
34
|
+
|
|
35
|
+
# Reordering feature disabled when not sorting by sort_order,
|
|
36
|
+
# the bulk actions column is present instead
|
|
37
|
+
table = soup.select_one("main table")
|
|
38
|
+
self.assertIsNotNone(table)
|
|
39
|
+
self.assertFalse(table.get("data-controller"))
|
|
40
|
+
bulk_actions_all = soup.select_one(
|
|
41
|
+
"main thead th:first-child input[type='checkbox']"
|
|
42
|
+
)
|
|
43
|
+
self.assertIsNotNone(bulk_actions_all)
|
|
44
|
+
self.assertTrue(
|
|
45
|
+
bulk_actions_all.has_attr("data-bulk-action-select-all-checkbox")
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def test_show_ordering_column(self):
|
|
49
|
+
index_url = reverse(self.get_url_name("list"))
|
|
50
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
51
|
+
response = self.client.get(custom_ordering_url)
|
|
52
|
+
self.assertEqual(response.status_code, 200)
|
|
53
|
+
soup = self.get_soup(response.content)
|
|
54
|
+
|
|
55
|
+
# The table should have the w-orderable controller
|
|
56
|
+
table = soup.select_one("main table")
|
|
57
|
+
self.assertIsNotNone(table)
|
|
58
|
+
self.assertEqual(table.get("data-controller"), "w-orderable")
|
|
59
|
+
self.assertEqual(
|
|
60
|
+
table.get("data-w-orderable-message-value"),
|
|
61
|
+
"'__LABEL__' has been moved successfully.",
|
|
62
|
+
)
|
|
63
|
+
self.assertEqual(
|
|
64
|
+
table.get("data-w-orderable-url-value"),
|
|
65
|
+
reverse(self.get_url_name("reorder"), args=[999999]),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# The bulk actions column should not be present
|
|
69
|
+
bulk_actions_all = table.select_one(
|
|
70
|
+
"thead th:first-child input[type='checkbox']"
|
|
71
|
+
)
|
|
72
|
+
self.assertIsNone(bulk_actions_all)
|
|
73
|
+
|
|
74
|
+
# The ordering column added as the first column
|
|
75
|
+
first_th = table.select_one("thead th:first-child")
|
|
76
|
+
self.assertIsNotNone(first_th)
|
|
77
|
+
self.assertEqual(first_th.text.strip(), "Sort")
|
|
78
|
+
|
|
79
|
+
# All rows have the corresponding attributes for reordering
|
|
80
|
+
rows = table.select("tbody tr")
|
|
81
|
+
self.assertEqual(len(rows), 3)
|
|
82
|
+
expected = [
|
|
83
|
+
{
|
|
84
|
+
"id": f"item_{quote(obj.pk)}",
|
|
85
|
+
"data-w-orderable-item-id": str(quote(obj.pk)),
|
|
86
|
+
"data-w-orderable-item-label": str(obj),
|
|
87
|
+
"data-w-orderable-target": "item",
|
|
88
|
+
}
|
|
89
|
+
for obj in [self.obj1, self.obj2, self.obj3]
|
|
90
|
+
]
|
|
91
|
+
for row, expected_attrs in zip(rows, expected):
|
|
92
|
+
for attr, value in expected_attrs.items():
|
|
93
|
+
self.assertEqual(row.get(attr), value)
|
|
94
|
+
handle = row.select_one("td button[data-w-orderable-target='handle']")
|
|
95
|
+
self.assertIsNotNone(handle)
|
|
96
|
+
|
|
97
|
+
def test_reordering_disabled_with_insufficient_permission(self):
|
|
98
|
+
self.user.is_superuser = False
|
|
99
|
+
self.user.save()
|
|
100
|
+
admin_permission = Permission.objects.get(
|
|
101
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
102
|
+
)
|
|
103
|
+
view_permission = Permission.objects.get(
|
|
104
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
105
|
+
codename=get_permission_codename("view", self.obj1._meta),
|
|
106
|
+
)
|
|
107
|
+
# Even with `change` permission, the reordering feature is not enabled
|
|
108
|
+
# because the model uses DraftStateMixin
|
|
109
|
+
change_permission = Permission.objects.get(
|
|
110
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
111
|
+
codename=get_permission_codename("change", self.obj1._meta),
|
|
112
|
+
)
|
|
113
|
+
self.user.user_permissions.add(
|
|
114
|
+
admin_permission, view_permission, change_permission
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
index_url = reverse(self.get_url_name("list"))
|
|
118
|
+
custom_ordering_url = index_url + "?ordering=sort_order"
|
|
119
|
+
response = self.client.get(custom_ordering_url)
|
|
120
|
+
self.assertEqual(response.status_code, 200)
|
|
121
|
+
soup = self.get_soup(response.content)
|
|
122
|
+
|
|
123
|
+
# Header button for enabling reordering should not be rendered
|
|
124
|
+
button = soup.select_one(
|
|
125
|
+
f".w-slim-header .w-dropdown a[href='{custom_ordering_url}']"
|
|
126
|
+
)
|
|
127
|
+
self.assertIsNone(button)
|
|
128
|
+
|
|
129
|
+
# Reordering feature not enabled
|
|
130
|
+
table = soup.select_one("main table")
|
|
131
|
+
self.assertIsNotNone(table)
|
|
132
|
+
self.assertFalse(table.get("data-controller"))
|
|
133
|
+
bulk_actions_all = soup.select_one(
|
|
134
|
+
"main thead th:first-child input[type='checkbox']"
|
|
135
|
+
)
|
|
136
|
+
self.assertIsNotNone(bulk_actions_all)
|
|
137
|
+
self.assertTrue(
|
|
138
|
+
bulk_actions_all.has_attr("data-bulk-action-select-all-checkbox")
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def test_minimal_permission(self):
|
|
142
|
+
self.user.is_superuser = False
|
|
143
|
+
self.user.save()
|
|
144
|
+
admin_permission = Permission.objects.get(
|
|
145
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
146
|
+
)
|
|
147
|
+
change_permission = Permission.objects.get(
|
|
148
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
149
|
+
codename=get_permission_codename("change", self.obj1._meta),
|
|
150
|
+
)
|
|
151
|
+
publish_permission = Permission.objects.get(
|
|
152
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
153
|
+
codename=get_permission_codename("publish", self.obj1._meta),
|
|
154
|
+
)
|
|
155
|
+
self.user.user_permissions.add(
|
|
156
|
+
admin_permission,
|
|
157
|
+
change_permission,
|
|
158
|
+
publish_permission,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.test_header_button_rendered()
|
|
162
|
+
self.test_show_ordering_column()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestCreateViewReordering(WagtailTestUtils, TestCase):
|
|
166
|
+
def setUp(self):
|
|
167
|
+
self.user = self.login()
|
|
168
|
+
FullFeaturedSnippet.objects.create(text="Toy 1", sort_order=0)
|
|
169
|
+
FullFeaturedSnippet.objects.create(text="Toy 2", sort_order=1)
|
|
170
|
+
FullFeaturedSnippet.objects.create(text="Toy 3", sort_order=2)
|
|
171
|
+
|
|
172
|
+
def test_create_sets_max_sort_order(self):
|
|
173
|
+
response = self.client.post(
|
|
174
|
+
reverse(FullFeaturedSnippet.snippet_viewset.get_url_name("add")),
|
|
175
|
+
data={"text": "New Toy"},
|
|
176
|
+
)
|
|
177
|
+
new_toy = FullFeaturedSnippet.objects.get(text="New Toy")
|
|
178
|
+
self.assertRedirects(
|
|
179
|
+
response,
|
|
180
|
+
reverse(
|
|
181
|
+
FullFeaturedSnippet.snippet_viewset.get_url_name("edit"),
|
|
182
|
+
args=(quote(new_toy.pk),),
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
new_toy = FullFeaturedSnippet.objects.get(text="New Toy")
|
|
186
|
+
self.assertEqual(new_toy.sort_order, 3)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class TestReorderView(WagtailTestUtils, TestCase):
|
|
190
|
+
def setUp(self):
|
|
191
|
+
self.user = self.login()
|
|
192
|
+
# We don't do any normalization, so the sort_order values may not be
|
|
193
|
+
# consecutive integers (e.g. after an item is deleted), and the update
|
|
194
|
+
# logic may cause the sort_order values to be negative or larger than
|
|
195
|
+
# the number of items in the queryset.
|
|
196
|
+
self.obj1 = FullFeaturedSnippet.objects.create(text="Toy 1", sort_order=0)
|
|
197
|
+
self.obj2 = FullFeaturedSnippet.objects.create(text="Toy 2", sort_order=1)
|
|
198
|
+
self.obj3 = FullFeaturedSnippet.objects.create(text="Toy 3", sort_order=2)
|
|
199
|
+
|
|
200
|
+
def get_url(self, obj):
|
|
201
|
+
return reverse(
|
|
202
|
+
FullFeaturedSnippet.snippet_viewset.get_url_name("reorder"),
|
|
203
|
+
args=(quote(obj.pk),),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def assertOrder(self, objs):
|
|
207
|
+
self.assertSequenceEqual(
|
|
208
|
+
[
|
|
209
|
+
(obj, obj.sort_order)
|
|
210
|
+
for obj in FullFeaturedSnippet.objects.order_by("sort_order")
|
|
211
|
+
],
|
|
212
|
+
objs,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def test_get_request_does_not_alter_order(self):
|
|
216
|
+
response = self.client.get(self.get_url(self.obj1))
|
|
217
|
+
self.assertEqual(response.status_code, 405)
|
|
218
|
+
|
|
219
|
+
# Ensure item order does not change
|
|
220
|
+
self.assertOrder([(self.obj1, 0), (self.obj2, 1), (self.obj3, 2)])
|
|
221
|
+
|
|
222
|
+
def test_post_request_without_position_argument_moves_to_the_end(self):
|
|
223
|
+
response = self.client.post(self.get_url(self.obj1))
|
|
224
|
+
self.assertEqual(response.status_code, 200)
|
|
225
|
+
|
|
226
|
+
# The item will be moved to the last position by taking the sort_order
|
|
227
|
+
# of the last item, and the sort_order of the other items updated by -1
|
|
228
|
+
self.assertOrder([(self.obj2, 0), (self.obj3, 1), (self.obj1, 2)])
|
|
229
|
+
|
|
230
|
+
def test_post_request_with_non_integer_position_moves_to_the_end(self):
|
|
231
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=good")
|
|
232
|
+
self.assertEqual(response.status_code, 200)
|
|
233
|
+
|
|
234
|
+
# The item will be moved to the last position by taking the sort_order
|
|
235
|
+
# of the last item, and the sort_order of the other items updated by -1
|
|
236
|
+
self.assertOrder([(self.obj2, 0), (self.obj3, 1), (self.obj1, 2)])
|
|
237
|
+
|
|
238
|
+
def test_move_position_up(self):
|
|
239
|
+
# Move obj3 to the first position
|
|
240
|
+
response = self.client.post(self.get_url(self.obj3) + "?position=0")
|
|
241
|
+
self.assertEqual(response.status_code, 200)
|
|
242
|
+
|
|
243
|
+
# Check if obj3 is now the first item by taking obj1's sort_order and
|
|
244
|
+
# incrementing sort_order of the other items after it (but before obj3's
|
|
245
|
+
# old position) by 1
|
|
246
|
+
self.assertOrder([(self.obj3, 0), (self.obj1, 1), (self.obj2, 2)])
|
|
247
|
+
|
|
248
|
+
def test_move_position_down(self):
|
|
249
|
+
# Move obj1 to the second position
|
|
250
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
251
|
+
self.assertEqual(response.status_code, 200)
|
|
252
|
+
|
|
253
|
+
# Check if obj1 is now the second item by taking obj2's sort_order
|
|
254
|
+
# and decreasing sort_order of the other items before it by 1
|
|
255
|
+
self.assertOrder([(self.obj2, 0), (self.obj1, 1), (self.obj3, 2)])
|
|
256
|
+
|
|
257
|
+
def test_move_position_to_same_position(self):
|
|
258
|
+
# Move obj1 to position 0 (where it already is)
|
|
259
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=0")
|
|
260
|
+
self.assertEqual(response.status_code, 200)
|
|
261
|
+
|
|
262
|
+
# Ensure item order does not change
|
|
263
|
+
self.assertOrder([(self.obj1, 0), (self.obj2, 1), (self.obj3, 2)])
|
|
264
|
+
|
|
265
|
+
def test_move_position_with_invalid_target_position(self):
|
|
266
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=99")
|
|
267
|
+
self.assertEqual(response.status_code, 200)
|
|
268
|
+
|
|
269
|
+
# The item will be moved to the last position by taking the sort_order
|
|
270
|
+
# of the last item, and the sort_order of the other items updated by -1
|
|
271
|
+
self.assertOrder([(self.obj2, 0), (self.obj3, 1), (self.obj1, 2)])
|
|
272
|
+
|
|
273
|
+
def test_insufficient_permission(self):
|
|
274
|
+
self.user.is_superuser = False
|
|
275
|
+
self.user.save()
|
|
276
|
+
admin_permission = Permission.objects.get(
|
|
277
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
278
|
+
)
|
|
279
|
+
view_permission = Permission.objects.get(
|
|
280
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
281
|
+
codename=get_permission_codename("view", self.obj1._meta),
|
|
282
|
+
)
|
|
283
|
+
self.user.user_permissions.add(admin_permission, view_permission)
|
|
284
|
+
|
|
285
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
286
|
+
self.assertEqual(response.status_code, 302)
|
|
287
|
+
self.assertRedirects(response, reverse("wagtailadmin_home"))
|
|
288
|
+
self.assertOrder([(self.obj1, 0), (self.obj2, 1), (self.obj3, 2)])
|
|
289
|
+
|
|
290
|
+
# `change` permission is not enough if the model uses DraftStateMixin
|
|
291
|
+
change_permission = Permission.objects.get(
|
|
292
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
293
|
+
codename=get_permission_codename("change", self.obj1._meta),
|
|
294
|
+
)
|
|
295
|
+
self.user.user_permissions.add(admin_permission, change_permission)
|
|
296
|
+
|
|
297
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
298
|
+
self.assertEqual(response.status_code, 302)
|
|
299
|
+
self.assertRedirects(response, reverse("wagtailadmin_home"))
|
|
300
|
+
self.assertOrder([(self.obj1, 0), (self.obj2, 1), (self.obj3, 2)])
|
|
301
|
+
|
|
302
|
+
def test_minimal_permission(self):
|
|
303
|
+
self.user.is_superuser = False
|
|
304
|
+
self.user.save()
|
|
305
|
+
admin_permission = Permission.objects.get(
|
|
306
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
307
|
+
)
|
|
308
|
+
publish_permission = Permission.objects.get(
|
|
309
|
+
content_type__app_label=self.obj1._meta.app_label,
|
|
310
|
+
codename=get_permission_codename("publish", self.obj1._meta),
|
|
311
|
+
)
|
|
312
|
+
self.user.user_permissions.add(admin_permission, publish_permission)
|
|
313
|
+
|
|
314
|
+
response = self.client.post(self.get_url(self.obj1) + "?position=1")
|
|
315
|
+
self.assertEqual(response.status_code, 200)
|
|
316
|
+
|
|
317
|
+
# Check if obj1 is now the second item by taking obj2's sort_order
|
|
318
|
+
# and decrementing sort_order of the other items before it by 1
|
|
319
|
+
self.assertOrder([(self.obj2, 0), (self.obj1, 1), (self.obj3, 2)])
|