Wagtail 6.1 release notes

May 1, 2024

What’s new

Universal listings continued

Continuing work on the Universal Listings project, this release rolls out universal listing styles for the following views:

  • Image listings

  • Document listings

  • Site and locale listings

  • Page and snippet history views

  • Form builder submissions

  • Collections listings

  • Groups

  • Users

  • Workflow and task views

  • Search promotions index views

  • Redirects index

Universal listing components like header buttons have also been tweaked to improve usability, and the PageListingViewSet now includes ChooseParentView to allow creating pages from custom page listings.

Thank you to everyone who worked on these features: Ben Enright, Sage Abdullah, Rohit Sharma, Storm Heg, Temidayo Azeez, and Abdelrahman Hamada.

Information-dense admin interface

Wagtail now provides a way for CMS users to control the information density of the admin interface, via their user profile preferences. The new setting allows switching between the “default” density and a new “snug” mode, which reduces the spacing and size of UI elements.

It’s also possible for site implementers to customize this for the needs of their project – see Custom UI information density.

This feature was developed by Ben Enright and Thibaud Colas.

Custom per-page-type listings

A new viewset class PageListingViewSet has been introduced, allowing developers to create custom page listings for specific page types similar to those previously provided by the Modeladmin package - see Custom page listings. This feature was developed by Matt Westcott.

Keyboard shortcuts overview

A new dialog is available from the help menu, providing an overview of keyboard shortcuts available in the Wagtail admin. This feature was developed by Karthik Ayangar and Rohit Sharma.

Better guidance for password-protected content

Wagtail now includes extra guidance in its private pages and private collections (documents) forms, to warn users about the pitfalls of the “shared password” option. For projects with higher security requirements, it’s now possible to disable the shared password option entirely. Thank you to Rohit Sharma, Salvo Polizzi, and Jake Howard for implementing those changes.

Favicon images generation

For sites managing favicons via the CMS, Wagtail now supports .ico favicon generation, with format-ico:

<link rel="icon" href="{% image favicon_image format-ico %}" />

This feature was developed by Jake Howard.

CVE-2024-32882: Permission check bypass when editing a model with per-field restrictions through wagtail.contrib.settings or ModelViewSet

This release addresses a permission vulnerability in the Wagtail admin interface. If a model has been made available for editing through the wagtail.contrib.settings module or ModelViewSet, and the permission argument on FieldPanel has been used to further restrict access to one or more fields of the model, a user with edit permission over the model but not the specific field can craft an HTTP POST request that bypasses the permission check on the individual field, allowing them to update its value.

The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin, or by a user who has not been granted edit access to the model in question. The editing interfaces for pages and snippets are also unaffected.

Many thanks to Ben Morse and Joshua Munn for reporting this issue, and Jake Howard and Sage Abdullah for the fix. For further details, please see the CVE-2024-32882 security advisory.

Other features

  • Add RelatedObjectsColumn to the table UI framework (Matt Westcott)

  • Reduce memory usage when rebuilding search indexes (Jake Howard)

  • Add system checks to ensure that WAGTAIL_DATE_FORMAT, WAGTAIL_DATETIME_FORMAT, WAGTAIL_TIME_FORMAT are correctly configured (Rohit Sharma, Coen van der Kamp)

  • Allow custom permissions with the same prefix as built-in permissions (Sage Abdullah)

  • Allow displaying permissions linked to the Admin model’s content type (Sage Abdullah)

  • Add support for Draftail’s JavaScript to use chooserUrls provided by entity options & for the Draftail widget to encode lazy URLs/ translations (Elhussein Almasri)

  • Added AbstractGroupApprovalTask to simplify customizing behavior of custom Task models (John-Scott Atlakson)

  • Add ability to bulk toggle permissions in the user group editing view, including shift+click for multiple selections (LB (Ben) Johnston, Kalob Taulien)

  • Update the minimum version of djangorestframework to 3.15.1 (Sage Abdullah)

  • Add support for related fields in generic IndexView.list_display (Abdelrahman Hamada)

  • Improve page fetching logic and cache route results per request. You can now use Page.route_for_request() to find the page route, and Page.find_for_request() to find the page given a request object and a URL path, see Page. Results are cached on request._wagtail_route_for_request (Gordon Pendleton)

  • Optimise rewriting of links / embeds in rich text using bulk database lookups (Andy Chosak)

  • Add normalization mechanism to StreamField so that assignments and defaults can be passed in a wider range of data types (Joshua Munn, Matt Westcott)

  • Allow specifying a STORAGES alias name for WAGTAILIMAGES_RENDITION_STORAGE (Alec Baron)

  • Update PASSWORD_REQUIRED_TEMPLATE setting to WAGTAIL_PASSWORD_REQUIRED_TEMPLATE with deprecation of previous naming (Saksham Misra, LB (Ben) Johnston)

  • Update DOCUMENT_PASSWORD_REQUIRED_TEMPLATE setting to WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE with deprecation of previous naming (Saksham Misra, LB (Ben) Johnston)

  • When editing settings (contrib) use the same icon in the editing view that was declared when registering the setting (Vince Salvino, Rohit Sharma)

  • Populate django-treebeard cache during page routing to improve performance of get_parent (Nigel van Keulen)

  • Add additional field types to Elasticsearch mapping (scott-8)

Bug fixes

  • Fix typo in __str__ for MySQL search index (Jake Howard)

  • Ensure that unit tests correctly check for migrations in all core Wagtail apps (Matt Westcott)

  • Correctly handle date objects on human_readable_date template tag (Jhonatan Lopes)

  • Ensure re-ordering buttons work correctly when using a nested InlinePanel (Adrien Hamraoui)

  • Consistently remove model’s verbose_name in group edit view when listing custom permissions (Sage Abdullah, Neeraj Yetheendran, Omkar Jadhav)

  • Resolve issue local development of docs when running make livehtml (Sage Abdullah)

  • Resolve issue with unwanted padding in chooser modal listings (Sage Abdullah)

  • Ensure form builder emails that have date or datetime fields correctly localize dates based on the configured LANGUAGE_CODE (Mark Niehues)

  • Ensure the Stimulus UnsavedController checks for nested removal/additions of inputs so that the unsaved warning shows in more valid cases when editing a page (Karthik Ayangar)

  • Ensure get_add_url() is always used to re-render the add button when the listing is refreshed in viewsets (Sage Abdullah)

  • Ensure dropdown content cannot get higher than the viewport and add scrolling within content if needed (Chiemezuo Akujobi)

  • Prevent snippets model index view from crashing when a model does not have an objects manager (Jhonatan Lopes)

  • Fix get_dummy_request’s resulting host name when running tests with ALLOWED_HOSTS = ["*"] (David Buxton)

  • Fix timezone handling in the timesince_last_update template tag (Matt Westcott)

  • Fix Postgres phrase search to respect the language set in settings (Ihar Marhitych)

  • Retain query parameters when switching between locales in the page chooser (Abdelrahman Hamada, Sage Abdullah)

  • Add w-kbd-scope-value with support for global so that specific keyboard shortcuts (e.g. ctrl+s/cmd+s) trigger consistently even when focused on fields (Neeraj Yetheendran)

  • Improve exception handling when generating image renditions concurrently (Andy Babic)

  • Respect WAGTAIL_ALLOW_UNICODE_SLUGS setting when auto-generating slugs (LB (Ben) Johnston)

  • Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)

  • Reinstate missing static files in style guide (Sage Abdullah)

  • Provide convert_mariadb_uuids management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)

Documentation

  • Add contributing development documentation on how to work with a fork of Wagtail (Nix Asteri, Dan Braghis)

  • Make sure the settings panel is listed in tabbed interface examples (Tibor Leupold)

  • Update content and page names to their US spelling instead of UK spelling (Victoria Poromon)

  • Update broken and incorrect links throughout the documentation (EK303)

  • Fix formatting of --purge-only in wagtail_update_image_renditions management command section (Pranith Beeram)

  • Update template components documentation to better explain the usage of the Laces library (Tibor Leupold)

  • Update Sphinx theme to 6.3.0 with a fix for the missing favicon (Sage Abdullah)

  • Document risk of XSS attacks on document upload in WAGTAILDOCS_SERVE_METHOD and example settings (Matt Westcott, with thanks to Georgios Roumeliotis of TwelveSec for the original report)

  • Add clarity to how custom StreamField validation works (Tibor Leupold)

  • Add additional reference to the wagtail_update_image_renditions management command on the using images page (LB (Ben) Johnston)

  • Correct information about line endings in Window development docs (Sage Abdullah)

  • Improve code snippets for “Create a footer for all pages” tutorial section (Drikus Roor)

  • Update list of third-party tutorials (LB (Ben) Johnston)

Maintenance

  • Move RichText HTML whitelist parser to use the faster, built in html.parser (Jake Howard)

  • Remove duplicate ‘path’ in default_exclude_fields_in_copy (Ramchandra Shahi Thakuri)

  • Update unit tests to always use the faster, built in html.parser & remove html5lib dependency (Jake Howard)

  • Adjust Eslint rules for TypeScript files (Karthik Ayangar)

  • Rename the React Button that only renders links (a element) to Link and remove unused prop & behaviors that was non-compliant for aria role usage (Advik Kabra)

  • Set up an wagtail.models.AbstractWorkflow model to support future customizations around workflows (Hossein)

  • Improve classnames template tag to handle nested lists of strings, use template tag for admin body element (LB (Ben) Johnston)

  • Merge UploadedDocument and UploadedImage into new UploadedFile model for easier shared code usage (Advik Kabra, Karl Hobley)

  • Optimize queries in dashboard panels (Sage Abdullah)

  • Optimize queries in group create/edit view (Sage Abdullah)

  • Move modal-workflow.js script usage to base admin template instead of ad-hoc imports (Elhussein Almasri)

  • Update all Draftail chooserUrls to be passed in via the Entity options instead of using window.chooserUrls globals, removing the need for inline scripts (Elhussein Almasri)

  • Enhance w-init (InitController) to support a detail value to be dispatched on events (Chiemezuo Akujobi)

  • Remove usage of inline scripts and instead use event dispatching to instantiate standalone Draftail editor instances (Chiemezuo Akujobi)

  • Refactor page_breadcrumbs tag to use shared breadcrumbs.html template (Sage Abdullah)

  • Add keyboard icon to admin icon set (Rohit Sharma)

  • Remove dead code in the minimap when elements are not found (LB (Ben) Johnston)

  • Ensure untrusted data sources are logged correctly in the Stimulus SwapController (LB (Ben) Johnston)

  • Update Wagtail logo in admin sidebar & favicon plus documentation to the latest version (Osaf AliSayed, Albina Starykova, LB (Ben) Johnston)

  • Remove usage of inline scripts and instead use a new Stimulus controller (w-block/BlockController) to instantiate StreamField blocks (Karthik Ayangar)

  • Update NPM Babel, TypeScript and Webpack packages (Neeraj Yetheendran)

  • Replace ad-hoc JavaScript and vendor Mousetrap usage to a new Stimulus controller (w-kbd/KeyboardController) (Neeraj Yetheendran)

  • Update django-filter to 24.x (Sebastian Muthwill)

  • Remove jQuery usage in telepath widget classes (Matt Westcott)

  • Remove xregexp (IE11 polyfill) along with window.XRegExp global util (LB (Ben) Johnston)

  • Refactor the Django port of urlify to use TypeScript, officially deprecate window.URLify global util (LB (Ben) Johnston)

Upgrade considerations - changes affecting Wagtail customizations

Changes to SubmissionsListView class

As part of the Universal Listings project, the SubmissionsListView for listing form submissions in the wagtail.contrib.forms app has been refactored to become a subclass of wagtail.admin.views.generic.base.BaseListingView. As a result, the class has undergone a number of changes, including the following:

  • The filtering mechanism has been reimplemented to use django-filter.

  • The ordering attribute has been renamed to default_ordering.

  • The template used to render the view has been significantly refactored to use the new universal listings UI.

register_user_listing_buttons hook signature changed

The function signature for the register_user_listing_buttons hook was updated to accept a request_user argument instead of context. If you use this hook, make sure to update your function signature to match the new one. The old signature with context will continue to work for now, but the context only contains the request object. Support for the old signature will be removed in a future release.

PASSWORD_REQUIRED_TEMPLATE has changed to WAGTAIL_PASSWORD_REQUIRED_TEMPLATE

The setting PASSWORD_REQUIRED_TEMPLATE has been deprecated, it will continue to work until a future release but the new name for this same setting will be WAGTAIL_PASSWORD_REQUIRED_TEMPLATE to align with other settings naming conventions.

See Frontend authentication.

DOCUMENT_PASSWORD_REQUIRED_TEMPLATE has changed to WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE

The setting DOCUMENT_PASSWORD_REQUIRED_TEMPLATE has been deprecated, it will continue to work until a future release but the new name for this same setting will be WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE to align with other settings naming conventions.

See Frontend authentication.

Upgrade considerations - changes to undocumented internals

Deprecation of user_listing_buttons template tag

The undocumented user_listing_buttons template tag has been deprecated and will be removed in a future release.

Deprecation of wagtailusers_groups:users URL pattern

The undocumented wagtailusers_groups:users URL pattern has been deprecated and will be removed in a future release. If you are using reverse with this URL pattern name, you should update your code to use the wagtailusers_users:index URL pattern name and the group ID as the group query parameter. For example:

reverse('wagtailusers_groups:users', args=[group.id])

should be updated to:

reverse('wagtailusers_users:index') + f'?group={group.id}'

The corresponding wagtailusers_groups:users_results URL pattern has been removed as part of this change.

A redirect from the old URL pattern to the new one has been added to ensure that existing URLs continue to work. This redirect will be removed in a future release.

Deprecation of window.chooserUrls within Draftail choosers

The undocumented usage of the JavaScript window.chooserUrls within Draftail choosers will be removed in a future release.

The following chooserUrl object values will be impacted.

  • anchorLinkChooser

  • documentChooser

  • emailLinkChooser

  • embedsChooser

  • externalLinkChooser

  • imageChooser

  • pageChooser

Overriding these inner values on the global window.chooserUrls object will still override their usage in the Draftail choosers for now but this capability will be removed in a future release.

Example

It’s recommended that usage of this global is removed in any customizations or third party packages and instead a custom Draftail Entity be used instead. See example below.

Old
# .../wagtail_hooks.py

@hooks.register("insert_editor_js")
def editor_js():
    return format_html(
        """
        <script>
            window.chooserUrls.myCustomChooser = '{0}';
        </script>
        """,
        reverse("myapp_chooser:choose"),
    )
New

Remove the insert_editor_js hook usage and instead pass the data needed via the Entity’s data.

# .../wagtail_hooks.py

from django.urls import reverse_lazy

@hooks.register("register_rich_text_features")
def register_my_custom_feature(features):
    # features.register_link_type...

    features.register_editor_plugin(
        "draftail",
        "custom-link",
        draftail_features.EntityFeature(
            {
                "type": "CUSTOM_ITEM",
                "icon": "doc-full-inverse",
                "description": gettext_lazy("Item"),
                "chooserUrls": {
                    # Important: `reverse_lazy` must be used unless the URL path is hard-coded
                    "myChooser": reverse_lazy("myapp_chooser:choose")
                },
            },
            js=["..."],
        ),
    )

Overriding existing chooserUrls values

To override existing chooser Entities’ chooserUrls values, here is an example of an unsupported monkey patch can achieve a similar goal.

However, it’s recommended that a custom Entity be created to be registered as a replacement feature for Draftail customizations as per the documentation.

# .../wagtail_hooks.py
from django.urls import reverse_lazy

from wagtail import hooks

@hooks.register("register_rich_text_features")
def override_embed_feature_url(features):
    features.plugins_by_editor["draftail"]["embed"].data["chooserUrls"]["embedsChooser"] = reverse_lazy("my_embeds:chooser")

Deprecation of window.initBlockWidget to initialize a StreamField block

The undocumented global function window.initBlockWidget has now been deprecated and will be removed in a future release.

Any complex customizations that have re-implemented parts of this functionality will need to be modified to adopt the new approach that uses Stimulus and avoids inline scripts.

The usage of this new approach is still unofficial and could change in the future, it’s recommended that the documented BlockWidget and StreamField approaches be used instead.

However, for comparison, here is the old and new approach below.

Old

Assuming we are using Django’s format_html to prepare the HTML output with JSON.dumps strings for the block data values.

The old approach would call the window.initBlockWidget global function with an inline script as follows:

<div
    id="{id}"
    data-block="{block_json}"
    data-value="{value_json}"
    data-error="{error_json}"
></div>
<script>
    initBlockWidget('{id}');
</script>

New

In the new approach, we no longer need to attach an inline script but instead use the Stimulus data attributes to attach the behavior with the w-block identifier as follows:

<div
    id="{id}"
    data-block
    data-controller="w-block"
    data-w-block-data-value="{block_json}"
    data-w-block-arguments-value="[{value_json},{error_json}]"
></div>

Removal of jQuery from base client-side Widget and BoundWidget classes

The JavaScript base classes Widget and BoundWidget that provide client-side access to form widgets (see Form widget client-side API) no longer use jQuery. The input property of BoundWidget (previously a jQuery collection) is now a native DOM element, and the element argument passed to the BoundWidget constructor (previously a jQuery collection) is now passed as a native DOM element if the HTML representation consists of a single element, and an iterable of elements (NodeList or array) otherwise. User code that extends these classes should be updated accordingly.

window.URLify deprecated

The undocumented client-side global util window.URLify is now deprecated and will be removed in a future release.

If this was required for slug field behavior, it’s recommended that the SlugInput widget be used instead. This will automatically convert values entered into a suitable slug in the browser while respecting the global configuration WAGTAIL_ALLOW_UNICODE_SLUGS.

from wagtail.admin.widgets.slug import SlugInput
# ... other imports

class MyPage(Page):
    promote_panels = [
        FieldPanel("slug", widget=SlugInput),
        # ... other panels
    ]

If you require this for custom JavaScript functionality, it’s recommended you either include your own implementation from the original Django URLify source. Alternatively, the slugify or parameterize (a Django URLify port) NPM packages might be suitable.

window.XRegExp polyfill removed

The legacy window.XRegExp global polyfill util has been removed and will throw an error if called.

Instead, any usage of this should be updated to the well supported browser native Regex implementation.

// old
const newStr = XRegExp.replace(originalStr, XRegExp('[ab+c]', 'g'), '')

// new (with RegExp)
const newStr = originalStr.replace(new RegExp('[ab+c]', 'g'), '')
// OR
const newStr = originalStr.replace(/[ab+c]/g, '')