` wrappers removed from rich text
In previous releases, rich text values were enclosed in a `
` element when rendered; this element has now been removed.
To restore the old behaviour, see [Legacy richtext](../reference/contrib/legacy_richtext.md).
### Prepopulating data for site history report
This release introduces logging of user actions, viewable through the “Site history” report. To pre-populate these logs with data from page revision history, run the management command: `./manage.py create_log_entries_from_revisions`.
### `clean_name` field added to form builder form field models
A `clean_name` field has been added to form field models that extend `AbstractForm`. This is used as the name attribute of the HTML form field, and the dictionary key that the submitted form data is stored under. Storing this on the model (rather than calculating it on-the-fly as was done previously) ensures that if the algorithm for generating the clean name changes in future, the existing data will not become inaccessible. A future version of Wagtail will drop the `unidecode` library currently used for this.
For forms created through the Wagtail admin interface, no action is required, as the new field will be populated on server startup. However, any process that creates form pages through direct insertion on the database (such as loading from fixtures) should now be updated to populate `clean_name`.
### New `next_url` keyword argument on `register_page_listing_buttons` and `register_page_listing_more_buttons` hooks
Functions registered through the hooks `register_page_listing_buttons` and `register_page_listing_more_buttons` now accept an additional keyword argument `next_url`. A hook function currently written as:
```python
@hooks.register('register_page_listing_buttons')
def page_listing_more_buttons(page, page_perms, is_parent=False):
yield wagtailadmin_widgets.Button(
'My button', '/goes/to/a/url/', priority=60
)
```
should now become:
```python
@hooks.register('register_page_listing_buttons')
def page_listing_more_buttons(page, page_perms, is_parent=False, next_url=None):
yield wagtailadmin_widgets.Button(
'My button', '/goes/to/a/url/', priority=60
)
```
The `next_url` argument specifies a URL to redirect back to after the action is complete, and can be passed as a query parameter to the linked URL, if the view supports it.
# 2.11.1.html.md
# Wagtail 2.11.1 release notes
*November 6, 2020*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure that cached `wagtail_site_root_paths` structures from older Wagtail versions are invalidated (Sævar Öfjörð Magnússon)
* Avoid circular import between wagtail.admin.auth and custom user models (Matt Westcott)
* Prevent error on resolving page URLs when a locale outside of `WAGTAIL_CONTENT_LANGUAGES` is active (Matt Westcott)
# 2.11.2.html.md
# Wagtail 2.11.2 release notes
*November 17, 2020*
> * [What’s new](#what-s-new)
## What’s new
### Facebook and Instagram embed finders
Two new embed finders have been added for Facebook and Instagram, to replace the previous configuration
using Facebook’s public oEmbed endpoint which was retired in October 2020. These require a Facebook
developer API key - for details of configuring this, see [Facebook and Instagram](../advanced_topics/embeds.md#facebook-and-instagram-embeds).
This feature was developed by Cynthia Kiser and Luis Nell.
### Bug fixes
* Improve performance of permission check on translations for edit page (Karl Hobley)
* Gracefully handle missing Locale records on `Locale.get_active` and `.localized` (Matt Westcott)
* Handle `get_supported_language_variant` returning a language variant not in `LANGUAGES` (Matt Westcott)
* Reinstate missing icon on settings edit view (Jérôme Lebleu)
* Avoid performance and pagination logic issues with a large number of languages (Karl Hobley)
* Allow deleting the default locale (Matt Westcott)
# 2.11.3.html.md
# Wagtail 2.11.3 release notes
*December 10, 2020*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
* Updated project template migrations to ensure that initial homepage creation runs before addition of locale field (Dan Braghis)
* Restore ability to use translatable strings in `LANGUAGES` / `WAGTAIL_CONTENT_LANGUAGES` settings (Andreas Morgenstern)
* Allow `locale` / `translation_of` API filters to be used in combination with search (Matt Westcott)
* Prevent error on `create_log_entries_from_revisions` when checking publish state on a revision that cannot be restored (Kristin Riebe)
## Upgrade considerations
### `run_before` declaration needed in initial homepage migration
The migration that creates the initial site homepage needs to be updated to ensure that will continue working under Wagtail 2.11. If you have kept the `home` app from the original project layout generated by the `wagtail start` command, this will be `home/migrations/0002_create_homepage.py`. Inside the `Migration` class, add the line `run_before = [('wagtailcore', '0053_locale_model')]` - for example:
```python
# ...
class Migration(migrations.Migration):
run_before = [
('wagtailcore', '0053_locale_model'), # added for Wagtail 2.11 compatibility
]
dependencies = [
('home', '0001_initial'),
]
operations = [
migrations.RunPython(create_homepage, remove_homepage),
]
```
This fix applies to any migration that creates page instances programmatically. If you installed Wagtail into an existing Django project by following the instructions at [Integrating Wagtail into a Django project](../getting_started/integrating_into_django.md), you most likely created the initial homepage manually, and no change is required in this case.
**Further background:** Wagtail 2.11 adds a `locale` field to the Page model, and since the existing migrations in your project pre-date this, they are designed to run against a version of the Page model that has no `locale` field. As a result, they need to run before the new migrations that have been added to `wagtailcore` within Wagtail 2.11. However, in the old version of the homepage migration, there is nothing to ensure that this sequence is followed. The actual order chosen is an internal implementation detail of Django, and in particular is liable to change as you continue developing your project under Wagtail 2.11 and create new migrations that depend on the current state of `wagtailcore`. In this situation, a user installing your project on a clean database may encounter the following error when running `manage.py migrate`:
```default
django.db.utils.IntegrityError: NOT NULL constraint failed: wagtailcore_page.locale_id
```
Adding the `run_before` directive will ensure that the migrations run in the intended order, avoiding this error.
# 2.11.4.html.md
# Wagtail 2.11.4 release notes
*February 16, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent delete button showing on collection / workflow edit views when delete permission is absent (Helder Correia)
* Ensure aliases are published when the source page is published (Karl Hobley)
* Make page privacy rules apply to aliases (Karl Hobley)
# 2.11.5.html.md
# Wagtail 2.11.5 release notes
*February 18, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Pin django-treebeard to <4.5 to prevent migration conflicts (Matt Westcott)
# 2.11.6.html.md
# Wagtail 2.11.6 release notes
*March 5, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Un-pin django-treebeard following upstream fix for migration issue (Matt Westcott)
> * Prevent crash when copying an alias page (Karl Hobley)
> * Prevent errors on page editing after changing LANGUAGE_CODE (Matt Westcott)
> * Correctly handle model inheritance and `ClusterableModel` on `copy_for_translation` (Karl Hobley)
# 2.11.7.html.md
# Wagtail 2.11.7 release notes
*April 19, 2021*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2021-29434: Improper validation of URLs (‘Cross-site Scripting’) in rich text fields
This release addresses a cross-site scripting (XSS) vulnerability in rich text fields. When saving the contents of a rich text field in the admin interface, Wagtail did not apply server-side checks to ensure that link URLs use a valid protocol. A malicious user with access to the admin interface could thus craft a POST request to publish content with javascript: URLs containing arbitrary code. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to Kevin Breen for reporting this issue.
# 2.11.8.html.md
# Wagtail 2.11.8 release notes
*June 17, 2021*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2021-32681: Improper escaping of HTML (‘Cross-site Scripting’) in Wagtail StreamField blocks
This release addresses a cross-site scripting (XSS) vulnerability in StreamField. When the `{% include_block %}` template tag is used to output the value of a plain-text StreamField block (`CharBlock`, `TextBlock` or a similar user-defined block derived from `FieldBlock`), and that block does not specify a template for rendering, the tag output is not properly escaped as HTML. This could allow users to insert arbitrary HTML or scripting. This vulnerability is only exploitable by users with the ability to author StreamField content (i.e. users with ‘editor’ access to the Wagtail admin).
Site implementers who wish to retain the existing behavior of allowing editors to insert HTML content in these blocks (and are willing to accept the risk of untrusted editors inserting arbitrary code) may disable the escaping by surrounding the relevant `{% include_block %}` tag in `{% autoescape off %}...{% endautoescape %}`.
Many thanks to Karen Tracey for reporting this issue. For further details, please see [the CVE-2021-32681 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-xfrw-hxr5-ghqf).
# 2.11.9.html.md
# Wagtail 2.11.9 release notes
*January 24, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Update Pillow dependency to allow 9.x (Rizwan Mansuri)
# 2.11.html.md
# Wagtail 2.11 (LTS) release notes
*November 2, 2020*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
Wagtail 2.11 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### Multi-lingual content
With this release, Wagtail now has official support for authoring content
for multiple languages/regions.
To find out more about this feature, see [Multi-language content](../advanced_topics/i18n.md#multi-language-content).
We have also developed a new plugin for translating content between different
locales called `wagtail-localize`. You can find details about `wagtail-localize`
on its [GitHub page](https://github.com/wagtail/wagtail-localize).
This feature was sponsored by The Mozilla Foundation and Torchbox.
### Page aliases
This release introduces support for creating page aliases.
Page aliases are exact copies of another page that sit in another part of the tree.
They remain in sync with the original page until this link is removed by converting them into a regular page, or deleting the original page.
A page alias can be created through the “Copy Page” UI by selecting the “Alias” checkbox when creating a page copy.
This feature was sponsored by The Mozilla Foundation.
### Collections hierarchy
Collections (for organising images, documents or other media) can now be managed as a hierarchy rather than a flat list. This feature was developed by Robert Rollins.
### Other features
* Add `before_edit_snippet`, `before_create_snippet` and `before_delete_snippet` hooks and documentation (Karl Hobley. Sponsored by the Mozilla Foundation)
* Add `register_snippet_listing_buttons` and `construct_snippet_listing_buttons` hooks and documentation (Karl Hobley. Sponsored by the Mozilla Foundation)
* Add `wagtail --version` to available Wagtail CLI commands (Kalob Taulien)
* Add `hooks.register_temporarily` utility function for testing hooks (Karl Hobley. Sponsored by the Mozilla Foundation)
* Remove `unidecode` and use `anyascii` in for Unicode to ASCII conversion (Robbie Mackay)
* Add `render` helper to `RoutablePageMixin` to support serving template responses according to Wagtail conventions (Andy Babic)
* Specify minimum Python version in setup.py (Vince Salvino)
* Extend treebeard’s `fix_tree` method with the ability to non-destructively fix path issues and add a –full option to apply path fixes (Matt Westcott)
* Add support for hierarchical/nested Collections (Robert Rollins)
* Show user’s full name in report views (Matt Westcott)
* Improve Wagtail admin page load performance by caching SVG icons sprite in localStorage (Coen van der Kamp)
* Support SVG icons in ModelAdmin menu items (Scott Cranfill)
* Support SVG icons in admin breadcrumbs (Coen van der Kamp)
* Serve PDFs inline in the browser (Matt Westcott)
* Make document `content-type` and `content-disposition` configurable via `WAGTAILDOCS_CONTENT_TYPES` and `WAGTAILDOCS_INLINE_CONTENT_TYPES` (Matt Westcott)
* Slug generation no longer removes stopwords (Andy Chosak, Scott Cranfill)
* Add check to disallow StreamField block names that do not match Python variable syntax (François Poulain)
* The `BASE_URL` setting is now converted to a string, if it isn’t already, when constructing API URLs (thenewguy)
* Preview from ‘pages awaiting moderation’ now opens in a new window (Cynthia Kiser)
* Add document extension validation if `WAGTAIL_DOCS_EXTENSIONS` is set to a list of allowed extensions (Meghana Bhange)
* Use `django-admin` command in place of `django-admin.py` (minusf)
* Add `register_snippet_action_menu_item` and `construct_snippet_action_menu` hooks to modify the actions available when creating / editing a snippet (Karl Hobley)
* Moved `generate_signature` and `verify_signature` functions into `wagtail.images.utils` (Noah H)
* Implement `bulk_to_python` on all structural StreamField block types (Matt Westcott)
* Add natural key support to `GroupCollectionPermission` (Jim Jazwiecki)
* Implement `prepopulated_fields` for `wagtail.contrib.modeladmin` (David Bramwell)
* Change `classname` keyword argument on basic StreamField blocks to `form_classname` (Meghana Bhange)
* Replace page explorer pushPage/popPage with gotoPage for more flexible explorer navigation (Karl Hobley)
### Bug fixes
* Make page-level actions accessible to keyboard users in page listing tables (Jesse Menn)
* `WAGTAILFRONTENDCACHE_LANGUAGES` was being interpreted incorrectly. It now accepts a list of strings, as documented (Karl Hobley)
* Update oEmbed endpoints to use https where available (Matt Westcott)
* Revise `edit_handler` bind order in ModelAdmin views and fix duplicate form instance creation (Jérôme Lebleu)
* Properly distinguish child blocks when comparing revisions with nested StreamBlocks (Martin Mena)
* Correctly handle Turkish ‘İ’ characters in client-side slug generation (Matt Westcott)
* Page chooser widgets now reflect custom `get_admin_display_title` methods (Saptak Sengupta)
* `Page.copy()` now raises an error if the page being copied is unsaved (Anton Zhyltsou)
* `Page.copy()` now triggers a `page_published` if the copied page is live (Anton Zhyltsou)
* The Elasticsearch `URLS` setting can now take a string on its own instead of a list (Sævar Öfjörð Magnússon)
* Avoid retranslating month / weekday names that Django already provides (Matt Westcott)
* Fixed padding around checkbox and radio inputs (Cole Maclean)
* Fix spacing around the privacy indicator panel (Sævar Öfjörð Magnússon, Dan Braghis)
* Consistently redirect to admin home on permission denied (Matt Westcott, Anton Zhyltsou)
## Upgrade considerations
### `run_before` declaration needed in initial homepage migration
The migration that creates the initial site homepage needs to be updated to ensure that will continue working under Wagtail 2.11. If you have kept the `home` app from the original project layout generated by the `wagtail start` command, this will be `home/migrations/0002_create_homepage.py`. Inside the `Migration` class, add the line `run_before = [('wagtailcore', '0053_locale_model')]` - for example:
```python
# ...
class Migration(migrations.Migration):
run_before = [
('wagtailcore', '0053_locale_model'), # added for Wagtail 2.11 compatibility
]
dependencies = [
('home', '0001_initial'),
]
operations = [
migrations.RunPython(create_homepage, remove_homepage),
]
```
This fix applies to any migration that creates page instances programmatically. If you installed Wagtail into an existing Django project by following the instructions at [Integrating Wagtail into a Django project](../getting_started/integrating_into_django.md), you most likely created the initial homepage manually, and no change is required in this case.
**Further background:** Wagtail 2.11 adds a `locale` field to the Page model, and since the existing migrations in your project pre-date this, they are designed to run against a version of the Page model that has no `locale` field. As a result, they need to run before the new migrations that have been added to `wagtailcore` within Wagtail 2.11. However, in the old version of the homepage migration, there is nothing to ensure that this sequence is followed. The actual order chosen is an internal implementation detail of Django, and in particular is liable to change as you continue developing your project under Wagtail 2.11 and create new migrations that depend on the current state of `wagtailcore`. In this situation, a user installing your project on a clean database may encounter the following error when running `manage.py migrate`:
```default
django.db.utils.IntegrityError: NOT NULL constraint failed: wagtailcore_page.locale_id
```
Adding the `run_before` directive will ensure that the migrations run in the intended order, avoiding this error.
### IE11 support being phased out
This release begins the process of phasing out support for Internet Explorer.
### SiteMiddleware moved to `wagtail.contrib.legacy`
The SiteMiddleware class (which provides the `request.site` property, and has been deprecated since Wagtail 2.9) has been moved to the `wagtail.contrib.legacy` namespace. On projects where this is still in use, the `'wagtail.core.middleware.SiteMiddleware'` entry in `MIDDLEWARE` should be changed to `'wagtail.contrib.legacy.sitemiddleware.SiteMiddleware'`.
### Collection model enforces alphabetical ordering
As part of the hierarchical collections support, the `path` field on the Collection model now enforces alphabetical ordering. Previously, collections were stored in the order in which they were created - and then sorted by name where displayed in the CMS. This change will be handled automatically through migrations when upgrading to Wagtail 2.11.
However, if your project creates new collections programmatically after migrations have run, and assigns the `path` field directly - for example, by loading from a fixture file - this code will need to be updated to insert them in alphabetical order. Otherwise, errors may occur when subsequently adding new collections through the Wagtail admin. This can be done as follows:
* Update paths to match alphabetical order. For example, if you have a fixture that creates the collections `Zebras` and `Aardvarks` with paths `00010001` and `00010002` respectively, these paths should be swapped.
* *Alternatively*, after creating the collections, run the Python code:
```python
from wagtail.core.models import Collection
Collection.fix_tree(fix_paths=True)
```
or the management command:
```console
python manage.py fixtree --full
```
### `Site.get_site_root_paths` now returns language code
In previous releases, `Site.get_site_root_paths` returned a list of `(site_id, root_path, root_url)` tuples. To support the new internationalisation model, this has now been changed to a list of named tuples with the fields: `site_id`, `root_path`, `root_url` and `language_code`. Existing code that handled this as a 3-tuple should be updated accordingly.
### `classname` argument on StreamField blocks is now `form_classname`
Basic StreamField block types such as CharBlock previously accepted a `classname` keyword argument, to specify a `class` attribute to appear on the page editing form. For consistency with StructBlock, this has now been changed to `form_classname`. The `classname` argument is still recognised, but deprecated.
# 2.12.1.html.md
# Wagtail 2.12.1 release notes
*February 16, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure aliases are published when the source page is published (Karl Hobley)
* Make page privacy rules apply to aliases (Karl Hobley)
* Prevent error when saving embeds that do not include a thumbnail URL (Cynthia Kiser)
* Ensure that duplicate embed records are deleted when upgrading (Matt Westcott)
* Prevent failure when running `manage.py dumpdata` with no arguments (Matt Westcott)
# 2.12.2.html.md
# Wagtail 2.12.2 release notes
*February 18, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Pin django-treebeard to <4.5 to prevent migration conflicts (Matt Westcott)
# 2.12.3.html.md
# Wagtail 2.12.3 release notes
*March 5, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Un-pin django-treebeard following upstream fix for migration issue (Matt Westcott)
> * Prevent crash when copying an alias page (Karl Hobley)
> * Prevent errors on page editing after changing LANGUAGE_CODE (Matt Westcott)
> * Correctly handle model inheritance and `ClusterableModel` on `copy_for_translation` (Karl Hobley)
# 2.12.4.html.md
# Wagtail 2.12.4 release notes
*April 19, 2021*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2021-29434: Improper validation of URLs (‘Cross-site Scripting’) in rich text fields
This release addresses a cross-site scripting (XSS) vulnerability in rich text fields. When saving the contents of a rich text field in the admin interface, Wagtail did not apply server-side checks to ensure that link URLs use a valid protocol. A malicious user with access to the admin interface could thus craft a POST request to publish content with javascript: URLs containing arbitrary code. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to Kevin Breen for reporting this issue.
### Bug fixes
* Prevent reverse migration errors in images and documents (Mike Brown)
* Avoid wagtailembeds migration failure on MySQL 8.0.13+ (Matt Westcott)
# 2.12.5.html.md
# Wagtail 2.12.5 release notes
*June 17, 2021*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2021-32681: Improper escaping of HTML (‘Cross-site Scripting’) in Wagtail StreamField blocks
This release addresses a cross-site scripting (XSS) vulnerability in StreamField. When the `{% include_block %}` template tag is used to output the value of a plain-text StreamField block (`CharBlock`, `TextBlock` or a similar user-defined block derived from `FieldBlock`), and that block does not specify a template for rendering, the tag output is not properly escaped as HTML. This could allow users to insert arbitrary HTML or scripting. This vulnerability is only exploitable by users with the ability to author StreamField content (i.e. users with ‘editor’ access to the Wagtail admin).
Site implementers who wish to retain the existing behaviour of allowing editors to insert HTML content in these blocks (and are willing to accept the risk of untrusted editors inserting arbitrary code) may disable the escaping by surrounding the relevant `{% include_block %}` tag in `{% autoescape off %}...{% endautoescape %}`.
Many thanks to Karen Tracey for reporting this issue. For further details, please see [the CVE-2021-32681 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-xfrw-hxr5-ghqf).
# 2.12.6.html.md
# Wagtail 2.12.6 release notes
*July 13, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Prevent embed thumbnail_url migration from failing on URLs longer than 200 characters (Matt Westcott)
# 2.12.html.md
# Wagtail 2.12 release notes
*February 2, 2021*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Image / document choose permission
Images and documents now support a distinct ‘choose’ permission type, to control the set of items displayed in the chooser interfaces when inserting images and documents into a page, and allow setting up collections that are only available for use by specific user groups. This feature was developed by Robert Rollins.
### In-place StreamField updating
StreamField values now formally support being updated in-place from Python code, allowing blocks to be inserted, modified and deleted rather than having to assign a new list of blocks to the field. For further details, see [Modifying StreamField data](../topics/streamfield.md#modifying-streamfield-data). This feature was developed by Matt Westcott.
### Admin colour themes
Wagtail’s admin now uses CSS custom properties for its primary teal colour. Applying brand colours for the whole user interface only takes a few lines of CSS, and third-party extensions can reuse Wagtail’s CSS variables to support the same degree of customization. Read on [Custom user interface colors](../advanced_topics/customization/admin_templates.md#custom-user-interface-colors). This feature was developed by Joshua Marantz.
### Other features
* Added support for Python 3.9
* Added `WAGTAILIMAGES_IMAGE_FORM_BASE` and `WAGTAILDOCS_DOCUMENT_FORM_BASE` settings to customize the forms for images and documents (Dan Braghis)
* Switch pagination icons to use SVG instead of icon fonts (Scott Cranfill)
* Added string representation to image Format class (Andreas Nüßlein)
* Support returning None from `register_page_action_menu_item` and `register_snippet_action_menu_item` to skip registering an item (Vadim Karpenko)
* Fields on a custom image model can now be defined as required / `blank=False` (Matt Westcott)
* Add combined index for Postgres search backend (Will Giddens)
* Add `Page.specific_deferred` property for accessing specific page instance without up-front database queries (Andy Babic)
* Add hash lookup to embeds to support URLs longer than 255 characters (Coen van der Kamp)
### Bug fixes
* Stop menu icon overlapping the breadcrumb on small viewport widths in page editor (Karran Besen)
* Make sure document chooser pagination preserves the selected collection when moving between pages (Alex Sa)
* Gracefully handle oEmbed endpoints returning non-JSON responses (Matt Westcott)
* Fix unique constraint on WorkflowState for SQL Server compatibility (David Beitey)
* Reinstate chevron on collection dropdown (Mike Brown)
* Prevent delete button showing on collection / workflow edit views when delete permission is absent (Helder Correia)
* Move labels above the form field in the image format chooser, to avoid styling issues at tablet size (Helen Chapman)
* `{% include_block with context %}` now passes local variables into the block template (Jonny Scholes)
## Upgrade considerations
### Removed support for Elasticsearch 2
Elasticsearch version 2 is no longer supported as of this release; please upgrade to Elasticsearch 5 or above before upgrading Wagtail.
### `stream_data` on StreamField values is deprecated
The `stream_data` property of StreamValue is commonly used to access the underlying data of a StreamField. However, this is discouraged, as it is an undocumented internal attribute and has different data representations depending on whether the value originated from the database or in memory, typically leading to errors on preview if this has not been properly accounted for. As such, `stream_data` is now deprecated.
The recommended alternative is to index the StreamField value directly as a list; for example, `page.body[0].block_type` and `page.body[0].value` instead of `page.body.stream_data[0]['type']` and `page.body.stream_data[0]['value']`. This has the advantage that it will return the same Python objects as when the StreamField is used in template code (such as Page instances for `PageChooserBlock`). However, in most cases, existing code using `stream_data` is written to expect the raw JSON-like representation of the data, and for this the new property `raw_data` (added in Wagtail 2.12) can be used as a drop-in replacement for `stream_data`.
# 2.13.1.html.md
# Wagtail 2.13.1 release notes
*June 1, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Ensure comment notification checkbox is fully hidden when commenting is disabled (Karl Hobley)
> * Prevent commenting from failing for user models with UUID primary keys (Jacob Topp-Mugglestone)
> * Fix incorrect link in comment notification HTML email (Matt Westcott)
# 2.13.2.html.md
# Wagtail 2.13.2 release notes
*June 17, 2021*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2021-32681: Improper escaping of HTML (‘Cross-site Scripting’) in Wagtail StreamField blocks
This release addresses a cross-site scripting (XSS) vulnerability in StreamField. When the `{% include_block %}` template tag is used to output the value of a plain-text StreamField block (`CharBlock`, `TextBlock` or a similar user-defined block derived from `FieldBlock`), and that block does not specify a template for rendering, the tag output is not properly escaped as HTML. This could allow users to insert arbitrary HTML or scripting. This vulnerability is only exploitable by users with the ability to author StreamField content (i.e. users with ‘editor’ access to the Wagtail admin).
Site implementers who wish to retain the existing behavior of allowing editors to insert HTML content in these blocks (and are willing to accept the risk of untrusted editors inserting arbitrary code) may disable the escaping by surrounding the relevant `{% include_block %}` tag in `{% autoescape off %}...{% endautoescape %}`.
Many thanks to Karen Tracey for reporting this issue. For further details, please see [the CVE-2021-32681 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-xfrw-hxr5-ghqf).
# 2.13.3.html.md
# Wagtail 2.13.3 release notes
*July 5, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Prevent error when using rich text on views where commenting is unavailable (Jacob Topp-Mugglestone)
> * Include form media on account settings page (Matt Westcott)
> * Avoid error when rendering validation error messages on ListBlock children (Matt Westcott)
> * Prevent comments CSS from overriding admin UI color customizations (Matt Westcott)
> * Avoid validation error when editing rich text content preceding a comment (Jacob Topp-Mugglestone)
# 2.13.4.html.md
# Wagtail 2.13.4 release notes
*July 13, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Prevent embed thumbnail_url migration from failing on URLs longer than 200 characters (Matt Westcott)
# 2.13.5.html.md
# Wagtail 2.13.5 release notes
*October 14, 2021*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
> * Allow relation name used for admin commenting to be overridden to avoid conflicts with third-party commenting apps (Matt Westcott)
> * Corrected badly-formed format strings in translations (Matt Westcott)
> * Correctly handle non-numeric user IDs for deleted users in reports (Dan Braghis)
## Upgrade considerations
### Customizing relation name for admin comments
The admin commenting feature introduced in Wagtail 2.13 added a relation named `comments` to the Page and User
models. This can cause conflicts with third-party apps that implement commenting functionality, and so this will be
renamed to `wagtail_admin_comments` in Wagtail 2.15. Developers who are affected by this issue, and have thus been
unable to upgrade to Wagtail 2.13 or above, can now “opt in” to the Wagtail 2.15 behavior by adding the following
line to their project settings:
```python
WAGTAIL_COMMENTS_RELATION_NAME = 'wagtail_admin_comments'
```
This will allow third-party commenting apps to work in Wagtail 2.13.5 alongside Wagtail’s admin commenting functionality.
Reusable library code that needs to preserve backwards compatibility with previous Wagtail versions
can find out the relation name as follows:
```python
try:
from wagtail.core.models import COMMENTS_RELATION_NAME
except ImportError:
COMMENTS_RELATION_NAME = 'comments'
```
# 2.13.html.md
# Wagtail 2.13 release notes
*May 12, 2021*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
> * [Feedback](#feedback)
## What’s new
### StreamField performance and functionality updates
The StreamField editing interface has been rebuilt on a client-side rendering model, powered by the [telepath](https://wagtail.github.io/telepath/) library. This provides better performance, increased customizability and UI enhancements including the ability to duplicate blocks. For further background, see the blog post [Telepath - the next evolution of StreamField](https://wagtail.org/blog/telepath/).
This feature was developed by Matt Westcott and Karl Hobley and sponsored by [YouGov](https://yougov.co.uk/), inspired by earlier work on [react-streamfield](https://github.com/wagtail/wagtail-react-streamfield) completed by Bertrand Bordage through the [Wagtail’s First Hatch](https://www.kickstarter.com/projects/noripyt/wagtails-first-hatch) crowdfunder.
### Simple translation module
In Wagtail 2.12 we shipped the new localisation support, but in order to translate content an external library had to be used, such as [wagtail-localize](https://www.wagtail-localize.org).
In this release, a new contrib app has been introduced called [simple_translation](../reference/contrib/simple_translation.md). This allows you to create copies of pages and translatable snippets in other languages and translate them as regular Wagtail pages. It does not include any more advanced translation features such as using external services, PO files, or an interface that helps keep translations in sync with the original language.
This module was contributed by Coen van der Kamp.
### Commenting
The page editor now supports leaving comments on fields and StreamField blocks, by entering commenting mode (using the button in the top right of the editor). Inline comments are available in rich text fields using the Draftail editor.
This feature was developed by Jacob Topp-Mugglestone, Karl Hobley and Simon Evans and sponsored by [The Motley Fool](https://www.fool.com/).
### Combined account settings
The “Account settings” section available at the bottom of the admin menu has been updated to include all settings on a single form. This feature was developed by Karl Hobley.
### Redirect export
The redirects module now includes support for exporting the list of redirects to XLSX or CSV. This feature was developed by Martin Sandström.
### Sphinx Wagtail Theme
The [documentation](https://docs.wagtail.org/) now uses our brand new [Sphinx Wagtail Theme](https://github.com/wagtail/sphinx_wagtail_theme), with a search feature powered by [Algolia DocSearch](https://docsearch.algolia.com/).
Feedback and feature requests for the theme may be reported to the [sphinx_wagtail_theme issue list](https://github.com/wagtail/sphinx_wagtail_theme/issues), and to Wagtail’s issues for the search.
Thank you to Storm Heg, Tibor Leupold, Thibaud Colas, Coen van der Kamp, Olly Willans, Naomi Morduch Toubman, Scott Cranfill, and Andy Chosak for making this happen!
### Django 3.2 support
Django 3.2 is formally supported in this release. Note that Wagtail 2.13 will be the last release to support Django 2.2.
### Other features
* Support passing `min_num`, `max_num` and `block_counts` arguments directly to `StreamField` (Haydn Greatnews, Matt Westcott)
* Add the option to set rich text images as decorative, without alt text (Helen Chapman, Thibaud Colas)
* Add support for `__year` filter in Elasticsearch queries (Seb Brown)
* Add `PageQuerySet.defer_streamfields()` (Andy Babic)
* Utilize `PageQuerySet.defer_streamfields()` to improve efficiency in a few key places (Andy Babic)
* Support passing multiple models as arguments to `type()`, `not_type()`, `exact_type()` and `not_exact_type()` methods on `PageQuerySet` (Andy Babic)
* Update default attribute copying behaviour of `Page.get_specific()` and added the `copy_attrs_exclude` option (Andy Babic)
* Update `PageQueryset.specific(defer=True)` to only perform a single database query (Andy Babic)
* Switched `register_setting`, `register_settings_menu_item` to use SVG icons (Thibaud Colas)
* Add support to SVG icons for `SearchArea` subclasses in `register_admin_search_area` (Thibaud Colas)
* Add specialized `wagtail.reorder` page audit log action. This was previously covered by the `wagtail.move` action (Storm Heg)
* `get_settings` template tag now supports specifying the variable name with `{% get_settings as var %}` (Samir Shah)
* Reinstate submitter’s name on moderation notification email (Matt Westcott)
* Add a new switch input widget as an alternative to checkboxes (Karl Hobley)
* Allow `{% pageurl %}` fallback to be a direct URL or an object with a `get_absolute_url` method (Andy Babic)
* Support slicing on StreamField / StreamBlock values (Matt Westcott)
* Switch Wagtail choosers to use SVG icons instead of font icon (Storm Heg)
* Save revision when restart workflow (Ihor Marhitych)
* Add a visible indicator of unsaved changes to the page editor (Jacob Topp-Mugglestone)
### Bug fixes
* StreamField required status is now consistently handled by the `blank` keyword argument (Matt Westcott)
* Show ‘required’ asterisks for blocks inside required StreamFields (Matt Westcott)
* Make image chooser “Select format” fields translatable (Helen Chapman, Thibaud Colas)
* Fix pagination on ‘view users in a group’ (Sagar Agarwal)
* Prevent page privacy menu from being triggered by pressing enter on a char field (Sagar Agarwal)
* Validate host/scheme of return URLs on password authentication forms (Susan Dreher)
* Reordering a page now includes the correct user in the audit log (Storm Heg)
* Fix reverse migration errors in images and documents (Mike Brown)
* Make “Collection” and “Parent” form field labels translatable (Thibaud Colas)
* Apply enough chevron padding to all applicable select elements (Scott Cranfill)
* Reduce database queries in the page edit view (Ihor Marhitych)
## Upgrade considerations
### End of Internet Explorer 11 support
Wagtail 2.13 will be the last Wagtail release to support IE11. Users accessing the admin with IE11 will be shown a warning message advising that support is being phased out.
### Updated handling of non-required StreamFields
The rules for determining whether a StreamField is required (i.e. at least one block must be provided) have been simplified and made consistent with other field types. Non-required fields are now indicated by `blank=True` on the `StreamField` definition; the default is `blank=False` (the field is required). In previous versions, to make a field non-required, it was necessary to define a top-level `StreamBlock` with `required=False` (which applied the validation rule) as well as setting `blank=True` (which removed the asterisk from the form field). You should review your use of StreamField to check that `blank=True` is used on the fields you wish to make optional.
### New client-side implementation for custom StreamField blocks
For the majority of cases, the new StreamField implementation in this release will be a like-for-like upgrade, and no code changes will be necessary - this includes projects where custom block types have been defined by extending `StructBlock`, `ListBlock` and `StreamBlock`. However, certain complex customizations may need to be reimplemented to work with the new client-side rendering model:
* When customizing the form template for a `StructBlock` using the `form_template` attribute, the HTML of each child block must be enclosed in an element with a `data-contentpath` attribute equal to the block’s name. This attribute is used by the commenting framework to attach comments to the correct fields. See [Custom editing interfaces for StructBlock](../advanced_topics/customization/streamfield_blocks.md#custom-editing-interfaces-for-structblock).
* If a `StructBlock` subclass overrides the `get_form_context` method as part of customizing the form template, and that method contains logic that causes the returned context to vary depending on the block value, this will no longer work as intended. This is because `get_form_context` is now invoked once with the block’s default (blank) value in order to construct a template for the client-side rendering to use; previously it was called for each block in the stream. In the new implementation, any Python-side processing that needs to happen on a per-block-value basis can be performed in the block’s `get_form_state` method; the data returned from that method will then be available in the client-side `render` method.
* If `FieldBlock` is used to wrap a Django widget with non-standard client-side behaviour - such as requiring a JavaScript function to be called on initialisation, or combining multiple HTML elements such that it is not possible to read or write its data by accessing a single element’s `value` property - then you will need to supply a JavaScript handler object to define how the widget is rendered and populated, and how to extract data from it.
* Packages that replace the StreamField interface at a low level, such as `wagtail-react-streamfield`, are likely to be incompatible (but the new StreamField implementation will generally offer equivalent functionality).
For further details, see [How to build custom StreamField blocks](../advanced_topics/customization/streamfield_blocks.md#custom-streamfield-blocks).
### Switched `register_setting`, `register_settings_menu_item` to use SVG icons
Setting menu items now use SVG icons by default. For sites reusing built-in Wagtail icons, no changes should be required. For sites using custom font icons, update the menu items’ definition to use the `classnames` attribute:
```python
# With register_setting,
# Before:
@register_setting(icon='custom-cog')
# After:
@register_setting(icon='', classnames='icon icon-custom-cog')
# Or with register_settings_menu_item,
@hooks.register('register_settings_menu_item')
def register_frank_menu_item():
# Before:
return SettingMenuItem(CustomSetting, icon='custom-cog')
# After:
return SettingMenuItem(CustomSetting, icon='', classnames='icon icon-custom-cog')
```
### `CommentPanel`
`Page.settings_panels` now includes `CommentPanel`, which is used to save and load comments. If you are overriding page settings edit handlers
without directly extending `Page.settings_panels` (ie `settings_panels = Page.settings_panels + [ FieldPanel('my_field') ]` would need no
change here) and want to use the new commenting system, your list of edit handlers should be updated to include `CommentPanel`. For example:
```python
from django.db import models
from wagtail.core.models import Page
from wagtail.admin.edit_handlers import CommentPanel
class HomePage(Page):
settings_panels = [
# My existing panels here
CommentPanel(),
]
```
## Feedback
We would love to [receive your feedback](https://forms.gle/G5WYo6sLiZiwdfsQA) on this release.
# 2.14.1.html.md
# Wagtail 2.14.1 release notes
*August 12, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Prevent failure on Twitter embeds and others which return cache_age as a string (Matt Westcott)
> * Fix Uncaught ReferenceError when editing links in Hallo (Cynthia Kiser)
# 2.14.2.html.md
# Wagtail 2.14.2 release notes
*October 14, 2021*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
> * Allow relation name used for admin commenting to be overridden to avoid conflicts with third-party commenting apps (Matt Westcott)
> * Corrected badly-formed format strings in translations (Matt Westcott)
> * Page history now works correctly when it contains changes by a deleted user (Dan Braghis)
## Upgrade considerations
### Customizing relation name for admin comments
The admin commenting feature introduced in Wagtail 2.13 added a relation named `comments` to the Page and User
models. This can cause conflicts with third-party apps that implement commenting functionality, and so this will be
renamed to `wagtail_admin_comments` in Wagtail 2.15. Developers who are affected by this issue, and have thus been
unable to upgrade to Wagtail 2.13 or above, can now “opt in” to the Wagtail 2.15 behavior by adding the following
line to their project settings:
```python
WAGTAIL_COMMENTS_RELATION_NAME = 'wagtail_admin_comments'
```
This will allow third-party commenting apps to work in Wagtail 2.14.2 alongside Wagtail’s admin commenting functionality.
Reusable library code that needs to preserve backwards compatibility with previous Wagtail versions
can find out the relation name as follows:
```python
try:
from wagtail.core.models import COMMENTS_RELATION_NAME
except ImportError:
COMMENTS_RELATION_NAME = 'comments'
```
# 2.14.html.md
# Wagtail 2.14 release notes
*August 2, 2021*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### New features
> * Added `ancestor_of` API filter. See [Filtering by tree position (pages only)](../advanced_topics/api/v2/usage.md#apiv2-filter-by-tree-position). (Jaap Roes)
> * Added support for customizing group management views. See [Customizing group edit/create views](../extending/customizing_group_views.md#customizing-group-views). (Jan Seifert)
> * Added `full_url` property to image renditions (Shreyash Srivastava)
> * Added locale selector when choosing translatable snippets (Karl Hobley)
> * Added `WAGTAIL_WORKFLOW_ENABLED` setting for enabling / disabling moderation workflows globally (Matt Westcott)
> * Allow specifying `max_width` and `max_height` on EmbedBlock (Petr Dlouhý)
> * Add warning when StreamField is used without a StreamFieldPanel (Naomi Morduch Toubman)
> * Added keyboard and screen reader support to Wagtail user bar (LB Johnston, Storm Heg)
> * Added instructions on copying and aliasing pages to the editor’s guide in documentation (Vlad Podgurschi)
> * Add Google Data Studio to the list of oEmbed providers (Petr Dlouhý)
> * Allow ListBlock to raise validation errors that are not attached to an individual child block (Matt Westcott)
> * Use `DATETIME_FORMAT` for localization in templates (Andrew Stone)
> * Added documentation on multi-site, multi-instance and multi-tenancy setups (Coen Van Der Kamp)
> * Updated Facebook / Instagram oEmbed endpoints to v11.0 (Thomas Kremmel)
> * Performance improvements for admin listing pages (Jake Howard, Dan Braghis, Tom Usher)
### Bug fixes
> * Invalid filter values for foreign key fields in the API now give an error instead of crashing (Tidiane Dia)
> * Ordering specified in the `construct_explorer_page_queryset` hook is now taken into account again by the page explorer API (Andre Fonseca)
> * Deleting a page from its listing view no longer results in a 404 error (Tidiane Dia)
> * The Wagtail admin urls will now respect the `APPEND_SLASH` setting (Tidiane Dia)
> * Prevent “Forgotten password” link from overlapping with field on mobile devices (Helen Chapman)
> * Snippet admin urls are now namespaced to avoid ambiguity with the primary key component of the url (Matt Westcott)
> * Prevent error on copying pages with ClusterTaggableManager relations and multi-level inheritance (Chris Pollard)
> * Prevent failure on root page when registering the Page model with ModelAdmin (Jake Howard)
> * Prevent error when filtering page search results with a malformed content_type (Chris Pollard)
> * Prevent multiple submissions of “update” form when uploading images / documents (Mike Brown)
> * Ensure HTML title is populated on project template 404 page (Matt Westcott)
> * Respect cache_age parameters on embeds (Gordon Pendleton)
> * Page comparison view now reflects request-level customizations to edit handlers (Matt Westcott)
> * Add `block.super` to remaining `extra_js` & `extra_css` blocks (Andrew Stone)
> * Ensure that `editor` and `features` arguments on RichTextField are preserved by `clone()` (Daniel Fairhead)
> * Rename ‘spin’ CSS animation to avoid clashes with other libraries (Kevin Gutiérrez)
> * Prevent crash when copying a page from a section where the user has no publish permission (Karl Hobley)
> * Ensure that rich text conversion correctly handles images / embeds inside links or inline styles (Matt Westcott)
## Upgrade considerations
### Removed support for Django 2.2
Django 2.2 is no longer supported as of this release; please upgrade to Django 3.0 or above before upgrading Wagtail.
### User bar with keyboard and screen reader support
The Wagtail user bar (“edit bird”) widget now supports keyboard and screen reader navigation. To make the most of this, we now recommend placing the widget near the top of the page ``, so users can reach it without having to go through the whole page. See [Wagtail user bar](../topics/writing_templates.md#wagtailuserbar-tag) for more information.
For implementers of custom user bar menu items, we also now require the addition of `role="menuitem"` on the `a` element to provide the correct semantics. See [construct_wagtail_userbar](../reference/hooks.md#construct-wagtail-userbar) for more information.
### Deprecation of Facebook / Instagram oEmbed product
As of June 2021, the procedure for setting up a Facebook app to handle Facebook / Instagram embedded content (see [Facebook and Instagram](../advanced_topics/embeds.md#facebook-and-instagram-embeds)) has changed. It is now necessary to activate the “oEmbed Read” feature on the app, and submit it to Facebook for review. Apps that activated the oEmbed Product before June 8, 2021 must be migrated to oEmbed Read by September 7, 2021 to continue working. No change to the Wagtail code or configuration is required.
# 2.15.1.html.md
# Wagtail 2.15.1 release notes
*November 11, 2021*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Fix syntax when logging image rendition generation (Jake Howard)
> * Increase version range for django-filter dependency (Serafeim Papastefanos)
> * Prevent bulk action checkboxes from displaying on page reports and other non-explorer listings (Matt Westcott)
> * Fix errors on publishing pages via bulk actions (Matt Westcott)
> * Fix `csrf_token` issue when using the Approve or Unlock buttons on pages on the Wagtail admin home (Matt Westcott)
# 2.15.2.html.md
# Wagtail 2.15.2 release notes
*January 18, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### CVE-2022-21683: Comment reply notifications sent to incorrect users
This release addresses an information disclosure issue in Wagtail’s commenting feature. Previously, when notifications for new replies in comment threads were sent, they were sent to all users who had replied or commented anywhere on the site, rather than only in the relevant threads. This meant that a user could listen in to new comment replies on pages they did not have editing access to, as long as they had left a comment or reply somewhere on the site.
Many thanks to Ihor Marhitych for reporting this issue. For further details, please see [the CVE-2022-21683 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-xqxm-2rpm-3889).
### Bug fixes
> * Fixed transform operations in Filter.run() when image has been re-oriented (Justin Michalicek)
> * Remove extraneous header action buttons when creating or editing workflows and tasks (Matt Westcott)
> * Ensure that bulk publish actions pick up the latest draft revision (Matt Westcott)
> * Ensure the `checkbox_aria_label` is used correctly in the Bulk Actions checkboxes (Vu Pham)
> * Prevent error on MySQL search backend when searching three or more terms (Aldán Creo)
> * Allow wagtail.search app migrations to complete on versions of SQLite without full-text search support (Matt Westcott)
> * Update Pillow dependency to allow 9.x (Matt Westcott)
## Upgrade considerations
### Support for SQLite without full-text search support
This release restores the ability to run Wagtail against installations of SQLite that do not include the `fts5` extension for full-text search support. On these installations, the fallback search backend (without support for full-text queries) will be used, and the database table for storing indexed content will not be created.
If SQLite is subsequently upgraded to a version with `fts5` support, existing databases will still be missing this table, and full-text search will continue to be unavailable until it is created. To correct this, first make a backup copy of the database (since rolling back the migration could potentially reverse other schema changes), then run:
```console
./manage.py migrate wagtailsearch 0005
./manage.py migrate
./manage.py update_index
```
Additionally, since the database search backend now needs to run a query on initialization to check for the presence of this table, calling `wagtail.search.backends.get_search_backend` during application startup may now fail with a “Models aren’t loaded yet” error. Code that does this should be updated to only call `get_search_backend` at the point when a search query is to be performed.
# 2.15.3.html.md
# Wagtail 2.15.3 release notes
*January 26, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Implement correct check for SQLite installations without full-text search support (Matt Westcott)
# 2.15.4.html.md
# Wagtail 2.15.4 release notes
*February 11, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Fix issue where invalid bulk action URLs would incorrectly trigger a server error (500) instead of a valid not found (404) (Ihor Marhitych)
> * Fix issue where bulk actions would not work for object IDs greater than 999 when `USE_THOUSAND_SEPARATOR` (Dennis McGregor)
> * Fix syntax when logging image rendition generation (Jake Howard)
# 2.15.5.html.md
# Wagtail 2.15.5 release notes
*April 11, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
> * Allow bulk publishing of pages without revisions (Andy Chosak)
> * Ensure that all descendant pages are logged when deleting a page, not just immediate children (Jake Howard)
> * Generate new translation keys for translatable `Orderable` when page is copied without being published (Kalob Taulien, Dan Braghis)
> * Ignore GenericRelation when copying pages (John-Scott Atlakson)
## Upgrade considerations
### Jinja2 compatibility
Developers using Jinja2 templating should note that the template tags in this release (and earlier releases in the 2.15.x series) are compatible with Jinja2 2.11.x and 3.0.x. Jinja2 2.11.x is unmaintained and requires `markupsafe` to be pinned to version `<2.1` to work; Jinja2 3.1.x has breaking changes and is not compatible. We therefore recommend that you use Jinja2 3.0.x, or 2.11.x with fully pinned dependencies.
# 2.15.6.html.md
# Wagtail 2.15.6 release notes
*September 5, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Ensure the upgrade notification request for the latest release, which can be disabled via the `WAGTAIL_ENABLE_UPDATE_CHECK` sends the referrer origin with `strict-origin-when-cross-origin` (Karl Hobley)
> * On the Locked pages report, limit the “locked by” filter to just users who have locked pages (Stefan Hammer)
> * Ensure Python 3.10 compatibility when using Elasticsearch backend (Przemysław Buczkowski, Matt Westcott)
# 2.15.html.md
# Wagtail 2.15 (LTS) release notes
*November 4, 2021*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
Wagtail 2.15 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### New database search backend
Wagtail has a new search backend that uses the full-text search features of the database in use. It supports SQLite, PostgreSQL, MySQL, and MariaDB.
This new search backend replaces both the existing generic database and PostgreSQL-specific search backends.
To switch to this new backend, see the upgrade considerations below.
This feature was developed by Aldán Creo as part of Google Summer of Code.
### Bulk actions
Bulk actions are now available for Page, User, Image, and Document models in the Wagtail Admin, allowing users to perform actions like publication or deletion on groups of objects at once.
This feature was developed by Shohan Dutta Roy, mentored by Dan Braghis, Jacob Topp-Mugglestone, and Storm Heg.
### Audit logging for all models
Audit logging has been extended so that all models (not just pages) can have actions logged against them. The Site History report now includes logs from all object types and snippets and ModelAdmin provide a history view showing previous edits to an object. This feature was developed by Matt Westcott, and sponsored by [The Motley Fool](https://www.fool.com/).
### Collection management permissions
Permission for managing collections can now be assigned to individual subtrees of the collection hierarchy, allowing sub-teams within a site to control how their images and documents are organized. For more information, see [Collection management permissions](../topics/permissions.md#collection-management-permissions). This feature was developed by Cynthia Kiser.
### Typed table block
A new `TypedTableBlock` block type is available for StreamField, allowing authors to create tables where the cell values are any StreamField block type, including rich text. For more information, see [Typed table block](../reference/contrib/typed_table_block.md). This feature was developed by Matt Westcott, Coen van der Kamp and Scott Cranfill, and sponsored by [YouGov](https://yougov.com/).
### Windows high contrast support
As part of a broad push to improve the accessibility of the administration interface, Wagtail now supports [Windows high contrast mode](https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696). There are remaining known issues but we are confident Wagtail is now much more usable for people relying on this assistive technology.
Individual fixes were implemented by a large number of first-time and seasoned contributors:
> * Comments icon now matches link color (Dmitrii Faiazov, LB (Ben Johnston))
> * Sidebar logo is now visible in high contrast mode (Dmitrii Faiazov, LB (Ben Johnston))
> * Icons in links and buttons now use the appropriate “active control” color (Dmitrii Faiazov, LB (Ben Johnston))
> * Comments dropdown now has a border (Shariq Jamil, LB (Ben Johnston))
> * Make StreamField block chooser menu buttons appear as buttons (Dmitrii Faiazov, LB (Ben Johnston))
> * Add a separator to identify the search forms (Dmitrii Faiazov, LB (Ben Johnston))
> * Update tab styles so the active tab can be identified (Dmitrii Faiazov, LB (Ben Johnston))
> * Make hamburger menu a button for tab and high contrast accessibility (Amy Chan, Dan Braghis)
> * Tag fields now have the correct background (Desai Akshata, LB (Ben Johnston))
> * Added sidebar vertical separation with main content (Onkar Apte, LB (Ben Johnston))
> * Added vertical separation between field panels (Chakita Muttaraju, LB (Ben Johnston))
> * Switch widgets on/off states are now visually distinguishable (Sakshi Uppoor, Thibaud Colas)
> * Checkbox widgets on/off states are now visually distinguishable (Thibaud Colas, Jacob Topp-Mugglestone, LB (Ben Johnston))
Particular thanks to LB, who reviewed almost all of those contributions, and Kyle Bayliss, who did the initial audit to identify High contrast mode issues.
### Other features
> * Add the ability for the page chooser to convert external urls that match a page to internal links, see [WAGTAILADMIN_EXTERNAL_LINK_CONVERSION](../reference/settings.md#wagtailadmin-external-link-conversion) (Jacob Topp-Mugglestone. Sponsored by The Motley Fool)
> * Added “Extending Wagtail” section to documentation (Matt Westcott)
> * Introduced [template components](../extending/template_components.md), a standard mechanism for renderable objects in the admin (Matt Westcott)
> * Support `min_num` / `max_num` options on ListBlock (Matt Westcott)
> * Implemented automatic tree synchronisation for [contrib.simple_translation](../reference/contrib/simple_translation.md) (Mitchel Cabuloy)
> * Added a background_position_style property to renditions. This can be used to crop images using its focal point in the browser. See [Setting the background-position inline style based on the focal point](../advanced_topics/images/focal_points.md#rendition-background-position-style) (Karl Hobley)
> * Added a distinct `wagtail.copy_for_translation` log action type (Karl Hobley)
> * Add a debug logger around image rendition generation (Jake Howard)
> * Convert Documents and Images to class based views for easier overriding (Matt Westcott)
> * Isolate admin URLs for Documents and Images search listing results with the name ‘listing_results’ (Matt Westcott)
> * Removed `request.is_ajax()` usage in Documents, Image and Snippet views (Matt Westcott)
> * Simplify generic admin view templates plus ensure `page_title` and `page_subtitle` are used consistently (Matt Westcott)
> * Extend support for [collapsing edit panels](../reference/panels.md#collapsible) from just MultiFieldPanels to all kinds of panels (Fabien Le Frapper, Robbie Mackay)
> * Add object count to header within modeladmin listing view (Jonathan “Yoni” Knoll)
> * Add ability to return HTML in multiple image upload errors (Gordon Pendleton)
> * Upgrade internal JS tooling; Node v14 plus other smaller package upgrades (LB (Ben Johnston))
> * Add support for `non_field_errors` rendering in Workflow action modal (LB (Ben Johnston))
> * Support calling `get_image_model` and `get_document_model` at import time (Matt Westcott)
> * When copying a page, default the ‘Publish copied page’ field to false (Justin Slay)
> * Open Preview and Live page links in the same tab, except where it would interrupt editing a Page (Sagar Agarwal)
> * Added `ExcelDateFormatter` to `wagtail.admin.views.mixins` so that dates in Excel exports will appear in the locale’s `SHORT_DATETIME_FORMAT` (Andrew Stone)
> * Add TIDAL support to the list of oEmbed providers (Wout De Puysseleir)
> * Add `label_format` attribute to customize the label shown for a collapsed StructBlock (Matt Westcott)
> * User Group permissions editing in the admin will now show all custom object permissions in one row instead of a separate table (Kamil Marut)
> * Create `ImageFileMixin` to extract shared file handling methods from `AbstractImage` and `AbstractRendition` (Fabien Le Frapper)
> * Add `before_delete_page` and `register_permissions` examples to Hooks documentation (Jane Liu, Daniel Fairhead)
> * Add clarity to modeladmin template override behavior in the documentation (Joe Howard, Dan Swain)
> * Add section about CSV exports to security documentation (Matt Westcott)
> * Add initial support for Django 4.0 deprecations (Matt Westcott, Jochen Wersdörfer)
> * Translations in `nl_NL` are moved to the `nl` po files. `nl_NL` translation files are deleted. Projects that use `LANGUAGE_CODE = 'nl-nl'` will automatically fallback to `nl`. (Loïc Teixeira, Coen van der Kamp)
> * Add documentation for how to redirect to a separate page on Form builder submissions using `RoutablePageMixin` (Nick Smith)
> * Refactored index listing views and made column sort-by headings more consistent (Matt Westcott)
> * The title field on Image and Document uploads will now default to the filename without the file extension and this behavior can be customized (LB Johnston)
> * Add support for Python 3.10 (Matt Westcott)
> * Introduce, `autocomplete`, a separate method which performs partial matching on specific autocomplete fields. This is useful for suggesting pages to the user in real-time as they type their query. (Karl Hobley, Matt Westcott)
> * Use SVG icons in modeladmin headers and StreamField buttons/headers (Jérôme Lebleu)
> * Add tags to existing Django registered checks (LB Johnston)
> * Upgrade admin frontend JS libraries jQuery to 3.6.0 (Fabien Le Frapper)
> * Added `request.preview_mode` so that template rendering can vary based on preview mode (Andy Chosak)
### Bug fixes
> * Delete button is now correct colour on snippets and modeladmin listings (Brandon Murch)
> * Ensure that StreamBlock / ListBlock-level validation errors are counted towards error counts (Matt Westcott)
> * InlinePanel add button is now keyboard navigatable (Jesse Menn)
> * Remove redundant ‘clear’ button from site root page chooser (Matt Westcott)
> * Make ModelAdmin IndexView keyboard-navigable (Saptak Sengupta)
> * Prevent error on refreshing page previews when multiple preview tabs are open (Alex Tomkins)
> * Menu sidebar hamburger icon on smaller viewports now correctly indicates it is a button to screen readers and can be accessed via keyboard (Amy Chan, Dan Braghis)
> * `blocks.MultipleChoiceBlock`, `forms.CheckboxSelectMultiple` and `ArrayField` checkboxes will now stack instead of display inline to align with all other checkboxes fields (Seb Brown)
> * Screen readers can now access login screen field labels (Amy Chan)
> * Admin breadcrumbs home icon now shows for users with access to a subtree only (Stefan Hammer)
> * Add handling of invalid inline styles submitted to `RichText` so `ConfigException` is not thrown (Alex Tomkins)
> * Ensure comment notifications dropdown handles longer translations without overflowing content (Krzysztof Jeziorny)
> * Set `default_auto_field` in `postgres_search` `AppConfig` (Nick Moreton)
> * Ensure admin tab JS events are handled on page load (Andrew Stone)
> * `EmailNotificationMixin` and `send_notification` should only send emails to active users (Bryan Williams)
> * Disable Task confirmation now shows the correct value for quantity of tasks in progress (LB Johnston)
> * Page history now works correctly when it contains changes by a deleted user (Dan Braghis)
> * Add `gettext_lazy` to `ModelAdmin` built in view titles so that language settings are correctly used (Matt Westcott)
> * Tabbing and keyboard interaction on the Wagtail userbar now aligns with ARIA best practices (Storm Heg)
> * Add full support for custom `edit_handler` usage by adding missing `bind_to` call to `PreviewOnEdit` view (Stefan Hammer)
> * Only show active (not disabled) tasks in the workflow task chooser (LB Johnston)
> * CSS build scripts now output to the correct directory paths on Windows (Vince Salvino)
> * Capture log output from style fallback to avoid noise in unit tests (Matt Westcott)
> * Nested InlinePanel usage no longer fails to save when creating two or more items (Indresh P, Rinish Sam, Anirudh V S)
> * Changed relation name used for admin commenting from `comments` to `wagtail_admin_comments` to avoid conflicts with third-party commenting apps (Matt Westcott)
> * CSS variables are now correctly used for the filtering menu in modeladmin (Noah H)
> * Panel heading attribute is no longer ignored when nested inside a `MultiFieldPanel` (Jérôme Lebleu)
## Upgrade considerations
### Database search backends replaced
The following search backends (configured in `WAGTAILSEARCH_BACKENDS`) have been deprecated:
> - `wagtail.search.backends.db` (the default if `WAGTAILSEARCH_BACKENDS` is not specified)
> - `wagtail.contrib.postgres_search.backend`
Both of these backends have now been replaced by `wagtail.search.backends.database`. This new
backend supports all of the features of the PostgreSQL backend, and also supports other databases.
It will be made the default backend in Wagtail 3.0. To enable the new backend, edit (or add) the
`WAGTAILSEARCH_BACKENDS` setting as follows:
```python
WAGTAILSEARCH_BACKENDS = {
'default': {
'BACKEND': 'wagtail.search.backends.database',
}
}
```
Also remove `'wagtail.contrib.postgres_search'` from `INSTALLED_APPS` if this was previously set.
After switching to this backend, you will need to run the `manage.py update_index` management
command to populate the search index (see [update_index](../reference/management_commands.md#update-index)).
If you have used the PostgreSQL-specific `SEARCH_CONFIG`, this will continue to work as before with the new backend. For example:
```python
WAGTAILSEARCH_BACKENDS = {
'default': {
'BACKEND': 'wagtail.search.backends.database',
'SEARCH_CONFIG': 'english',
}
}
```
However, as a PostgreSQL specific feature, this will be ignored when using a different database.
### Admin homepage panels, summary items and action menu items now use components
Several Wagtail hooks provide a mechanism for passing Python objects to be rendered as HTML inside admin views, and the APIs for these objects have been updated to adopt a common [template components](../extending/template_components.md) pattern. The affected objects are:
> * Homepage panels (as registered with the [construct_homepage_panels](../reference/hooks.md#construct-homepage-panels) hook)
> * Homepage summary items (as registered with the [construct_homepage_summary_items](../reference/hooks.md#construct-homepage-summary-items) hook)
> * Page action menu items (as registered with the [register_page_action_menu_item](../reference/hooks.md#register-page-action-menu-item) and [construct_page_action_menu](../reference/hooks.md#construct-page-action-menu) hooks)
> * Snippet action menu items (as registered with the [register_snippet_action_menu_item](../reference/hooks.md#register-snippet-action-menu-item) and [construct_snippet_action_menu](../reference/hooks.md#construct-snippet-action-menu) hooks)
User code that creates these objects should be updated to follow the component API. This will typically require the following changes:
> * Homepage panels should be made subclasses of `wagtail.admin.ui.components.Component`, and the `render(self)` method should be changed to `render_html(self, parent_context)`. (Alternatively, rather than defining `render_html`, it may be more convenient to reimplement it with a template, as per [Creating components](../extending/template_components.md#creating-template-components).)
> * Summary item classes can continue to inherit from `wagtail.admin.site_summary.SummaryItem` (which is now a subclass of `Component`) as before, but:
> * Any `template` attribute should be changed to `template_name`;
> * Any place where the `render(self)` method is overridden should be changed to `render_html(self, parent_context)`;
> * Any place where the `get_context(self)` method is overridden should be changed to `get_context_data(self, parent_context)`.
> * Action menu items for pages and snippets can continue to inherit from `wagtail.admin.action_menu.ActionMenuItem` and `wagtail.snippets.action_menu.ActionMenuItem` respectively - these are now subclasses of `Component` - but:
> * Any `template` attribute should be changed to `template_name`;
> * Any `get_context` method should be renamed to `get_context_data`;
> * The `get_url`, `is_shown`, `get_context_data` and `render_html` methods no longer accept a `request` parameter. The request object is available in the context dictionary as `context['request']`.
### Passing callables as messages in `register_log_actions` is deprecated
When defining new action types for [audit logging](../extending/audit_log.md#audit-log) with the [register_log_actions](../reference/hooks.md#register-log-actions) hook, it was previously possible to pass a callable as the message. This is now deprecated - to define a message that depends on the log entry’s data, you should now create a subclass of `wagtail.core.log_actions.LogFormatter`. For example:
```python
from django.utils.translation import gettext_lazy as _
from wagtail.core import hooks
@hooks.register('register_log_actions')
def additional_log_actions(actions):
def greeting_message(data):
return _('Hello %(audience)s') % {
'audience': data['audience'],
}
actions.register_action('wagtail_package.greet_audience', _('Greet audience'), greeting_message)
```
should now be rewritten as:
```python
from django.utils.translation import gettext_lazy as _
from wagtail.core import hooks
from wagtail.core.log_actions import LogFormatter
@hooks.register('register_log_actions')
def additional_log_actions(actions):
@actions.register_action('wagtail_package.greet_audience')
class GreetingActionFormatter(LogFormatter):
label = _('Greet audience')
def format_message(self, log_entry):
return _('Hello %(audience)s') % {
'audience': log_entry.data['audience'],
}
```
### `PageLogEntry.objects.log_action` is deprecated
Audit logging is now supported on all model types, not just pages, and so the `PageLogEntry.objects.log_action`
method for logging actions performed on pages is deprecated in favor of the general-purpose `log` function. Code that
calls `PageLogEntry.objects.log_action` should now import the `log` function from `wagtail.core.log_actions` and
call this instead (all arguments are unchanged).
Additionally, for logging actions on non-Page models, it is generally no longer necessary to subclass `BaseLogEntry`; see [Audit log](../extending/audit_log.md#audit-log) for further details.
### Removed support for Internet Explorer (IE11)
If this affects you or your organization, consider which alternative browsers you may be able to use.
Wagtail is fully compatible with Microsoft Edge, Microsoft’s replacement for Internet Explorer. You may consider using its [IE mode](https://learn.microsoft.com/en-us/deployedge/edge-ie-mode) to keep access to IE11-only sites, while other sites and apps like Wagtail can leverage modern browser capabilities.
### `search()` method partial match future deprecation
Before the `autocomplete()` method was introduced, the search method also did partial matching.
This behavior is will be deprecated in a future release and you should either switch to the new
`autocomplete()` method or pass `partial_match=False` into the search method to opt-in to the
new behavior. The partial matching in `search()` will be completely removed in a future release.
See: [Searching QuerySets](../topics/search/searching.md#wagtailsearch-searching-pages)
### Change of relation name for admin comments
The `related_name` of the relation linking the Page and User models to admin comments has been
changed from `comments` to `wagtail_admin_comments`, to avoid conflicts with third-party apps
that implement commenting. If you have any code that references the `comments` relation
(including fixture files), this should be updated to refer to `wagtail_admin_comments` instead.
If this is not feasible, the previous behavior can be restored by adding
`WAGTAIL_COMMENTS_RELATION_NAME = 'comments'` to your project’s settings.
Reusable library code that needs to preserve backwards compatibility with previous Wagtail versions
can find out the relation name as follows:
```python
try:
from wagtail.core.models import COMMENTS_RELATION_NAME
except ImportError:
COMMENTS_RELATION_NAME = 'comments'
```
### Bulk action views not covered by existing hooks
Bulk action views provide alternative routes to actions like publishing or copying a page.
If your site relies on hooks like `before_publish_page` or `before_copy_page` to perform
checks, or add additional functionality, those hooks will not be called on the
corresponding bulk action views. If you want to add this to the bulk action views as well,
use the new bulk action hooks: [before_bulk_action](../reference/hooks.md#before-bulk-action) and [after_bulk_action](../reference/hooks.md#after-bulk-action).
# 2.16.1.html.md
# Wagtail 2.16.1 release notes
*February 11, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
> * Ensure that correct sidebar submenus open when labels use non-Latin alphabets (Matt Westcott)
> * Fix issue where invalid bulk action URLs would incorrectly trigger a server error (500) instead of a valid not found (404) (Ihor Marhitych)
> * Fix issue where bulk actions would not work for object IDs greater than 999 when `USE_THOUSAND_SEPARATOR` (Dennis McGregor)
> * Set cookie for sidebar collapsed state to “SameSite: lax” (LB (Ben Johnston))
> * Prevent error on creating automatic redirects for sites with non-standard ports (Matt Westcott)
> * Restore ability to customize admin UI colors via CSS (LB (Ben Johnston))
# 2.16.2.html.md
# Wagtail 2.16.2 release notes
*April 11, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
* Update django-treebeard dependency to 4.5.1 or above (Serafeim Papastefanos)
* Fix permission error when sorting pages having page type restrictions (Thijs Kramer)
* Allow bulk publishing of pages without revisions (Andy Chosak)
* Ensure that all descendant pages are logged when deleting a page, not just immediate children (Jake Howard)
* Refactor `FormPagesListView` in wagtail.contrib.forms to avoid undefined `locale` variable when subclassing (Dan Braghis)
* Ensure page copy in Wagtail admin doesn’t ignore `exclude_fields_in_copy` (John-Scott Atlakson)
* Generate new translation keys for translatable `Orderable`s when page is copied without being published (Kalob Taulien, Dan Braghis)
* Ignore `GenericRelation` when copying pages (John-Scott Atlakson)
* Ensure ‘next’ links from image / document listings do not redirect back to partial AJAX view (Matt Westcott)
* Skip creation of automatic redirects when page cannot be routed (Matt Westcott)
* Prevent JS errors on locale switcher in page chooser (Matt Westcott)
## Upgrade considerations
### Jinja2 compatibility
Developers using Jinja2 templating should note that the template tags in this release (and earlier releases in the 2.15.x and 2.16.x series) are compatible with Jinja2 2.11.x and 3.0.x. Jinja2 2.11.x is unmaintained and requires `markupsafe` to be pinned to version `<2.1` to work; Jinja2 3.1.x has breaking changes and is not compatible. We therefore recommend that you use Jinja2 3.0.x, or 2.11.x with fully pinned dependencies.
# 2.16.3.html.md
# Wagtail 2.16.3 release notes
*September 5, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure the upgrade notification request for the latest release, which can be disabled via the `WAGTAIL_ENABLE_UPDATE_CHECK` sends the referrer origin with `strict-origin-when-cross-origin` (Karl Hobley)
* On the Locked pages report, limit the “locked by” filter to just users who have locked pages (Stefan Hammer)
* Ensure Python 3.10 compatibility when using Elasticsearch backend (Przemysław Buczkowski, Matt Westcott)
# 2.16.html.md
# Wagtail 2.16 release notes
*February 7, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Django 4.0 support
This release adds support for Django 4.0.
### Slim sidebar
As part of a [wider redesign](https://github.com/wagtail/wagtail/discussions/7739) of Wagtail’s administration interface, we have replaced the sidebar with a slim, keyboard-friendly version. This re-implementation comes with significant accessibility improvements for keyboard and screen reader users, and will enable us to make navigation between views much snappier in the future. Please have a look at [upgrade considerations](#upgrade-considerations) for more details on differences with the previous version.
### Automatic redirect creation
Wagtail projects using the `wagtail.contrib.redirects` app now benefit from ‘automatic redirect creation’ - which creates redirects for pages and their descedants whenever a URL-impacting change is made; such as a slug being changed, or a page being moved to a different part of the tree.
This feature should be beneficial to most ‘standard’ Wagtail projects and, in most cases, will have only a minor impact on responsiveness when making such changes. However, if you find this feature is not a good fit for your project, you can disabled it by adding the following to your project settings:
```python
WAGTAILREDIRECTS_AUTO_CREATE = False
```
Thank you to [The National Archives](https://www.nationalarchives.gov.uk) for kindly sponsoring this feature.
### Other features
* Added persistent IDs for ListBlock items, allowing commenting and improvements to revision comparisons (Matt Westcott, Tidiane Dia, with sponsorship from [NHS](https://www.nhs.uk/))
* Added Aging Pages report (Tidiane Dia)
* Add more SketchFab oEmbed patterns for models (Tom Usher)
* Added `page_slug_changed` signal for Pages (Andy Babic)
* Add collapse option to `StreamField`, `StreamBlock`, and `ListBlock` which will load all sub-blocks initially collapsed (Matt Westcott)
* Private pages can now be fetched over the API (Nabil Khalil)
* Added `alias_of` field to the pages API (Dmitrii Faiazov)
* Add support for Azure CDN and Front Door front-end cache invalidation (Tomasz Knapik)
* Fixed `default_app_config` deprecations for Django >= 3.2 (Tibor Leupold)
* Removed WOFF fonts
* Improved styling of workflow timeline modal view (Tidiane Dia)
* Add secondary actions menu in edit page headers (Tidiane Dia)
* Add system check for missing core Page fields in `search_fields` (LB (Ben Johnston))
* Improve CircleCI frontend & backend build caches, add automated browser accessibility test suite in CircleCI (Thibaud Colas)
* Add a ‘remember me’ checkbox to the admin sign in form, if unticked (default) the auth session will expire if the browser is closed (Michael Karamuth, Jake Howard)
* When returning to image or document listing views after editing, filters (collection or tag) are now remembered (Tidiane Dia)
* Improve the visibility of field error messages, in Windows high-contrast mode and out (Jason Attwood)
* Improve implementations of visually-hidden text in explorer and main menu toggle (Martin Coote)
* Add locale labels to page listings (Dan Braghis)
* Add locale labels to page reports (Dan Braghis)
* Change release check domain to releases.wagtail.org (Jake Howard)
* Add the user who submitted a page for moderation to the “Awaiting your review” homepage summary panel (Tidiane Dia)
* When moving pages, default to the current parent section (Tidiane Dia)
* Add borders to TypedTableBlock to help visualize rows and columns (Scott Cranfill)
* Set default submit button label on generic create views to ‘Create’ instead of ‘Save’ (Matt Westcott)
* Improve display of image listing for long image titles (Krzysztof Jeziorny)
* Use SVG icons in admin home page site summary items (Jérôme Lebleu)
* Ensure site summary items wrap on smaller devices on the admin home page (Jérôme Lebleu)
* Rework Workflow task chooser modal to align with other chooser modals, using consistent pagination and leveraging class based views (Matt Westcott)
* Implemented a locale switcher on the forms listing page in the admin (Dan Braghis)
* Implemented a locale switcher on the page chooser modal (Dan Braghis)
* Implemented the `wagtail_site` template tag for Jinja2 (Vladimir Tananko)
* Change webmaster to website administrator in the admin (Naomi Morduch Toubman)
* Added documentation for creating custom submenus in the admin menu (Sævar Öfjörð Magnússon)
* Choice blocks in StreamField now show label rather than value when collapsed (Jérôme Lebleu)
* Added documentation to clarify configuration of user-uploaded files (Cynthia Kiser)
* Change security contact address to security@wagtail.org (Jake Howard)
### Bug fixes
* Accessibility fixes for Windows high contrast mode; Dashboard icons color and contrast, help/error/warning blocks for fields and general content, side comment buttons within the page editor, dropdown buttons (Sakshi Uppoor, Shariq Jamil, LB (Ben Johnston), Jason Attwood)
* Rename additional ‘spin’ CSS animations to avoid clashes with other libraries (Kevin Gutiérrez)
* Pages are refreshed from database on create before passing to hooks. Page aliases get correct `first_published_date` and `last_published_date` (Dan Braghis)
* Additional login form fields from `WAGTAILADMIN_USER_LOGIN_FORM` are now rendered correctly (Michael Karamuth)
* Fix icon only button styling issue on small devices where height would not be set correctly (Vu Pham)
* Add padding to the Draftail editor to ensure `ol` items are not cut off (Khanh Hoang)
* Prevent opening choosers multiple times for Image, Page, Document, Snippet (LB (Ben Johnston))
* Ensure subsequent changes to styles files are picked up by Gulp watch (Jason Attwood)
* Ensure that programmatic page moves are correctly logged as ‘move’ and not ‘reorder’ in some cases (Andy Babic)
## Upgrade considerations
### Removed support for Django 3.0 and 3.1
Django 3.0 and 3.1 are no longer supported as of this release; please upgrade to Django 3.2 or above before upgrading Wagtail.
### Removed support for Python 3.6
Python 3.6 is no longer supported as of this release; please upgrade to Python 3.7 or above before upgrading Wagtail.
### StreamField ListBlock now returns `ListValue` rather than a list instance
The data type returned as the value of a ListBlock is now a custom class, `ListValue`, rather than a Python `list` object. This change allows it to provide a `bound_blocks` property that exposes the list items as [`BoundBlock` objects](../advanced_topics/boundblocks_and_values.md) rather than plain values. `ListValue` objects are mutable sequences that behave similarly to lists, and so all code that iterates over them, accesses individual elements, or manipulates them should continue to work. However, code that specifically expects a `list` object (e.g. using `isinstance` or testing for equality against a list) may need to be updated. For example, a unit test that tests the value of a `ListBlock` as follows:
```python
self.assertEqual(page.body[0].value, ['hello', 'goodbye'])
```
should be rewritten as:
```python
self.assertEqual(list(page.body[0].value), ['hello', 'goodbye'])
```
### Change to `set` method on tag fields
This release upgrades the [django-taggit](https://django-taggit.readthedocs.io/en/latest/) library to 2.x, which introduces one breaking change: the `TaggableManager.set` method now accepts a list of tags as a single argument, rather than a variable number of arguments. Code such as `page.tags.set('red', 'blue')` should be updated to `page.tags.set(['red', 'blue'])`.
### `wagtail.admin.views.generic.DeleteView` follows Django 4.0 conventions
The internal (undocumented) class-based view `wagtail.admin.views.generic.DeleteView` has been updated to align with [Django 4.0’s `DeleteView` implementation](https://docs.djangoproject.com/en/stable/releases/4.0/#deleteview-changes), which uses `FormMixin` to handle POST requests. Any custom deletion logic in `delete()` handlers should be moved to `form_valid()`.
### Renamed admin/expanding-formset.js
`admin/expanding_formset.js` has been renamed to `admin/expanding-formset.js` as part of frontend code clean up work. Check for any customized admin views that are extending expanding formsets, or have overridden template and copied the previous file name used in an import as these may need updating.
### Deprecated sidebar capabilities
The new sidebar largely supports the same customizations as its predecessor, with a few exceptions:
- Top-level menu items should now always provide an `icon_name`, so they can be visually distinguished when the sidebar is collapsed.
- `MenuItem` and its sub-classes no longer supports customizing arbitrary HTML attributes.
- `MenuItem` can no longer be sub-classed to customize its HTML output or load additional JavaScript
For sites relying on those capabilities, we provide a `WAGTAIL_SLIM_SIDEBAR = False` setting to switch back to the legacy sidebar. The legacy sidebar and this setting will be removed in Wagtail 2.18.
# 2.2.1.html.md
# Wagtail 2.2.1 release notes
*August 13, 2018*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Pin Beautiful Soup to 4.6.0 due to further regressions in formatting empty elements (Matt Westcott)
* Prevent AppRegistryNotReady error when wagtail.contrib.sitemaps is in INSTALLED_APPS (Matt Westcott)
# 2.2.2.html.md
# Wagtail 2.2.2 release notes
*August 29, 2018*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Seek to the beginning of image files when uploading, to restore compatibility with django-storages Google Cloud and Azure backends (Mikalai Radchuk)
* Respect next param on login (Loic Teixeira)
# 2.2.html.md
# Wagtail 2.2 release notes
*August 10, 2018*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Faceted search
Wagtail search now includes support for facets, allowing you to display search result counts broken down by a particular field value. For further details, see [Faceted search](../topics/search/searching.md#wagtailsearch-faceted-search). This feature was developed by Karl Hobley.
### Improved admin page search
The page search in the Wagtail admin now supports filtering by page type and ordering search results by title, creation date and status. This feature was developed by Karl Hobley.
### Other features
* Added another valid AudioBoom oEmbed pattern (Bertrand Bordage)
* Added `annotate_score` support to PostgreSQL search backend (Bertrand Bordage)
* Pillow’s image optimization is now applied when saving PNG images (Dmitry Vasilev)
* JS / CSS media files can now be associated with Draftail feature definitions (Matt Westcott)
* The `{% slugurl %}` template tag is now site-aware (Samir Shah)
* Added `file_size` field to documents (Karl Hobley)
* Added `file_hash` field to images (Karl Hobley)
* Update documentation (configuring Django for Wagtail) to contain all current settings options (Matt Westcott, LB (Ben Johnston))
* Added `defer` flag to `PageQuerySet.specific` (Karl Hobley)
* Snippets can now be deleted from the listing view (LB (Ben Johnston))
* Increased max length of redirect URL field to 255 (Michael Harrison)
* Added documentation for new JS/CSS media files association with Draftail feature definitions (Ed Henderson)
* Added accessible color contrast guidelines to the style guide (Catherine Farman)
* Admin modal views no longer rely on JavaScript `eval()`, for better CSP compliance (Matt Westcott)
* Update editor guide for embeds and documents in rich text (Kevin Howbrook)
* Improved performance of sitemap generation (Michael van Tellingen, Bertrand Bordage)
* Added an internal API for autocomplete (Karl Hobley)
### Bug fixes
* Handle all exceptions from `Image.get_file_size` (Andrew Plummer)
* Fix display of breadcrumbs in ModelAdmin (LB (Ben Johnston))
* Remove duplicate border radius of avatars (Benjamin Thurm)
* Site.get_site_root_paths() preferring other sites over the default when some sites share the same root_page (Andy Babic)
* Pages with missing model definitions no longer crash the API (Abdulmalik Abdulwahab)
* Rich text image chooser no longer skips format selection after a validation error (Matt Westcott)
* Null characters in URLs no longer crash the redirect middleware on PostgreSQL (Andrew Crewdson, Matt Westcott)
* Permission checks no longer prevent a non-live page from being unscheduled (Abdulmalik Abdulwahab)
* Copy-paste between Draftail editors now preserves all formatting/content (Thibaud Colas)
* Fix alignment of checkboxes and radio buttons on Firefox (Matt Westcott)
## Upgrade considerations
### JavaScript templates in modal workflows are deprecated
The `wagtail.admin.modal_workflow` module (used internally by Wagtail to handle modal popup interfaces such as the page chooser) has been updated to avoid returning JavaScript code as part of HTTP responses. User code that relies on this functionality can be updated as follows:
* Eliminate template tags from the .js template. Any dynamic data needed by the template can instead be passed in a dict to `render_modal_workflow`, as a keyword argument `json_data`; this data will then be available as the second parameter of the JavaScript function.
* At the point where you call the `ModalWorkflow` constructor, add an `onload` option - a dictionary of functions to be called on loading each step of the workflow. Move the code from the .js template into this dictionary. Then, on the call to `render_modal_workflow`, rather than passing the .js template name (which should now be replaced by `None`), pass a `step` item in the `json_data` dictionary to indicate the `onload` function to be called.
Additionally, if your code calls `loadResponseText` as part of a jQuery AJAX callback, this should now be passed all three arguments from the callback (the response data, status string and XMLHttpRequest object).
### `Page.get_sitemap_urls()` now accepts an optional `request` keyword argument
The `Page.get_sitemap_urls()` method used by the `wagtail.contrib.sitemaps` module has been updated to receive an optional `request` keyword argument. If you have overridden this method in your page models, you will need to update the method signature to accept this argument (and pass it on when calling `super`, if applicable).
# 2.3.html.md
# Wagtail 2.3 (LTS) release notes
*October 23, 2018*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
Wagtail 2.3 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 8 months).
Note that Wagtail 2.3 will be the last release branch to support Django 1.11.
## What’s new
### Added Django 2.1 support
Wagtail is now compatible with Django 2.1. Compatibility fixes were contributed by Ryan Verner and Matt Westcott.
### Improved color contrast
Colour contrast within the admin interface has been improved, and now complies with WCAG 2 level AA. This was completed by Coen van der Kamp and Naomi Morduch Toubman based on earlier work from Edd Baldry, Naa Marteki Reed and Ben Enright.
### Other features
* Added ‘scale’ image filter (Oliver Wilkerson)
* Added meta tag to prevent search engines from indexing admin pages (Karl Hobley)
* EmbedBlock now validates against recognized embed providers on save (Bertrand Bordage)
* Made cache control headers on Wagtail admin consistent with Django admin (Tomasz Knapik)
* Notification emails now include an “Auto-Submitted: auto-generated” header (Dan Braghis)
* Image chooser panels now show alt text as title (Samir Shah)
* Added `download_url` field to images in the API (Michael Harrison)
* Dummy requests for preview now preserve the HTTP Authorization header (Ben Dickinson)
### Bug fixes
* Respect next param on login (Loic Teixeira)
* InlinePanel now handles relations that specify a related_query_name (Aram Dulyan)
* before_delete_page / after_delete_page hooks now run within the same database transaction as the page deletion (Tomasz Knapik)
* Seek to the beginning of image files when uploading, to restore compatibility with django-storages Google Cloud and Azure backends (Mikalai Radchuk)
* Snippet chooser modal no longer fails on snippet models with UUID primary keys (Sævar Öfjörð Magnússon)
* Restored localization in date/time pickers (David Moore, Thibaud Colas)
* Tag input field no longer treats ‘б’ on Russian keyboards as a comma (Michael Borisov)
* Disabled autocomplete dropdowns on date/time chooser fields (Janneke Janssen)
* Split up `wagtail.admin.forms` to make it less prone to circular imports (Matt Westcott)
* Disable linking to root page in rich text, making the page non-functional (Matt Westcott)
* Pages should be editable and save-able even if there are broken page or document links in rich text (Matt Westcott)
* Avoid redundant round-trips of JSON StreamField data on save, improving performance and preventing consistency issues on fixture loading (Andy Chosak, Matt Westcott)
* Users are not logged out when changing their own password through the Users area (Matt Westcott)
## Upgrade considerations
### `wagtail.admin.forms` reorganized
The `wagtail.admin.forms` module has been split up into submodules to make it less prone to producing circular imports, particularly when a custom user model is in use. The following (undocumented) definitions have now been moved to new locations:
| Definition | New location |
|----------------------------------------------|---------------------------------------|
| LoginForm | wagtail.admin.forms.auth |
| PasswordResetForm | wagtail.admin.forms.auth |
| URLOrAbsolutePathValidator | wagtail.admin.forms.choosers |
| URLOrAbsolutePathField | wagtail.admin.forms.choosers |
| ExternalLinkChooserForm | wagtail.admin.forms.choosers |
| EmailLinkChooserForm | wagtail.admin.forms.choosers |
| CollectionViewRestrictionForm | wagtail.admin.forms.collections |
| CollectionForm | wagtail.admin.forms.collections |
| BaseCollectionMemberForm | wagtail.admin.forms.collections |
| BaseGroupCollectionMemberPermissionFormSet | wagtail.admin.forms.collections |
| collection_member_permission_formset_factory | wagtail.admin.forms.collections |
| CopyForm | wagtail.admin.forms.pages |
| PageViewRestrictionForm | wagtail.admin.forms.pages |
| SearchForm | wagtail.admin.forms.search |
| BaseViewRestrictionForm | wagtail.admin.forms.view_restrictions |
The following definitions remain in `wagtail.admin.forms`: `FORM_FIELD_OVERRIDES`, `DIRECT_FORM_FIELD_OVERRIDES`, `formfield_for_dbfield`, `WagtailAdminModelFormMetaclass`, `WagtailAdminModelForm` and `WagtailAdminPageForm`.
# 2.4.html.md
# Wagtail 2.4 release notes
*December 19, 2018*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### New “Welcome to your Wagtail site” Starter Page
When using the `wagtail start` command to make a new site, users will now be greeted with a proper starter page. Thanks to Timothy Allen and Scott Cranfill for pulling this off!
> 
### Other features
* Added support for Python 3.7 (Matt Westcott)
* Added `max_count` option on page models to limit the number of pages of a particular type that can be created (Dan Braghis)
* Document and image choosers now show the document / image’s collection (Alejandro Garza, Janneke Janssen)
* New `image_url` template tag allows to generate dynamic image URLs, so image renditions are being created outside the main request which improves performance. Requires extra configuration, see [Dynamic image serve view](../advanced_topics/images/image_serve_view.md) (Yannick Chabbert, Dan Braghis).
* Added ability to run individual tests through tox (Benjamin Bach)
* Collection listings are now ordered by name (Seb Brown)
* Added `file_hash` field to documents (Karl Hobley, Dan Braghis)
* Added last login to the user overview (Noah B Johnson)
* Changed design of image editing page (Janneke Janssen, Ben Enright)
* Added Slovak character map for JavaScript slug generation (Andy Chosak)
* Make documentation links on welcome page work for prereleases (Matt Westcott)
* Allow overridden `copy()` methods in `Page` subclasses to be called from the page copy view (Robert Rollins)
* Users without a preferred language set on their profile now use language selected by Django’s `LocaleMiddleware` (Benjamin Bach)
* Added hooks to customize the actions menu on the page create/edit views (Matt Westcott)
* Cleanup: Use `functools.partial()` instead of `django.utils.functional.curry()` (Sergey Fedoseev)
* Added `before_move_page` and `after_move_page` hooks (Maylon Pedroso)
* Bulk deletion button for snippets is now hidden until items are selected (Karl Hobley)
### Bug fixes
* Query objects returned from `PageQuerySet.type_q` can now be merged with `|` (Brady Moe)
* Add `rel="noopener noreferrer"` to target blank links (Anselm Bradford)
* Additional fields on custom document models now show on the multiple document upload view (Robert Rollins, Sergey Fedoseev)
* Help text does not overflow when using a combination of BooleanField and FieldPanel in page model (Dzianis Sheka)
* Document chooser now displays more useful help message when there are no documents in Wagtail document library (gmmoraes, Stas Rudakou)
* Allow custom logos of any height in the admin menu (Meteor0id)
* Allow nav menu to take up all available space instead of scrolling (Meteor0id)
* Users without the edit permission no longer see “Edit” links in list of pages waiting for moderation (Justin Focus, Fedor Selitsky)
* Redirects now return 404 when destination is unspecified or a page with no site (Hillary Jeffrey)
* Refactor all breakpoint definitions, removing style overlaps (Janneke Janssen)
* Updated draftjs_exporter to 2.1.5 to fix bug in handling adjacent entities (Thibaud Colas)
* Page titles consisting only of stopwords now generate a non-empty default slug (Andy Chosak, Janneke Janssen)
* Sitemap generator now allows passing a sitemap instance in the URL configuration (Mitchel Cabuloy, Dan Braghis)
## Upgrade considerations
### Removed support for Django 1.11
Django 1.11 is no longer supported in this release; please upgrade your project to Django 2.0 or 2.1 before upgrading to Wagtail 2.4.
### Custom image model migrations created on Wagtail <1.8 may fail
Projects with a custom image model (see [Custom image models](../advanced_topics/images/custom_image_model.md#custom-image-model)) created on Wagtail 1.7 or earlier are likely to have one or more migrations that refer to the (now-deleted) `wagtailimages.Filter` model. In Wagtail 2.4, the migrations that defined this model have been squashed, which may result in the error `ValueError: Related model 'wagtailimages.Filter' cannot be resolved` when bringing up a new instance of the database. To rectify this, check your project’s migrations for `ForeignKey` references to `wagtailimages.Filter`, and change them to `IntegerField` definitions. For example, the line:
```python
('filter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailimages.Filter')),
```
should become:
```python
('filter', models.IntegerField(blank=True, null=True)),
```
# 2.5.1.html.md
# Wagtail 2.5.1 release notes
*May 7, 2019*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent crash when comparing StructBlocks in revision history (Adrian Turjak, Matt Westcott)
# 2.5.2.html.md
# Wagtail 2.5.2 release notes
*August 1, 2019*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Delay dirty form check to prevent “unsaved changes” warning from being wrongly triggered (Thibaud Colas)
# 2.5.html.md
# Wagtail 2.5 release notes
*April 24, 2019*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Django 2.2 support
This release is compatible with Django 2.2. Compatibility fixes were contributed by Matt Westcott and Andy Babic.
### New Markdown shortcuts in rich text
Wagtail’s rich text editor now supports using Markdown shortcuts for inline formatting:
* `**` for bold
* `_` for italic
* `~` for strikethrough (if enabled)
* ``` for code (if enabled)
To learn other shortcuts, have a look at the [keyboard shortcuts](https://www.draftail.org/docs/keyboard-shortcuts) reference.
### Other features
* Added support for customizing EditHandler-based forms on a per-request basis (Bertrand Bordage)
* Added more informative error message when `|richtext` filter is applied to a non-string value (mukesh5)
* Automatic search indexing can now be disabled on a per-model basis via the `search_auto_update` attribute (Karl Hobley)
* Improved diffing of StreamFields when comparing page revisions (Karl Hobley)
* Highlight broken links to pages and missing documents in rich text (Brady Moe)
* Preserve links when copy-pasting rich text content from Wagtail to other tools (Thibaud Colas)
* Rich text to contentstate conversion now prioritizes more specific rules, to accommodate `
` and ` ` elements with attributes (Matt Westcott)
* Added limit image upload size by number of pixels (Thomas Elliott)
* Added `manage.py wagtail_update_index` alias to avoid clashes with `update_index` commands from other packages (Matt Westcott)
* Renamed `target_model` argument on `PageChooserBlock` to `page_type` (Loic Teixeira)
* `edit_handler` and `panels` can now be defined on a `ModelAdmin` definition (Thomas Kremmel)
* Add Learn Wagtail to third-party tutorials in documentation (Matt Westcott)
* Add a Django setting `TAG_LIMIT` to limit number of tags that can be added to any taggit model (Mani)
* Added instructions on how to generate urls for `ModelAdmin` to documentation (LB (Ben Johnston), Andy Babic)
* Added option to specify a fallback URL on `{% pageurl %}` (Arthur Holzner)
* Add support for more rich text formats, disabled by default: `blockquote`, `superscript`, `subscript`, `strikethrough`, `code` (Md Arifin Ibne Matin)
* Added `max_count_per_parent` option on page models to limit the number of pages of a given type that can be created under one parent page (Wesley van Lee)
* `StreamField` field blocks now accept a `validators` argument (Tom Usher)
* Added edit / delete buttons to snippet index and “don’t delete” option to confirmation screen, for consistency with pages (Kevin Howbrook)
* Added name attributes to all built-in page action menu items (LB (Ben Johnston))
* Added validation on the filter string to the Jinja2 image template tag (Jonny Scholes)
* Changed the pages reordering UI toggle to make it easier to find (Katie Locke, Thibaud Colas)
* Added support for rich text link rewrite handlers for `external` and `email` links (Md Arifin Ibne Matin)
* Clarify installation instructions in documentation, especially regarding virtual environments. (Naomi Morduch Toubman)
### Bug fixes
* Set `SERVER_PORT` to 443 in `Page.dummy_request()` for HTTPS sites (Sergey Fedoseev)
* Include port number in `Host` header of `Page.dummy_request()` (Sergey Fedoseev)
* Validation error messages in `InlinePanel` no longer count towards `max_num` when disabling the ‘add’ button (Todd Dembrey, Thibaud Colas)
* Rich text to contentstate conversion now ignores stray closing tags (frmdstryr)
* Escape backslashes in `postgres_search` queries (Hammy Goonan)
* Parent page link in page chooser search results no longer navigates away (Asanka Lihiniyagoda, Sævar Öfjörð Magnússon)
* `routablepageurl` tag now correctly omits domain part when multiple sites exist at the same root (Gassan Gousseinov)
* Added missing collection column specifier on document listing template (Sergey Fedoseev)
* Page Copy will now also copy ParentalManyToMany field relations (LB (Ben Johnston))
* Admin HTML header now includes correct language code (Matt Westcott)
* Unclear error message when saving image after focal point edit (Hugo van den Berg)
* Increase max length on `Embed.thumbnail_url` to 255 characters (Kevin Howbrook)
* `send_mail` now correctly uses the `html_message` kwarg for HTML messages (Tiago Requeijo)
* Page copying no longer allowed if page model has reached its `max_count` (Andy Babic)
* Don’t show page type on page chooser button when multiple types are allowed (Thijs Kramer)
* Make sure page chooser search results correspond to the latest search by canceling previous requests (Esper Kuijs)
* Inform user when moving a page from one parent to another where there is an already existing page with the same slug (Casper Timmers)
* User add/edit forms now support form widgets with JS/CSS media (Damian Grinwis)
* Rich text processing now preserves non-breaking spaces instead of converting them to normal spaces (Wesley van Lee)
* Prevent autocomplete dropdowns from appearing over date choosers on Chrome (Kevin Howbrook)
* Prevent crash when logging HTTP errors on Cloudflare cache purging (Kevin Howbrook)
* Prevent rich text editor crash when filtering copy-pasted content and the last block is to be removed, e.g. unsupported image (Thibaud Colas)
* Removing rich text links / documents now also works when the text selection is backwards (Thibaud Colas)
* Prevent the rich text editor from crashing when copy-paste filtering removes all of its content (Thibaud Colas)
* Page chooser now respects custom `get_admin_display_title` methods on parent page and breadcrumb (Haydn Greatnews)
* Added consistent whitespace around sortable table headings (Matt Westcott)
* Moved locale names for Chinese (Simplified) and Chinese (Traditional) to `zh_Hans` and `zh_Hant` (Matt Westcott)
## Upgrade considerations
### `EditHandler.bind_to_model` and `EditHandler.bind_to_instance` deprecated
The internal `EditHandler` methods `bind_to_model` and `bind_to_instance` have been deprecated, in favor of a new combined `bind_to` method which accepts `model`, `instance`, `request` and `form` as optional keyword arguments. Any user code which calls `EditHandler.bind_to_model(model)` should be updated to use `EditHandler.bind_to(model=model)` instead; any user code which calls `EditHandler.bind_to_instance(instance, request, form)` should be updated to use `EditHandler.bind_to(instance=instance, request=request, form=form)`.
### Changes to admin pagination helpers
Several changes have been made to pagination handling within the Wagtail admin; these are internal API changes, but may affect applications and third-party packages that add new paginated object listings, including chooser modals, to the admin. The `paginate` function in `wagtail.utils.pagination` has been deprecated in favor of the `django.core.paginator.Paginator.get_page` method introduced in Django 2.0 - a call such as:
```python
from wagtail.utils.pagination import paginate
paginator, page = paginate(request, object_list, per_page=25)
```
should be replaced with:
```python
from django.core.paginator import Paginator
paginator = Paginator(object_list, per_page=25)
page = paginator.get_page(request.GET.get('p'))
```
Additionally, the `is_ajax` flag on the template `wagtailadmin/shared/pagination_nav.html` has been deprecated in favour of a new template `wagtailadmin/shared/ajax_pagination_nav.html`:
```html+django
{% include "wagtailadmin/shared/pagination_nav.html" with items=page_obj is_ajax=1 %}
```
should become:
```html+django
{% include "wagtailadmin/shared/ajax_pagination_nav.html" with items=page_obj %}
```
### New rich text formats
Wagtail now has built-in support for new rich text formats, disabled by default:
* `blockquote`, using the `blockquote` Draft.js block type, saved as a `
` tag.
* `superscript`, using the `SUPERSCRIPT` Draft.js inline style, saved as a `` tag.
* `subscript`, using the `SUBSCRIPT` Draft.js inline style, saved as a `` tag.
* `strikethrough`, using the `STRIKETHROUGH` Draft.js inline style, saved as a `` tag.
* `code`, using the `CODE` Draft.js inline style, saved as a `` tag.
Projects already using those exact Draft.js type and HTML tag combinations can safely replace their feature definitions with the new built-ins. Projects that use the same feature identifier can keep their existing feature definitions as overrides. Finally, if the Draft.js types / HTML tags are used but with a different combination, do not enable the new feature definitions to avoid conflicts in storage or editor behavior.
### `register_link_type` and `register_embed_type` methods for rich text tag rewriting have changed
The `FeatureRegistry.register_link_type` and `FeatureRegistry.register_embed_type` methods, which define how links and embedded media in rich text are converted to HTML, now accept a handler class. Previously, they were passed an identifier string and a rewrite function. For details of updating your code to the new convention, see [Rewrite handlers](../extending/rich_text_internals.md#rich-text-rewrite-handlers).
### Chinese language locales changed to `zh_Hans` and `zh_Hant`
The translations for Chinese (Simplified) and Chinese (Traditional) are now available under the locale names `zh_Hans` and `zh_Hant` respectively, rather than `zh_CN` and `zh_TW`. Projects that currently use the old names for the `LANGUAGE_CODE` setting may need to update the settings file to use the new names.
# 2.6.1.html.md
# Wagtail 2.6.1 release notes
*August 5, 2019*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent JavaScript errors caused by unescaped quote characters in translation strings (Matt Westcott)
# 2.6.2.html.md
# Wagtail 2.6.2 release notes
*September 19, 2019*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent search indexing failures on Postgres 9.4 and Django >= 2.2.1 (Matt Westcott)
# 2.6.3.html.md
# Wagtail 2.6.3 release notes
*October 22, 2019*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Altering Django REST Framework’s `DEFAULT_AUTHENTICATION_CLASSES` setting no longer breaks the page explorer menu and admin API (Matt Westcott)
# 2.6.html.md
# Wagtail 2.6 release notes
*August 1, 2019*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Accessibility targets and improvements
Wagtail now has official accessibility support targets: we are aiming for compliance with [WCAG2.1](https://www.w3.org/TR/WCAG21/), AA level. WCAG 2.1 is the international standard that underpins many national accessibility laws.
Wagtail isn’t fully compliant just yet, but we have made many changes to the admin interface to get there. We thank the UK Government (in particular the CMS team at the Department for International Trade), who commissioned many of these improvements.
Here are changes that should make Wagtail more usable for all users regardless of abilities:
* Increase font-size across the whole admin (Beth Menzies, Katie Locke)
* Improved text color contrast across the whole admin (Beth Menzies, Katie Locke)
* Added consistent focus outline styles across the whole admin (Thibaud Colas)
* Ensured the ‘add child page’ button displays when focused (Helen Chapman, Katie Locke)
This release also contains many big improvements for screen reader users:
* Added more ARIA landmarks across the admin interface and welcome page for screen reader users to navigate the CMS more easily (Beth Menzies)
* Improved heading structure for screen reader users navigating the CMS admin (Beth Menzies, Helen Chapman)
* Make icon font implementation more screen-reader-friendly (Thibaud Colas)
* Removed buggy tab order customizations in the CMS admin (Jordan Bauer)
* Screen readers now treat page-level action dropdowns as navigation instead of menus (Helen Chapman)
* Fixed occurrences of invalid HTML across the CMS admin (Thibaud Colas)
* Add empty alt attributes to all images in the CMS admin (Andreas Bernacca)
* Fixed focus not moving to the pages explorer menu when open (Helen Chapman)
We’ve also had a look at how controls are labeled across the UI for screen reader users:
* Add image dimensions in image gallery and image choosers for screen reader users (Helen Chapman)
* Add more contextual information for screen readers in the explorer menu’s links (Helen Chapman)
* Make URL generator preview image alt translatable (Thibaud Colas)
* Screen readers now announce “Dashboard” for the main nav’s logo link instead of Wagtail’s version number (Thibaud Colas)
* Remove duplicate labels in image gallery and image choosers for screen reader users (Helen Chapman)
* Added a label to the modals’ “close” button for screen reader users (Helen Chapman, Katie Locke)
* Added labels to permission checkboxes for screen reader users (Helen Chapman, Katie Locke)
* Improve screen-reader labels for action links in page listing (Helen Chapman, Katie Locke)
* Add screen-reader labels for table headings in page listing (Helen Chapman, Katie Locke)
* Add screen reader labels for page privacy toggle, edit lock, status tag in page explorer & edit views (Helen Chapman, Katie Locke)
* Add screen-reader labels for dashboard summary cards (Helen Chapman, Katie Locke)
* Add screen-reader labels for privacy toggle of collections (Helen Chapman, Katie Locke)
Again, this is still a work in progress – if you are aware of other existing accessibility issues, please do [open an issue](https://github.com/wagtail/wagtail/issues?q=is%3Aopen+is%3Aissue+label%3AAccessibility) if there isn’t one already.
### Other features
* Added support for `short_description` for field labels in modeladmin’s `InspectView` (Wesley van Lee)
* Rearranged SCSS folder structure to the client folder and split them approximately according to ITCSS. (Naomi Morduch Toubman, Jonny Scholes, Janneke Janssen, Hugo van den Berg)
* Added support for specifying cell alignment on TableBlock (Samuel Mendes)
* Added more informative error when a non-image object is passed to the `image` template tag (Deniz Dogan)
* Added ButtonHelper examples in the modelAdmin primer page within documentation (Kalob Taulien)
* Multiple clarifications, grammar, and typo fixes throughout documentation (Dan Swain)
* Use correct URL in API example in documentation (Michael Bunsen)
* Move datetime widget initializer JS into the widget’s form media instead of page editor media (Matt Westcott)
* Add form field prefixes for input forms in chooser modals (Matt Westcott)
* Removed version number from the logo link’s title. The version can now be found under the Settings menu (Thibaud Colas)
* Added “don’t delete” option to confirmation screen when deleting images, documents and modeladmin models (Kevin Howbrook)
* Added `branding_title` template block for the admin title prefix (Dillen Meijboom)
* Added support for custom search handler classes to modeladmin’s IndexView, and added a class that uses the default Wagtail search backend for searching (Seb Brown, Andy Babic)
* Update group edit view to expose the `Permission` object for each checkbox (George Hickman)
* Improve performance of Pages for Moderation panel (Fidel Ramos)
* Added `process_child_object` and `exclude_fields` arguments to `Page.copy()` to make it easier for third-party apps to customize copy behavior (Karl Hobley)
* Added `Page.with_content_json()`, allowing revision content loading behavior to be customized on a per-model basis (Karl Hobley)
* Added `construct_settings_menu` hook (Jordan Bauer, Quadric)
* Fixed compatibility of date / time choosers with wagtail-react-streamfield (Mike Hearn)
* Performance optimization of several admin functions, including breadcrumbs, home and index pages (Fidel Ramos)
### Bug fixes
* ModelAdmin no longer fails when filtering over a foreign key relation (Jason Dilworth, Matt Westcott)
* The Wagtail version number is now visible within the Settings menu (Kevin Howbrook)
* Scaling images now rounds values to an integer so that images render without errors (Adrian Brunyate)
* Revised test decorator to ensure TestPageEditHandlers test cases run correctly (Alex Tomkins)
* Wagtail bird animation in admin now ends correctly on all browsers (Deniz Dogan)
* Explorer menu no longer shows sibling pages for which the user does not have access (Mike Hearn)
* Admin HTML now includes the correct `dir` attribute for the active language (Andreas Bernacca)
* Fix type error when using `--chunk_size` argument on `./manage.py update_index` (Seb Brown)
* Avoid rendering entire form in EditHandler’s `repr` method (Alex Tomkins)
* Add empty alt attributes to HTML output of Embedly and oEmbed embed finders (Andreas Bernacca)
* Clear pending AJAX request if error occurs on page chooser (Matt Westcott)
* Prevent text from overlapping in focal point editing UI (Beth Menzies)
* Restore custom “Date” icon for scheduled publishing panel in Edit page’s Settings tab (Helen Chapman)
* Added missing form media to user edit form template (Matt Westcott)
* `Page.copy()` no longer copies child objects when the accessor name is included in `exclude_fields_in_copy` (Karl Hobley)
* Clicking the privacy toggle while the page is still loading no longer loads the wrong data in the page (Helen Chapman)
* Added missing `is_stored_locally` method to `AbstractDocument` (jonny5532)
* Query model no longer removes punctuation as part of string normalization (William Blackie)
* Make login test helper work with user models with non-default username fields (Andrew Miller)
* Delay dirty form check to prevent “unsaved changes” warning from being wrongly triggered (Thibaud Colas)
## Upgrade considerations
### Removed support for Python 3.4
Python 3.4 is no longer supported as of this release; please upgrade to Python 3.5 or above before upgrading Wagtail.
### Icon font implementation changes
The icon font implementation has been changed to be invisible for screen-reader users, by switching to using [Private Use Areas](https://en.wikipedia.org/wiki/Private_Use_Areas) Unicode code points. All of the icon classes (`icon-user`, `icon-search`, etc) should still work the same, except for two which have been removed because they were duplicates:
* `icon-picture` is removed. Use `icon-image` instead (same visual).
* `icon-file-text-alt` is removed. Use `icon-doc-full` instead (same visual).
For a list of all available icons, please see the [UI Styleguide](../contributing/ui_guidelines.md#styleguide).
# 2.7.1.html.md
# Wagtail 2.7.1 release notes
*January 8, 2020*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Management command startup checks under `ManifestStaticFilesStorage` no longer fail if `collectstatic` has not been run first (Alex Tomkins)
# 2.7.2.html.md
# Wagtail 2.7.2 release notes
*April 14, 2020*
## CVE-2020-11001: Possible XSS attack via page revision comparison view
This release addresses a cross-site scripting (XSS) vulnerability on the page revision comparison view within the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin could potentially craft a page revision history that, when viewed by a user with higher privileges, could perform actions with that user’s credentials. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to Vlad Gerasimenko for reporting this issue.
# 2.7.3.html.md
# Wagtail 2.7.3 release notes
*May 4, 2020*
## CVE-2020-11037: Potential timing attack on password-protected private pages
This release addresses a potential timing attack on pages or documents that have been protected with a shared password through Wagtail’s “Privacy” controls. This password check is performed through a character-by-character string comparison, and so an attacker who is able to measure the time taken by this check to a high degree of accuracy could potentially use timing differences to gain knowledge of the password. (This is [understood to be feasible on a local network, but not on the public internet](https://groups.google.com/d/msg/django-developers/iAaq0pvHXuA/fpUuwjK3i2wJ).)
Many thanks to Thibaud Colas for reporting this issue.
# 2.7.4.html.md
# Wagtail 2.7.4 release notes
*July 20, 2020*
## CVE-2020-15118: HTML injection through form field help text
This release addresses an HTML injection vulnerability through help text in the `wagtail.contrib.forms` form builder app. When a form page type is made available to Wagtail editors, and the page template is built using Django’s standard form rendering helpers such as `form.as_p` [(as directed in the documentation)](../reference/contrib/forms/index.md#form-builder-usage), any HTML tags used within a form field’s help text will be rendered unescaped in the page. Allowing HTML within help text is an intentional design decision by Django (see the docs for [`help_text`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.Field.help_text)); however, as a matter of policy Wagtail does not allow editors to insert arbitrary HTML by default, as this could potentially be used to carry out cross-site scripting attacks, including privilege escalation. This functionality should therefore not have been made available to editor-level users.
The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Site owners who wish to re-enable the use of HTML within help text (and are willing to accept the risk of this being exploited by editors) may set `WAGTAILFORMS_HELP_TEXT_ALLOW_HTML = True` in their configuration settings.
Many thanks to Timothy Bautista for reporting this issue.
## Additional fixes
* Expand Pillow dependency range to include 7.x (Harris Lapiroff, Matt Westcott)
# 2.7.html.md
# Wagtail 2.7 (LTS) release notes
*November 6, 2019*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
Wagtail 2.7 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### Improved StreamField design

The design of the StreamField user interface has been updated to improve clarity and usability, including better handling of nested blocks. This work was completed by Bertrand Bordage as part of the [Wagtail’s First Hatch](https://www.kickstarter.com/projects/noripyt/wagtails-first-hatch) crowdfunding campaign. We would like to thank all [supporters of the campaign](https://wagtail.org/blog/wagtails-first-hatch-backers/).
### WebP image support
Images can now be uploaded and rendered in [WebP](https://developers.google.com/speed/webp) format; see [Image file formats](../advanced_topics/images/image_file_formats.md#image-file-formats) for details. This feature was developed by frmdstryr, Karl Hobley and Matt Westcott.
### Other features
* Added Elasticsearch 7 support (pySilver)
* Added Python 3.8 support (John Carter, Matt Westcott)
* Added `construct_page_listing_buttons` hook (Michael van Tellingen)
* Added more detailed documentation and troubleshooting for installing OpenCV for feature detection (Daniele Procida)
* Move and refactor upgrade notification JS (Jonny Scholes)
* Remove need for Elasticsearch `update_all_types` workaround, upgrade minimum release to 6.4.0 or above (Jonathan Liuti)
* Add ability to insert internal anchor links/links with fragment identifiers in Draftail (rich text) fields (Iman Syed)
* Added Table Block caption for accessibility (Rahmi Pruitt)
* Add ability for users to change their own name via the account settings page (Kevin Howbrook)
* Add ability to insert telephone numbers as links in Draftail (rich text) fields (Mikael Engström and Liam Brenner)
* Increase delay before search in the snippet chooser, to prevent redundant search request round trips (Robert Rollins)
* Add `WAGTAIL_EMAIL_MANAGEMENT_ENABLED` setting to determine whether users can change their email address (Janne Alatalo)
* Recognise Soundcloud artist URLs as embeddable (Kiril Staikov)
* Add `WAGTAILDOCS_SERVE_METHOD` setting to determine how document downloads will be linked to and served (Tobias McNulty, Matt Westcott)
* Add `WAGTAIL_MODERATION_ENABLED` setting to enable / disable the ‘Submit for Moderation’ option (Jacob Topp-Mugglestone) - thanks to [The Motley Fool](https://www.fool.com/) for sponsoring this feature
* Added settings to customize pagination page size for the Images admin area (Brian Whitton)
* Added ARIA role to TableBlock output (Matt Westcott)
* Added cache-busting query parameters to static files within the Wagtail admin (Matt Westcott)
* Allow `register_page_action_menu_item` and `construct_page_action_menu` hooks to override the default menu action (Rahmi Pruitt, Matt Westcott) - thanks to [The Motley Fool](https://www.fool.com/) for sponsoring review of this feature
* `WAGTAILIMAGES_MAX_IMAGE_PIXELS` limit now takes the number of animation frames into account (Karl Hobley)
### Bug fixes
* Added line breaks to long filenames on multiple image / document uploader (Kevin Howbrook)
* Added https support for Scribd oEmbed provider (Rodrigo)
* Changed StreamField group label color so labels are visible (Catherine Farman)
* Prevented images with a very wide aspect ratio from being displayed distorted in the rich text editor (Iman Syed)
* Prevent exception when deleting a model with a protected One-to-one relationship (Neal Todd)
* Added labels to snippet bulk edit checkboxes for screen reader users (Martey Dodoo)
* Middleware responses during page preview are now properly returned to the user (Matt Westcott)
* Default text of page links in rich text uses the public page title rather than the admin display title (Andy Chosak)
* Specific page permission checks are now enforced when viewing a page revision (Andy Chosak)
* `pageurl` and `slugurl` tags no longer fail when `request.site` is `None` (Samir Shah)
* Output form media on add/edit image forms with custom models (Matt Westcott)
* Output form media on add/edit document forms with custom models (Sergey Fedoseev)
* Fixes layout for the clear checkbox in default FileField widget (Mikalai Radchuk)
* Remove ASCII conversion from Postgres search backend, to support stemming in non-Latin alphabets (Pavel Denisov)
* Prevent tab labels on page edit view from being cut off on very narrow screens (Kevin Howbrook)
* Very long words in page listings are now broken where necessary (Kevin Howbrook)
* Language chosen in user preferences no longer persists on subsequent requests (Bojan Mihelac)
* Prevent new block IDs from being assigned on repeated calls to `StreamBlock.get_prep_value` (Colin Klein)
* Prevent broken images in notification emails when static files are hosted on a remote domain (Eduard Luca)
* Replace styleguide example avatar with default image to avoid issues when custom user model is used (Matt Westcott)
* `DraftailRichTextArea` is no longer treated as a hidden field by Django’s form logic (Sergey Fedoseev)
* Replace format() placeholders in translatable strings with % formatting (Matt Westcott)
* Altering Django REST Framework’s `DEFAULT_AUTHENTICATION_CLASSES` setting no longer breaks the page explorer menu and admin API (Matt Westcott)
* Regression - missing label for external link URL field in link chooser (Stefani Castellanos)
## Upgrade considerations
### Query strings added to static file URLs within the admin
To avoid problems caused by outdated cached JavaScript / CSS files following a Wagtail upgrade, URLs to static files within the Wagtail admin now include a version-specific query parameter of the form `?v=1a2b3c4d`. Under certain front-end cache configurations (such as [Cloudflare’s ‘No Query String’ caching level](https://support.cloudflare.com/hc/en-us/articles/200168256-What-are-Cloudflare-s-caching-levels-)), the presence of this parameter may prevent the file from being cached at all. If you are using such a setup, and have some other method in place to expire outdated files (e.g. clearing the cache on deployment), you can disable the query parameter by setting `WAGTAILADMIN_STATIC_FILE_VERSION_STRINGS` to False in your project settings. (Note that this is automatically disabled when `ManifestStaticFilesStorage` is in use.)
### `Page.dummy_request` is deprecated
The internal `Page.dummy_request` method (which generates an HTTP request object simulating a real page request, for use in previews) has been deprecated, as it did not correctly handle errors generated during middleware processing. Any code that calls this method to render page previews should be updated to use the new method `Page.make_preview_request(original_request=None, preview_mode=None)`, which builds the request and calls `Page.serve_preview` as a single operation.
### Changes to document serving on remote storage backends (Amazon S3 etc)
This release introduces a new setting [WAGTAILDOCS_SERVE_METHOD](../reference/settings.md#wagtaildocs-serve-method) to control how document downloads are served. On previous versions of Wagtail, document files would always be served through a Django view, to allow permission checks to be applied. When using a remote storage backend such as Amazon S3, this meant that the document would be downloaded to the Django server on every download request.
In Wagtail 2.7, the default behavior on remote storage backends is to redirect to the storage’s underlying URL after performing the permission check. If this is unsuitable for your project (for example, your storage provider is configured to block public access, or revealing its URL would be a security risk) you can revert to the previous behavior by setting `WAGTAILDOCS_SERVE_METHOD` to `'serve_view'`.
### Template change for page action menu hooks
When customizing the action menu on the page edit view through the [register_page_action_menu_item](../reference/hooks.md#register-page-action-menu-item) or [construct_page_action_menu](../reference/hooks.md#construct-page-action-menu) hook, the `ActionMenuItem` object’s `template` attribute or `render_html` method can be overridden to customize the menu item’s HTML. As of Wagtail 2.7, the HTML returned from these should *not* include the enclosing `` element.
Any add-on library that uses this feature and needs to preserve backward compatibility with previous Wagtail versions can conditionally reinsert the ` ` wrapper through its `render_html` method - for example:
> ```python
> from django.utils.html import format_html
> from wagtail import VERSION as WAGTAIL_VERSION
> from wagtail.admin.action_menu import ActionMenuItem
> class CustomMenuItem(ActionMenuItem):
> template = 'myapp/my_menu_item.html'
> def render_html(self, request, parent_context):
> html = super().render_html(request, parent_context)
> if WAGTAIL_VERSION < (2, 7):
> html = format_html(' {} ', html)
> return html
> ```
### `wagtail.admin.utils` and `wagtail.admin.decorators` modules deprecated
The modules `wagtail.admin.utils` and `wagtail.admin.decorators` have been deprecated. The helper functions defined here exist primarily for Wagtail’s internal use; however, some of them (particularly `send_mail` and `permission_required`) may be found in user code, and import lines will need to be updated. The new locations for these definitions are as follows:
| Definition | Old location | New location |
|---------------------------------|--------------------------|----------------------------|
| any_permission_required | wagtail.admin.utils | wagtail.admin.auth |
| permission_denied | wagtail.admin.utils | wagtail.admin.auth |
| permission_required | wagtail.admin.utils | wagtail.admin.auth |
| PermissionPolicyChecker | wagtail.admin.utils | wagtail.admin.auth |
| user_has_any_page_permission | wagtail.admin.utils | wagtail.admin.auth |
| user_passes_test | wagtail.admin.utils | wagtail.admin.auth |
| users_with_page_permission | wagtail.admin.utils | wagtail.admin.auth |
| reject_request | wagtail.admin.decorators | wagtail.admin.auth |
| require_admin_access | wagtail.admin.decorators | wagtail.admin.auth |
| get_available_admin_languages | wagtail.admin.utils | wagtail.admin.localization |
| get_available_admin_time_zones | wagtail.admin.utils | wagtail.admin.localization |
| get_js_translation_strings | wagtail.admin.utils | wagtail.admin.localization |
| WAGTAILADMIN_PROVIDED_LANGUAGES | wagtail.admin.utils | wagtail.admin.localization |
| send_mail | wagtail.admin.utils | wagtail.admin.mail |
| send_notification | wagtail.admin.utils | wagtail.admin.mail |
| get_object_usage | wagtail.admin.utils | wagtail.admin.models |
| popular_tags_for_model | wagtail.admin.utils | wagtail.admin.models |
| get_site_for_user | wagtail.admin.utils | wagtail.admin.navigation |
# 2.8.1.html.md
# Wagtail 2.8.1 release notes
*April 14, 2020*
## CVE-2020-11001: Possible XSS attack via page revision comparison view
This release addresses a cross-site scripting (XSS) vulnerability on the page revision comparison view within the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin could potentially craft a page revision history that, when viewed by a user with higher privileges, could perform actions with that user’s credentials. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to Vlad Gerasimenko for reporting this issue.
# 2.8.2.html.md
# Wagtail 2.8.2 release notes
*May 4, 2020*
## CVE-2020-11037: Potential timing attack on password-protected private pages
This release addresses a potential timing attack on pages or documents that have been protected with a shared password through Wagtail’s “Privacy” controls. This password check is performed through a character-by-character string comparison, and so an attacker who is able to measure the time taken by this check to a high degree of accuracy could potentially use timing differences to gain knowledge of the password. (This is [understood to be feasible on a local network, but not on the public internet](https://groups.google.com/d/msg/django-developers/iAaq0pvHXuA/fpUuwjK3i2wJ).)
Many thanks to Thibaud Colas for reporting this issue.
# 2.8.html.md
# Wagtail 2.8 release notes
*February 3, 2020*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Django 3.0 support
This release is compatible with Django 3.0. Compatibility fixes were contributed by Matt Westcott and Mads Jensen.
### Improved page locking
The page locking feature has been revised so that the editor locking a page is given exclusive edit access to it, rather than it becoming read-only to everyone. A new Reports menu allows admin / moderator level users to see the currently locked pages, and unlock them if required.
This feature was developed by Karl Hobley and Jacob Topp-Mugglestone. Thanks to [The Motley Fool](https://www.fool.com/) for sponsoring this feature.
### Other features
* Removed leftover Python 2.x compatibility code (Sergey Fedoseev)
* Combine flake8 configurations (Sergey Fedoseev)
* Improve diffing behavior for text fields (Aliosha Padovani)
* Improve contrast of disabled inputs (Nick Smith)
* Added `get_document_model_string` function (Andrey Smirnov)
* Added support for Cloudflare API tokens for frontend cache invalidation (Tom Usher)
* Cloudflare frontend cache invalidation requests are now sent in chunks of 30 to fit within API limits (Tom Usher)
* Added `ancestors` field to the pages endpoint in admin API (Karl Hobley)
* Removed Django admin management of `Page` & `Site` models (Andreas Bernacca)
* Cleaned up Django docs URLs in documentation (Pete Andrew)
* Add StreamFieldPanel to available panel types in documentation (Dan Swain)
* Add `{{ block.super }}` example to ModelAdmin customization in documentation (Dan Swain)
* Add ability to filter image index by a tag (Benedikt Willi)
* Add partial experimental support for nested InlinePanels (Matt Westcott, Sam Costigan, Andy Chosak, Scott Cranfill)
* Added cache control headers when serving documents (Johannes Vogel)
* Use `sensitive_post_parameters` on password reset form (Dan Braghis)
* Add `WAGTAILEMBEDS_RESPONSIVE_HTML` setting to remove automatic addition of `responsive-object` around embeds (Kalob Taulien)
### Bug fixes
* Rename documents listing column ‘uploaded’ to ‘created’ (LB (Ben Johnston))
* Unbundle the l18n library as it was bundled to avoid installation errors which have been resolved (Matt Westcott)
* Prevent error when comparing pages that reference a model with a custom primary key (Fidel Ramos)
* Moved `get_document_model` location so it can be imported when Models are not yet loaded (Andrey Smirnov)
* Use correct HTML escaping of Jinja2 form templates for StructBlocks (Brady Moe)
* All templates with wagtailsettings and modeladmin now use `block.super` for `extra_js` & `extra_css` (Timothy Bautista)
* Layout issue when using `FieldRowPanel` with a heading (Andreas Bernacca)
* `file_size` and `file_hash` now updated when Document file changed (Andreas Bernacca)
* Fixed order of URLs in project template so that static / media URLs are not blocked (Nick Smith)
* Added `verbose_name_plural` to form submission model (Janneke Janssen)
* Prevent `update_index` failures and incorrect front-end rendering on blank `TableBlock` (Carlo Ascani)
* Dropdown initialization on the search page after AJAX call (Eric Sherman)
* Make sure all modal chooser search results correspond to the latest search by canceling previous requests (Esper Kuijs)
## Upgrade considerations
### Removed support for Django 2.0
Django 2.0 is no longer supported as of this release; please upgrade to Django 2.1 or above before upgrading Wagtail.
### Edit locking behaviour changed
The behavior of the page locking feature in the admin interface has been changed.
In past versions, the page lock would apply to all users including the user who
locked the page. Now, the user who locked the page can still edit it but all other
users cannot.
Pages that were locked before this release will continue to be locked in the same
way as before, so this only applies to newly locked pages. If you would like to
restore the previous behavior, you can set the
`WAGTAILADMIN_GLOBAL_PAGE_EDIT_LOCK` setting to `True`.
### Responsive HTML for embeds no longer added by default
In previous versions of Wagtail, embedded media elements were given
a class name of `responsive-object` and an inline `padding-bottom` style to assist
in styling them responsively. These are no longer added by default. To restore the previous
behavior, add `WAGTAILEMBEDS_RESPONSIVE_HTML = True` to your project settings.
### API endpoint classes have moved
For consistency with Django REST Framework, the `PagesAPIEndpoint`, `ImagesAPIEndpoint` and `DocumentsAPIEndpoint` classes have been renamed to `PagesAPIViewSet`, `ImagesAPIViewSet` and `DocumentsAPIViewSet` and moved to the `views` module in their respective packages. Projects using the Wagtail API should update their registration code accordingly.
Old code:
```python
from wagtail.api.v2.endpoints import PagesAPIEndpoint
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.endpoints import ImagesAPIEndpoint
from wagtail.documents.api.v2.endpoints import DocumentsAPIEndpoint
api_router = WagtailAPIRouter('wagtailapi')
api_router.register_endpoint('pages', PagesAPIEndpoint)
api_router.register_endpoint('images', ImagesAPIEndpoint)
api_router.register_endpoint('documents', DocumentsAPIEndpoint)
```
New code:
```python
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet
api_router = WagtailAPIRouter('wagtailapi')
api_router.register_endpoint('pages', PagesAPIViewSet)
api_router.register_endpoint('images', ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet)
```
### `wagtail.documents.models.get_document_model` has moved
The `get_document_model` function should now be imported from `wagtail.documents` rather than `wagtail.documents.models`. See [Custom document model](../advanced_topics/documents/custom_document_model.md#custom-document-model).
### Removed `Page` and `Site` models from Django admin
The `Page` and `Site` models are no longer editable through the Django admin backend. If required these models can be re-registered within your own project using Django’s [`ModelAdmin`](https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin):
```python
# my_app/admin.py
from django.contrib import admin
from wagtail.core.models import Page, Site
admin.site.register(Site)
admin.site.register(Page)
```
# 2.9.1.html.md
# Wagtail 2.9.1 release notes
*June 30, 2020*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix incorrect method name in SiteMiddleware deprecation warning (LB (Ben Johnston))
* `wagtail.contrib.sitemaps` no longer depends on SiteMiddleware (Matt Westcott)
* Purge image renditions cache when renditions are deleted (Pascal Widdershoven, Matt Westcott)
# 2.9.2.html.md
# Wagtail 2.9.2 release notes
*July 3, 2020*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent startup failure when `wagtail.contrib.sitemaps` is in `INSTALLED_APPS` (Matt Westcott)
# 2.9.3.html.md
# Wagtail 2.9.3 release notes
*July 20, 2020*
## CVE-2020-15118: HTML injection through form field help text
This release addresses an HTML injection vulnerability through help text in the `wagtail.contrib.forms` form builder app. When a form page type is made available to Wagtail editors, and the page template is built using Django’s standard form rendering helpers such as `form.as_p` [(as directed in the documentation)](../reference/contrib/forms/index.md#form-builder-usage), any HTML tags used within a form field’s help text will be rendered unescaped in the page. Allowing HTML within help text is an intentional design decision by Django (see the docs for [`help_text`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.Field.help_text)); however, as a matter of policy Wagtail does not allow editors to insert arbitrary HTML by default, as this could potentially be used to carry out cross-site scripting attacks, including privilege escalation. This functionality should therefore not have been made available to editor-level users.
The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Site owners who wish to re-enable the use of HTML within help text (and are willing to accept the risk of this being exploited by editors) may set `WAGTAILFORMS_HELP_TEXT_ALLOW_HTML = True` in their configuration settings.
Many thanks to Timothy Bautista for reporting this issue.
# 2.9.html.md
# Wagtail 2.9 release notes
*May 4, 2020*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Report data exports
Data from reports, form submissions and ModelAdmin can now be exported to both XLSX and CSV format. For ModelAdmin, this is enabled by specifying a `list_export` attribute on the ModelAdmin class. This feature was developed by Jacob Topp-Mugglestone and sponsored by [The Motley Fool](https://www.fool.com/).
### CVE-2020-11037: Potential timing attack on password-protected private pages
This release addresses a potential timing attack on pages or documents that have been protected with a shared password through Wagtail’s “Privacy” controls. This password check is performed through a character-by-character string comparison, and so an attacker who is able to measure the time taken by this check to a high degree of accuracy could potentially use timing differences to gain knowledge of the password. (This is [understood to be feasible on a local network, but not on the public internet](https://groups.google.com/d/msg/django-developers/iAaq0pvHXuA/fpUuwjK3i2wJ).)
Many thanks to Thibaud Colas for reporting this issue.
### Other features
* Added support for creating custom reports (Jacob Topp-Mugglestone)
* Skip page validation when unpublishing a page (Samir Shah)
* Added [MultipleChoiceBlock](../reference/streamfield/blocks.md#streamfield-multiplechoiceblock) block type for StreamField (James O’Toole)
* ChoiceBlock now accepts a `widget` keyword argument (James O’Toole)
* Reduced contrast of rich text toolbar (Jack Paine)
* Support the rel attribute on custom ModelAdmin buttons (Andy Chosak)
* Server-side page slug generation now respects `WAGTAIL_ALLOW_UNICODE_SLUGS` (Arkadiusz Michał Ryś)
* Wagtail admin no longer depends on SiteMiddleware, avoiding incompatibility with Django sites framework and redundant database queries (aritas1, timmysmalls, Matt Westcott)
* Tag field autocompletion now handles custom tag models (Matt Westcott)
* `wagtail_serve` URL route can now be omitted for headless sites (Storm Heg)
* Allow free tagging to be disabled on custom tag models (Matt Westcott)
* Allow disabling page preview by setting `preview_modes` to an empty list (Casper Timmers)
* Add Vidyard to oEmbed provider list (Steve Lyall)
* Optimise compiling media definitions for complex StreamBlocks (pimarc)
* FieldPanel now accepts a ‘heading’ argument (Jacob Topp-Mugglestone)
* Replaced deprecated `ugettext` / `ungettext` calls with `gettext` / `ngettext` (Mohamed Feddad)
* ListBlocks now call child block `bulk_to_python` if defined (Andy Chosak)
* Site settings are now identifiable/cacheable by request as well as site (Andy Babic)
* Added `select_related` attribute to site settings to enable more efficient fetching of foreign key values (Andy Babic)
* Add caching of image renditions (Tom Dyson, Tim Kamanin)
* Add documentation for reporting security issues and internationalisation (Matt Westcott)
* Fields on a custom image model can now be defined as required `blank=False` (Matt Westcott)
### Bug fixes
* Added ARIA alert role to live search forms in the admin (Casper Timmers)
* Reordered login form elements to match expected tab order (Kjartan Sverrisson)
* Re-added ‘Close Explorer’ button on mobile viewports (Sævar Öfjörð Magnússon)
* Added a more descriptive label to Password reset link for screen reader users (Casper Timmers, Martin Coote)
* Improved Wagtail logo contrast by adding a background (Brian Edelman, Simon Evans, Ben Enright)
* Prevent duplicate notification messages on page locking (Jacob Topp-Mugglestone)
* Rendering of non field errors for InlinePanel items (Storm Heg)
* `{% image ... as var %}` now clears the context variable when passed None as an image (Maylon Pedroso)
* `refresh_index` method on Elasticsearch no longer fails (Lars van de Kerkhof)
* Document tags no longer fail to update when replacing the document file at the same time (Matt Westcott)
* Prevent error from very tall / wide images being resized to 0 pixels (Fidel Ramos)
* Remove excess margin when editing snippets (Quadric)
* Added `scope` attribute to table headers in TableBlock output (Quadric)
* Prevent KeyError when accessing a StreamField on a deferred queryset (Paulo Alvarado)
* Hide empty ‘view live’ links (Karran Besen)
* Mark up a few strings for translation (Luiz Boaretto)
* Invalid focal_point attribute on image edit view (Michał (Quadric) Sieradzki)
* No longer expose the `.delete()` method on the default Page.objects manager (Nick Smith)
* `exclude_fields_in_copy` on Page models will now work for modelcluster parental / many to many relations (LB (Ben Johnston))
* Response header (content disposition) now correctly handles filenames with non-ascii characters when using a storage backend (Rich Brennan)
* Improved accessibility fixes for `main`, `header` and `footer` elements in the admin page layout (Mitchel Cabuloy)
* Prevent version number from obscuring long settings menus (Naomi Morduch Toubman)
* Admin views using TemplateResponse now respect the user’s language setting (Jacob Topp-Mugglestone)
* Fixed incorrect language code for Japanese in language setting dropdown (Tomonori Tanabe)
## Upgrade considerations
### Removed support for Django 2.1
Django 2.1 is no longer supported as of this release; please upgrade to Django 2.2 or above before upgrading Wagtail.
### `SiteMiddleware` and `request.site` deprecated
Wagtail’s `wagtail.core.middleware.SiteMiddleware`, which makes the current site object available as the property `request.site`, is now deprecated as it clashes with Django’s sites framework and makes unnecessary database queries on non-Wagtail views. References to `request.site` in your code should be removed; the recommended way of retrieving the current site is `Site.find_for_request(request)` in Python code, and the `{% wagtail_site %}` tag within Django templates.
For example:
```python
# old version
def get_menu_items(request):
return request.site.root_page.get_children().live()
# new version
from wagtail.core.models import Site
def get_menu_items(request):
return Site.find_for_request(request).root_page.get_children().live()
```
```html+django
{# old version #}
Welcome to the {{ request.site.site_name }} website!
{# new version #}
{% load wagtailcore_tags %}
{% wagtail_site as current_site %}
Welcome to the {{ current_site.site_name }} website!
```
Once these are removed, `'wagtail.core.middleware.SiteMiddleware'` can be removed from your project’s `MIDDLEWARE` setting.
### Page / Collection managers no longer expose a `delete` method
For [consistency with standard Django models](https://docs.djangoproject.com/en/stable/ref/models/instances/#deleting-objects), the `delete()` method is no longer available on the default Page and Collection managers. Code such as `Page.objects.delete()` should be changed to `Page.objects.all().delete()`.
# 3.0.1.html.md
# Wagtail 3.0.1 release notes
*June 16, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Other features
* Add warning when `WAGTAILADMIN_BASE_URL` is not configured (Matt Westcott)
### Bug fixes
* Ensure `TabbedInterface` will not show a tab if no panels are visible due to permissions (Paarth Agarwal)
* Specific snippets list language picker was not properly styled (Sage Abdullah)
* Ensure the upgrade notification request for the latest release, which can be disabled via the `WAGTAIL_ENABLE_UPDATE_CHECK` sends the referrer origin with `strict-origin-when-cross-origin` (Karl Hobley)
* Fix misaligned spinner icon on page action button (LB (Ben Johnston))
* Ensure radio buttons / checkboxes display vertically under Django 4.0 (Matt Westcott)
* Prevent failures when splitting blocks at the start or end of a block, or with highlighted text (Jacob Topp-Mugglestone)
* Allow scheduled publishing to complete when the initial editor did not have publish permission (Matt Westcott)
* Stop emails from breaking when `WAGTAILADMIN_BASE_URL` is absent due to the request object not being available (Matt Westcott)
* Make try/except on sending email less broad so that legitimate template rendering errors are exposed (Matt Westcott)
# 3.0.2.html.md
# Wagtail 3.0.2 release notes
*August 30, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure string representation of `FormSubmission` returns a string (LB (Ben Johnston))
* Fix `updatemodulepaths` command for Python 3.7 (Matt Westcott)
* Fix issue where comments could not be added to already saved StreamField content
(Jacob Topp-Mugglestone)
* Remove outdated reference to Image.LoaderError (Matt Westcott)
# 3.0.3.html.md
# Wagtail 3.0.3 release notes
*September 5, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* On the Locked pages report, limit the “locked by” filter to just users who have locked pages (Stefan Hammer
* Prevent JavaScript error when using StreamField on views without commenting support, such as snippets (Jacob Topp-Mugglestone)
# 3.0.html.md
# Wagtail 3.0 release notes
*May 16, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - deprecation of old functionality](#upgrade-considerations-deprecation-of-old-functionality)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
## What’s new
### Page editor redesign
This release contains significant UI changes that affect all of Wagtail’s admin, largely driven by the implementation of the new Page Editor. These include:
* Fully remove the legacy sidebar, with slim sidebar replacing it for all users (Thibaud Colas)
* Add support for adding custom attributes for link menu items in the slim sidebar (Thibaud Colas)
* Convert all UI code to CSS logical properties for Right-to-Left (RTL) language support (Thibaud Colas)
* Switch the Wagtail branding font and monospace font to a system font stack (Steven Steinwand, Paarth Agarwal, Rishank Kanaparti)
* Remove most uppercased text styles from admin UI (Paarth Agarwal)
* Implement new tabs design across the admin interface (Steven Steinwand)
Other changes that are specific to the Page Editor include:
* Implement new slim page editor header with breadcrumb and secondary page menu (Steven Steinwand, Karl Hobley)
* Move page meta information from the header to a new status side panel component inside of the page editing UI (Steven Steinwand, Karl Hobley)
Further updates to the page editor are expected in the next release. Development on this feature was sponsored by Google.
### Rich text block splitting
Rich text blocks within StreamField now provide the ability to split a block at the cursor position, allowing new blocks to be inserted in between. This feature was developed by Jacob Topp-Mugglestone and sponsored by The Motley Fool.
### Removal of special-purpose field panel types
The panel types `StreamFieldPanel`, `RichTextFieldPanel`, `ImageChooserPanel`, `DocumentChooserPanel` and `SnippetChooserPanel` have been phased out, and can now be replaced with `FieldPanel`. Additionally, `PageChooserPanel` is only required when passing a `page_type` or `can_choose_root`, and can otherwise be replaced with `FieldPanel`. In all cases, `FieldPanel` will now automatically select the most appropriate form element. This feature was developed by Matt Westcott.
### Permission-dependent FieldPanels
[`FieldPanel`](../reference/panels.md#wagtail.admin.panels.FieldPanel) now accepts a `permission` keyword argument to specify that the field should only be available to users with a given permission level. This feature was developed by Matt Westcott and sponsored by Google as part of Wagtail’s page editor redevelopment.
### Page descriptions
With every Wagtail Page you are able to add a helpful description text, similar to a `help_text` model attribute. By adding `page_description` to your Page model you’ll be adding a short description that can be seen in different places within Wagtail:
```python
class LandingPage(Page):
page_description = "Use this page for converting users"
```
### Image duplicate detection
Trying to upload an image that’s a duplicate of one already in the image library will now lead to a confirmation step. This feature was developed by Tidiane Dia and sponsored by The Motley Fool.
### Image renditions can now be prefetched
When using a queryset to render a list of items with images, you can now make use of Django’s built-in `prefetch_related()` queryset method to prefetch the renditions needed for rendering with a single extra query. For long lists of items, or where multiple renditions are used for each item, this can provide a significant boost to performance. This feature was developed by Andy Babic.
### Other features
* Upgrade ESLint and Stylelint configurations to latest shared Wagtail configs (Thibaud Colas, Paarth Agarwal)
* Major updates to frontend tooling; move Node tooling from Gulp to Webpack, upgrade to Node v16 and npm v8, eslint v8, stylelint v14 and others (Thibaud Colas)
* Change comment headers’ date formatting to use browser APIs instead of requiring a library (LB (Ben Johnston))
* Lint with flake8-comprehensions and flake8-assertive, including adding a pre-commit hook for these (Mads Jensen, Dan Braghis)
* Add black configuration and reformat code using it (Dan Braghis)
* Remove UI code for legacy browser support: polyfills, IE11 workarounds, Modernizr (Thibaud Colas)
* Remove redirect auto-creation recipe from documentation as this feature is now supported in Wagtail core (Andy Babic)
* Remove IE11 warnings (Gianluca De Cola)
* Replace `content_json` `TextField` with `content` `JSONField` in `PageRevision` (Sage Abdullah)
* Remove `replace_text` management command (Sage Abdullah)
* Replace `data_json` `TextField` with `data` `JSONField` in `BaseLogEntry` (Sage Abdullah)
* Remove the legacy Hallo rich text editor as it has moved to an external package (LB (Ben Johnston))
* Increase the size of checkboxes throughout the UI, and simplify their alignment (Steven Steinwand)
* Adopt [MyST](https://myst-parser.readthedocs.io/en/stable/index.html) for parsing documentation written in Markdown, replaces recommonmark (LB (Ben Johnston), Thibaud Colas)
* Installing docs extras requirements in CircleCI so issues with the docs requirements are picked up earlier (Thibaud Colas)
* Remove core usage of jinjalint and migrate to curlylint to resolve dependency incompatibility issues (Thibaud Colas)
* Switch focus outlines implementation to `:focus-visible` for cross-browser consistency (Paarth Agarwal)
* Migrate multiple documentation pages from RST to MD - including the editor’s guide (Vibhakar Solanki, LB (Ben Johnston), Shwet Khatri)
* Add documentation for defining custom form validation on models used in Wagtail’s `ModelAdmin` (Serafeim Papastefanos)
* Update `README.md` logo to work for GitHub dark mode (Paarth Agarwal)
* Avoid an unnecessary page reload when pressing enter within the header search bar (Images, Pages, Documents) (Riley de Mestre)
* Removed unofficial length parameter on `If-Modified-Since` header in `sendfile_streaming_backend` which was only used by IE (Mariusz Felisiak)
* Add Pinterest support to the list of default oEmbed providers (Dharmik Gangani)
* Update Jinja2 template support for Jinja2 3.1 (Seb Brown)
* Add ability for `StreamField` to use `JSONField` to store data, rather than `TextField` (Sage Abdullah)
* Split up linting / formatting tasks in Makefile into client and server components (Hitansh Shah)
* Add support for embedding Instagram reels (Luis Nell)
* Use Django’s JavaScript catalog feature to manage translatable strings in JavaScript (Karl Hobley)
* Add `trimmed` attribute to all blocktrans tags, so spacing is more reliable in translated strings (Harris Lapiroff)
* Add documentation that describes how to use `ModelAdmin` to manage `Tag`s (Abdulmajeed Isa)
* Rename the setting `BASE_URL` (undocumented) to [`WAGTAILADMIN_BASE_URL`](../reference/settings.md#wagtailadmin-base-url) and add to documentation, `BASE_URL` will be removed in a future release (Sandil Ranasinghe)
* Validate to and from email addresses within form builder pages when using `AbstractEmailForm` (Jake Howard)
* Add [`WAGTAILIMAGES_RENDITION_STORAGE`](../reference/settings.md#wagtailimages-rendition-storage) setting to allow an alternative image rendition storage (Heather White)
* Add [`wagtail_update_image_renditions` management command](../reference/management_commands.md#wagtail-update-image-renditions) to regenerate image renditions or purge all existing renditions (Hitansh Shah, Onno Timmerman, Damian Moore)
* Add the ability for choices to be separated by new lines instead of just commas within the form builder, commas will still be supported if used (Abdulmajeed Isa)
* Add internationalisation UI to modeladmin (Andrés Martano)
* Support chunking in `PageQuerySet.specific()` to reduce memory consumption (Andy Babic)
* Add useful help text to Tag fields to advise what content is allowed inside tags, including when `TAG_SPACES_ALLOWED` is `True` or `False` (Abdulmajeed Isa)
* Change `AbstractFormSubmission`’s `form_data` to use JSONField to store form submissions (Jake Howard)
### Bug fixes
* Update django-treebeard dependency to 4.5.1 or above (Serafeim Papastefanos)
* When using `simple_translations` ensure that the user is redirected to the page edit view when submitting for a single locale (Mitchel Cabuloy)
* When previewing unsaved changes to `Form` pages, ensure that all added fields are correctly shown in the preview (Joshua Munn)
* When Documents (e.g. PDFs) have been configured to be served inline via `WAGTAILDOCS_CONTENT_TYPES` & `WAGTAILDOCS_INLINE_CONTENT_TYPES` ensure that the filename is correctly set in the `Content-Disposition` header so that saving the files will use the correct filename (John-Scott Atlakson)
* Improve the contrast of the “Remember me” checkbox against the login page’s background (Steven Steinwand)
* Group permission rows with custom permissions no longer have extra padding (Steven Steinwand)
* Make sure the focus outline of checkboxes is fully around the outer border (Steven Steinwand)
* Consistently set `aria-haspopup="menu"` for all sidebar menu items that have sub-menus (LB (Ben Johnston))
* Make sure `aria-expanded` is always explicitly set as a string in sidebar (LB (Ben Johnston))
* Use a button element instead of a link for page explorer menu item, for the correct semantics and behavior (LB (Ben Johnston))
* Make sure the “Title” column label can be translated in the page chooser and page move UI (Stephanie Cheng Smith)
* Remove redundant `role="main"` attributes on `` elements causing HTML validation issues (Luis Espinoza)
* Allow bulk publishing of pages without revisions (Andy Chosak)
* Stop skipping heading levels in Wagtail welcome page (Jesse Menn)
* Add missing `lang` attributes to `` elements (James Ray)
* Add missing translation usage in Workflow templates (Anuja Verma, Saurabh Kumar)
* Avoid 503 server error when entering tags over 100chars and instead show a user facing validation error (Vu Pham, Khanh Hoang)
* Ensure `thumb_col_header_text` is correctly used by `ThumbnailMixin` within `ModelAdmin` as the column header label (Kyle J. Roux)
* Ensure page copy in Wagtail admin doesn’t ignore `exclude_fields_in_copy` (John-Scott Atlakson)
* Generate new translation keys for translatable `Orderable`s when page is copied without being published (Kalob Taulien, Dan Braghis)
* Ignore `GenericRelation` when copying pages (John-Scott Atlakson)
* Implement ARIA tabs markup and keyboards interactions for admin tabs (Steven Steinwand)
* Ensure `wagtail updatemodulepaths` works when system locale is not UTF-8 (Matt Westcott)
* Re-establish focus trap for Pages explorer in slim sidebar (Thibaud Colas)
* Ensure the icon font loads correctly when `STATIC_URL` is not `"/static/"` (Jacob Topp-Mugglestone)
## Upgrade considerations - changes affecting all projects
### Changes to module paths
Various modules of Wagtail have been reorganized, and imports should be updated as follows:
* The `wagtail.core.utils` module is renamed to `wagtail.coreutils`
* All other modules under `wagtail.core` can now be found under `wagtail` - for example, `from wagtail.core.models import Page` should be changed to `from wagtail.models import Page`
* The `wagtail.tests` module is renamed to `wagtail.test`
* `wagtail.admin.edit_handlers` is renamed to `wagtail.admin.panels`
* `wagtail.contrib.forms.edit_handlers` is renamed to `wagtail.contrib.forms.panels`
These changes can be applied automatically to your project codebase by running the following commands from the project root:
```sh
wagtail updatemodulepaths --list # list the files to be changed without updating them
wagtail updatemodulepaths --diff # show the changes to be made, without updating files
wagtail updatemodulepaths # actually update the files
```
### Removal of special-purpose field panel types
Within panel definitions on models, `StreamFieldPanel`, `RichTextFieldPanel`, `ImageChooserPanel`, `DocumentChooserPanel` and `SnippetChooserPanel` should now be replaced with `FieldPanel`. Additionally, `PageChooserPanel` can be replaced with `FieldPanel` if it does not use the `page_type` or `can_choose_root` arguments.
### `BASE_URL` setting renamed to `WAGTAILADMIN_BASE_URL`
References to `BASE_URL` in your settings should be updated to [`WAGTAILADMIN_BASE_URL`](../reference/settings.md#wagtailadmin-base-url). This setting was not previously documented, but was part of the default project template when starting a project with the `wagtail start` command, and specifies the full base URL for the Wagtail admin, for use primarily in email notifications.
### `use_json_field` argument added to `StreamField`
All uses of `StreamField` should be updated to include the argument `use_json_field=True`. After adding this, make sure to generate and run migrations. This converts the field to use `JSONField` as its internal type instead of `TextField`, which will allow you to use `JSONField` lookups and transforms on the field. This change is necessary to ensure that the database migration is applied; a future release will drop support for `TextField`-based StreamFields.
### SQLite now requires the `JSON1` extension enabled
Due to [`JSONField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.JSONField) requirements, SQLite will only be supported with the JSON1 extension enabled.
See [Enabling JSON1 extension on SQLite](https://docs.djangoproject.com/en/stable/ref/databases/#sqlite-json1) and [JSON1 extension](https://www.sqlite.org/json1.html) for details.
## Upgrade considerations - deprecation of old functionality
### Removed support for Internet Explorer (IE11)
IE11 support was officially dropped in Wagtail 2.15, and as of this release there will no longer be a warning shown to users of this browser. Wagtail is fully compatible with Microsoft Edge, Microsoft’s replacement for Internet Explorer. You may consider using its [IE mode](https://learn.microsoft.com/en-us/deployedge/edge-ie-mode) to keep access to IE11-only sites, while other sites and apps like Wagtail can leverage modern browser capabilities.
### Hallo legacy rich text editor has moved to an external package
Hallo was deprecated in [Wagtail v2.0 (February 2018)](https://docs.wagtail.org/en/stable/releases/2.0.html#new-rich-text-editor) and has had only a minimal level of support since then. If you still require Hallo for your Wagtail installation, you will need to install the [Wagtail Hallo editor](https://github.com/wagtail/wagtail-hallo) legacy package. We encourage all users of the Hallo editor to take steps to migrate to the new Draftail editor as this external package is unlikely to have ongoing maintenance. `window.registerHalloPlugin` will no longer be created on the page editor load, unless the legacy package is installed.
### Removal of legacy `clean_name` on `AbstractFormField`
If you are upgrading a pre-2.10 project that uses the [Wagtail form builder](../reference/contrib/forms/index.md#form-builder), and has existing form submission data that needs to be preserved, you must first upgrade to a version between 2.10 and 2.16, and run migrations and start the application server, before upgrading to 3.0. This ensures that the `clean_name` field introduced in Wagtail 2.10 is populated. The mechanism for doing this (which had a dependency on the [Unidecode](https://pypi.org/project/Unidecode/) package) has been dropped in Wagtail 3.0. Any new form fields created under Wagtail 2.10 or above use the [AnyAscii](https://pypi.org/project/anyascii/) library instead.
### Removed support for Jinja2 2.x
Jinja2 2.x is no longer supported as of this release; if you are using Jinja2 templating on your project, please upgrade to Jinja2 3.0 or above.
## Upgrade considerations - changes affecting Wagtail customizations
### API changes to panels (EditHandlers)
Various changes have been made to the internal API for defining panel types, previously known as edit handlers. As noted above, the module `wagtail.admin.edit_handlers` has been renamed to `wagtail.admin.panels`, and `wagtail.contrib.forms.edit_handlers` is renamed to `wagtail.contrib.forms.panels`.
Additionally, the base `wagtail.admin.edit_handlers.EditHandler` class has been renamed to `wagtail.admin.panels.Panel`, and `wagtail.admin.edit_handlers.BaseCompositeEditHandler` has been renamed to `wagtail.admin.panels.PanelGroup`.
Template paths have also been renamed accordingly - templates previously within `wagtailadmin/edit_handlers/` are now located under `wagtailadmin/panels/`, and `wagtailforms/edit_handlers/form_responses_panel.html` is now at `wagtailforms/panels/form_responses_panel.html`.
Where possible, third-party packages that implement their own field panel types should be updated to allow using a plain `FieldPanel` instead, in line with Wagtail dropping its own special-purpose field panel types such as `StreamFieldPanel` and `ImageChooserPanel`. The steps for doing this will depend on the package’s functionality, but in general:
* If the panel sets a custom template, your code should instead define [a `Widget` class](https://docs.djangoproject.com/en/stable/ref/forms/widgets/) that produces your desired HTML rendering.
* If the panel provides a `widget_overrides` method, your code should instead call [`register_form_field_override`](../extending/forms.md) so that the desired widget is always selected for the relevant model field type.
* If the panel provides a `get_comparison_class` method, your code should instead call `wagtail.admin.compare.register_comparison_class` to register the comparison class against the relevant model field type.
Within the `Panel` class, the methods `widget_overrides`, `required_fields` and `required_formsets` have been deprecated in favor of a new `get_form_options` method that returns a dict of configuration options to be passed on to the generated form class:
* Panels that define `required_fields` should instead return this value as a `fields` item in the dict returned from `get_form_options`
* Panels that define `required_formsets` should instead return this value as a `formsets` item in the dict returned from `get_form_options`
* Panels that define `widget_overrides` should instead return this value as a `widgets` item in the dict returned from `get_form_options`
The methods `on_request_bound`, `on_instance_bound` and `on_form_bound` are no longer used. In previous versions, over the course of serving a request an edit handler would have the attributes `request`, `model`, `instance` and `form` attached to it, with the corresponding `on_*_bound` method being called at that point. In the new implementation, only the `model` attribute and `on_model_bound` method are still available. This means it is no longer possible to vary or patch the form class in response to per-request information such as the user object. For permission checks, you should use the new `permission` option on `FieldPanel`; for other per-request customizations to the form object, use [a custom form class](../advanced_topics/customization/page_editing_interface.md#custom-edit-handler-forms) with an overridden `__init__` method. (The current user object is available from the form as `self.for_user`.)
Binding to a request, instance and form object is now handled by a new class `Panel.BoundPanel`. Any initialization logic previously performed in `on_request_bound`, `on_instance_bound` or `on_form_bound` can instead be moved to the constructor method of a subclass of `BoundPanel`:
```python
class CustomPanel(Panel):
class BoundPanel(Panel.BoundPanel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# The attributes self.panel, self.request, self.instance and self.form
# are available here
```
The template context for panels derived from `BaseChooserPanel` has changed. `BaseChooserPanel` is deprecated and now functionally identical to `FieldPanel`; as a result, the context variable `is_chosen`, and the variable name given by the panel’s `object_type_name` property, are no longer available on the template. The only available variables are now `field` and `show_add_comment_button`. If your template depends on these additional variables, you will need to pass them explicitly by overriding the `BoundPanel.get_context_data` method.
### API changes to ModelAdmin
Some changes of behavior have been made to ModelAdmin as a result of the panel API changes:
* When overriding the `get_form_class` method of a ModelAdmin `CreateView` or `EditView` to pass a custom form class, that form class must now inherit from `wagtail.admin.forms.models.WagtailAdminModelForm`. Passing a plain Django ModelForm subclass is no longer valid.
* The `ModelAdmin.get_form_fields_exclude` method is no longer passed a `request` argument. Subclasses that override this method should remove this from the method signature. If the request object is being used to vary the set of fields based on the user’s permission, this can be replaced with the new `permission` option on `FieldPanel`.
* The `ModelAdmin.get_edit_handler` method is no longer passed a `request` or `instance` argument. Subclasses that override this method should remove this from the method signature.
### Replaced `content_json` `TextField` with `content` `JSONField` in `PageRevision`
The `content_json` field in the `PageRevision` model has been renamed to `content`, and this field now internally uses `JSONField` instead of `TextField`. If you have a large number of `PageRevision` objects, running the migrations might take a while.
### Replaced `data_json` `TextField` with `data` `JSONField` in `BaseLogEntry`
The `data_json` field in the `BaseLogEntry` model (and its subclasses `PageLogEntry` and `ModelLogEntry`) has been renamed to `data`, and this field now internally uses `JSONField` instead of `TextField`. If you have a large number of objects for these models, running the migrations might take a while.
If you have models that are subclasses of `BaseLogEntry` in your project, be careful when generating new migrations for these models. As the field is changed and renamed at the same time, Django’s `makemigrations` command will generate `RemoveField` and `AddField` operations instead of `AlterField` and `RenameField`. To avoid data loss, make sure to adjust the migrations accordingly. For example with a model named `MyCustomLogEntry`, change the following operations:
```python
operations = [
migrations.RemoveField(
model_name='mycustomlogentry',
name='data_json',
),
migrations.AddField(
model_name='mycustomlogentry',
name='data',
field=models.JSONField(blank=True, default=dict),
),
]
```
to the following operations:
```python
operations = [
migrations.AlterField(
model_name="mycustomlogentry",
name="data_json",
field=models.JSONField(blank=True, default=dict),
),
migrations.RenameField(
model_name="mycustomlogentry",
old_name="data_json",
new_name="data",
),
]
```
### Replaced `form_data` `TextField` with `JSONField` in `AbstractFormSubmission`
The `form_data` field in the `AbstractFormSubmission` model (and its subclasses `FormSubmission`) has been converted to `JSONField` instead of `TextField`. If you have customizations that programmatically add form submissions you will need to ensure that the `form_data` that is output is no longer a JSON string but instead a serializable Python object. When interacting with the `form_data` you will now receive a Python object and not a string.
Example change
```python
def process_form_submission(self, form):
self.get_submission_class().objects.create(
# form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
form_data=form.cleaned_data, # new
page=self, user=form.user
)
```
### Removed `size` argument from `wagtail.utils.sendfile_streaming_backend.was_modified_since`
The `size` argument of the undocumented `wagtail.utils.sendfile_streaming_backend.was_modified_since` function has been removed. This argument was used to add a `length` parameter to the HTTP header; however, this was never part of the HTTP/1.0 and HTTP/1.1 specifications see [RFC7232](https://httpwg.org/specs/rfc7232.html#header.if-modified-since) and existed only as a an unofficial implementation in IE browsers.
# 4.0.1.html.md
# Wagtail 4.0.1 release notes
*September 5, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* On the Locked pages report, limit the “locked by” filter to just users who have locked pages (Stefan Hammer)
* Prevent JavaScript error when using StreamField on views without commenting support, such as snippets (Jacob Topp-Mugglestone)
* Modify base template for new projects so that links opened from the preview panel open in a new window (Sage Abdullah)
* Prevent circular import error between custom document models and document chooser blocks (Matt Westcott)
# 4.0.2.html.md
# Wagtail 4.0.2 release notes
*September 23, 2022*
> * [What’s new](#what-s-new)
## What’s new
* Update all images and sections of the Wagtail Editor’s guide to align with the new admin interface changes from Wagtail 3.0 and 4.0 (Thibaud Colas)
* Ensure all images in the documentation have a suitable alt text (Thibaud Colas)
### Bug fixes
* Ensure tag autocompletion dropdown has a solid background (LB (Ben) Johnston)
* Allow inline panels to be ordered (LB (Ben) Johnston)
* Only show draft / live status tags on snippets that have `DraftStateMixin` applied (Sage Abdullah)
* Prevent JS error when initializing chooser modals with no tabs (LB (Ben) Johnston)
* Add missing vertical spacing between chooser modal header and body when there are no tabs (LB (Ben) Johnston)
* Reinstate specific labels for chooser buttons (for example ‘Choose another page’, ‘Edit this page’ not ‘Change’, ‘Edit’) so that it is clearer for users and non-English translations (Matt Westcott)
* Resolve issue where searches with a tag and a query param in the image listing would result in an `FilterFieldError` (Stefan Hammer)
* Add missing vertical space between header and content in embed chooser modal (LB (Ben) Johnston)
* Use the correct type scale for heading levels in rich text (Steven Steinwand)
* Update alignment and reveal logic of fields’ comment buttons (Steven Steinwand)
* Regression from Markdown conversion in documentation for API configuration - update to correctly use PEP-8 for example code (Storm Heg)
* Prevent ‘Delete’ link on page edit view from redirecting back to the deleted page (LB (Ben) Johnston)
* Prevent JS error on images index view when collections dropdown is omitted (Tidiane Dia)
* Prevent “Entries per page” dropdown on images index view from reverting to 10 (Tidiane Dia)
* Set related_name on user revision relation to avoid conflict with django-reversion (Matt Westcott)
* Ensure the “recent edits” panel on the Dashboard (home) page works when page record is missing (Matt Westcott)
* Only add Translate buttons when the `simple_translation` app is installed (Dan Braghis)
* Ensure that `MultiFieldPanel` correctly outputs all child classnames in the template (Matt Westcott)
* Remove over-eager caching on ModelAdmin permission checks (Matt Westcott, Stefan Hammer)
# 4.0.4.html.md
# Wagtail 4.0.4 release notes
*October 18, 2022*
> * [What’s new](#what-s-new)
## What’s new
* Render `help_text` when set on `FieldPanel`, `MultiFieldPanel`, `FieldRowPanel`, and other panel APIs where it previously worked without official support (Matt Westcott)
* Update special-purpose `FieldPanel` deprecation message to add clarity for developers (Matt Westcott)
### Bug fixes
* Add back rendering of `help_text` for InlinePanel (Matt Westcott)
* Ensure that `AbstractForm` & `AbstractEmailForm` page models correctly pass the form to the preview context (Dan Bentley)
* Use the correct custom font for the Wagtail userbar (Umar Farouk Yunusa)
* Ensure that buttons on custom chooser widgets are correctly shown on hover (Thibaud Colas)
# 4.0.html.md
# Wagtail 4.0 release notes
*August 31, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Django 4.1 support
This release adds support for Django 4.1. When upgrading, please note that the `django-taggit` library also needs to be updated to 3.0.0 or above.
### Global settings models
The new `BaseGenericSetting` base model class allows defining a settings model that applies to all sites rather than just a single site.
See [the Settings documentation](../reference/contrib/settings.md) for more information. This feature was implemented by Kyle Bayliss.
### Image renditions can now be prefetched by filter
When using a queryset to render a list of images, you can now use the `prefetch_renditions()` queryset method to prefetch the renditions needed for rendering with a single extra query, similar to `prefetch_related`. If you have many renditions per image, you can also call it with filters as arguments - `prefetch_renditions("fill-700x586", "min-600x400")` - to fetch only the renditions you intend on using for a smaller query. For long lists of images, this can provide a significant boost to performance. See [Prefetching image renditions](../advanced_topics/images/renditions.md#prefetching-image-renditions) for more examples. This feature was developed by Tidiane Dia and Karl Hobley.
### Page editor redesign
Following from Wagtail 3.0, this release contains significant UI changes that affect all of Wagtail’s admin, largely driven by the implementation of the new Page Editor. These include:
* Updating all widget styles across the admin UI, including basic form widgets, as well as choosers.
* Updating field styles across forms, with help text consistently under fields, error messages above, and comment buttons to the side.
* Making all sections of the page editing UI collapsible by default.
* New designs for StreamField and InlinePanel blocks, with better support for nested blocks.
* Updating the side panels to prevent overlap with form fields unless necessary.
Further updates to the page editor are expected in the next release. Those changes were implemented by Thibaud Colas. Development on this feature was sponsored by Google.
### Rich text improvements
As part of the page editor redesign project sponsored by Google, we have made several improvements to our rich text editor:
* Inline toolbar: The toolbar now shows inline, to avoid clashing with the page’s header.
* Command palette: Start a block with a slash ‘/’ to open the palette and change the text format.
* Character count: The character count is displayed underneath the editor, live-updating as you type. This counts the length of the text, not of any formatting.
* Paste to auto-create links: To add a link from your copy-paste clipboard, select text and paste the URL.
* Text shortcuts undo: The editor normally converts text starting with `1. ` to a list item. It’s now possible to un-do this change and keep the text as-is. This works for all Markdown-style shortcuts.
* RTL support: The editor’s UI now displays correctly in right-to-left languages.
* Focus-aware placeholder: The editor’s placeholder text will now follow the user’s focus, to make it easier to understand where to type in long fields.
* Empty heading highlight: The editor now highlights empty headings and list items by showing their type (“Heading 3”) as a placeholder, so content is less likely to be published with empty headings.
* Split and insert: rich text fields can now be split while inserting a new StreamField block of the desired type.
### Live preview panel
Wagtail’s page preview is now available in a side panel within the page editor. This preview auto-updates as users type, and can display the page in three different viewports: mobile, tablet, desktop. The existing preview functionality is still present, moved inside the preview panel rather than at the bottom of the page editor. The auto-update delay can be configured with the `WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL` setting. This feature was developed by Sage Abdullah.
### Admin color themes
In Wagtail 2.12, we introduced theming support for Wagtail’s primary brand color. This has now been extended to almost all of Wagtail’s color palette. View our [Custom user interface colors](../advanced_topics/customization/admin_templates.md#custom-user-interface-colors) documentation for more information, an overview of Wagtail’s customizable color palette, and a live demo of the supported customizations. This was implemented by Thibaud Colas, under the page editor redesign project sponsored by Google.
### Windows High Contrast mode support improvements
In Wagtail 2.16, we introduced support for Windows High Contrast mode (WHCM). This release sees a lot of improvements to our support, thanks to our new contributor Anuja Verma, who has been working on this as part of the [Contrast Themes](https://github.com/wagtail/wagtail/discussions/8193) Google Summer of Code project, with support from Jane Hughes, Scott Cranfill, and Thibaud Colas.
* Improve help block styles with less reliance on communication via color alone in forced colors mode
* Add a bottom border to top messages so they stand out from the header in forced colors mode
* Make progress bars’ progress visible in forced colors mode
* Make checkboxes visible in forced colors mode
* Display the correct color for icons in forced colors mode
* Add a border around modal dialogs so they can be identified in forced colors mode
* Ensure disabled buttons are distinguishable from active buttons in forced colors mode
* Ensure that the fields on login and password reset forms are visible in forced colors mode
* Missing an outline on dropdown content and malformed tooltip arrow in forced colors mode
### UX unification and consistency improvements
In Wagtail 3.0, a new Page Editor experience was introduced, this release brings many of the UX and UI improvements to other parts of Wagtail for a more consistent experience.
The bulk of these enhancements have been from Paarth Agarwal, who has been doing the [UX Unification](https://github.com/wagtail/wagtail/discussions/8158) internship project alongside other Google Summer of Code participants. This internship has been sponsored by Torchbox with mentoring support from LB (Ben Johnston), Thibaud Colas and Helen Chapman.
* **Login and password reset**
* Refreshed design for login and password reset pages to better suit a wider range of device sizes
* Better accessibility and screen reader support for the sign-in form due to a more appropriate DOM structure and skip link improvements
* Remove usage of inline script to focus on username field and instead use `autofocus`
* Wagtail logo added with the ability to override this logo via [Custom branding](../advanced_topics/customization/admin_templates.md#custom-branding)
* **Breadcrumbs**
* Add Breadcrumbs to the Wagtail pattern library
* Enhance new breadcrumbs so they can be added to any header or container element
* Adopt new breadcrumbs on the page explorer (listing) view and the page chooser modal, remove legacy breadcrumbs code for move page as no longer used
* Rename `explorer_breadcrumb` template tag to `breadcrumbs` as it is now used in multiple locations
* Remove legacy (non-next) breadcrumbs no longer used, remove `ModelAdmin` usage of breadcrumbs completely and adopt consistent ‘back’ link approach
* **Headers**
* Update classes and styles for the shared header templates to align with UI guidelines
* Ensure the shared header template is more reusable, add ability to include classes, extra content and other blocks
* Switch all report workflow, redirects, form submissions, site settings views to use Wagtail’s reusable header component
* Resolve issues throughout Wagtail where the sidebar toggle would overlap header content on small devices
* **Tabs**
* Add Tabs to the Wagtail pattern library
* Adopt new Tabs component in the workflow history report page, removing the bespoke implementation there
* **Dashboard (home) view**
* Migrate the dashboard (home) view header to the shared header template and update designs
* Refresh designs for Home (Dashboard) site summary panels, use theme spacing and colors
* Add support for RTL layouts and better support for small devices
* Add CSS support for more than three summary items if added via customizations
* **Page Listing view**
* Adopt the slim header in page listing views, with buttons moved under the “Actions” dropdown
* Add convenient access to the translate page button in the parent “more” button
### Previews, revisions and drafts for snippets
Snippets can now be given a previewable HTML representation, revision history, and draft / live states through the use of the mixins `PreviewableMixin`, `RevisionMixin`, and `DraftStateMixin`. For more details, see:
* [Making snippets previewable](../topics/snippets/features.md#wagtailsnippets-making-snippets-previewable)
* [Saving revisions of snippets](../topics/snippets/features.md#wagtailsnippets-saving-revisions-of-snippets)
* [Saving draft changes of snippets](../topics/snippets/features.md#wagtailsnippets-saving-draft-changes-of-snippets)
These features were developed by Sage Abdullah.
### Documentation improvements
The documentation now has dark mode which will be turned on by default if set in your browser or OS preferences, it can also be toggled on and off manually. The colors and fonts of the documentation now align with the design updates introduced in Wagtail 3.0. These features were developed by Vince Salvino.
There are also many improvements to the documentation both under the hood and in the layout;
* Convert the rest of the documentation to Markdown, in place of RST, which will make it much easier for others to contribute to better documentation (Khanh Hoang, Vu Pham, Daniel Kirkham, LB (Ben) Johnston, Thiago Costa de Souza, Benedict Faw, Noble Mittal, Sævar Öfjörð Magnússon, Sandeep M A, Stefano Silvestri)
* Replace latin abbreviations (i.e. / e.g.) with common English phrases so that documentation is easier to understand (Dominik Lech)
* Improve the organization of the settings reference page with logical grouping and better internal linking (Akash Kumar Sen)
* Improve the accessibility of the documentation with higher contrast colors, consistent focus outline, better keyboard only navigation through the sidebar (LB (Ben) Johnston, Vince Salvino)
* Better sidebar scrolling behavior, it is now sticky on larger devices and scrollable on its own (Paarth Agarwal, LB (Ben) Johnston)
* Fix links showing incorrectly in Safari (Tibor Leupold)
* See other features below for new feature specific documentation added.
### Other features
* Add clarity to confirmation when being asked to convert an external link to an internal one (Thijs Kramer)
* Add `base_url_path` to `ModelAdmin` so that the default URL structure of app_label/model_name can be overridden (Vu Pham, Khanh Hoang)
* Add `full_url` to the API output of `ImageRenditionField` (Paarth Agarwal)
* Use `InlinePanel`’s label when available for field comparison label (Sandil Ranasinghe)
* Drop support for Safari 13 by removing left/right positioning in favor of CSS logical properties (Thibaud Colas)
* Use `FormData` instead of jQuery’s `form.serialize` when editing documents or images just added so that additional fields can be better supported (Stefan Hammer)
* Add informational Codecov status checks for GitHub CI pipelines (Tom Hu)
* Make it possible to reuse and customize Wagtail’s fonts with CSS variables (LB (Ben) Johnston)
* Add better handling and informative developer errors for cross linking URLS (e.g. success after add) in generic views `wagtail.admin.views.generic` (Matt Westcott)
* Introduce `wagtail.admin.widgets.chooser.BaseChooser` to make it easier to build custom chooser inputs (Matt Westcott)
* Introduce JavaScript chooser module, including a SearchController class which encapsulates the standard pattern of re-rendering the results panel in response to search queries and pagination (Matt Westcott)
* Migrate Image and Document choosers to new JavaScript chooser module (Matt Westcott)
* Add ability to select multiple items at once within bulk actions selections when holding shift on subsequent clicks (Hitansh Shah)
* Upgrade notification, shown to admins on the dashboard if Wagtail is out of date, will now link to the release notes for the closest minor branch instead of the latest patch (Tibor Leupold)
* Upgrade notification can now be configured to only show updates when there is a new LTS available via `WAGTAIL_ENABLE_UPDATE_CHECK = 'lts'` (Tibor Leupold)
* Implement redesign of the Workflow Status dialog, fixing accessibility issues (Steven Steinwand)
* Add the ability to change the number of images displayed per page in the image library (Tidiane Dia, with sponsorship from YouGov)
* Allow users to sort by different fields in the image library (Tidiane Dia, with sponsorship from YouGov)
* Add `prefetch_renditions` method to `ImageQueryset` for performance optimization on image listings (Tidiane Dia, Karl Hobley)
* Add ability to define a custom `get_field_clean_name` method when defining `FormField` models that extend `AbstractFormField` (LB (Ben) Johnston)
* Migrate Home (Dashboard) view to use generic Wagtail class based view (LB (Ben) Johnston)
* Combine most of Wagtail’s stylesheets into the global `core.css` file (Thibaud Colas)
* Update `ReportView` to extend from generic `wagtail.admin.views.generic.models.IndexView` (Sage Abdullah)
* Update pages `Unpublish` view to extend from generic `wagtail.admin.views.generic.models.UnpublishView` (Sage Abdullah)
* Introduce a `wagtail.admin.viewsets.chooser.ChooserViewSet` module to serve as a common base implementation for chooser modals (Matt Westcott)
* Add documentation for `wagtail.admin.viewsets.model.ModelViewSet` (Matt Westcott)
* Added [multi-site support](../advanced_topics/api/v2/usage.md#api-filtering-pages-by-site) to the API (Sævar Öfjörð Magnússon)
* Add `add_to_admin_menu` option for `ModelAdmin` (Oliver Parker)
* Implement [Fuzzy matching](../topics/search/searching.md#fuzzy-matching) for Elasticsearch (Nick Smith)
* Cache model permission codenames in `PermissionHelper` (Tidiane Dia)
* Selecting a new parent page for moving a page now uses the chooser modal which allows searching (Viggo de Vries)
* Add clarity to the search indexing documentation for how `boost` works when using Postgres with the database search backend (Tibor Leupold)
* Updated `django-filter` version to support 22 (Yuekui)
* Use `.iterator()` in a few more places in the admin, to make it more stable on sites with many pages (Andy Babic)
* Migrate some simple React component files to TypeScript (LB (Ben) Johnston)
* Deprecate the usage and documentation of the `wagtail.contrib.modeladmin.menus.SubMenu` class, provide a warning if used directing developers to use `wagtail.admin.menu.Menu` instead (Matt Westcott)
* Replace human-readable-date hover pattern with accessible tooltip variant across all of admin (Bernd de Ridder)
* Added `WAGTAILADMIN_USER_PASSWORD_RESET_FORM` setting for overriding the admin password reset form (Michael Karamuth)
* Prefetch workflow states in edit page view to avoid queries in other parts of the view/templates that need it (Tidiane Dia)
* Remove the edit link from edit bird in previews to avoid confusion (Sævar Öfjörð Magnússon)
* Introduce new template fragment and block level enclosure tags for easier template composition (Thibaud Colas)
* Add a `classnames` template tag to easily build up classes from variables provided to a template (Paarth Agarwal)
* Clean up multiple eslint rules usage and configs to align better with the Wagtail coding guidelines (LB (Ben Johnston))
* Make `ModelAdmin` `InspectView` footer actions consistent with other parts of the UI (Thibaud Colas)
* Add support for Twitter and other text-only embeds in Draftail embed previews (Iman Syed, Paarth Agarwal)
* Use new modal dialog component for privacy settings modal (Sage Abdullah)
* Add `menu_item_name` to modify `MenuItem`’s name for `ModelAdmin` (Alexander Rogovskyy, Vu Pham)
* Add an extra confirmation prompt when deleting pages with a large number of child pages, see [WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT](../reference/settings.md#wagtailadmin-unsafe-page-deletion-limit) (Jaspreet Singh)
* Add shortcut for accessing StreamField blocks by block name with new [`blocks_by_name` and `first_block_by_name` methods on `StreamValue`](../topics/streamfield.md#streamfield-retrieving-blocks-by-name) (Tidiane Dia, Matt Westcott)
* Add HTML-aware max_length validation and character count on RichTextField and RichTextBlock (Matt Westcott, Thibaud Colas)
* Remove `is_parent` kwarg in various page button hooks as this approach is no longer required (Paarth Agarwal)
* Improve security of redirect imports by adding a file hash (signature) check for so that any tampering of file contents between requests will throw a `BadSignature` error (Jaap Roes)
* Allow generic chooser viewsets to support non-model data such as an API endpoint (Matt Westcott)
* Added `path` and `re_path` decorators to the `RoutablePageMixin` module which emulate their Django URL utils equivalent, redirect `re_path` to the original `route` decorator (Tidiane Dia)
* `BaseChooser` widget now provides a Telepath adapter that’s directly usable for any subclasses that use the chooser widget and modal JS as-is with no customizations (Matt Westcott)
* Introduce new template fragment and block level enclosure tags for easier template composition (Thibaud Colas)
* Implement the new chooser widget styles as part of the page editor redesign (Thibaud Colas)
* Update base Draftail/TextField form designs as part of the page editor redesign (Thibaud Colas)
* Move commenting trigger to inline toolbar and move block splitting to the block toolbar and command palette only in Draftail (Thibaud Colas)
* Pages are now locked when they are scheduled for publishing (Karl Hobley)
* Simplify page chooser views by converting to class-based views (Matt Westcott)
* Add “Translate” button within pages’ Actions dropdown when editing pages (Sage Abdullah)
* Add translated labels to the bulk actions tags and collections bulk update fields (Stefan Hammer)
* Add support for bulk actions, including [Adding bulk actions to the snippets listing](../extending/custom_bulk_actions.md#wagtailsnippets-custom-bulk-actions) (Shohan Dutta Roy)
### Bug fixes
* Fix issue where `ModelAdmin` index listings with export list enabled would show buttons with an incorrect layout (Josh Woodcock)
* Fix typo in `ResumeWorkflowActionFormatter` message (Stefan Hammer)
* Throw a meaningful error when saving an image to an unrecognized image format (Christian Franke)
* Remove extra padding for headers with breadcrumbs on mobile viewport (Steven Steinwand)
* Replace `PageRevision` with generic `Revision` model (Sage Abdullah)
* Ensure that custom document or image models support custom tag models (Matt Westcott)
* Ensure comments use translated values for their placeholder text (Stefan Hammer)
* Ensure the upgrade notification, shown to admins on the dashboard if Wagtail is out of date, content is translatable (LB (Ben) Johnston)
* Only show the re-ordering option to users that have permission to publish pages within the page listing (Stefan Hammer)
* Ensure default sidebar branding (bird logo) is not cropped in RTL mode (Steven Steinwand)
* Add an accessible label to the image focal point input when editing images (Lucie Le Frapper)
* Remove unused header search JavaScript on the redirects import page (LB (Ben) Johnston)
* Ensure non-square avatar images will correctly show throughout the admin (LB (Ben) Johnston)
* Ignore translations in test files and re-include some translations that were accidentally ignored (Stefan Hammer)
* Show alternative message when no page types are available to be created (Jaspreet Singh)
* Prevent error on sending notifications for the legacy moderation process when no user was specified (Yves Serrano)
* Ensure `aria-label` is not set on locale selection dropdown within page chooser modal as it was a duplicate of the button contents (LB (Ben Johnston))
* Revise the `ModelAdmin` title column behavior to only link to ‘edit’ if the user has the correct permissions, fallback to the ‘inspect’ view or a non-clickable title if needed (Stefan Hammer)
* Ensure that `DecimalBlock` preserves the `Decimal` type when retrieving from the database (Yves Serrano)
* When no snippets are added, ensure the snippet chooser modal has the correct URL for creating a new snippet (Matt Westcott)
* `ngettext` in Wagtail’s internal JavaScript internationalisation utilities now works (LB (Ben) Johnston)
* Ensure the linting/formatting npm scripts work on Windows (Anuja Verma)
* Fix display of dates in exported xlsx files on macOS Preview and Numbers (Jaap Roes)
* Remove outdated reference to 30-character limit on usernames in help text (minusf)
* Resolve multiple form submissions index listing page layout issues including title not being visible on mobile and interaction with large tables (Paarth Agarwal)
* Ensure `ModelAdmin` single selection lists show correctly with Django 4.0 form template changes (Coen van der Kamp)
* Ensure icons within help blocks have accessible contrasting colors, and links have a darker color plus underline to indicate they are links (Paarth Agarwal)
* Ensure consistent sidebar icon position whether expanded or collapsed (Scott Cranfill)
* Avoid redirects import error if the file had lots of columns (Jaap Roes)
* Resolve accessibility and styling issues with the expanding status panel (Sage Abdullah)
* Avoid 503 `AttributeError` when an empty search param `q=` is combined with other filters in the Images index view (Paritosh Kabra)
* Fix error with string representation of FormSubmission not returning a string (LB (Ben) Johnston)
* Ensure that bulk actions correctly support models with non-integer primary keys (id) (LB (Ben) Johnston)
* Make it possible to toggle collapsible panels in the edit UI with the keyboard (Thibaud Colas)
* Re-implement checkbox styles so the checked state is visible in forced colors mode (Thibaud Colas)
* Re-implement switch component styles so the checked state is visible in forced colors mode (Thibaud Colas)
* Always render select widgets consistently regardless of where they are in the admin (Thibaud Colas)
* Make sure input labels and always take up the available space (Thibaud Colas)
* Correctly style BooleanBlock within StructBlock (Thibaud Colas)
* Make sure comment icons can’t overlap with help text (Thibaud Colas)
* Make it possible to scroll input fields in admin on safari mobile (Thibaud Colas)
* Stop rich text fields from overlapping with sidebar (Thibaud Colas)
* Prevent comment buttons from overlapping with fields (Thibaud Colas)
* Resolve MySQL search compatibility issue with Django 4.1 (Andy Chosak)
* Resolve layout issues with reports (including form submissions listings) on md device widths (Akash Kumar Sen, LB (Ben) Johnston)
* Resolve Layout issue with page explorer’s inner header item on small device widths (Akash Kumar Sen)
* Ensure that `BaseSiteSetting` / `BaseGenericSetting` objects can be pickled (Andy Babic)
* Ensure `DocumentChooserBlock` can be deconstructed for migrations (Matt Westcott)
* Resolve frontend console error and unintended console logging issues (Matt Westcott, Paarth Agarwal)
* Resolve issue with sites that have not yet migrated away from `BaseSetting` when upgrading to Wagtail 4.0 (Stefan Hammer)
* Use correct classnames for showing/hiding edit button on chooser widget (Matt Westcott)
* Render MultiFieldPanel’s heading even when nested (Thibaud Colas)
* Make sure select widgets render correctly regardless of the Django field and widget type (Thibaud Colas)
* Consistently display boolean field labels above the widget so they render correctly (Thibaud Colas)
* Address form field label alignment issues by always displaying labels above the widget (Thibaud Colas)
* Make sure rich text URL editing tooltip is fully visible when displayed inside InlinePanel blocks (Thibaud Colas)
* Allow input fields to scroll horizontally in Safari iOS (Thibaud Colas)
* Ensure screen readers are made aware of page level messages added dynamically to the top of the page (Paarth Agarwal)
* Fix `updatemodulepaths` command for Python 3.7 (Matt Westcott)
* Only show locale filter in choosers when i18n is enabled in settings (Matt Westcott)
* Ensure that the live preview panel correctly clears the cache when a new page is created (Sage Abdullah)
* Ensure that there is a larger hoverable area for add block (+) within the Draftail editor (Steven Steinwand)
* Resolve multiple header styling issues for modal, alignment on small devices, outside click handling target on medium devices, close button target size and hover styles (Paarth Agarwal)
* Fix issue where comments could not be added in StreamField that were already saved (Jacob Topp-Mugglestone)
* Remove outdated reference to Image.LoaderError (Matt Westcott)
## Upgrade considerations
### Changes to `Page.serve()` and `Page.serve_preview()` methods
As part of making previews available to non-page models, the `serve_preview()` method has been decoupled from the `serve()` method and extracted into the `PreviewableMixin` class. If you have overridden the `serve()` method in your page models, you will likely need to override `serve_preview()`, `get_preview_template()`, and/or `get_preview_context()` methods to handle previews accordingly. Alternatively, you can also override the `preview_modes` property to return an empty list to disable previews.
### Opening links within the live preview panel
The live preview panel utilizes an iframe to display the preview in the editor page, which requires the page in the iframe to have the `X-Frame-Options` header set to `SAMEORIGIN` (or unset). If you click a link within the preview panel, you may notice that the iframe stops working. This is because the link is loaded within the iframe and the linked page may have the `X-Frame-Options` header set to `DENY`. To work around this problem, add the following ` ` tag within your `` element in your `base.html` template, before any ` ` elements:
```html+django
{% if request.in_preview_panel %}
{% endif %}
```
This will make all links in the live preview panel open in a new tab.
As of Wagtail 4.0.1, new Wagtail projects created through the `wagtail start` command already include this change in the base template.
### `base_url_path` keyword argument added to AdminURLHelper
The `wagtail.contrib.modeladmin.helpers.AdminURLHelper` class now accepts a `base_url_path` keyword argument on its constructor. Custom subclasses of this class should be updated to accept this keyword argument.
### Dropped support for Safari 13
Safari 13 will no longer be officially supported as of this release, this deviates the current support for the last 3 version of Safari by a few months and was required to add better support for RTL languages.
### `PageRevision` replaced with `Revision`
The `PageRevision` model has been replaced with a generic `Revision` model. If you use the `PageRevision` model in your code, make sure that:
* Creation of `PageRevision` objects should be updated to create `Revision` objects using the page’s `id` as the `object_id`, the default `Page` model’s content type as the `base_content_type`, and the page’s specific content type as the `content_type`.
* Queries that use the `PageRevision.objects` manager should be updated to use the `Revision.page_revisions` manager.
* `Revision` queries that use `Page.id` should be updated to cast the `Page.id` to a string before using it in the query (e.g. by using `str()` or `Cast("page_id", output_field=CharField())`).
* `Page` queries that use `PageRevision.page_id` should be updated to cast the `Revision.object_id` to an integer before using it in the query (e.g. by using `int()` or `Cast("object_id", output_field=IntegerField())`).
* Access to `PageRevision.page` should be updated to `Revision.content_object`.
If you maintain a package across multiple Wagtail versions that includes a model with a `ForeignKey` to the `PageRevision` model, you can create a helper function to correctly resolve the model depending on the installed Wagtail version, for example:
```python
from django.db import models
from wagtail import VERSION as WAGTAIL_VERSION
def get_revision_model():
if WAGTAIL_VERSION >= (4, 0):
return "wagtailcore.Revision"
return "wagtailcore.PageRevision"
class MyModel(models.Model):
# Before
# revision = models.ForeignKey("wagtailcore.PageRevision")
revision = models.ForeignKey(get_revision_model(), on_delete=models.CASCADE)
```
### `Page.get_latest_revision_as_page` renamed to `Page.get_latest_revision_as_object`
The `Page.get_latest_revision_as_page` method has been renamed to `Page.get_latest_revision_as_object`. The old name still exists for backwards-compatibility, but calling it will raise a `RemovedInWagtail50Warning`.
### `AdminChooser` replaced with `BaseChooser`
Custom choosers should no longer use `wagtail.admin.widgets.chooser.AdminChooser` which has been replaced with `wagtail.admin.widgets.chooser.BaseChooser`.
### `get_snippet_edit_handler` moved to `wagtail.admin.panels.get_edit_handler`
The `get_snippet_edit_handler` function in `wagtail.snippets.views.snippets` has been moved to `get_edit_handler` in `wagtail.admin.panels`.
### `explorer_breadcrumb` template tag has been renamed to `breadcrumbs`, `move_breadcrumb` has been removed
The `explorer_breadcrumb` template tag is not documented, however if used it will need to be renamed to `breadcrumbs` and the `url_name` is now a required arg.
The `move_breadcrumb` template tag is no longer used and has been removed.
### `wagtail.contrib.modeladmin.menus.SubMenu` is deprecated
The `wagtail.contrib.modeladmin.menus.SubMenu` class should no longer be used for constructing submenus of the admin sidebar menu. Instead, import `wagtail.admin.menu.Menu` and pass the list of menu items as the `items` keyword argument.
### Chooser widget JavaScript initializers replaced with classes
The internal JavaScript functions `createPageChooser`, `createSnippetChooser`, `createDocumentChooser` and `createImageChooser` used for initializing chooser widgets have been replaced by classes, and user code that calls them needs to be updated accordingly:
* `createPageChooser(id)` should be replaced with `new PageChooser(id)`
* `createSnippetChooser(id)` should be replaced with `new SnippetChooser(id)`
* `createDocumentChooser(id)` should be replaced with `new DocumentChooser(id)`
* `createImageChooser(id)` should be replaced with `new ImageChooser(id)`
### URL route names for image, document and snippet apps have changed
If your code contains references to URL route names within the `wagtailimages`, `wagtaildocs` or `wagtailsnippets` namespaces, these should be updated as follows:
* `wagtailimages:chooser` is now `wagtailimages_chooser:choose`
* `wagtailimages:chooser_results` is now `wagtailimages_chooser:choose_results`
* `wagtailimages:image_chosen` is now `wagtailimages_chooser:chosen`
* `wagtailimages:chooser_upload` is now `wagtailimages_chooser:create`
* `wagtailimages:chooser_select_format` is now `wagtailimages_chooser:select_format`
* `wagtaildocs:chooser` is now `wagtaildocs_chooser:choose`
* `wagtaildocs:chooser_results` is now `wagtaildocs_chooser:choose_results`
* `wagtaildocs:document_chosen` is now `wagtaildocs_chooser:chosen`
* `wagtaildocs:chooser_upload` is now `wagtaildocs_chooser:create`
* `wagtailsnippets:list`, `wagtailsnippets:list_results`, `wagtailsnippets:add`, `wagtailsnippets:edit`, `wagtailsnippets:delete-multiple`, `wagtailsnippets:delete`, `wagtailsnippets:usage`, `wagtailsnippets:history`: These now exist in a separate `wagtailsnippets_{app_label}_{model_name}` namespace for each snippet model, and no longer take `app_label` and `model_name` as arguments.
* `wagtailsnippets:choose`, `wagtailsnippets:choose_results`, `wagtailsnippets:chosen`: These now exist in a separate `wagtailsnippetchoosers_{app_label}_{model_name}` namespace for each snippet model, and no longer take `app_label` and `model_name` as arguments.
### Auto-updating preview
As part of the introduction of the new live preview panel, we have changed the `WAGTAIL_AUTO_UPDATE_PREVIEW` setting to be on (`True`) by default. This can still be turned off by setting it to `False`. The `WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL` setting has been introduced for sites willing to reduce the performance cost of the live preview without turning it off completely.
### Slim header on page listings
The page explorer listings now use Wagtail’s new slim header, replacing the previous large teal header. The parent page’s metadata and related actions are now available within the “Info” side panel, while the majority of buttons are now available under the Actions dropdown in the header, identically to the page create/edit forms.
Customizing which actions are available and adding extra actions is still possible, but has to be done with the [`register_page_header_buttons`](../reference/hooks.md#register-page-header-buttons) hook, rather than [`register_page_listing_buttons`](../reference/hooks.md#register-page-listing-buttons) and [`register_page_listing_more_buttons`](../reference/hooks.md#register-page-listing-more-buttons). Those hooks still work as-is to define actions for each page within the listings.
### `is_parent` removed from page button hooks
* The following hooks `construct_page_listing_buttons`, `register_page_listing_buttons`, `register_page_listing_more_buttons` no longer accept the `is_parent` keyword argument and this should be removed.
* `is_parent` was the previous approach for determining whether the buttons would show in the listing rows or the page’s more button, this can be now achieved with discrete hooks instead.
### Changed CSS variables for admin color themes
As part of our support for theming across all colors, we’ve had to rename or remove some of the pre-existing CSS variables. Wagtail’s indigo is now customizable with `--w-color-primary`, and the teal is customizable as `--w-color-secondary`. See [Custom user interface colors](../advanced_topics/customization/admin_templates.md#custom-user-interface-colors) for an overview of all customizable colors. Here are replaced variables:
- `--color-primary` is now `--w-color-secondary`
- `--color-primary-hue` is now `--w-color-secondary-hue`
- `--color-primary-saturation` is now `--w-color-secondary-saturation`
- `--color-primary-lightness` is now `--w-color-secondary-lightness`
- `--color-primary-darker` is now `--w-color-secondary-400`
- `--color-primary-darker-hue` is now `--w-color-secondary-400-hue`
- `--color-primary-darker-saturation` is now `--w-color-secondary-400-saturation`
- `--color-primary-darker-lightness` is now `--w-color-secondary-400-lightness`
- `--color-primary-dark` is now `--w-color-secondary-600`
- `--color-primary-dark-hue` is now `--w-color-secondary-600-hue`
- `--color-primary-dark-saturation` is now `--w-color-secondary-600-saturation`
- `--color-primary-dark-lightness` is now `--w-color-secondary-600-lightness`
- `--color-primary-lighter` is now `--w-color-secondary-100`
- `--color-primary-lighter-hue` is now `--w-color-secondary-100-hue`
- `--color-primary-lighter-saturation` is now `--w-color-secondary-100-saturation`
- `--color-primary-lighter-lightness` is now `--w-color-secondary-100-lightness`
- `--color-primary-light` is now `--w-color-secondary-50`
- `--color-primary-light-hue` is now `--w-color-secondary-50-hue`
- `--color-primary-light-saturation` is now `--w-color-secondary-50-saturation`
- `--color-primary-light-lightness` is now `--w-color-secondary-50-lightness`
We’ve additionally removed all `--color-input-focus` and `--color-input-focus-border` variables, as Wagtail’s form fields no longer have a different color on focus.
### `WAGTAILDOCS_DOCUMENT_FORM_BASE` and `WAGTAILIMAGES_IMAGE_FORM_BASE` must inherit from `BaseDocumentForm` / `BaseImageForm`
Previously, it was valid to specify an arbitrary model form as the `WAGTAILDOCS_DOCUMENT_FORM_BASE` / `WAGTAILIMAGES_IMAGE_FORM_BASE` settings. This is no longer supported; these forms must now inherit from `wagtail.documents.forms.BaseDocumentForm` and `wagtail.images.forms.BaseImageForm` respectively.
### Panel customizations
As part of the page editor redesign, we have removed support for the `classname="full"` customization to panels. Existing `title` and `collapsed` customizations remain unchanged.
### Optional replacement for regex only `route` decorator for `RoutablePageMixin`
- This is an optional replacement, there are no immediate plans to remove the `route` decorator at this time.
- The `RoutablePageMixin` contrib module now provides a `path` decorator that behaves the same way as Django’s [`django.urls.path()`](https://docs.djangoproject.com/en/stable/ref/urls/#django.urls.path) function.
- `RoutablePageMixin`’s `route` decorator will now redirect to a new `re_path` decorator that emulates the behavior of [`django.urls.re_path()`](https://docs.djangoproject.com/en/stable/ref/urls/#django.urls.re_path).
### `BaseSetting` model replaced by `BaseSiteSetting`
The `wagtail.contrib.settings.models.BaseSetting` model has been replaced by two new base models `BaseSiteSetting` and `BaseGenericSetting`, to accommodate settings that are shared across all sites. Existing setting models that inherit `BaseSetting` should be updated to use `BaseSiteSetting` instead:
```python
from wagtail.contrib.settings.models import BaseSetting, register_setting
@register_setting
class SiteSpecificSocialMediaSettings(BaseSetting):
facebook = models.URLField()
```
should become
```python
from wagtail.contrib.settings.models import BaseSiteSetting, register_setting
@register_setting
class SiteSpecificSocialMediaSettings(BaseSiteSetting):
facebook = models.URLField()
```
# 4.1.1.html.md
# Wagtail 4.1.1 release notes
*November 11, 2022*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix issue where lock/unlock buttons would not work on the Dashboard (home) page or the page index listing via the status sidebar (Stefan Hammer)
* Fix disabled style on StreamField add button (Matt Westcott)
* Ensure models are fully loaded before registering snippets, to avoid circular import issues (Matt Westcott)
* Prevent fields without a `verbose_name` property from breaking usage report views (Matt Westcott)
* Exclude tags from the reference index (Matt Westcott)
* Fix errors in handling generic foreign keys when populating the reference index (Matt Westcott)
* Prevent error in handling null ParentalKeys when populating the reference index (Matt Westcott)
* Make sure minimap error indicators follow the minimap scrolling (Thibaud Colas)
* Ensure background HTTP request to clear stale preview data correctly respects the `CSRF_HEADER_NAME` setting (Sage Abdullah)
* Prevent error on aging pages report when “Last published by” user has been deleted (Joshua Munn)
# 4.1.2.html.md
# Wagtail 4.1.2 release notes
*February 6, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Make “Cancel scheduled publish” button correctly redirect back to the edit view (Sage Abdullah)
* Prevent crash when reverting revisions on a snippet with `PreviewableMixin` applied (Sage Abdullah)
* Use consistent heading styles on top-level fields in the page editor (Sage Abdullah)
* Allow button labels to wrap onto two lines in dropdown buttons (Coen van der Kamp)
* Move DateField, DateTimeField, TimeField comment buttons to be right next to the fields (Theresa Okoro)
* Support text resizing in workflow steps cards (Ivy Jeptoo)
* Use the correct padding for autocomplete block picker (Umar Farouk Yunusa)
* Fix horizontal positioning of rich text inline toolbar (Thibaud Colas)
* Close the userbar when clicking its toggle (Albina Starykova)
* Do not show bulk actions checkbox in page type usage view (Sage Abdullah)
* Prevent account name from overflowing the sidebar (Aman Pandey)
* Ensure edit form is displayed as unlocked immediately after canceling a workflow (Sage Abdullah)
* Prevent `latest_revision` pointer from being copied over when copying translatable snippets for translation (Sage Abdullah)
### Documentation
* Document potential data loss for BaseLogEntry migration in 3.0 (Sage Abdullah)
* Add documentation for the reference index mechanism (Daniel Kirkham)
# 4.1.3.html.md
# Wagtail 4.1.3 release notes
*March 13, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Add right-to-left (RTL) support for the following form components: Switch, Minimap, live preview (Thibaud Colas)
* Improve right-to-left (RTL) positioning for the following components: Page explorer, Sidebar sub-menu, rich text tooltips, rich text toolbar trigger, editor section headers (Thibaud Colas)
* Ensure links within help blocks meet color contrast guidelines for accessibility (Theresa Okoro)
* Support creating `StructValue` copies (Tidiane Dia)
* Fix “Edit this page” missing from userbar (Satvik Vashisht)
* Prevent audit log report from failing on missing models (Andy Chosak)
* Add missing log information for `wagtail.schedule.cancel` (Stefan Hammer)
* Fix timezone activation leaking into subsequent requests in `require_admin_access()` (Stefan Hammer)
* Prevent matches from unrelated models from leaking into SQLite FTS searches (Matt Westcott)
* Update Algolia DocSearch to use new application and correct versioning setup (Thibaud Colas)
### Documentation
* Docs: Clarify `ClusterableModel` requirements for using relations with `RevisionMixin`-enabled models (Sage Abdullah)
# 4.1.4.html.md
# Wagtail 4.1.4 release notes
*April 3, 2023*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2023-28836: Stored XSS attack via ModelAdmin views
This release addresses a stored cross-site scripting (XSS) vulnerability on ModelAdmin views within the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin could potentially craft pages and documents that, when viewed by a user with higher privileges, could perform actions with that user’s credentials. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin, and only affects sites with ModelAdmin enabled.
Many thanks to Thibaud Colas for reporting this issue. For further details, please see [the CVE-2023-28836 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-5286-f2rf-35c2).
### CVE-2023-28837: Denial-of-service via memory exhaustion when uploading large files
This release addresses a memory exhaustion bug in Wagtail’s handling of uploaded images and documents. For both images and documents, files are loaded into memory during upload for additional processing. A user with access to upload images or documents through the Wagtail admin interface could upload a file so large that it results in a crash or denial of service.
The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin. It can only be exploited by admin users with permission to upload images or documents.
Many thanks to Jake Howard for reporting this issue. For further details, please see [the CVE-2023-28837 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-33pv-vcgh-jfg9).
### Bug fixes
* Fix radio and checkbox elements shrinking when using a long label (Sage Abdullah)
* Fix select elements expanding beyond their container when using a long option label (Sage Abdullah)
* Fix timezone handling of `TemplateResponse`s for users with a custom timezone (Stefan Hammer, Sage Abdullah)
* Ensure TableBlock initialization correctly runs after load and its width is aligned with the parent panel (Dan Braghis)
* Ensure that the JavaScript media files are loaded by default in Snippet index listings for date fields (Sage Abdullah)
* Fix server-side caching of the icons sprite (Thibaud Colas)
* Always show Add buttons, guide lines, Move up/down, Duplicate, Delete; in StreamField and Inline Panel (Thibaud Colas)
* Ensure datetimepicker widget overlay shows over modals & drop-downs (LB (Ben) Johnston)
### Maintenance
* Render large image renditions to disk (Jake Howard)
# 4.1.5.html.md
# Wagtail 4.1.5 release notes
*May 2, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent TableBlock from becoming uneditable after save (Sage Abdullah)
# 4.1.6.html.md
# Wagtail 4.1.6 release notes
*May 25, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Rectify previous fix for TableBlock becoming uneditable after save (Sage Abdullah)
* Ensure that copying page correctly picks up the latest revision (Matt Westcott)
* Adjust collection field alignment in multi-upload forms (LB (Ben) Johnston)
* Prevent lowercase conversions of IndexView column headers (Virag Jain)
### Documentation
* Update documentation for `log_action` parameter on `RevisionMixin.save_revision` (Christer Jensen)
# 4.1.7.html.md
# Wagtail 4.1.7 release notes
*September 27, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Maintenance
* Relax Willow dependency to allow use of current Pillow versions with security fixes (Dan Braghis)
# 4.1.8.html.md
# Wagtail 4.1.8 release notes
*September 28, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Maintenance
* Additionally update Pillow dependency to allow use of versions with security fixes (Dan Braghis)
# 4.1.9.html.md
# Wagtail 4.1.9 release notes
*October 19, 2023*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2023-45809: Disclosure of user names via admin bulk action views
This release addresses an information disclosure vulnerability in the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin can make a direct URL request to the admin view that handles bulk actions on user accounts. While authentication rules prevent the user from making any changes, the error message discloses the display names of user accounts, and by modifying URL parameters, the user can retrieve the display name for any user. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to quyenheu for reporting this issue. For further details, please see [the CVE-2023-45809 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-fc75-58r8-rm3h).
# 4.1.html.md
# Wagtail 4.1 (LTS) release notes
*November 1, 2022*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
Wagtail 4.1 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### “What’s New” dashboard banner and “Help” menu
To help with onboarding new users, Wagtail now displays a banner on the dashboard, pointing users to our Editor Guide. The sidebar also contains a new “Help” menu item with a prominent indicator to call attention to the new content: a “What’s new” page showcasing new features, and a link to the Editor Guide.
Users can dismiss the new banner and the sidebar items’ indicators by interacting with the corresponding UI element. We store the state in the user’s profile so we only call attention to the content once.
To turn off the new banner, set [`WAGTAIL_ENABLE_WHATS_NEW_BANNER`](../reference/settings.md#wagtail-enable-whats-new-banner) to `False` in your settings. The new menu items can be removed and customized with the following hooks:
- [`register_help_menu_item`](../reference/hooks.md#register-help-menu-item) – to add new items to the “Help” menu
- [`construct_help_menu`](../reference/hooks.md#construct-help-menu) – to change or remove existing items from the “Help” menu
- [`construct_main_menu`](../reference/hooks.md#construct-main-menu) – to remove the new “Help” menu altogether
### Page editor minimap
When navigating long page editing interfaces, it can be tedious to pinpoint a specific section, or find all validation errors. To help with those tasks, the page editor now has a new “minimap” side panel, with a table of contents of all sections on the form. Clicking a section’s label scrolls straight to this part of the page, and an indicator bar represents which sections are currently visible on the page. When validation errors are present, each section shows a count of the number of errors. This feature was implemented by Thibaud Colas based on a prototype by LB (Ben) Johnston.
### New UI for scheduled publishing
Scheduled publishing settings can now be found within the Status side panel of the page editing view. This change aims to improve clarity over who can set schedules, and when they take effect. This feature was developed by Sage Abdullah.
### Customizable snippet admin views
The `register_snippet` function now accepts a `SnippetViewSet` class, allowing various aspects of the snippet’s admin views to be customized. See [Customizing admin views for snippets](../topics/snippets/customizing.md#wagtailsnippets-custom-admin-views). This feature was developed by Sage Abdullah.
### Scheduled publishing for snippets
Snippet models that inherit from `DraftStateMixin` can now be assigned go-live and expiry dates. This feature was developed by Sage Abdullah.
### Object usage reporting
Images, documents and snippets now provide a usage report, listing the places where references to those objects appear. This report is powered by a new `ReferenceIndex` model which records cross-references between objects whenever those objects to save; this allows it to work more efficiently than the old report available through the `WAGTAIL_USAGE_COUNT_ENABLED` setting, as well as handling references within StreamField and rich text fields.
Note that on first upgrading to Wagtail 4.1, you will need to run the `rebuild_references_index` management command to populate the references table and ensure that reference counts are displayed accurately. By default, references are tracked for all models in the project, including ones not managed through Wagtail - to disable this for specific models, see [Manage the reference index](../advanced_topics/reference_index.md#managing-the-reference-index).
This feature was developed by Karl Hobley and Matt Westcott.
### Documentation improvements
There are multiple improvements to the documentation theme this release, here are some highlights.
* Code snippets now have a quick copy to clipboard button (Mohammad Areeb)
* Improve the dark mode theme adoption, avoid flashing the wrong theme on first load, reduce the need for scrollbars in page TOC, link underline fixes in Safari (LB (Ben) Johnston, Kartik Kankurte)
* Better accessibility support with a skip to content link (LB (Ben) Johnston)
### Other features
* Formalised support for Python 3.11 (Matt Westcott)
* Add basic keyboard control and screen reader support for page listing re-ordering (Paarth Agarwal, Thomas van der Hoeven)
* Add `PageQuerySet.private` method as an alias of `not_public` (Mehrdad Moradizadeh)
* Most images in the admin will now only load once they are visible on screen (Jake Howard)
* Allow setting default attributes on image tags [Adding default attributes to all images](../topics/images.md#adding-default-attributes-to-images) (Jake Howard)
* Optimise the performance of the Wagtail userbar to remove duplicated queries, improving page loads when viewing live pages while signed in (Jake Howard)
* Remove legacy styling classes for buttons and refactor button styles to be more maintainable (Paarth Agarwal, LB (Ben Johnston))
* Add button variations to the pattern library (Paarth Agarwal)
* Provide a more accessible page title where the unique information is shown first and the CMS name is shown last (Mehrdad Moradizadeh)
* Pull out behavior from `AbstractFormField` to `FormMixin` and `AbstractEmailForm` to `EmailFormMixin` to allow use with subclasses of `Page` [Using FormMixin or EmailFormMixin to use with other Page subclasses](../reference/contrib/forms/customization.md#form-builder-mixins) (Mehrdad Moradizadeh, Kurt Wall)
* Add a `docs.wagtail.org/.well-known/security.txt` so that the security policy is available as per the specification on [https://securitytxt.org/](https://securitytxt.org/) (Jake Howard)
* Add unit tests for the `classnames` Wagtail admin template tag (Mehrdad Moradizadeh)
* Show an inverse locked indicator when the page has been locked by the current user in reports and dashboard listings (Vaibhav Shukla, LB (Ben Johnston))
* Add clarity to the development documentation that `admonition` should not be used and titles for `note` are not supported, including clean up of some existing incorrect usage (LB (Ben Johnston))
* Unify the styling of delete/destructive button styles across the admin interface (Paarth Agarwal)
* Adopt new designs and unify the styling styles for `.button-secondary` buttons across the admin interface (Paarth Agarwal)
* Refine designs for disabled buttons throughout the admin interface (Paarth Agarwal)
* Update expanding formset add buttons to use `button` not link for behaviour and remove support for disabled as a class (LB (Ben) Johnston)
* Add robust unit testing for authentication scenarios across the user management admin pages (Mehrdad Moradizadeh)
* Avoid assuming an integer PK named ‘id’ on multiple upload views (Matt Westcott)
* Add a toggle to collapse/expand all page panels at once (Helen Chapman)
* Improve the GitHub Workflows (CI) security (Alex (sashashura))
* Use `search` type input in documentation search (LB (Ben) Johnston)
* Render `help_text` when set on `FieldPanel`, `MultiFieldPanel`, `FieldRowPanel`, and other panel APIs where it previously worked without official support (Matt Westcott)
* Consolidate usage of Excel libraries to a single library `openpyxl`, removing usage of `XlsxWriter`, `tablib`, `xlrd` and `xlwt` (Jaap Roes)
* Adopt generic class based views for the create User create view, User edit view, user delete view and Users index listing / search results (Mehrdad Moradizadeh)
* Add `button-secondary bicolor` variants to the pattern library and styleguide (Adinapunyo Banerjee)
* Add better support for non-integer / non-`id` primary keys into Wagtail’s generic views, including for custom Snippets and User models (Mehrdad Moradizadeh)
* Upgrade jQuery UI to version 1.13.2 (LB (Ben) Johnston)
* Update pattern library background & text examples (Albina Starykova)
* Switch StreamField blocks to use a `` element so screen reader users can bypass them more easily (Thibaud Colas)
* Add anchor links to StreamField blocks so users can navigate straight to a given block (Thibaud Colas)
* Support “Ctrl + f” in-page search within collapsed StreamField blocks (Thibaud Colas)
* Remember the last opened side panel in the page editor, activating it on page load (Sage Abdullah)
* Ensure that the [`update_index`](../reference/management_commands.md#update-index) command can run without console output if called with `--verbosity 0` (Ben Sturmfels, Oliver Parker)
* Improve side panels’ resizing in page editor and listings (Steven Steinwand)
* Adjust breadcrumb text alignment and size in page listings & page editor (Steven Steinwand)
* Improvements to getting started tutorial aimed at developers who are very new to Python and have no Django experience (Damilola Oladele)
* The `image_url` template tag, when using the serve view to redirect rather than serve directly, will now use temporary redirects with a cache header instead of permanent redirects (Jake Howard)
* Add new test assertions to `WagtailPageTestCase` - `assertPageIsRoutable`, `assertPageIsRenderable`, `assertPageIsEditable`, `assertPageIsPreviewable` (Andy Babic)
* Add documentation to the performance section about how to better create image URLs when not used directly on the page (Jake Howard)
* Add ability to provide a required `permission` to `PanelGroup`, used by `TabbedInterface`, `ObjectList`, `FieldRowPanel` and `MultiFieldPanel` (Oliver Parker)
* Update documentation screenshots of the admin interface to align with changes in this release (Thibaud Colas)
### Bug fixes
* Prevent `PageQuerySet.not_public` from returning all pages when no page restrictions exist (Mehrdad Moradizadeh)
* Ensure that duplicate block ids are unique when duplicating stream blocks in the page editor (Joshua Munn)
* Revise color usage so that privacy & locked indicators can be seen in Windows High Contrast mode (LB (Ben Johnston))
* Ensure that disabled buttons have a consistent presentation on hover to indicate no interaction is available (Paarth Agarwal)
* Update the ‘Locked pages’ report menu title so that it is consistent with other pages reports and its own title on viewing (Nicholas Johnson)
* Support `formfield_callback` handling on `ModelForm.Meta` for future Django 4.2 release (Matt Westcott)
* Ensure that `ModelAdmin` correctly supports filters in combination with subsequent searches without clearing the applied filters (Stefan Hammer)
* Add missing translated values to site settings’ headers plus models presented in listings and audit report filtering labels (Stefan Hammer)
* Remove `capitalize()` calls to avoid issues with other languages or incorrectly presented model names for reporting and parts of site settings (Stefan Hammer)
* Add back rendering of `help_text` for InlinePanel (Matt Westcott)
* Ensure `for_user` argument is passed to the form class when previewing pages (Matt Westcott)
* Ensure the capitalization of the `timesince_simple` tag is consistently added in the template based on usage in context (Stefan Hammer)
* Add missing translation usage for the `timesince_last_update` and ensure the translated labels can be easier to work with in Transifex (Stefan Hammer)
* Add additional checks for duplicate form field `clean_name` values in the Form Builder validation and increase performance of checks (Dan Bentley)
* Use correct color for labels of radio and checkbox fields (Steven Steinwand)
* Adjust spacing of fields’ error messages and position in tables (Steven Steinwand)
* Update dead or redirected links throughout the documentation (LB (Ben) Johnston)
* Use different icons for workflow timeline component, so the steps can be distinguished with other means than color (Sam Moran)
* Use the correct custom font for the Wagtail userbar (Umar Farouk Yunusa)
* StreamField blocks are now collapsible with the keyboard (Thibaud Colas)
* StreamField block headings now have a label for screen reader users (Thibaud Colas)
* Display the “\*” required field indicator for StreamField blocks (Thibaud Colas)
* Resolve inconsistency in action button positions in InlinePanel (Thibaud Colas)
* Use h3 elements with a counter in InlinePanel so screen reader users can navigate by heading (Thibaud Colas)
* Ensure that buttons on custom chooser widgets are correctly shown on hover (Thibaud Colas)
* Add missing asterisk to title field placeholder (Seremba Patrick, Stefan Hammer)
* Avoid creating an extra rich text block when inserting a new block at the end of the content (Matt Westcott)
* Removed the extra dot in the Wagtail version shown within the admin settings menu item (Loveth Omokaro)
* Fully remove the obsolete `wagtailsearch_editorspick` table that prevents flushing the database (Matt Westcott)
* Update latest version message on Dashboard to accept dev build version format used on nlightly builds (Sam Moran)
* Ensure `ChooserBlock.extract_references` uses the model class, not the model string (Alex Tomkins)
* Regression in field width for authentication pages (log in / password reset) (Chisom Okeoma)
* Ensure the new minimap correctly pluralizes error counts for `aria-label`s (Matt Westcott)
## Upgrade considerations
### `rebuild_references_index` management command
After upgrading, you will need to run `./manage.py rebuild_references_index` to populate the references table and ensure that usage counts for images, documents and snippets are displayed accurately. By default, references are tracked for all models in the project, including ones not managed through Wagtail - to disable this for specific models, see [Manage the reference index](../advanced_topics/reference_index.md#managing-the-reference-index).
### Recommend `WagtailPageTestCase` in place of `WagtailPageTests`
* `WagtailPageTestCase` is the base testing class and is now recommended over using `WagtailPageTests` [Testing your Wagtail site](../advanced_topics/testing.md#testing-reference).
* `WagtailPageTests` will continue to work and does log in the user on test `setUp` but may be deprecated in the future.
```python
# class MyPageTests(WagtailPageTests): # old
class MyPageTests(WagtailPageTestCase): # new
def setUp(self):
# WagtailPageTestCase will not log in during setUp - so add if needed
super().setUp()
self.login()
def test_can_create_a_page(self):
# ...
```
### Button styling class changes
The `button-secondary` class is no longer compatible with either the `.serious` or `.no` classes, this partially worked previously but is no longer officially supported.
When adding custom buttons using the `ModelAdmin` `ButtonHelper` class, custom buttons will no longer include the `button-secondary` class by default in index listings.
If using the hook `register_user_listing_buttons` to register buttons along with the undocumented `UserListingButton` class, the `button-secondary` class will no longer be included by default.
Avoid using `disabled` as a class on `button` elements, instead use the [`disabled` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled) as support for this as a class may be removed in a future version of Wagtail and is not accessible.
If using custom `expanding-formset` the add button will no longer support the `disabled` class but instead must require the `disabled` attribute to be set.
The following button classes have been removed, none of which were being used within the admin but may have been used by custom code or packages:
* `button-neutral`
* `button-strokeonhover`
* `hover-no`
* `unbutton`
* `yes`
### Dropped support for importing `.xls` Spreadsheet files into Redirects
* `.xls` legacy Microsoft Excel 97-2003 spreadsheets will no longer be supported for importing into the contrib Redirects listing.
* `.xlsx`, `.csv`, `.tsv` formats are still supported.
# 4.2.1.html.md
# Wagtail 4.2.1 release notes
*March 13, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Support creating `StructValue` copies (Tidiane Dia)
* Fix image uploads on storage backends that require file pointer to be at the start of the file (Matt Westcott)
* Fix “Edit this page” missing from userbar (Satvik Vashisht)
* Prevent audit log report from failing on missing models (Andy Chosak)
* Fix page/snippet cannot proceed a `GroupApprovalTask` if it’s locked by someone outside of the group (Sage Abdullah)
* Add missing log information for `wagtail.schedule.cancel` (Stefan Hammer)
* Fix timezone activation leaking into subsequent requests in `require_admin_access()` (Stefan Hammer)
* Fix dialog component’s message to have rounded corners at the top side (Sam)
* Prevent matches from unrelated models from leaking into SQLite FTS searches (Matt Westcott)
* Prevent duplicate addition of StreamField blocks with the new block picker (Deepam Priyadarshi)
* Update Algolia DocSearch to use new application and correct versioning setup (Thibaud Colas)
### Documentation
* Docs: Clarify `ClusterableModel` requirements for using relations with `RevisionMixin`-enabled models (Sage Abdullah)
# 4.2.2.html.md
# Wagtail 4.2.2 release notes
*April 3, 2023*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2023-28836: Stored XSS attack via ModelAdmin views
This release addresses a stored cross-site scripting (XSS) vulnerability on ModelAdmin views within the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin could potentially craft pages and documents that, when viewed by a user with higher privileges, could perform actions with that user’s credentials. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin, and only affects sites with ModelAdmin enabled.
Many thanks to Thibaud Colas for reporting this issue. For further details, please see [the CVE-2023-28836 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-5286-f2rf-35c2).
### CVE-2023-28837: Denial-of-service via memory exhaustion when uploading large files
This release addresses a memory exhaustion bug in Wagtail’s handling of uploaded images and documents. For both images and documents, files are loaded into memory during upload for additional processing. A user with access to upload images or documents through the Wagtail admin interface could upload a file so large that it results in a crash or denial of service.
The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin. It can only be exploited by admin users with permission to upload images or documents.
Many thanks to Jake Howard for reporting this issue. For further details, please see [the CVE-2023-28837 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-33pv-vcgh-jfg9).
### Bug fixes
* Fix radio and checkbox elements shrinking when using a long label (Sage Abdullah)
* Fix select elements expanding beyond their container when using a long option label (Sage Abdullah)
* Fix timezone handling of `TemplateResponse`s for users with a custom timezone (Stefan Hammer, Sage Abdullah)
* Ensure TableBlock initialization correctly runs after load and its width is aligned with the parent panel (Dan Braghis)
* Ensure that the JavaScript media files are loaded by default in Snippet index listings for date fields (Sage Abdullah)
* Fix server-side caching of the icons sprite (Thibaud Colas)
* Avoid showing scrollbars in the block picker unless necessary (Babitha Kumari)
* Always show Add buttons, guide lines, Move up/down, Duplicate, Delete; in StreamField and Inline Panel (Thibaud Colas)
* Ensure datetimepicker widget overlay shows over modals & drop-downs (LB (Ben) Johnston)
### Documentation
* Fix module path for `MultipleChooserPanel` in panel reference docs
### Maintenance
* Render large image renditions to disk (Jake Howard)
# 4.2.3.html.md
# Wagtail 4.2.3 release notes
*May 2, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent TableBlock from becoming uneditable after save (Sage Abdullah)
# 4.2.4.html.md
# Wagtail 4.2.4 release notes
*May 25, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Rectify previous fix for TableBlock becoming uneditable after save (Sage Abdullah)
* Ensure that copying page correctly picks up the latest revision (Matt Westcott)
* Adjust collection field alignment in multi-upload forms (LB (Ben) Johnston)
* Prevent lowercase conversions of IndexView column headers (Virag Jain)
### Documentation
* Update documentation for `log_action` parameter on `RevisionMixin.save_revision` (Christer Jensen)
# 4.2.html.md
# Wagtail 4.2 release notes
*February 6, 2023*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### StreamField data migration helpers
Wagtail now provides a set of utilities for creating data migrations on StreamField data. For more information, see [StreamField data migrations](../advanced_topics/streamfield_migrations.md#streamfield-data-migrations). This feature was developed by Sandil Ranasinghe, initially as the [wagtail-streamfield-migration-toolkit](https://github.com/wagtail/wagtail-streamfield-migration-toolkit) add-on package, as part of the Google Summer of Code 2022 initiative, with support from Jacob Topp-Mugglestone, Joshua Munn and Karl Hobley.
### Locking for snippets
Snippets can now be locked by users to prevent other users from editing, through the use of the `LockableMixin`. For more details, see [Locking snippets](../topics/snippets/features.md#wagtailsnippets-locking-snippets).
This feature was developed by Sage Abdullah.
### Workflows for snippets
Snippets can now be assigned to workflows through the use of the `WorkflowMixin`, allowing new changes to be submitted for moderation before they are published. For more details, see [Enabling workflows for snippets](../topics/snippets/features.md#wagtailsnippets-enabling-workflows).
This feature was developed by Sage Abdullah.
### `fullpageurl` template tag
Wagtail now provides a `fullpageurl` template tag (for both Django templates and Jinja2) to output a page’s full URL including the domain. For more details, see [fullpageurl](../topics/writing_templates.md#fullpageurl-tag).
This feature was developed by Jake Howard.
### CSP compatibility & Stimulus adoption
Wagtail now uses the Stimulus framework for client-side interactivity (see [RFC 78](https://github.com/wagtail/rfcs/pull/78)). Our [Outreachy contributor](https://wagtail.org/blog/our-very-first-outreachy-interns/) Loveth Omokaro has refactored significant portions of the admin interface:
* The Skip Link component displayed on all pages.
* The dashboard’s Upgrade notification message.
* Auto-submitting of listing filters.
* Loading of Wagtail’s icon sprite
* Page lock/unlock actions
* Workflow enable actions
Those changes improve the maintainability of the code, and help us move towards compatibility with strict CSP (Content Security Policy) rules. Thank you to Loveth and project mentors LB (Ben) Johnston, Thibaud Colas, and Paarth Agarwal.
### Accessibility checker integration
The CMS now includes an accessibility checker in the [user bar](../topics/writing_templates.md#wagtailuserbar-tag), to assist users in building more accessible websites and follow [ATAG 2.0 guidelines](https://www.w3.org/TR/ATAG20/). The checker, which is based on the Axe testing engine, is designed for content authors to identify and fix accessibility issues on their own. It scans the loaded page for errors and displays the results, with three rules turned on in this release. It’s configurable with the [`construct_wagtail_userbar`](../reference/hooks.md#construct-wagtail-userbar) hook.
This new feature was implemented by Albina Starykova as part of an [Outreachy internship](https://wagtail.org/blog/our-very-first-outreachy-interns/), with support from mentors Thibaud Colas, Sage Abdullah, and Joshua Munn.
### Rich text improvements
Following feedback from Wagtail users on [rich text UI improvements in Wagtail 4.0](4.0.md#rich-text-improvements-4), we have further refined the behavior of rich text fields to cater for different scenarios:
* Users can now choose between an “inline” floating toolbar, and a fixed toolbar at the top of the editor. Both toolbars display all formatting options.
* The ‘/’ command palette and block picker in rich text fields now contain all formatting options except text styles.
* The ‘/’ command palette and block picker are now always available no matter where the cursor is placed, to support inserting content at any point within text, transforming existing content, and splitting StreamField blocks in the middle of a paragraph when needed.
* The block picker interface now displays two columns so more options are visible without scrolling.
Thank you to all who provided feedback, participants to our usability testing sessions, and to Nick Lee and Thibaud Colas for the implementation.
### Multiple chooser panel
A new panel type [MultipleChooserPanel](../reference/panels.md#multiple-chooser-panel) is available. This is a variant of `InlinePanel` which improves the editor experience when adding large numbers of linked items - rather than creating and populating each sub-form individually, a chooser modal is opened allowing multiple objects to be selected at once.
This feature was developed by Matt Westcott, and sponsored by [YouGov](https://yougov.com/).
### Other features
* Test assertion [`WagtailPageTestCase.assertCanCreate`](../advanced_topics/testing.md#testing-reference) now supports the kwarg `publish=True` to determine whether to publish the page (Harry Percival, Akua Dokua Asiedu, Matt Westcott)
* Ensure that the `rebuild_references_index` command can run without console output if called with `--verbosity 0` (Omerzahid Ali, Aman Pandey)
* Add full support for secondary buttons with icons in the Wagtail design system - `button bicolor button--icon button-secondary` including the `button-small` variant (Seremba Patrick)
* Add [`purge_embeds`](../reference/management_commands.md#purge-embeds) management command to delete all the cached embed objects in the database (Aman Pandey)
* Make it possible to resize the page editor’s side panels (Sage Abdullah)
* Add ability to include [`form_fields` as an APIField](../advanced_topics/api/v2/configuration.md#form-page-fields-api-field) on `FormPage` (Sævar Öfjörð Magnússon, Suyash Singh, LB (Ben) Johnston)
* Ensure that images listings are more consistently aligned when there are fewer images uploaded (Theresa Okoro)
* Add more informative validation error messages for non-unique slugs within the admin interface and for programmatic page creation (Benjamin Bach)
* Always show the page editor title field’s border when the field is empty (Thibaud Colas)
* Snippet models extending `DraftStateMixin` now automatically define a “Publish” permission type (Sage Abdullah)
* Users now remain on the edit page after saving a snippet as draft (Sage Abdullah)
* Base project template now populates the meta description tag from the search description field (Aman Pandey)
* Added support for `azure-mgmt-cdn` version >= 10 and `azure-mgmt-frontdoor` version >= 1 in the frontend cache invalidator (Sylvain Fankhauser)
* Add a system check to warn when a `django-storages` backend is configured to allow overwriting (Rishabh Jain)
* Update admin focus outline color to have higher contrast against white backgrounds (Thibaud Colas)
* Implement latest design for the admin dashboard header (Thibaud Colas, Steven Steinwand)
* Restyle the userbar to follow the visual design of the Wagtail admin (Albina Starykova)
* Adjust the size of panel labels on the “Account” form (Thibaud Colas)
* Delay hiding the contents of the side panels when closing, so the animation is smoother (Thibaud Colas)
* ListBlock now shows item-by-item differences when comparing versions (Tidiane Dia)
* Switch StreamField blocks to use the same picker interface as within rich text fields (Thibaud Colas)
### Bug fixes
* Make sure workflow timeline icons are visible in high-contrast mode (Loveth Omokaro)
* Ensure authentication forms (login, password reset) have a visible border in Windows high-contrast mode (Loveth Omokaro)
* Ensure visual consistency between buttons and links as buttons in Windows high-contrast mode (Albina Starykova)
* Ensure `ChooserBlock.extract_references` uses the model class, not the model string (Alex Tomkins)
* Incorrectly formatted link in the documentation for Wagtail community support (Bolarinwa Comfort Ajayi)
* Ensure logo shows correctly on log in page in Windows high-contrast mode (Loveth Omokaro)
* Comments notice background overflows its container (Yekasumah)
* Ensure links within help blocks meet color contrast guidelines for accessibility (Theresa Okoro)
* Ensure the skip link (used for keyboard control) meets color contrast guidelines for accessibility (Dauda Yusuf)
* Ensure tag fields correctly show in both dark and light Windows high-contrast modes (Albina Starykova)
* Ensure new tooltips & tooltip menus have visible borders and tip triangle in Windows high-contrast mode (Juliet Adeboye)
* Ensure there is a visual difference of ‘active/current link’ vs normal links in Windows high-contrast mode (Mohammad Areeb)
* Avoid issues where trailing whitespace could be accidentally removed in translations for new page & snippet headers (Florian Vogt)
* Make sure minimap error indicators follow the minimap scrolling (Thibaud Colas)
* Remove the ability to view or add comments to `InlinePanel` inner fields to avoid lost or incorrectly linked comments (Jacob Topp-Mugglestone)
* Use consistent heading styles on top-level fields in the page editor (Sage Abdullah)
* Allow button labels to wrap onto two lines in dropdown buttons (Coen van der Kamp)
* Remove spurious horizontal resize handle from text areas (Matt Westcott)
* Move DateField, DateTimeField, TimeField comment buttons to be right next to the fields (Theresa Okoro)
* Support text resizing in workflow steps cards (Ivy Jeptoo)
* Ignore images added via fixtures when using `WAGTAILIMAGES_FEATURE_DETECTION_ENABLED` to avoid errors for images that do not exist (Aman Pandey)
* Restore ability to perform JSONField query operations against StreamField when running against the Django 4.2 development branch (Sage Abdullah)
* Ensure there is correct grammar and pluralization for Tab error counts shown to screen readers (Aman Pandey)
* Pass through expected `cc`, `bcc` and `reply_to` to the Django mail helper from `wagtail.admin.mail.send_mail` (Ben Gosney)
* Allow reviewing or reverting to a Page’s initial revision (Andy Chosak)
* Use the correct padding for autocomplete block picker (Umar Farouk Yunusa)
* Ensure that short content pages (such as editing snippets) do not show an inconsistent background (Sage Abdullah)
* Fix horizontal positioning of rich text inline toolbar (Thibaud Colas)
* Ensure that `DecimalBlock` correctly handles `None`, when `required=False`, values (Natarajan Balaji)
* Close the userbar when clicking its toggle (Albina Starykova)
* Add a border around the userbar menu in Windows high-contrast mode so it can be identified (Albina Starykova)
* Make sure browser font resizing applies to the userbar (Albina Starykova)
* Fix check for `delete_url_name` attribute in generic `DeleteView` (Alex Simpson)
* Re-implement design system colors so HSL values exactly match the desired RGB (Albina Starykova)
* Resolve issue where workflow and other notification emails would not include the correct tab URL for account notification management (LB (Ben) Johnston)
* Use consistent spacing above and below page headers (Thibaud Colas)
* Use the correct icon sizes and spacing in slim header (Thibaud Colas)
* Use the correct color for placeholders in rich text fields (Thibaud Colas)
* Prevent obstructing the outline around rich text fields (Thibaud Colas)
* Page editor dropdowns now use indigo backgrounds like elsewhere in the admin interface (Thibaud Colas)
* Allow parsing of multiple key/value pairs from string in `wagtail.search.utils.parse_query_string` (Beniamin Bucur)
* Prevent memory exhaustion when purging a large number of revisions (Jake Howard)
* Add right-to-left (RTL) support for the following form components: Switch, Minimap, live preview (Thibaud Colas)
* Improve right-to-left (RTL) positioning for the following components: Page explorer, Sidebar sub-menu, rich text tooltips, rich text toolbar trigger, editor section headers (Thibaud Colas)
* Center-align StreamField and rich text block picker buttons with the dotted guide line (Thibaud Colas)
* Search bar in chooser modals now performs autocomplete searches under PostgreSQL (Matt Westcott)
* Server-side document filenames are preserved when replacing a document file (Suyash Singh, Matt Westcott)
* Do not show bulk actions checkbox in page type usage view (Sage Abdullah)
* Prevent account name from overflowing the sidebar (Aman Pandey)
* Ensure edit form is displayed as unlocked immediately after canceling a workflow (Sage Abdullah)
* Prevent `latest_revision` pointer from being copied over when copying translatable snippets for translation (Sage Abdullah)
### Documentation
* Wagtail’s documentation (v2.9 to v4.0) has been updated on [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions) for [Dash](https://kapeli.com/dash) or [Zeal](https://zealdocs.org/) offline docs applications (Damilola Oladele, Mary Ayobami, Elizabeth Bassey)
* Wagtail’s documentation (v2 to v4.0) has been added to [DevDocs](https://devdocs.io/wagtail/) which has offline support and is easily accessible in any browser (Vallabh Tiwari)
* Add custom permissions section to permissions documentation page (Dan Hayden)
* Add documentation for how to get started with [contributing translations](../contributing/translations.md#contributing-translations) for the Wagtail admin (Ogunbanjo Oluwadamilare)
* Officially recommend `fnm` over `nvm` in development documentation (LB (Ben) Johnston)
* Mention the importance of passing `request` and `current_site` to `get_url` on the [performance](../advanced_topics/performance.md#performance-overview) documentation page (Jake Howard)
* Add documentation for [`register_user_listing_buttons`](../reference/hooks.md#register-user-listing-buttons) hook (LB (Ben Johnston))
* Add development (contributing to Wagtail) documentation notes for [development on Windows](../contributing/developing.md#development-on-windows) (Akua Dokua Asiedu)
* Mention Wagtail’s usage of Django’s default user model by default (Temidayo Azeez)
* Add links to treebeard documentation for relevant methods (Temidayo Azeez)
* Add clarification on where to register entity plugins (Mark McOsker)
* Fix logo in README not being visible in high-contrast mode (Benita Anawonah)
* Improve ‘first wagtail site’ tutorial (Akua Dokua Asiedu)
* Grammatical adjustments of `page models` usage guide (Damilola Oladele)
* Add class inheritance information to StreamField block reference (Temidayo Azeez)
* Document the hook [`register_image_operations`](../reference/hooks.md#register-image-operations) and add an example of a [custom Image filter](../extending/custom_image_filters.md#custom-image-filters) (Coen van der Kamp)
* Fix incorrect example code for StreamField migration of `RichTextField` (Matt Westcott)
* Document the policy needed to create invalidations in CloudFront (Jake Howard)
* Document how to add permission restriction to a report view (Rishabh jain)
* Add example for how to configure API `renderer_classes` (Aman Pandey)
* Document potential data loss for BaseLogEntry migration in 3.0 (Sage Abdullah)
* Add documentation for the reference index mechanism (Daniel Kirkham)
### Maintenance
* Switch to using [Willow](https://github.com/wagtail/Willow/) instead of Pillow for images (Darrel O’Pry)
* Remove unsquashed `testapp` migrations (Matt Westcott)
* Upgrade to Node 18 for frontend build tooling (LB (Ben) Johnston)
* Run Python tests with coverage and upload coverage data to codecov (Sage Abdullah)
* Clean up duplicate JavaScript for the `escapeHtml` function (Jordan Rob)
* Ensure that translation file generation ignores JavaScript unit tests and clean up unit tests for Django gettext utils (LB (Ben Johnston))
* Migrated `initButtonSelects` from core.js to own TypeScript file and add unit tests (Loveth Omokaro)
* Migrated `initSkipLink` util to TypeScript and add JSDoc & unit tests (Juliet Adeboye)
* Clean up some unused utility classes and migrate `unlist` to Tailwind utility class `w-list-none` (Loveth Omokaro)
* Clean up linting on legacy code and add shared util `hasOwn` in TypeScript (Loveth Omokaro)
* Remove unnecessary box-sizing: border-box declarations in SCSS (Albina Starykova)
* Migrated `initTooltips` to TypeScript and add JSDoc and unit tests (Fatuma Abdullahi)
* Migrated `initTagField` from core.js to own TypeScript file and add unit tests (Chisom Okeoma)
* Added unit tests & JSDoc to `initDismissibles` (Yekasumah)
* Standardise on `classname` for passing HTML class attributes (LB (Ben Johnston))
* Clean up expanding formset and `InlinePanel` JavaScript initialization code and adopt a class approach (Matt Westcott)
* Extracted revision and draft state logic from generic views into mixins (Sage Abdullah)
* Extracted generic lock / unlock views from page lock / unlock views (Sage Abdullah)
* Move `identity` JavaScript util into shared utils folder (LB (Ben Johnston))
* Remove unnecessary declaration of function to determine URL query params, instead use `URLSearchParams` (Loveth Omokaro)
* Update `tsconfig` to better support modern TypeScript development and clean up some code quality issues via Eslint (Loveth Omokaro)
* Switch userbar to initialize a Web Component to avoid styling clashes (Albina Starykova)
* Refactor userbar stylesheets to use the same CSS loading as the rest of the admin (Albina Starykova)
* Remove unused search-bar and button-filter styles (Thibaud Colas)
* Use util method to construct dummy requests in tests (Jake Howard)
* Remove unused dev-only react-axe integration (Thibaud Colas)
* Split up `wagtail.admin.panels` into submodules, existing exports have been preserved (Matt Westcott)
* Refactor userbar styles to use the same stylesheet as other components (Thibaud Colas)
* Add deprecation warnings for `wagtail.core` and other imports deprecated in Wagtail 3.0 (Matt Westcott)
* Upgraded Transifex configuration to v3 (Loic Teixeira)
* Replace repeated HTML `avatar` component with a template tag include `{% avatar ... %}` throughout the admin interface (Aman Pandey)
* Refactor accessibility checker userbar item (Albina Starykova)
## Upgrade considerations
### Wagtail-specific image field (`WagtailImageField`)
The `AbstractImage` and `AbstractRendition` models use a Wagtail-specific `WagtailImageField` which extends Django’s `ImageField`
to use [Willow](https://github.com/wagtail/Willow/) for image file handling. This will generate a new migration if you
are using a [custom image model](../advanced_topics/images/custom_image_model.md#custom-image-model).
### Comments within `InlinePanel` not supported
When the commenting system was introduced, support for `InlinePanel` fields was incorrectly added. This has led to issues
where comments can be lost on save, or in most cases will be added to the incorrect item within the `InlinePanel`. The ability
to add comments here has now been removed and as such any existing comments that were added will no longer show.
See https://github.com/wagtail/wagtail/issues/9685 for tracking of adding this back officially in the future.
### Adoption of `classname` convention for some template tags & includes
Some undocumented Wagtail admin template tags and includes have been refactored to adopt a more consistent naming of `classname`.
If these are used within packages or customizations they will need to be updated to the new variable naming convention.
| Name | New (`classname`) | Old (various) |
|---------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `icon` (see note) | `{% icon name='spinner' classname='...' %}` | `{% icon name='spinner class_name='...' %}` |
| `dialog_toggle` | `{% dialog_toggle classname='...' %}` | `{% dialog_toggle class_name='...' %}` |
| `paginate` | `{% paginate pages classname="..." %}` | `{% paginate pages classnames="..." %}` |
| `tab_nav_link` | `{% include 'wagtailadmin/shared/tabs/tab_nav_link.html' with classname="..." %}` | `{% include 'wagtailadmin/shared/tabs/tab_nav_link.html' with classes="..." %}` |
| `side_panel_button` | `{% include 'wagtailadmin/shared/side_panels/includes/side_panel_button.html' with classname="..." %}` | `{% include 'wagtailadmin/shared/side_panels/includes/side_panel_button.html' with classes="..." %}` |
Note that the `icon` template tag will still support `class_name` with a deprecation warning. Support will be dropped in a future release.
### `InlinePanel` JavaScript function is now a class
The (internal, undocumented) `InlinePanel` JavaScript function, used to initialize client-side behavior for inline panels, has been converted to a class. Any user code that calls this function should now replace `InlinePanel(...)` calls with `new InlinePanel(...)`. Additionally, child form controls are now initialized automatically, and so it is no longer necessary to call `initChildControls`, `updateChildCount`, `updateMoveButtonDisabledStates` or `updateAddButtonState`.
Python code that uses the `InlinePanel` panel type is not affected by this change.
### `WAGTAILADMIN_GLOBAL_PAGE_EDIT_LOCK` setting is now `WAGTAILADMIN_GLOBAL_EDIT_LOCK`
The `WAGTAILADMIN_GLOBAL_PAGE_EDIT_LOCK` setting has been renamed to [`WAGTAILADMIN_GLOBAL_EDIT_LOCK`](../reference/settings.md#wagtailadmin-global-edit-lock).
### Wagtail userbar as a web component
The [`wagtailuserbar`](../topics/writing_templates.md#wagtailuserbar-tag) template tag now initializes the userbar as a [Web Component](https://developer.mozilla.org/en-US/docs/Web/Web_Components), with a `wagtail-userbar` custom element using shadow DOM to apply styles without any collisions with the host page.
For any site customizing the position of the userbar, target the styles to `wagtail-userbar::part(userbar)` instead of `.wagtail-userbar`. For example:
```css
wagtail-userbar::part(userbar) {
bottom: 30px;
}
```
### Configuration of the accessibility checker user bar item
Like other userbar items, the new accessibility checker is configurable with the [`construct_wagtail_userbar`](../reference/hooks.md#construct-wagtail-userbar) hook. For example, to remove the new item, use:
```python
from wagtail.admin.userbar import AccessibilityItem
@hooks.register('construct_wagtail_userbar')
def remove_userbar_accessibility_checks(request, items):
items[:] = [item for item in items if not isinstance(item, AccessibilityItem)]
```
### Support for legacy versions of `azure-mgmt-cdn` and `azure-mgmt-frontdoor` packages will be dropped
If you are using the front-end cache invalidator module (`wagtail.contrib.frontend_cache`) with Azure CDN or Azure Front Door, the following packages need to be updated:
* For Azure CDN: upgrade `azure-mgmt-cdn` to version 10 or above
* For Azure Front Door: upgrade `azure-mgmt-frontdoor` to version 1 or above
Support for older versions will be dropped in a future release.
### Changes to `Workflow` and `Task` methods
To accommodate workflows support for snippets, the `page` parameter in [`Workflow.start()`](../reference/models.md#wagtail.models.Workflow.start) has been renamed to `obj`.
In addition, some methods on the base [`Task`](../reference/models.md#wagtail.models.Task) model have been changed. If you have [custom Task types](../extending/custom_tasks.md), make sure to update the methods to reflect the following changes:
- `page_locked_for_user()` is now [`locked_for_user()`](../reference/models.md#wagtail.models.Task.locked_for_user). Using `page_locked_for_user()` is deprecated and will be removed in a future release.
- The `page` parameter in `user_can_access_editor()`, `locked_for_user()`, `user_can_lock()`, `user_can_unlock()`, `get_actions()`, has been renamed to `obj`.
### Changes to `WorkflowState` and `TaskState` models
To accommodate workflows support for snippets, the `WorkflowState.page` foreign key has been replaced with a `GenericForeignKey` as `WorkflowState.content_object`. The generic foreign key is defined using a combination of the new `WorkflowState.base_content_type` and `WorkflowState.object_id` fields.
The `TaskState.page_revision` foreign key has been renamed to `TaskState.revision`.
### `wagtail.admin.forms.search.SearchForm` validation logic
The `wagtail.admin.forms.search.SearchForm` class (which is internal and undocumented, but may be in use by applications that extend the Wagtail admin) no longer treats an empty search field as invalid. Any code that checks `form.is_valid` to determine whether or not to apply a `search()` filter to a queryset should now explicitly check that `form.cleaned_data["q"]` is non-empty.
# 5.0.1.html.md
# Wagtail 5.0.1 release notes
*May 25, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Rectify previous fix for TableBlock becoming uneditable after save (Sage Abdullah)
* Ensure that copying page correctly picks up the latest revision (Matt Westcott)
* Ensure comment buttons always respect `WAGTAILADMIN_COMMENTS_ENABLED` (Thibaud Colas)
* Fix error when deleting a single snippet through the bulk actions interface (Sage Abdullah)
* Pass the correct `for_update` value for `get_form_class` in `SnippetViewSet` edit views (Sage Abdullah)
* Move comment notifications toggle to the comments side panel (Sage Abdullah)
* Remove comment button on InlinePanel fields (Sage Abdullah)
* Fix missing link to `UsageView` from `EditView` for snippets (Christer Jensen)
* Prevent lowercase conversions of IndexView column headers (Virag Jain)
* Fix various color issues in dark mode (Thibaud Colas)
### Documentation
* Update documentation for `log_action` parameter on `RevisionMixin.save_revision` (Christer Jensen)
# 5.0.2.html.md
# Wagtail 5.0.2 release notes
*June 21, 2023*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### New features
* Added [TitleFieldPanel](../reference/panels.md#title-field-panel) to support title / slug field synchronization (LB (Ben) Johnston)
### Bug fixes
* Prevent JS error when reverting the spinner on a submit button after a validation error (LB (Ben) Johnston)
* Prevent crash when comparing page revisions that include `MultipleChooserPanel` (Matt Westcott)
* Ensure that title and slug continue syncing after entering non-URL-safe characters (LB (Ben) Johnston)
* Ensure that title and slug are synced on keypress, not just on blur (LB (Ben) Johnston)
* Add a more visible active state for side panel toggle buttons (Thibaud Colas)
* Use custom dark theme colors for revision comparisons (Thibaud Colas)
## Upgrade considerations
### Use of `TitleFieldPanel` for the page title field
This release introduces a new [TitleFieldPanel](../reference/panels.md#title-field-panel) class, which is used by default for the page title field and provides the mechanism for synchronizing the slug field with the title. Before Wagtail 5.0, this happened automatically on any field named ‘title’.
If you have used `FieldPanel("title")` directly in a panel definition (rather than extending `Page.content_panels` as standard), and wish to restore the previous behavior of auto-populating the slug, you will need to change this to `TitleFieldPanel("title")`. For example:
```python
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
# ...
content_panels = [
MultiFieldPanel([
FieldPanel("title"),
FieldPanel("subtitle"),
]),
]
```
should become:
```python
from wagtail.admin.panels import FieldPanel, MultiFieldPanel, TitleFieldPanel
# ...
content_panels = [
MultiFieldPanel([
TitleFieldPanel("title"),
FieldPanel("subtitle"),
]),
]
```
# 5.0.3.html.md
# Wagtail 5.0.3 release notes
*September 25, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Avoid use of `ignore_conflicts` when creating extra permissions for snippets, for SQL Server compatibility (Sage Abdullah)
* Ensure sequence on `wagtailsearchpromotions_query` table is correctly set after migrating data (Jake Howard)
* Update Pillow dependency to 9.1.0 (Daniel Kirkham)
# 5.0.4.html.md
# Wagtail 5.0.4 release notes
*October 4, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Maintenance
* Relax Willow / Pillow dependency to allow use of current Pillow versions with security fixes (Dan Braghis)
# 5.0.5.html.md
# Wagtail 5.0.5 release notes
*October 19, 2023*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2023-45809: Disclosure of user names via admin bulk action views
This release addresses an information disclosure vulnerability in the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin can make a direct URL request to the admin view that handles bulk actions on user accounts. While authentication rules prevent the user from making any changes, the error message discloses the display names of user accounts, and by modifying URL parameters, the user can retrieve the display name for any user. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to quyenheu for reporting this issue. For further details, please see [the CVE-2023-45809 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-fc75-58r8-rm3h).
# 5.0.html.md
# Wagtail 5.0 release notes
*May 2, 2023*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Django 4.2 support
This release adds support for Django 4.2.
### Object usage information on deleting objects
On deleting a page, image, document or snippet, the confirmation screen now provides a summary of where the object is used, allowing users to see the effect that deletion will have elsewhere on the site. This also prevents objects from being deleted in cases where deletion would be blocked by an `on_delete=PROTECT` constraint. This feature was developed by Sage Abdullah.
### SVG image support
The image library can now be configured to allow uploading SVG images. These are handled by the `{% image %}` template tag as normal, with some limitations on image operations - for full details, see [SVG images](../topics/images.md#svg-images). This feature was developed by Joshua Munn, and sponsored by [YouGov](https://yougov.com/).
### Custom validation support for StreamField
Support for adding custom validation logic to StreamField blocks has been formalized and simplified. For most purposes, raising a `ValidationError` from the block’s `clean` method is now sufficient; more complex behaviors (such as attaching errors to a specific child block) are possible through block-specific subclasses of `ValidationError`. For more details, see [StreamField validation](../advanced_topics/streamfield_validation.md#streamfield-validation). This feature was developed by Matt Westcott.
### Customizable SVG icons
Wagtail’s icon set is now fully updated, customizable, and extendable. Built-in icons are now based on the latest [FontAwesome](https://fontawesome.com/) visuals, with capabilities to both customize existing icons as well as add new ones. In particular, this includes:
* A new `{% icon %}` icon template tag to reuse icons in custom templates.
* A `register_icons` hook to register new icons and override existing ones.
* Live documentation of all available icons in the styleguide, showcasing the icons available on the current site.
* A list of [all built-in icons](../advanced_topics/icons.md#icons) within our developer documentation.
* Support for customizing icons for snippets via `SnippetViewSet.icon`.
* Support for custom panel icons, with defaults, displayed for top-level editor panels.
* New icons for StreamField blocks
For more details, see our new [icons documentation](../advanced_topics/icons.md#icons).
This has been made possible thanks to a multi-year refactoring effort to migrate all icons to SVG. Thank you to all contributors who participated in this effort: Coen van der Kamp, LB (Ben) Johnston, Dan Braghis, Daniel Kirkham, Sage Abdullah, Thibaud Colas, Scott Cranfill, Storm Heg, Steve Steinwand, Jérôme Lebleu, Abayomi Victory.
### Accessibility checker improvements
The [built-in accessibility checker](../advanced_topics/accessibility_considerations.md#authoring-accessible-content) has been updated with:
* 5 more Axe rules enabled by default.
* Sorting of checker results according to their position on the page.
* Highlight styles to more easily identify elements with errors.
* Configuration APIs in [`AccessibilityItem`](../advanced_topics/accessibility_considerations.md#wagtail.admin.userbar.AccessibilityItem) for simpler customization of the checks performed.
Those improvements were implemented by Albina Starykova as part of an [Outreachy internship](https://wagtail.org/blog/introducing-wagtails-new-accessibility-checker/), with support from mentors Thibaud Colas, Sage Abdullah, and Joshua Munn.
### Always-on minimap
Following its introduction in Wagtail 4.1, we have made several improvements to the page editor minimap:
* It now stays opened until dismissed, so users can keep it expanded if desired.
* Its “expanded” state is preserved when navigating between different views of the CMS.
* The minimap and “Collapse all” button now appear next to side panels rather than underneath, so they can be used at any time.
* Clicking any item reveals the minimap, with appropriate text for screen reader users.
* Navigating to a collapsed section of the page will reveal this section.
Thank you to everyone who provided feedback on this new addition to the editor experience. Those changes were implemented by Thibaud Colas.
### Dark mode
Wagtail’s admin interface now supports dark mode. The new dark theme can be enabled in account preferences, as well as configuring permanent usage of the light theme, or following system preferences.
We hope this new theme will bring accessibility improvements for users who prefer light text on dark backgrounds, and energy usage efficiency improvements for users of OLED monitors. This feature was developed by Thibaud Colas, with designs from Ben Enright.
### Snippets parity with ModelAdmin
Continuing on recent improvements to snippets, we have made the following additions to how snippets can be customized in the admin interface:
* Allow customizing the base URL and URL namespace for snippet views.
* Allow customizing the default ordering and number of items per page for snippet listing views.
* Allow admin templates for snippets to be overridden on a per-model or per-app basis.
* Allow overriding the base queryset to be used in snippet `IndexView`.
* Allow customizing the `search_fields` and search backend via SnippetViewSet.
* Allow filters on snippet index views to be customized through the `list_filter` attribute.
* Allow `panels` / `edit_handler` to be specified via `SnippetViewSet`.
* Support for customizing icons for snippets via `SnippetViewSet.icon`.
* Allow snippets to be registered into arbitrary admin menu items.
For more details, see [Customizing admin views for snippets](../topics/snippets/customizing.md#wagtailsnippets-custom-admin-views).
Developed by Sage Abdullah, these features were implemented as part of [RFC 85: Snippets parity with ModelAdmin](https://github.com/wagtail/rfcs/pull/85). We will start the deprecation process of the ModelAdmin contrib package in the next feature release and publish it as a separate package for users who wish to continue using it. The ModelAdmin package will be removed in Wagtail 6.0.
### Other features
* Add `WAGTAILIMAGES_EXTENSIONS` setting to restrict image uploads to specific file types (Aman Pandey, Ananjan-R)
* Update user list column level to `Access level` to be easier to understand (Vallabh Tiwari)
* Migrate `.button-longrunning` behavior to a Stimulus controller with support for custom label element & duration (Loveth Omokaro)
* Implement new simplified userbar designs (Albina Starykova)
* Add usage view for pages (Sage Abdullah)
* Copy page form now updates the slug field dynamically with a slugified value on blur (Loveth Omokaro)
* Ensure selected collection is kept when navigating from documents or images listings to add multiple views & upon upload (Aman Pandey, Bojan Mihelac)
* Keep applied filters when downloading form submissions (Suyash Srivastava)
* Messages added dynamically via JavaScript now have an icon to be consistent with those supplied in the page’s HTML (Aman Pandey)
* Switch lock/unlock side panel toggle to a switch, with more appropriate confirmation message status (Sage Abdullah)
* Ensure that changed or cleared selection from choosers will dispatch a DOM `change` event (George Sakkis)
* Add the ability to [disable model indexing](../topics/search/indexing.md#wagtailsearch-disable-indexing) by setting `search_fields = []` (Daniel Kirkham)
* Enhance `wagtail.search.utils.parse_query_string` to allow inner single quotes for key/value parsing (Aman Pandey)
* Add helpful properties to [`Locale`](../reference/models.md#locale-model-ref) for more convenient usage within templates, see [Basic example](../advanced_topics/i18n.md#i18n-basic-example) (Andy Babic)
* Re-label “StreamField blocks” option in block picker to “Blocks” (Thibaud Colas)
* Switch styleguide navigation to use panel components and minimap (Thibaud Colas)
* Explicitly specify `MenuItem.name` for Snippets, Reports, and Settings menu items (Sage Abdullah)
* Move the help text of fields and blocks directly below their label for easier reading (Thibaud Colas)
* The select all checkbox in simple translation’s submit translation page will now be in sync with other checkbox changes (Hanoon)
* Revise alignment and spacing of form fields and sections (Thibaud Colas)
* Update Wagtail’s type scale so StreamField block labels and field labels are the same size (Thibaud Colas)
* Style comments as per page editor design, in side panel (Karl Hobley, Thibaud Colas)
* ReferenceIndex modified to only index Wagtail-related models, and allow other models to be explicitly registered (Daniel Kirkham)
### Bug fixes
* Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal)
* Adopt a no-JavaScript and more accessible solution for the ‘Reset to default’ switch to Gravatar when editing user profile (Loveth Omokaro)
* Ensure `Site.get_site_root_paths` works on cache backends that do not preserve Python objects (Jaap Roes)
* Ignore right clicks on side panel resizer (Sage Abdullah)
* Resize in the correct direction for RTL languages with the side panel resizer (Sage Abdullah)
* Fix image uploads on storage backends that require file pointer to be at the start of the file (Matt Westcott)
* Fix “Edit this page” missing from userbar (Satvik Vashisht)
* No longer allow invalid duplicate site hostname creation as hostnames and domain names are a case insensitive (Coen van der Kamp)
* Image and Document multiple upload update forms now correctly use the progress button (longrunning) behavior when clicked (Loveth Omokaro)
* Prevent audit log report from failing on missing models (Andy Chosak)
* Ensure that the privacy collection privacy edit button is styled as a button (Jatin Kumar)
* Fix page/snippet cannot proceed a `GroupApprovalTask` if it’s locked by someone outside of the group (Sage Abdullah)
* Allow manual lock even if `WorkflowLock` is currently applied (Sage Abdullah)
* Add missing log information for `wagtail.schedule.cancel` (Stefan Hammer)
* Fix timezone activation leaking into subsequent requests in `require_admin_access()` (Stefan Hammer)
* Fix dialog component’s message to have rounded corners at the top side (Sam)
* When multiple documents are uploaded and then subsequently updated, ensure that existing success messages are cleared correctly (Aman Pandey)
* Prevent matches from unrelated models from leaking into SQLite FTS searches (Matt Westcott)
* Prevent duplicate addition of StreamField blocks with the new block picker (Deepam Priyadarshi)
* Enable partial search on images and documents index view where available (Mng)
* Adopt a no-JavaScript and more accessible solution for option selection in reporting, using HTML only `radio` input fields (Mehul Aggarwal)
* Ensure that document search results count shows the correct all matches, not the paginate total (Andy Chosak)
* Fix radio and checkbox elements shrinking when using a long label (Sage Abdullah)
* Fix select elements expanding beyond their container when using a long option label (Sage Abdullah)
* Fix timezone handling of `TemplateResponse`s for users with a custom timezone (Stefan Hammer, Sage Abdullah)
* Ensure TableBlock initialization correctly runs after load and its width is aligned with the parent panel (Dan Braghis)
* Ensure that the JavaScript media files are loaded by default in Snippet index listings for date fields (Sage Abdullah)
* Fix server-side caching of the icons sprite (Thibaud Colas)
* Avoid showing scrollbars in the block picker unless necessary (Babitha Kumari)
* Always show Add buttons, guide lines, Move up/down, Duplicate, Delete; in StreamField and Inline Panel (Thibaud Colas)
* Make admin JS i18n endpoint accessible to non-authenticated users (Matt Westcott)
* Autosize text area field will now correctly resize when switching between comments toggle states (Suyash Srivastava)
* Fix incorrect API serialization for document `download_url` when `WAGTAILDOCS_SERVE_METHOD` is `direct` (Swojak-A)
* Fix template configuration of snippets index results view (fidoriel, Sage Abdullah)
* Prevent long preview mode names from making the select element overflow the side panel (Sage Abdullah)
* When i18n is not enabled, avoid making a Locale query on every page view (Dan Braghis)
* Fix initialization of commenting widgets within StreamField (Thibaud Colas)
* Fix various regressions in the commenting UI (Thibaud Colas)
* Prevent TableBlock from becoming uneditable after save (Sage Abdullah)
* Correctly show the “new item” badge within menu sections previously dismissed (Sage Abdullah)
* Fix side panel stuck in resize state when pointer is released outside the grip (Sage Abdullah)
### Documentation
* Add code block to make it easier to understand contribution docs (Suyash Singh)
* Fix broken formatting for MultiFieldPanel / FieldRowPanel permission kwarg docs (Matt Westcott)
* Add helpful troubleshooting links and refine wording for getting started with development (Loveth Omokaro)
* Ensure search autocomplete overlay on mobile does not overflow the viewport (Ayman Makroo)
* Improve documentation for InlinePanel (Vallabh Tiwari)
* Remove confusing `SettingsPanel` reference in the page editing `TabbedInterface` example as `SettingsPanel` no longer shows anything as of 4.1 (Kenny Wolf, Julian Bigler)
* Add contributor guidelines for building [Stimulus Controllers](../contributing/ui_guidelines.md#ui-guidelines-stimulus) (Thibaud Colas, Loveth Omokaro, LB (Ben) Johnston)
* Fix typo in “Extending Draftail” documentation (Hans Kelson)
* Clarify `ClusterableModel` requirements for using relations with `RevisionMixin`-enabled models (Sage Abdullah)
* Add guide to making your first contribution (LB (Ben) Johnston)
### Maintenance
* Removed features deprecated in Wagtail 3.0 and 4.0 (Matt Westcott)
* Update djhtml (html formatting) library to v 1.5.2 (Loveth Omokaro)
* Re-enable `strictPropertyInitialization` in tsconfig (Thibaud Colas)
* Refactor accessibility checker userbar item (Albina Starykova)
* Removed unused `Page.get_static_site_paths` method (Yosr Karoui)
* Provisional Django 5.0 compatibility fixes (Sage Abdullah)
* Add unit tests for `CollapseAll` and `MinimapItem` components (Albina Starykova)
* Code quality fixes (GLEF1X)
* Refactor image / document / snippet usage views into a shared generic view (Sage Abdullah)
* Rename the Stimulus `AutoFieldController` to the less confusing `SubmitController` (Loveth Omokaro)
* Replace `script` tags with `template` tag for image/document bulk uploads (Rishabh Kumar Bahukhandi)
* Remove unneeded float styles on 404 page (Fabien Le Frapper)
* Convert userbar implementation to TypeScript (Albina Starykova)
* Migrate slug field behavior to a Stimulus controller and create new `SlugInput` widget (Loveth Omokaro)
* Refactor `status` HTML usage to shared template tag (Aman Pandey, LB (Ben) Johnston, Himanshu Garg)
* Add curlylint and update djhtml, semgrep versions in pre-commit config (Himanshu Garg)
* Use shared header template for `ModelAdmin` and Snippets type index header (Aman Pandey)
* Move models and forms for `wagtailsearch.Query` to `wagtail.contrib.search_promotions` (Karl Hobley)
* Migrate `initErrorDetection` (tabs error counts) to a Stimulus Controller `w-count` (Aman Pandey)
* Migrate `window.addMessage` behavior to a global event listener & Stimulus Controller approach with `w-messages` (Aman Pandey)
* Update Algolia DocSearch to use new application and correct versioning setup (Thibaud Colas)
* Move snippet choosers and model check registration to `SnippetViewSet.on_register()` (Sage Abdullah)
* Remove unused snippets delete-multiple view (Sage Abdullah)
* Improve performance of determining live page URLs across the admin interface using [`pageurl` template tag](../advanced_topics/performance.md#performance-page-urls) (Satvik Vashisht)
* Migrate `window.initSlugAutoPopulate` behavior to a Stimulus Controller `w-sync` (Loveth Omokaro)
* Rename `status` classes to `w-status` to align with preferred CSS class naming conventions (Mansi Gundre)
* Include wagtail-factories in `wagtail.test.utils` to avoid cross-dependency issues (Matt Westcott)
* Fix search tests to correctly reflect behavior of search backends other than the fallback backend (Matt Westcott)
* Migrate select all checkbox in simple translation’s submit translation page to Stimulus controller `w-bulk`, remove inline script usage (Hanoon)
* Refactor `SnippetViewSet` to extend `ModelViewSet` (Sage Abdullah)
* Migrate initDismissibles behavior to a Stimulus controller `w-disimissible` (Loveth Omokaro)
* Replace jQuery autosize v3 with Stimulus `w-autosize` controller using autosize npm package v6 (Suyash Srivastava)
* Update `w-action` controller to support a click method (Suyash Srivastava)
* Migrate the site settings switcher select from jQuery to a refined version of the `w-action` controller usage (Aadi jindal, LB (Ben) Johnston)
* Always use expanded Sass output so CSS processing is identical in development and production builds (Thibaud Colas)
* Refactor admin color palette to semantic, theme-agnostic design tokens (Thibaud Colas)
## Upgrade considerations
### Removal of deprecated features
The following features deprecated in Wagtail 3.0 have been fully removed. See [Wagtail 3.0 release notes](3.0.md) for details on these changes, including how to remove usage of these features:
* The modules `wagtail.core`, `wagtail.tests`, `wagtail.admin.edit_handlers` and `wagtail.contrib.forms.edit_handlers` are removed.
* The field panel classes `StreamFieldPanel`, `RichTextFieldPanel`, `ImageChooserPanel`, `DocumentChooserPanel` and `SnippetChooserPanel` are removed.
* StreamField definitions must include `use_json_field=True` (except migrations created before Wagtail 5.0).
* The `BASE_URL` setting is no longer recognized.
* The `ModelAdmin.get_form_fields_exclude` method is no longer passed a `request` argument.
* The `ModelAdmin.get_edit_handler` method is no longer passed a `request` or `instance` argument.
* The `widget_overrides`, `required_fields`, `required_formsets`, `bind_to`, `render_as_object` and `render_as_field` methods on `Panel` (previously `EditHandler`) are removed.
The following features deprecated in Wagtail 4.0 have been fully removed. See [Wagtail 4.0 release notes](4.0.md) for details on these changes, including how to remove usage of these features:
* The `wagtail.contrib.settings.models.BaseSetting` class is removed.
* The `Page.get_latest_revision_as_page` method is removed.
* The `page` and `page_id` properties and `as_page_object` method on `Revision` are removed.
* The JavaScript functions `createPageChooser`, `createSnippetChooser`, `createDocumentChooser` and `createImageChooser` are removed.
* The `wagtail.contrib.modeladmin.menus.SubMenu` class is removed.
* Subclasses of `wagtail.contrib.modeladmin.helpers.AdminURLHelper` are now required to accept a `base_url_path` keyword argument on the constructor.
* The `wagtail.admin.widgets.chooser.AdminChooser` class is removed.
* The `wagtail.snippets.views.snippets.get_snippet_edit_handler` function is removed.
### Dropped support for Django 4.0
Django 4.0 reached end of life on 1st April 2023 and is no longer supported by Wagtail. Django 3.2 (LTS) is still supported until April 2024.
### Elasticsearch backend no longer performs partial matching on `search`
The `search` method on pages, images and documents, and on the backend object returned by `wagtail.search.backends.get_search_backend()`, no longer performs partial word matching when the Elasticsearch backend is in use. Previously, a search query such as `Page.objects.search("cat")` would return results containing the word “caterpillar”, while `Page.objects.search("cat", partial_match=False)` would only return results for the exact word “cat”. The `search` method now always performs exact word matches, and the `partial_match` argument has no effect. This change makes the Elasticsearch backend consistent with the database-backed full-text search backends.
To revert to the previous partial word matching behavior, use the `autocomplete` method instead - for example, `Page.objects.autocomplete("cat")`. It may also be necessary to add an [AutocompleteField](../topics/search/indexing.md#wagtailsearch-index-autocompletefield) entry for the relevant fields on the model’s `search_fields` definition, as the old `SearchField("some_field", partial_match=True)` format is no longer supported.
The `partial_match` argument on `search` and `SearchField` is now deprecated, and should be removed from your code; it will be dropped entirely in Wagtail 6.
### ReferenceIndex no longer tracks models used outside of Wagtail
When introduced in Wagtail 4.1, the `ReferenceIndex` model recorded references across all of a project’s models by default. The default set of models being indexed has now been changed to only those used within the Wagtail admin, specifically:
* all Page types
* Images
* Documents
* models registered as Snippets
* models registered with ModelAdmin
This change will remove the impact of the indexing on non-Wagtail apps and models.
If you have models that still require reference indexing, and which are not registered as snippets or with ModelAdmin, you will need to explicitly register them within your app’s `AppConfig.ready()` method. See [Reference index](../advanced_topics/reference_index.md#registering-a-model-for-indexing) for further details.
The use of `wagtail_reference_index_ignore` to prevent indexing of models is unchanged, but in many cases it may no longer be necessary.
It is recommended that the `rebuild_references_index` management command is run after the upgrade to remove any unnecessary records.
### `Page.get_static_site_paths` method removed
The undocumented `Page.get_static_site_paths` method (which returns a generator of URL paths for use by static site generator packages) has been removed. Packages relying on this functionality should provide their own fallback implementation.
### `wagtailsearch.Query` has moved to `wagtail.contrib.search_promotions`
The `wagtailsearch.Query` model has been moved from the `search` application to the contrib application `wagtail.contrib.search_promotions`.
All imports will need to be updated and migrations will need to be run via a management command, some imports will still work with a warning until a future version.
To continue using the `Query` model, you must also add the `wagtail.contrib.search_promotions` application to your project’s `INSTALLED_APPS` setting.
#### Migration command
If you have daily hits records in the `wagtailsearch.Query` you can run the management command to move these records to the new location.
```sh
./manage.py copy_daily_hits_from_wagtailsearch
```
#### Managing stored search queries
The `search_garbage_collect` command used to remove old stored search queries and daily hits has been moved to [`searchpromotions_garbage_collect`](../reference/contrib/searchpromotions.md#searchpromotions-garbage-collect).
#### Import updates
| **Import** | **Old import** | **New import** |
|---------------|----------------------------------------------|-----------------------------------------------------------------|
| `Query` Model | `from wagtail.search.models import Query` | `from wagtail.contrib.search_promotions.models import Query` |
| `QueryForm` | `from wagtail.search.forms import QueryForm` | `from wagtail.contrib.search_promotions.forms import QueryForm` |
### Changes to header CSS classes in `ModelAdmin` templates
If there are custom styles in place for the `ModelAdmin`’s header content or more complex template overrides in use, there are a few changes for the following classes to be aware of.
| **Content** | **Old classes** | **New classes** |
|----------------------------------|-----------------------|-------------------|
| Heading & search (contains `h1`) | `.left.header-left` | `.left` |
| Action buttons (`header_extra`) | `.right.header-right` | `.right` |
### Slug field auto-cleaning now relies on data attributes
The slug field JavaScript behavior was previously attached to any field with an ID of `id_slug`, this has now changed to be any field with the appropriate Stimulus data attributes.
If using a custom edit handler or set of panels for page models, the correct widget will now need to be used for these data attributes to be included. This widget will use the `WAGTAIL_ALLOW_UNICODE_SLUGS` Django setting.
```python
from wagtail.admin.widgets.slug import SlugInput
# ... other imports
class MyPage(Page):
promote_panels = [
FieldPanel("slug", widget=SlugInput),
# ... other panels
]
```
Additionally, the slug behavior can be attached to any field easily by including the following attributes in HTML or via Django’s widget `attrs`.
```html
```
To allow unicode values, add the data attribute value;
```html
```
### Changes to title / slug field synchronization
The mechanism for synchronizing the slug field with the page title has changed, and is no longer hard-coded to activate on fields named ‘title’. Notably, this change affects page panel definitions that use `FieldPanel("title")` directly (rather than the convention of extending `Page.content_panels`), as well as non-page models such as snippets.
To assist in upgrading these definitions, Wagtail 5.0.2 provides a new [TitleFieldPanel](../reference/panels.md#title-field-panel) class to be used in place of `FieldPanel("title")`. For example:
```python
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
# ...
content_panels = [
MultiFieldPanel([
FieldPanel("title"),
FieldPanel("subtitle"),
]),
]
```
should become:
```python
from wagtail.admin.panels import FieldPanel, MultiFieldPanel, TitleFieldPanel
# ...
content_panels = [
MultiFieldPanel([
TitleFieldPanel("title"),
FieldPanel("subtitle"),
]),
]
```
If you have made deeper customizations to this behavior, or are unable to upgrade to Wagtail 5.0.2 or above, please read on as you may need to make some changes to adopt the new approach.
The title field will sync its value with the slug field on Pages if the Page is not published and the slug has not been manually changed. This JavaScript behavior previously attached to any field with an ID of `id_title`; this has now changed to be any field with the appropriate Stimulus data attributes.
There is a new Stimulus controller `w-sync` which allows any field to change one or more other fields when its value changes, the other field in this case will be the slug field (`w-slug`) with the id `id_slug`.
If you need to hook into this behavior, the new approach will now correctly dispatch `change` events on the slug field. Alternatively, you can modify the data attributes on the fields to adjust this behavior.
To adjust the target field (the one to be updated), you cam modify `"data-w-sync-target-value"`, the default being `"body:not(.page-is-live) [data-edit-form] #id_slug"` (find the field with id `id_slug` when the page is not live).
To adjust what triggers the initial check (to see if the fields should be in sync), or the trigger the sync, you can use the Stimulus `data-action` attributes.
```html
```
Above we have adjusted these attributes to add a ‘change’ event listener to trigger the sync and also adjusted to look for a field with `some_other_slug` instead.
### Auto height/size text area widget now relies on data attributes
If you are using the `wagtail.admin.widgets.AdminAutoHeightTextInput` only, this change will have no impact when upgrading. However, if you are relying on the global `autosize` function at `window.autosize` on the client, this will no longer work.
It is recommended that the `AdminAutoHeightTextInput` widget be used instead. You can also adopt the `data-controller` attribute and this will now function as before. Alternatively, you can simply add the required Stimulus data controller attribute as shown below.
**Old syntax**
```html
```
**New syntax**
```html
```
There are no additional data attributes supported at this time.
### Progress button (`button-longrunning`) now relies on data attributes
The `button-longrunning` class usage has been updated to use the newly adopted Stimulus approach, the previous data attributes will be deprecated in a future release.
If using the old approach, ensure any HTML templates are updated to the new approach before the next major release.
**Old syntax**
```html+django
{% icon name="spinner" %}
{% trans 'Create' %}
```
**New syntax**
Minimum required attributes are `data-controller` and a `data-action`.
```html+django
{% icon name="spinner" %}
{% trans 'Create' %}
```
#### Examples of additional capabilities
Stimulus [targets](https://stimulus.hotwired.dev/reference/targets) and [actions](https://stimulus.hotwired.dev/reference/actions) can be leveraged to revise the behavior via data attributes.
* `` - custom duration can be declared on the element
* `` - custom ‘active’ class to replace the default `button-longrunning-active` (must be a single string without spaces)
* `{% trans 'Create' %} ` - any element can be the button label (not just `em`)
* `` - any event can be used to trigger the in progress behavior
* `` - only trigger the progress behavior once
* `` - disabled on load (once JS starts) and becomes enabled after 5s duration
### JavaScript `window.addMessages` replaced with event dispatching
The undocumented `window.addMessage` function is no longer available and will throw an error if called, if similar functionality is required use DOM Event dispatching instead as follows.
```javascript
// old
window.addMessage('success', 'Content has updated');
```
```javascript
// new
document.dispatchEvent(
new CustomEvent('w-messages:add', {
detail: { text: 'Content has updated', type: 'success' },
}),
);
// new (clearing existing messages before adding a new one)
document.dispatchEvent(
new CustomEvent('w-messages:add', {
detail: {
clear: true,
text: 'All content has updated',
type: 'success',
},
}),
);
// message types 'success', 'error', 'warning' are supported
```
Note that this event name may change in the future and this functionality is still not officially supported.
### Changes to StreamField `ValidationError` classes
The client-side handling of StreamField validation errors has been updated. The JavaScript classes `StreamBlockValidationError`, `ListBlockValidationError`, `StructBlockValidationError` and `TypedTableBlockValidationError` have been removed, and the corresponding Python classes can no longer be serialized using Telepath. Instead, the `setError` methods on client-side block objects now accept a plain JSON representation of the error, obtained from the `as_json_data` method on the Python class. Custom JavaScript code that works with these objects must be updated accordingly.
Additionally, the Python `StreamBlockValidationError`, `ListBlockValidationError`, `StructBlockValidationError` and `TypedTableBlockValidationError` classes no longer provide a `params` dict with `block_errors` and `non_block_errors` items; these are now available as the attributes `block_errors` and `non_block_errors` on the exception itself (or `cell_errors` and `non_block_errors` in the case of `TypedTableBlockValidationError`).
### Snippets `delete-multiple` view removed
The ability to remove multiple snippet instances from the `DeleteView` and the undocumented `wagtailsnippets_{app_label}_{model_name}:delete-multiple` URL pattern have been removed. The view’s functionality has been replaced by the delete action of the bulk actions feature introduced in Wagtail 4.0.
The delete bulk action view now also calls the `{before,after}_delete_snippet` hooks, in addition to the `{before,after}_bulk_action` hooks.
If you have customized the `IndexView` and/or `DeleteView` views in a `SnippetViewSet` subclass, make sure that the `delete_multiple_url_name` attribute is renamed to `delete_url_name`.
### Snippets index views template name changed
The template name for the index view of a snippet model has changed from `wagtailsnippets/snippets/type_index.html` and `wagtailsnippets/snippets/results.html` to `wagtailsnippets/snippets/index.html` and `wagtailsnippets/snippets/index_results.html`. In addition, the model index view that lists the snippet types now looks for the template `wagtailsnippets/snippets/model_index.html` before resorting to the generic index template. If you have customized these templates, make sure to update them accordingly.
### `status` classes are now `w-status`
Please update any custom styling or usage within the admin when working with status tags to the following new classes.
| Old | New |
|---------------------|----------------------|
| `status-tag` | `w-status` |
| `primary` | `w-status--primary` |
| `disabled` | `w-status--disabled` |
| `status-tag--label` | `w-status--label` |
Note that a new template tag has been built for usage within the admin that may make it easier to generate status tags.
```html+django
{% load wagtailadmin_tags %}
{% status "live" url="/test-url/" title=trans_title hidden_label=trans_hidden_label classname="w-status--primary" attrs='target="_blank" rel="noreferrer"' %}
{% status status_label classname="w-status--primary" %}
```
### Deprecated icon font
The Wagtail icon font has been deprecated and will be removed in a future release, as it is now unused in Wagtail itself. There are no changes to make for any icons usage via dedicated APIs such as `icon` class properties. Any direct icon font usage needs to be converted to SVG icons instead, as documented in our [icons overview](../advanced_topics/icons.md#icons).
To check whether your project uses the icon font, check for occurrences of:
* Loading of the `wagtail.woff` font file.
* Usage of `font-family: wagtail` in CSS.
* `icon-` CSS classes outside of SVG elements.
### Deprecated icons
The following icons are unused in Wagtail itself and will be removed in a future release. If you are using any of these icons, please replace them with an alternative (see our full [list of icons](../advanced_topics/icons.md#icons)), or re-add the icon to your own project.
| Icon name | Alternative |
|----------------------|------------------------------|
| `angle-double-left` | `arrow-left` |
| `angle-double-right` | `arrow-right` |
| `arrow-down-big` | `arrow-down` |
| `arrow-up-big` | `arrow-up` |
| `arrows-up-down` | `order` |
| `chain-broken` | `link` |
| `chevron-down` | `arrow-down` (identical) |
| `dots-vertical` | `dots-horizontal` |
| `download-alt` | `download` (identical) |
| `duplicate` | `copy` (identical) |
| `ellipsis-v` | `dots-horizontal` |
| `horizontalrule` | `minus` |
| `repeat` | `rotate` |
| `reset` | `rotate` |
| `tick` | `check` (identical) |
| `undo` | `rotate` |
| `uni52` | `folder-inverse` (identical) |
| `wagtail-inverse` | `wagtail-icon` |
### Snippets `get_admin_url_namespace()` and `get_admin_base_path()` moved to `SnippetViewSet`
The undocumented `get_admin_url_namespace()` and `get_admin_base_path()` methods that were set on snippet models at runtime have been moved to the [`SnippetViewSet`](../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet) class. If you use these methods, you could access them via [`SnippetModel.snippet_viewset.get_admin_url_namespace()`](../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.get_admin_url_namespace) and [`SnippetModel.snippet_viewset.get_admin_base_path()`](../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.get_admin_base_path), respectively.
### Snippets `get_usage()` and `usage_url()` methods removed
The undocumented `get_usage()` and `usage_url()` methods that were set on snippet models at runtime have been removed. Calls to the `get_usage()` method can be replaced with `wagtail.models.ReferenceIndex.get_grouped_references_to(object)`. The `usage_url()` method does not have a direct replacement, but the URL name can be retrieved via [`SnippetModel.snippet_viewset.get_url_name("usage")`](../reference/viewsets.md#wagtail.admin.viewsets.base.ViewSet.get_url_name), which can be used to construct the URL with [`reverse()`](https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.reverse).
# 5.1.1.html.md
# Wagtail 5.1.1 release notes
*August 14, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Other features
* Introduce `wagtail.admin.ui.tables.BooleanColumn` to display boolean values as icons (Sage Abdullah)
### Bug fixes
* Show not-`None` falsy values instead of blank in generic table cell template (Sage Abdullah)
* Fix `read_only` panels for fields with translatable choice labels (Florent Lebreton)
# 5.1.2.html.md
# Wagtail 5.1.2 release notes
*September 25, 2023*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
* Avoid use of `ignore_conflicts` when creating extra permissions for snippets, for SQL Server compatibility (Sage Abdullah)
* Ensure sequence on `wagtailsearchpromotions_query` table is correctly set after migrating data (Jake Howard)
* Change spreadsheet export headings to match listing view column headings (Christer Jensen, Sage Abdullah)
* Fix numbers, booleans, and `None` from being exported as strings (Christer Jensen)
* Restore fallback on full-word search for snippet choosers and generic index views (Matt Westcott)
* Restore compatibility with pre-7.15 versions of the Elasticsearch Python library, allowing use of Opensearch (Matt Westcott)
* Fix error when pickling BaseSiteSetting instances (Matt Westcott)
* For Python 3.13 support - upgrade Willow to v1.6.2, replace `imghdr` with Willow’s built-in MIME type detection (Jake Howard)
## Upgrade considerations
### Search within chooser interfaces requires `AutocompleteField` for full functionality
In Wagtail 4.2, the search bar within snippet chooser interfaces (and custom choosers created via `ChooserViewSet`) returned results for partial word matches - for example, a search for “wagt” would return results containing “Wagtail” - if this was supported by the search backend in use, and at least one `AutocompleteField` was present in the model’s `search_fields` definition. Otherwise, it would fall back to only matching on complete words. In Wagtail 5.0, this fallback behavior was removed, and consequently a model with no `AutocompleteField`s in place would return no results.
As of Wagtail 5.1.2, the fallback behavior has been restored. Nevertheless, it is strongly recommended that you add `AutocompleteField` to your models’ `search_fields` definitions, to ensure that users can receive search results continuously as they type. For example:
```python
from wagtail.search import index
# ... other imports
@register_snippet
class MySnippet(index.Indexed, models.Model):
search_fields = [
index.SearchField("name"),
index.AutocompleteField("name"),
]
```
# 5.1.3.html.md
# Wagtail 5.1.3 release notes
*October 19, 2023*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2023-45809: Disclosure of user names via admin bulk action views
This release addresses an information disclosure vulnerability in the Wagtail admin interface. A user with a limited-permission editor account for the Wagtail admin can make a direct URL request to the admin view that handles bulk actions on user accounts. While authentication rules prevent the user from making any changes, the error message discloses the display names of user accounts, and by modifying URL parameters, the user can retrieve the display name for any user. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to quyenheu for reporting this issue. For further details, please see [the CVE-2023-45809 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-fc75-58r8-rm3h).
### Bug fixes
* Fix `SnippetBulkAction` not respecting `models` definition (Sandro Rodrigues)
* Correctly quote non-numeric primary keys on snippet inspect view (Sage Abdullah)
* Prevent crash on snippet inspect view when displaying a null foreign key to an image (Sage Abdullah)
* Populate the correct return value when creating a new snippet within the snippet chooser (claudobahn)
* Reinstate missing filter by page type on page search (Matt Westcott)
* Use the correct action log when creating a redirect (Thibaud Colas)
# 5.1.html.md
# Wagtail 5.1 release notes
*August 1, 2023*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - deprecation of old functionality](#upgrade-considerations-deprecation-of-old-functionality)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
## What’s new
### Read-only panels
FieldPanels can now be marked as read-only with the `read_only=True` keyword argument, so that they are displayed in the admin but cannot be edited. This feature was developed by Andy Babic.
### Wagtail tutorial improvements
As part of Google Season of Docs 2023, we worked with technical writer Damilola Oladele to make improvements to Wagtail’s “Getting started” tutorial. Here are the specific changes made as part of this project:
* Revamp the start of the getting started section, with a separate quick install page
* Move the tutorial’s snippets section to come before tags
* Rewrite the getting started tutorial to address identified friction points
* Switch the Getting started tutorial’s snippets example to be more understandable
Thank you to Damilola for his work, and to Google for sponsoring this project.
### Custom template support for `wagtail start`
The `wagtail start` command now supports an optional `--template` argument that allows you to specify a custom project template to use. This is useful if you want to use a custom template that includes additional features or customizations. For more details, see [the project template reference](../reference/project_template.md). This feature was developed by Thibaud Colas.
### Search query boosting on Elasticsearch 6 and above
The `boost` option on `SearchField`, to increase the ranking of search results that match on the specified field, is now respected by Elasticsearch 6 and above. This was previously only supported up to Elasticsearch 5, due to a change in Elasticsearch’s API. This feature was developed by Shohan Dutta Roy.
### Elasticsearch 8 support
This release adds support for Elasticsearch 8. This can be set up by installing a version 8.x release of the `elasticsearch` Python package, and setting `wagtail.search.backends.elasticsearch8` as the search backend. Compatibility updates were contributed by Matt Westcott and Wesley van Lee.
### Extend Stimulus adoption
As part of tackling Wagtail’s technical debt and improving [CSP compatibility](https://github.com/wagtail/wagtail/issues/1288), we have continued extending our usage of Stimulus, based on the plans laid out in [RFC 78: Adopt Stimulus](https://github.com/wagtail/rfcs/pull/78).
* Add support for `attrs` on `FieldPanel` and other panels to aid in custom Stimulus usage (Aman Pandey, Antoni Martyniuk, LB (Ben) Johnston)
* Migrate Tagit initialization to a Stimulus Controller (LB (Ben) Johnston)
* Migrate legacy dropdown implementation to a Stimulus controller (Thibaud Colas)
* Migrate async header search and search with the Task chooser modal to `w-swap`, a Stimulus controller (LB (Ben) Johnston)
* Replace Bootstrap tooltips with a new `w-tooltip` Stimulus controller (LB (Ben) Johnston)
* Migrate dialog instantiation to a new `w-dialog` Stimulus controller (Loveth Omokaro, LB (Ben) Johnston)
* Support dialog template cloning using a new `w-teleport` Stimulus controller (Loveth Omokaro, LB (Ben) Johnston)
### AVIF image support
Wagtail now supports [AVIF](https://en.wikipedia.org/wiki/AVIF), a modern image format. We encourage all site implementers to consider using it to improve the performance of the sites and reduce their carbon footprint. For further details, see [image file format](../advanced_topics/images/image_file_formats.md#image-file-formats), [output image format](../topics/images.md#output-image-format) and [image quality](../topics/images.md#image-quality).
This feature was developed by Aman Pandey as part of the Google Summer of Code program and a [partnership with the Green Web Foundation](https://www.thegreenwebfoundation.org/news/working-with-the-wagtail-community-on-the-summer-of-code/) and Green Coding Berlin, with support from Dan Braghis, Thibaud Colas, Sage Abdullah, Arne Tarara (Green Coding Berlin), and Chris Adams (Green Web Foundation).
### Permissions consolidation
This release includes several changes to permissions, to make them easier to use and maintain, as well as to improve performance.
* Add initial implementation of `PagePermissionPolicy` (Sage Abdullah)
* Refactor `UserPagePermissionsProxy` and `PagePermissionTester` to use `PagePermissionPolicy` (Sage Abdullah, Tidiane Dia)
* Optimise queries in collection permission policies using cache on the user object (Sage Abdullah)
* Prevent ‘choose’ permission from being ignored when looking up ‘choose’, ‘edit’ and ‘delete’ permissions in combination (Sage Abdullah)
* Take user’s permissions into account for image / document counts on the admin dashboard (Sage Abdullah)
* Deprecate `UserPagePermissionsProxy` (Sage Abdullah)
* Refactor GroupPagePermission to use Django’s Permission model (Sage Abdullah)
### Snippet enhancements
We have made several improvements to snippets as part of [RFC 85: Snippets parity with ModelAdmin](https://github.com/wagtail/rfcs/pull/85), ahead of the deprecation of ModelAdmin contrib app.
* Add the ability to export snippets listing via `SnippetViewSet.list_export` (Sage Abdullah)
* Add Inspect view to snippets (Sage Abdullah)
* Reorganise snippets documentation to cover customizations and optional features (Sage Abdullah)
* Add docs for migrating from ModelAdmin to Snippets (Sage Abdullah)
* Purge revisions of non-page models in `purge_revisions` command (Sage Abdullah)
### Other features
* Mark calls to `md5` as not being used for secure purposes, to avoid flagging on FIPS-mode systems (Sean Kelly)
* Return filters from `parse_query_string` as a `QueryDict` to support multiple values (Aman Pandey)
* Explicitly specify `MenuItem.name` for all admin menu and submenu items (Justin Koestinger)
* Add oEmbed provider patterns for YouTube Shorts (e.g. [https://www.youtube.com/shorts/nX84KctJtG0](https://www.youtube.com/shorts/nX84KctJtG0)) and YouTube Live URLs (valnuro, Fabien Le Frapper)
* Add a predictable default ordering of the “Object/Other permissions” in the Group Editing view, allow this [ordering to be customized](../extending/customizing_group_views.md#customizing-group-views-permissions-order) (Daniel Kirkham)
* Implement a new design for chooser buttons with better accessibility (Thibaud Colas)
* Add [`AbstractImage.get_renditions()`](../advanced_topics/images/renditions.md#image-renditions-multiple) for efficient generation of multiple renditions (Andy Babic)
* Phone numbers entered via a link chooser will now have any spaces stripped out, ensuring a valid `href="tel:..."` attribute (Sahil Jangra)
* Auto-select the `StreamField` block when only one block type is declared (Sébastien Corbin)
* Add support for more [advanced Draftail customization APIs](../extending/extending_draftail.md#extending-the-draftail-editor-advanced) (Thibaud Colas)
* Add support for adding [HTML `attrs`](../reference/panels.md#panels-attrs) on `FieldPanel`, `FieldRowPanel`, `MultiFieldPanel`, and others (Aman Pandey, Antoni Martyniuk, LB (Ben) Johnston)
* Change to always cache renditions (Jake Howard)
* Update link/document rich text tooltips for consistency with the inline toolbar (Albina Starykova)
* Increase the contrast between the rich text / StreamField block picker and the page in dark mode (Albina Starykova)
* Change the default WebP quality to 80 to match AVIF (Aman Pandey)
* Adopt optimized Wagtail logo in the admin interface (Albina Starykova)
* Add support for presenting the userbar (Wagtail button) in dark mode (Albina Starykova)
### Bug fixes
* Prevent choosers from failing when initial value is an unrecognized ID such as when moving a page from a location where `parent_page_types` would disallow it (Dan Braghis)
* Move comment notifications toggle to the comments side panel (Sage Abdullah)
* Remove comment button on InlinePanel fields (Sage Abdullah)
* Fix missing link to `UsageView` from `EditView` for snippets (Christer Jensen)
* Prevent lowercase conversions of IndexView column headers (Virag Jain)
* Ensure that `RichText` objects with the same values compare as equal (NikilTn)
* Use `gettext_lazy` on generic model views so that language settings are correctly used (Matt Westcott)
* Prevent JS error when reverting the spinner on a submit button after a validation error (LB (Ben) Johnston)
* Prevent crash when comparing page revisions that include `MultipleChooserPanel` (Matt Westcott)
* Ensure that title and slug continue syncing after entering non-URL-safe characters (LB (Ben) Johnston)
* Ensure that title and slug are synced on keypress, not just on blur (LB (Ben) Johnston)
* Add a more visible active state for side panel toggle buttons (Thibaud Colas)
* Debounce and optimize live preview panel to prevent excessive requests (Sage Abdullah)
* Page listings actions under the “More” dropdown are now accessible for screen reader and keyboard users (Thibaud Colas)
* Bulk actions under the “More” dropdown are now accessible for screen reader and keyboard users (Thibaud Colas)
* Navigation to translations via the locale dropdown is now accessible for screen reader and keyboard users (Thibaud Colas)
* Make it possible for speech recognition users to reveal chooser buttons (Thibaud Colas)
* Use constant-time comparison for image serve URL signatures (Jake Howard)
* Ensure taggit field type-ahead options show correctly in the dark mode theme (Sage Abdullah)
* Fix the lock description message missing the model_name variable when locked only by system (Sébastien Corbin)
* Fix empty blocks created in migration operations (Sandil Ranasinghe)
* Ensure that `gettext_lazy` works correctly when using `verbose_name` on a generic Settings models (Sébastien Corbin)
* Remove unnecessary usage of `innerHTML` when modifying DOM content (LB (Ben) Johnston)
* Avoid `ValueError` when extending `PagesAPIViewSet` and setting `meta_fields` to an empty list (Henry Harutyunyan, Alex Morega)
* Improve accessibility for header search, remove autofocus on page load, advise screen readers that content has changed when results update (LB (Ben) Johnston)
* Fix incorrect override of `PagePermissionHelper.user_can_unpublish_obj()` in ModelAdmin (Sébastien Corbin)
* Prevent memory exhaustion when updating a large number of image renditions (Jake Howard)
* Add missing Time Zone conversions and date formatting throughout the admin (Stefan Hammer)
* Ensure that audit logs and revisions consistently use UTC and add migration for existing entries (Stefan Hammer)
* Make sure “critical” buttons have enough color contrast in dark mode (Albina Starykova)
* Improve visibility of scheduled publishing errors in status side panel (Sage Abdullah)
* Avoid N+1 queries in users index view (Tidiane Dia)
* Use a theme-agnostic color token for read-only panels support in dark mode (Thibaud Colas)
* Ensure collapsible StreamBlocks expand as necessary to show validation errors (Storm Heg)
* Ensure userbar dialog can sit above other website content (LB (Ben) Johnston)
* Fix preview panel loading issues (Sage Abdullah)
* Fix `search_promotions` `0004_copy_queries` migration for long-lived Wagtail instances (Sage Abdullah)
* Guard against `TypeError` in `0088_fix_log_entry_json_timestamps` migration (Sage Abdullah)
* Add migration to replace JSON null values with empty objects in log entries’ data (Sage Abdullah)
* Fix typo in the `page_header_buttons` template tag when accessing the context’s request object (Robert Rollins)
### Documentation
* Document how to add non-ModelAdmin views to a `ModelAdminGroup` (Onno Timmerman)
* Document how to add StructBlock data to a StreamField (Ramon Wenger)
* Update ReadTheDocs settings to v2 to resolve urllib3 issue in linkcheck extension (Thibaud Colas)
* Update documentation for `log_action` parameter on `RevisionMixin.save_revision` (Christer Jensen)
* Update color customization guidance to include theme-agnostic options (Thibaud Colas)
* Mark LTS releases in release note page titles (Thiago C. S. Tioma)
* Revise main Getting started tutorial for clarity (Kevin Chung (kev-odin))
* Update the [deployment documentation](../deployment/index.md#deployment-guide) page and remove outdated information (Jake Howard)
* Add more items to performance page regarding pre-fetching images and frontend caching (Jake Howard)
* Add docs for managing stored queries in `searchpromotions` (Scott Foster)
### Maintenance
* Removed support for Python 3.7 (Dan Braghis)
* Switch to ruff for flake8 / isort code checking (Oliver Parker)
* Deprecate `insert_editor_css` in favour of `insert_global_admin_css` (Ester Beltrami)
* Optimise use of `specific` on Task and TaskState (Matt Westcott)
* Use table UI component for workflow task index view (Matt Westcott)
* Make header search available on generic index view (Matt Westcott)
* Update pagination behavior to reject out-of-range / invalid page numbers (Matt Westcott)
* Remove color tokens which are duplicates / unused (Thibaud Colas)
* Add tests to help with maintenance of theme color tokens (Thibaud Colas)
* Split out a base listing view from generic index view (Matt Westcott)
* Update type hints in admin/ui/components.py so that `parent_context` is mutable (Andreas Nüßlein)
* Optimise the Settings context processor to avoid redundantly finding a Site to improve cache ratios (Jake Howard)
* Convert page listing to a class-based view (Matt Westcott)
* Clean up page reports and type usage views to be independent of page listing views (Matt Westcott)
* Refactor “More” dropdowns, locale selector, “Switch locales”, page actions, to use the same dropdown component (Thibaud Colas)
* Convert the `CONTRIBUTORS` file to Markdown (Dan Braghis)
* Move `django-filter` version upper bound to v23 (Yuekui)
* Update Pillow dependency to allow 10.x, only include support for >= 9.1.0 (Yuekui)
* Replace ModelAdmin history header human readable date template tag (LB (Ben) Johnston)
* Update uuid to v9 and Jest to v29, with `jest-environment-jsdom` and new snapshot format (LB (Ben) Johnston)
* Update test cases producing undesirable console output due to missing mocks, uncaught errors, warnings (LB (Ben) Johnston)
* Remove unused snippets \_header_with_history.html template (Thibaud Colas)
* Migrate away from using the `"wagtailadmin/shared/field_as_li.html"` template include (Storm Heg)
* Upgrade documentation theme `sphinx_wagtail_theme` to v6.1.1 which includes multiple styling fixes and always visible code copy buttons (LB (Ben) Johnston)
* Don’t update the reference index while deleting it (Andy Chosak)
## Upgrade considerations - changes affecting all projects
### Search within chooser interfaces requires `AutocompleteField` for full functionality
In Wagtail 4.2, the search bar within snippet chooser interfaces (and custom choosers created via `ChooserViewSet`) returned results for partial word matches - for example, a search for “wagt” would return results containing “Wagtail” - if this was supported by the search backend in use, and at least one `AutocompleteField` was present in the model’s `search_fields` definition. Otherwise, it would fall back to only matching on complete words. In Wagtail 5.0, this fallback behavior was removed, and consequently a model with no `AutocompleteField`s in place would return no results.
As of Wagtail 5.1.2, the fallback behavior has been restored. Nevertheless, it is strongly recommended that you add `AutocompleteField` to your models’ `search_fields` definitions, to ensure that users can receive search results continuously as they type. For example:
```python
from wagtail.search import index
# ... other imports
@register_snippet
class MySnippet(index.Indexed, models.Model):
search_fields = [
index.SearchField("name"),
index.AutocompleteField("name"),
]
```
### `GroupPagePermission` now uses Django’s `Permission` model
The `GroupPagePermission` model that is responsible for assigning page permissions to groups now uses Django’s `Permission` model instead of a custom string. This means that the `permission_type` `CharField` has been deprecated and replaced with a `permission` `ForeignKey` to the `Permission` model.
In addition to this, “edit” permissions now use the term `change` within the code. As a result, `GroupPagePermission`s that were previously recorded with `permission_type="edit"` are now recorded with a `Permission` object that has the `codename="change_page"` and a `content_type` that points to the `Page` model. Any permission checks that are done using `PagePermissionPolicy` should also use `change` instead of `edit`.
If you have any fixtures for the `GroupPagePermission` model, you will need to update them to use the new `Permission` model. For example, if you have a fixture that looks like this:
```json
{
"pk": 11,
"model": "wagtailcore.grouppagepermission",
"fields": {
"group": ["Event moderators"],
"page": 12,
"permission_type": "edit"
}
}
```
Update it to use a natural key for the `permission` field instead of the `permission_type` field:
```json
{
"pk": 11,
"model": "wagtailcore.grouppagepermission",
"fields": {
"group": ["Event moderators"],
"page": 12,
"permission": ["change_page", "wagtailcore", "page"]
}
}
```
If you have any code that creates `GroupPagePermission` objects, you will need to update it to use the `Permission` model instead of the `permission_type` string. For example, if you have code that looks like this:
```python
from wagtail.models import GroupPagePermission
permission = GroupPagePermission(group=group, page=page, permission_type="edit")
permission.save()
```
Update it to use the `Permission` model instead:
```python
from django.contrib.auth.models import Permission
from wagtail.models import GroupPagePermission
permission = GroupPagePermission(
group=group,
page=page,
permission=Permission.objects.get(content_type__app_label="wagtailcore", codename="change_page"),
)
permission.save()
```
During the deprecation period, the `permission_type` field will still be available on the `GroupPagePermission` model and is used to automatically populate empty `permission` field as part of a system check. The `permission_type` field will be removed in Wagtail 6.0.
### The default ordering of Group Editing Permissions models has changed
The ordering for “Object permissions” and “Other permissions” now follows a predictable order equivalent to Django’s default `Model` ordering.
This will be different to the previous indeterminate ordering.
The default ordering is now `["content_type__app_label", "content_type__model"]`. See [Customizing the group editor permissions ordering](../extending/customizing_group_views.md#customizing-group-views-permissions-order) for details on how to customize this order.
### JSON-timestamps stored in `ModelLogEntry` and `PageLogEntry` are now ISO-formatted and UTC
Previously, timestamps stored in the “data”-`JSONField` of `ModelLogEntry` and `PageLogEntry` have used the custom python format `%d %b %Y %H:%M`. Additionally, the `"go_live_at"` timestamp had been stored with the configured local timezone, instead of UTC.
This has now been fixed, all timestamps are now stored as UTC, and because the “data”-`JSONField` now uses Django’s `DjangoJSONEncoder`, those `datetime` objects are now automatically converted to the ISO format. This release contains a new migration `0088_fix_log_entry_json_timestamps` which converts all existing timestamps used by Wagtail to the new format.
If you’ve developed your own subclasses of `ModelLogEntry`, `PageLogEntry` or `BaseLogEntry`, or used those existing models to create custom log entries, and you’ve stored timestamps similarly to Wagtail’s old implementation (using `strftime("%d %b %Y %H:%M")`). You may want to adapt the storage of those timestamps to a consistent format too.
There are probably three places in your code, which have to be changed:
1. Creation: Instead of using `strftime("%d %b %Y %H:%M")`, you can now store the datetime directly in the “data” field. We’ve implemented a new helper `wagtail.utils.timestamps.ensure_utc()`, which ensures the correct timezone (UTC).
2. Display: To display the timestamp in the user’s timezone and format with a `LogFormatter`, we’ve created utils to parse (`wagtail.utils.timestamps.parse_datetime_localized()`) and render (`wagtail.utils.timestamps.render_timestamp()`) those timestamps. Look at the existing formatters [here](https://github.com/wagtail/wagtail/blob/main/wagtail/wagtail_hooks.py).
3. Migration: You can use the code of the above migration ([source](https://github.com/wagtail/wagtail/blob/main/wagtail/migrations/0088_fix_log_entry_json_timestamps.py)) as a guideline to migrate your existing timestamps in the database.
### Image Renditions are now cached by default
Wagtail will try to use the cache called “renditions”. If no such cache exists, it will fall back to using the default cache.
You can [configure the “renditions” cache](../advanced_topics/performance.md#custom-image-renditions-cache) to use a different cache backend or to provide
additional configuration parameters.
## Upgrade considerations - deprecation of old functionality
### Removed support for Python 3.7
Python 3.7 is no longer supported as of this release; please upgrade to Python 3.8 or above before upgrading Wagtail.
### Pillow dependency update
Wagtail no longer supports Pillow versions below `9.1.0`.
### Elasticsearch 5 and 6 backends are deprecated
The Elasticsearch 5 and 6 search backends are deprecated and will be removed in a future release; please upgrade to Elasticsearch 7 or above.
### `insert_editor_css` hook is deprecated
The `insert_editor_css` hook has been deprecated. The `insert_global_admin_css` hook has the same functionality, and all uses of `insert_editor_css` should be changed to `insert_global_admin_css`.
### `wagtail.contrib.modeladmin` is deprecated
As part of the [RFC 85: Snippets parity with ModelAdmin](https://github.com/wagtail/rfcs/pull/85) implementation, the `wagtail.contrib.modeladmin` app is deprecated. To manage non-page models in Wagtail, use [`wagtail.snippets`](../topics/snippets/index.md#snippets) instead.
If you still rely on ModelAdmin, use the separate [wagtail-modeladmin](https://github.com/wagtail-nest/wagtail-modeladmin) package. The `wagtail.contrib.modeladmin` module will be removed in a future release.
### `UserPagePermissionsProxy` is deprecated
The undocumented `wagtail.models.UserPagePermissionsProxy` class is deprecated.
If you use the `.for_page(page)` method of the class to get a `PagePermissionTester` instance, you can replace it with `page.permissions_for_user(user)`.
If you use the other methods, they can be replaced via the `wagtail.permission_policies.pages.PagePermissionPolicy` class. The following is a list of the `PagePermissionPolicy` equivalent of each method:
```python
from wagtail.models import UserPagePermissionsProxy
from wagtail.permission_policies.pages import PagePermissionPolicy
# proxy = UserPagePermissionsProxy(user)
permission_policy = PagePermissionPolicy()
# proxy.revisions_for_moderation()
permission_policy.revisions_for_moderation(user)
# proxy.explorable_pages()
permission_policy.explorable_instances(user)
# proxy.editable_pages()
permission_policy.instances_user_has_permission_for(user, "change")
# proxy.can_edit_pages()
permission_policy.instances_user_has_permission_for(user, "change").exists()
# proxy.publishable_pages()
permission_policy.instances_user_has_permission_for(user, "publish")
# proxy.can_publish_pages()
permission_policy.instances_user_has_permission_for(user, "publish").exists()
# proxy.can_remove_locks()
permission_policy.user_has_permission(user, "unlock")
```
The `UserPagePermissionsProxy` object that is available in page’s `ActionMenuItem` context as `user_page_permissions` (which might be used as part of a `register_page_action_menu_item` hook) has been deprecated. In cases where the page object is available (e.g. the page edit view), the `PagePermissionTester` object stored as the `user_page_permissions_tester` context variable can still be used.
The `UserPagePermissionsProxy` object that is available in the template context as `user_page_permissions` as a side-effect of the `page_permissions` template tag has also been deprecated.
If you use the `user_page_permissions` context variable or use the `UserPagePermissionsProxy` class directly, make sure to replace it either with the `PagePermissionTester` or the `PagePermissionPolicy` equivalent.
### `get_pages_with_direct_explore_permission`, `get_explorable_root_page`, and `users_with_page_permission` are deprecated
The undocumented `get_pages_with_direct_explore_permission` and `get_explorable_root_page` functions in `wagtail.admin.navigation` are deprecated. They can be replaced with `PagePermissionPolicy().instances_with_direct_explore_permission(user)` and `PagePermissionPolicy().explorable_root_instance(user)`, respectively.
The undocumented `users_with_page_permission` function in `wagtail.admin.auth` is also deprecated. It can be replaced with `PagePermissionPolicy().users_with_permission_for_instance(action, page, include_superusers)`.
### Shared include `wagtailadmin/shared/last_updated.html` is no longer available
The undocumented shared include `wagtailadmin/shared/last_updated.html` is no longer available as it used the legacy Bootstrap tooltips and was not accessible. If you need to achieve a similar output, an element that shows a simple date with a tooltip for the full date, use the `human_readable_date` template tag instead.
#### Before
```html+django
{% include "wagtailadmin/shared/last_updated.html" with last_updated=my_model.timestamp %}
```
#### After
```html+django
{% load wagtailadmin_tags %}
{% human_readable_date my_model.timestamp %}
```
### Shared include `field_as_li.html` will be removed
The documented include `"wagtailadmin/shared/field_as_li.html"` will be removed in a future release, if being used it will need to be replaced with `"wagtailadmin/shared/field.html"` wrapped within `li` tags.
#### Before
```html+django
{% include "wagtailadmin/shared/field_as_li.html" %}
```
#### After
```html+django
{% include "wagtailadmin/shared/field.html" %}
```
## Upgrade considerations - changes affecting Wagtail customizations
### Tag (Tagit) field usage now relies on data attributes
The `AdminTagWidget` widget has now been migrated to a Stimulus controller, if using this widget in Python, no changes are needed to adopt the new approach.
If the widget is being instantiated in JavaScript or HTML with the global util `window.initTagField`, this undocumented util should be replaced with the new `data-*` attributes approach. Additionally, any direct usage of the jQuery widget in JavaScript (e.g. `$('#my-element).tagit()`) should be removed.
The global util will be removed in a future release. It is recommended that the documented `AdminTagWidget` be used. However, if you need to use the JavaScript approach you can do this with the following example.
#### Old syntax
```html
```
#### New syntax
```html
```
Note: The `data-w-tag-options-value` is a JSON object serialized into string. Django’s HTML escaping will handle it automatically when you use the `AdminTagWidget`, but if you are manually writing the attributes, be sure to use quotation marks correctly.
### Header searching now relies on data attributes
Previously the header search relied on inline scripts and a `window.headerSearch` global to activate the behavior. This has now changed to a data attributes approach and the window global usage will be removed in a future major release.
If you are using the documented Wagtail [viewsets](../reference/viewsets.md#viewsets-reference), Snippets or `ModelAdmin` approaches to building custom admin views, there should be no change required.
If you are using the shared header template include for a custom search integration, here’s how to adopt the new approach.
#### Header include before
```html+django
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags %}
{% block extra_js %}
{{ block.super }}
{% endblock %}
{% block content %}
{% include "wagtailadmin/shared/header.html" with title="my title" search_url="myapp:index" %}
... other content
{% endblock %}
```
#### Header include after
Note: No need for `extra_js` usage at all.
```html+django
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags %}
{% block content %}
{% url 'myapp:search_results' as search_results_url %}
{% include "wagtailadmin/shared/header.html" with title="my title" search_url="myapp:index" search_results_url=search_results_url search_target="#my-results" %}
... other content
{% endblock %}
```
Alternatively, if you have customizations that manually declare or override `window.headerSearch`, here’s how to adopt the new approach.
#### Manual usage before
```html
```
#### Manual usage after
```html
```
### Tooltips now rely on new data attributes
The undocumented Bootstrap jQuery tooltip widget is no longer in use, you will need to update any HTML that is using these attributes to the new syntax.
```html
Label
Label
```
### Dialog hide/show custom events name change
The undocumented client-side Custom Event handling for dialog showing & hiding will change in a future release.
| Action | Old event | New event |
|----------|----------------|-----------------|
| Show | `wagtail:show` | `w-dialog:show` |
| Hide | `wagtail:hide` | `w-dialog:hide` |
Additionally, two new events will be dispatched when the dialog visibility changes.
| Action | Event name |
|----------|-------------------|
| Show | `w-dialog:shown` |
| Hide | `w-dialog:hidden` |
### Shared template `.../tables/attrs.html` has been renamed to `.../shared/attrs.html`
The undocumented shared template for rendering a dict of `attrs` to HTML, similar to Django form widgets, has been renamed.
| | Template location | Usage with `include` |
|-----|-----------------------------------------------------------|------------------------------------------------------------------------|
| Old | `wagtail/admin/templates/wagtailadmin/tables/attrs.html ` | `{% include "wagtailadmin/tables/attrs.html" with attrs=link_attrs %}` |
| New | `wagtail/admin/templates/wagtailadmin/shared/attrs.html` | `{% include "wagtailadmin/shared/attrs.html" with attrs=link_attrs %}` |
# 5.2.1.html.md
# Wagtail 5.2.1 release notes
*November 16, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Add a fallback background for the editing preview iframe for sites without a background (Ian Price)
* Remove search logging from project template so that new projects without the search promotions module will not error (Matt Westcott)
* Ensure text only email notifications for updated comments do not escape HTML characters (Rohit Sharma)
* Use logical OR operator to combine search fields for Django ORM in generic IndexView (Varun Kumar)
* Ensure that explorer_results views fill in the correct next_url parameter on action URLs (Matt Westcott)
* Fix crash when accessing the history view for a translatable snippet (Sage Abdullah)
* Prevent upload of SVG images from failing when image feature detection is enabled (Joshua Munn)
* Fix crash when using the locale switcher on the snippets create view (Sage Abdullah)
* Fix performance regression on reports from calling `decorate_paginated_queryset` before pagination / filtering (Alex Tomkins)
* Make searching on specific fields work correctly on Elasticsearch when boost is in use (Matt Westcott)
* Prevent snippet permission post-migrate hook from failing on multiple database configurations (Joe Tsoi)
* Reinstate ability to filter on page type when searching on an empty query (Sage Abdullah)
* Prevent error on locked pages report when a user has locked multiple pages (Matt Westcott)
### Documentation
* Fix code example for `{% picture ... as ... %}` template tag (Rezyapkin)
# 5.2.2.html.md
# Wagtail 5.2.2 release notes
*December 6, 2023*
> * [What’s new](#what-s-new)
## What’s new
### Django 5.0 support
This release adds support for Django 5.0.
### Bug fixes
* Use a visible border and background color to highlight active formatting in the rich text toolbar (Cassidy Pittman)
* Ensure image focal point box can be removed (Gunnar Scherf)
* Ensure that Snippets search results correctly use the `index_results.html` or `index_results_template_name` override on initial load (Stefan Hammer)
* Avoid error when attempting to moderate a page drafted by a now deleted user (Dan Braghis)
* Ensure workflow dashboard panels work when the page/snippet is missing (Sage Abdullah)
* Prevent custom controls from stacking on top of the comment button in Draftail toolbar (Ben Morse)
# 5.2.3.html.md
# Wagtail 5.2.3 release notes
*January 23, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent a ValueError with `FormSubmissionsPanel` on Django 5.0 when creating a new form page (Matt Westcott)
* Specify telepath 0.3.1 as the minimum supported version, for Django 5.0 compatibility (Matt Westcott)
# 5.2.4.html.md
# Wagtail 5.2.4 release notes
*April 3, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent TitleFieldPanel from raising an error when the slug field is missing or read-only (Rohit Sharma)
* Fix pagination links on model history and usage views (Matt Westcott)
* Fix crash when accessing workflow reports with a deleted snippet (Sage Abdullah)
* Prevent error on submitting an empty search in the admin under Elasticsearch (Maikel Martens)
# 5.2.5.html.md
# Wagtail 5.2.5 release notes
*May 1, 2024*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### Bug fixes
* 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)
* Provide [`convert_mariadb_uuids`](../reference/management_commands.md#convert-mariadb-uuids) management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
## Upgrade considerations
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
Django 5.0 introduces support for MariaDB’s native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](../reference/management_commands.md#convert-mariadb-uuids) management command (available as of Wagtail 5.2.5) after upgrading:
```sh
./manage.py convert_mariadb_uuids
```
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
# 5.2.6.html.md
# Wagtail 5.2.6 release notes
*July 11, 2024*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2024-39317: Regular expression denial-of-service via search query parsing
This release addresses a denial-of-service vulnerability in Wagtail. A bug in Wagtail’s [`parse_query_string`](../topics/search/searching.md#wagtailsearch-query-string-parsing) would result in it taking a long time to process suitably crafted inputs. When used to parse sufficiently long strings of characters without a space, `parse_query_string` would take an unexpectedly large amount of time to process, resulting in a denial of service.
In an initial Wagtail installation, the vulnerability can be exploited by any Wagtail admin user. It cannot be exploited by end users. If your Wagtail site has a custom search implementation which uses parse_query_string, it may be exploitable by other users (e.g. unauthenticated users).
Many thanks to Jake Howard for reporting and fixing this issue. For further details, please see [the CVE-2024-39317 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-jmp3-39vp-fwg8).
### Bug fixes
* Fix image preview when Willow optimizers are enabled (Alex Tomkins)
### Maintenance
* Remove django-pattern-library upper bound in testing dependencies (Sage Abdullah)
# 5.2.7.html.md
# Wagtail 5.2.7 release notes
*November 1, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent multiple URLs from being combined into one when pasting links into a rich text input (Thibaud Colas)
* Fix error on workflow settings view with multiple snippet types assigned to the same workflow on Postgres (Sage Abdullah)
# 5.2.8.html.md
# Wagtail 5.2.8 release notes
*February 3, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent database error when calling permission_order.register on app ready (Daniel Kirkham, Matt Westcott)
* Handle StreamField migrations where the field value is null (Joshua Munn)
* Prevent `StreamChildrenToListBlockOperation` from duplicating data across multiple StreamField instances (Joshua Munn)
* Prevent error on lazily loading StreamField blocks after the stream has been modified (Stefan Hammer)
* Prevent syntax error on MySQL search when query includes symbols (Matt Westcott)
# 5.2.html.md
# Wagtail 5.2 (LTS) release notes
*November 1, 2023*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - deprecation of old functionality](#upgrade-considerations-deprecation-of-old-functionality)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
Wagtail 5.2 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### Redesigned page listing view

The page explorer listing view has been redesigned to allow improved navigation and searching. This feature was developed by Ben Enright, Matt Westcott, Thibaud Colas and Sage Abdullah.
### OpenSearch support
[OpenSearch](https://opensearch.org/) is now formally supported as an alternative to Elasticsearch. For configuration details, see [OpenSearch configuration](../topics/search/backends.md#opensearch). This feature was developed by Matt Westcott.
### Responsive & multi-format images with the picture tag
Wagtail has new template tags to reduce the loading time and environmental footprint of images:
* The `picture` tag generates images in multiple formats and-or sizes in one batch, creating an HTML `` tag.
* The `srcset_image` tag generates images in multiple sizes, creating an ` ` tag with a `srcset` attribute.
As an example, the `picture` tag allows generating six variants of an image in one go:
```html+django
{% picture page.photo format-{avif,webp,jpeg} width-{400,800} sizes="80vw" %}
```
This outputs:
```html
```
We expect those changes to greatly reduce the weight of images for all Wagtail sites. We encourage all site implementers to consider using them to improve the performance of the sites and reduce their carbon footprint. For further details, For more details, see [Multiple formats](../topics/images.md#multiple-formats) and [Responsive images](../topics/images.md#responsive-images). Those new template tags are also supported in Jinja templates, see [Jinja2 template support](../reference/jinja2.md#jinja2) for the Jinja API.
This feature was developed by Paarth Agarwal and Thibaud Colas as part of the Google Summer of Code program and a [partnership with the Green Web Foundation](https://www.thegreenwebfoundation.org/news/working-with-the-wagtail-community-on-the-summer-of-code/) and Green Coding Berlin, with support from Dan Braghiș, Thibaud Colas, Sage Abdullah, Arne Tarara (Green Coding Berlin), and Chris Adams (Green Web Foundation). We also thank Aman Pandey for introducing [AVIF support](../advanced_topics/images/image_file_formats.md#image-file-formats) in Wagtail 5.1, Andy Babic for creating [`AbstractImage.get_renditions()`](../advanced_topics/images/renditions.md#image-renditions-multiple) in the same release; and Storm Heg, Mitchel Cabuloy, Coen van der Kamp, Tom Dyson, and Chris Lawton for their feedback on [RFC 71](https://github.com/wagtail/rfcs/pull/71).
### Support extending Wagtail client-side with Stimulus
Wagtail now officially supports client-side admin customizations with [Stimulus](https://stimulus.hotwired.dev/). The developer documentation has a dedicated page about [Extending client-side behavior](../extending/extending_client_side.md#extending-client-side). This covers fundamental topics of client-side extensibility, such as:
* Adding custom JavaScript
* Extending with DOM events and Wagtail’s custom DOM events
* Extending with Stimulus
* Extending with React
Thank you to core contributor LB (Ben) Johnston for writing this documentation.
### `ModelViewSet` improvements
Several features from [`SnippetViewSet`](../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet) have been implemented in [`ModelViewSet`](../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet), allowing you to use them without registering your models as snippets. For more details on using `ModelViewSet`, refer to [Generic views](../extending/generic_views.md#generic-views).
* Move `SnippetViewSet` menu registration mechanism to base `ViewSet` class (Sage Abdullah)
* Move `SnippetViewSet` template override mechanism to `ModelViewSet` (Sage Abdullah)
* Move `SnippetViewSet.list_display` to `ModelViewSet` (Sage Abdullah)
* Move `list_filter`, `filterset_class`, `search_fields`, `search_backend_name`, `list_export`, `export_filename`, `list_per_page`, and `ordering` from `SnippetViewSet` to `ModelViewSet` (Sage Abdullah, Cynthia Kiser)
* Add default header titles to generic `IndexView` and `CreateView` (Sage Abdullah)
* Add the ability to use filters and to export listings in generic `IndexView` (Sage Abdullah)
* Add generic `UsageView` to `ModelViewSet` (Sage Abdullah)
* Add generic `InspectView` to `ModelViewSet` (Sage Abdullah)
* Extract generic `HistoryView` from snippets and add it to `ModelViewSet` (Sage Abdullah)
* Extract generic breadcrumbs functionality from page breadcrumbs (Sage Abdullah)
* Add breadcrumbs support to custom `ModelViewSet` views (Sage Abdullah)
* Allow `ModelViewSet` to be used with models that have non-integer primary keys (Sage Abdullah)
* Enable reference index tracking for models registered with `ModelViewSet` (Sage Abdullah)
In addition, the following new features have been added to the generic admin views as part of `ModelViewSet`, which can also be used with `SnippetViewSet`.
* Allow overriding `IndexView.export_headings` via `ModelViewSet` (Christer Jensen, Sage Abdullah)
* Add the ability to define listing buttons on generic `IndexView` (Sage Abdullah)
### User interface refinements
Several tweaks have been made to the admin user interface which we hope will make it easier to use.
* Show the full first published at date within a tooltip on the Page status sidebar on the relative date (Rohit Sharma)
* Do not render minimap if there are no panel anchors (Sage Abdullah)
* Use dropdown buttons on listings in dashboard panels (Sage Abdullah)
* Implement breadcrumbs design refinements (Thibaud Colas)
* Add support for Shift + Click behavior in form submissions and simple translations submissions (LB (Ben) Johnston)
* Improve filtering of audit logging based on the user’s permissions (Stefan Hammer)
### External links in promoted search results
Promoted search result entries can now use an external URL along with custom link text, instead of linking to a page within Wagtail. This makes it easier to manage promoted content across multiple websites. Thank you to TopDevPros, and Brad Busenius from University of Chicago Library.
### Other features
* Add support for Python 3.12 (Matt Westcott)
* Add [`wagtailcache`](../topics/writing_templates.md#wagtailcache) and [`wagtailpagecache`](../topics/writing_templates.md#wagtailpagecache) template tags to ensure previewing Pages or Snippets will not be cached (Jake Howard)
* Always set help text element ID for form fields with help text in `field.html` template (Sage Abdullah)
* When copying a page or creating an alias, copy its view restrictions to the destination (Sandeep Choudhary, Suyash Singh)
* Support pickling of StreamField values (pySilver)
* Remove `wagtail.publish` log action on aliases when they are created from live source pages or the source page is published (Dan Braghis)
* Remove `wagtail.unpublish` log action on aliases when source page is unpublished (Dan Braghis)
* Add compare buttons to workflow dashboard panel (Matt Westcott)
* Support specifying a `get_object_list` method on `ChooserViewSet` (Matt Westcott)
* Add [`linked_fields` mechanism on chooser widgets](../extending/generic_views.md#chooser-viewsets-limiting-choices) to allow choices to be limited by fields on the calling page (Matt Westcott)
* Add support for merging cells within `TableBlock` with the [`mergedCells` option](../reference/contrib/table_block.md#table-block-options) (Gareth Palmer)
* When adding a panel within `InlinePanel`, focus will now shift to that content similar to `StreamField` (Faishal Manzar)
* Add support for `placement` in `human_readable_date` the tooltip template tag (Rohit Sharma)
* Support passing extra context variables via the `{% component %}` tag (Matt Westcott)
* Allow subclasses of `PagesAPIViewSet` override default [Page model via the `model`](../advanced_topics/api/v2/configuration.md#api-v2-configure-endpoints) attribute (Neeraj Yetheendran, Herbert Poul)
* Add support for subject and body in the Email link chooser form (TopDevPros, Alexandre Joly)
* Add a visual progress bar to the output of the `wagtail_update_image_renditions` management command (Faishal Manzar)
* Increase the read buffer size to improve efficiency and performance when generating file hashes for document or image uploads, use `hashlib.file_digest` if available (Python 3.11+) (Jake Howard)
* API ordering now [supports multiple fields](../advanced_topics/api/v2/usage.md#api-v2-usage-ordering) (Rohit Sharma, Jake Howard)
* Pass block value to `Block.get_template` to allow varying template based on value (Florian Delizy)
* Add [`InlinePanel` DOM events](../reference/panels.md#inline-panel-events) for when ready and when items added or removed (Faishal Manzar)
* Support `Filter` instances as input for [`AbstractImage.get_renditions()`](../advanced_topics/images/renditions.md#image-renditions-multiple) (Thibaud Colas)
* Improve error messages for image template tags (Thibaud Colas)
* The [`purge_revisions` management command](../reference/management_commands.md#purge-revisions) now respects revisions that have an `on_delete=PROTECT` foreign key relation and won’t delete them (Neeraj P Yetheendran, Meghana Reddy, Sage Abdullah, Storm Heg)
### Bug fixes
* Ensure that StreamField’s `FieldBlock`s correctly set the `required` and `aria-describedby` attributes (Storm Heg)
* Avoid an error when the moderation panel (admin dashboard) contains both snippets and private pages (Matt Westcott)
* When deleting collections, ensure the collection name is correctly shown in the success message (LB (Ben) Johnston)
* Filter out comments on Page editing counts that do not correspond to a valid field / block path on the page such as when a field has been removed (Matt Westcott)
* Allow `PublishMenuItem` to more easily support overriding its label via `construct_page_action_menu` (Sébastien Corbin)
* Allow locale selection when creating a page at the root level (Sage Abdullah)
* Ensure the admin login template correctly displays all `non_fields_errors` for any custom form validation (Sébastien Corbin)
* Ensure ‘mark as active’ label in workflow bulk action set active form can be translated (Rohit Sharma)
* Ensure the panel title for a user’s settings correctly reflects the `WAGTAIL_EMAIL_MANAGEMENT_ENABLED` setting by not showing ‘email’ if disabled (Omkar Jadhav)
* Update Spotify oEmbed provider URL parsing to resolve correctly (Dhrűv)
* Update link colors within help blocks to meet accessible contrast requirements (Rohit Sharma)
* Ensure the search promotions popular search terms picker correctly refers to the correct model (LB (Ben) Johnston)
* Correctly quote non-numeric primary keys on snippet inspect view (Sage Abdullah)
* Prevent crash on snippet inspect view when displaying a null foreign key to an image (Sage Abdullah)
* Ensure that pages in moderation show as “Live + In Moderation” in the page explorer rather than “Live + Draft” (Sage Abdullah)
* Prevent error when updating reference index for objects with a lazy ParentalKey-related object (Chris Shaw)
* Ignore conflicts when inserting reference index entries to prevent race conditions causing uniqueness errors (Chris Shaw)
* Populate the correct return value when creating a new snippet within the snippet chooser (claudobahn)
* Reinstate missing filter by page type on page search (Matt Westcott)
* Ensure very long words can wrap when viewing saved comments (Chiemezuo Akujobi)
* Avoid forgotten password link text conflicting with the supplied aria-label (Thibaud Colas)
* Fix log message to record the correct restriction type when removing a page view restriction (Rohit Sharma, Hazh. M. Adam)
* Avoid potential race condition with new Page subscriptions on the edit view (Alex Tomkins)
* Use the correct action log when creating a redirect (Thibaud Colas)
* Ensure that all password fields consistently allow leading & trailing whitespace (Neeraj P Yetheendran)
### Documentation
* Expand documentation on using `ViewSet` and `ModelViewSet` (Sage Abdullah)
* Document `WAGTAILADMIN_BASE_URL` on [“Integrating Wagtail into a Django project”](../getting_started/integrating_into_django.md) page (Shreshth Srivastava)
* Replace incorrect screenshot for authors listing on tutorial (Shreshth Srivastava)
* Add documentation for building [non-model-based choosers using the *queryish* library](../extending/generic_views.md#chooser-viewsets-non-model-data) (Matt Westcott)
* Fix incorrect tag library import on focal points example (Hatim Makki Hoho)
* Add reminder about including your custom Draftail feature in any overridden `WAGTAILADMIN_RICH_TEXT_EDITORS` setting (Charlie Sue)
* Mention the need to install `python3-venv` on Ubuntu (Brian Mugo)
* Document the use of the Google developer documentation style guide in documentation (Damilola Oladele)
* Fix Inconsistent URL Format in Getting Started tutorial (Olumide Micheal)
* Add more extensive documentation for the [`permission` kwarg support in Panels](../reference/panels.md#panels-permissions) (LB (Ben) Johnston)
* Update all `FieldPanel('title')` examples to use the recommended `TitleFieldPanel('title')` panel (Chinedu Ihedioha)
### Maintenance
#### Stimulus adoption
As part of our [adoption of Stimulus](https://github.com/wagtail/rfcs/blob/main/text/078-adopt-stimulus-js.md), in addition to the new documentation, we have migrated several existing components to the framework. Thank you to our core contributor LB who oversees this project, and to all contributors who refactored specific components.
* Migrate form submission listing checkbox toggling to the shared `w-bulk` Stimulus implementation (LB (Ben) Johnston)
* Migrate the editor unsaved messages popup to be driven by Stimulus using the shared `w-message` controller (LB (Ben) Johnston, Hussain Saherwala)
* Migrate all other `data-tippy` HTML attribute usage to the Stimulus data-\*-value attributes for w-tooltip & w-dropdown (Subhajit Ghosh, LB (Ben) Johnston)
* Migrate select all on focus/click behavior to Stimulus, used on the image URL generator (Chiemezuo Akujobi)
* Add support for a `reset` method to support Stimulus driven dynamic field resets via the `w-action` controller (Chiemezuo Akujobi)
* Add support for a `notify` target on the Stimulus dialog for dispatching events internally (Chiemezuo Akujobi)
* Migrate publishing schedule dialog field resets to Stimulus (Chiemezuo Akujobi)
#### Other maintenance
* Fix snippet search test to work on non-fallback database backends (Matt Westcott)
* Update ESLint, Prettier & Jest npm packages (LB (Ben) Johnston)
* Add npm scripts for TypeScript checks and formatting SCSS files (LB (Ben) Johnston)
* Run tests in parallel in some of the CI setup (Sage Abdullah)
* Remove unused WorkflowStatus view, urlpattern, and workflow-status.js (Storm Heg)
* Add support for options/attrs in Telepath widgets so that attrs render on the created DOM (Storm Heg)
* Update pre-commit hooks to be in sync with latest changes to ESLint & Prettier for client-side changes (Storm Heg)
* Add `WagtailTestUtils.get_soup()` method for testing HTML content (Storm Heg, Sage Abdullah)
* Allow `ViewSet` subclasses to customize `url_prefix` and `url_namespace` logic (Matt Westcott)
* Simplify `SnippetViewSet` registration code (Sage Abdullah)
* Rename groups `IndexView.results_template_name` to `results.html` (Sage Abdullah)
* Allow viewsets to define a common set of view kwargs (Matt Westcott)
* Do not use jest inside `stubs.js` to prevent Storybook from crashing (LB (Ben) Johnston)
* Refactor snippets templates to reuse the shared `slim_header.html` template (Sage Abdullah)
* Refactor `slim_header.html` template to reduce code duplication (Sage Abdullah)
* Upgrade Willow to v1.6.2 to support MIME type data without reliance on `imghdr` (Jake Howard)
* Replace `imghdr` with Willow’s built-in MIME type detection (Jake Howard)
* Replace `@total_ordering` usage with comparison functions implementation (Virag Jain)
* Replace `
{% endescapescript %}
```
#### New
Use the HTML [`template`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) element to avoid content from being parsed by the browser on load.
```html+django
Widget template content
```
### Adoption of `classname` convention within the Image `Format` instance
When using `wagtail.images.formats.Format`, the created instance set the argument for classes to the attribute `classnames` (plural), this has now changed to `classname` (singular).
For any custom code that accessed or modified this undocumented attribute, updates will need to be made as follows.
Accessing `self.classnames` will still work until a future release, simply returning `self.classname`, but this will raise a deprecation warning.
```python
# image_formats.py
from django.utils.html import format_html
from wagtail.images.formats import Format, register_image_format
class CustomImageFormat(Format):
def image_to_html(self, image, alt_text, extra_attributes=None):
# contrived example - pull out the class and render on outside element
classname = self.classname # not self.classnames
self.classname = "" # not self.classnames
inner_html = super().image_to_html(image, alt_text, extra_attributes)
return format_html("{} ", classname, inner_html)
custom_format = CustomImageFormat('custom_example', 'Custom example', 'example-image object-fit', 'width-750')
register_image_format(custom_format)
```
### Changes to search promotions contrib module
#### Deprecated `search_garbage_collect` management command has been removed
In 5.0 the documentation advised that the `search_garbage_collect` command used to remove old stored search queries and daily hits has been moved to [`searchpromotions_garbage_collect`](../reference/contrib/searchpromotions.md#searchpromotions-garbage-collect).
The old command has now been fully removed and if called will throw an error.
#### Changes to URL names and templates
Some search promotions URLs and templates have now moved from the main admin search module into the search promotions module.
| **Item** | **Old** | **New** |
|------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
| URL name | `wagtailsearch_admin:queries_chooser` | `wagtailsearchpromotions:chooser` |
| URL name | `wagtailsearch_admin:queries_chooserresults` | `wagtailsearchpromotions:queries_chooserresults` |
| Template | `wagtail/search/templates/wagtailsearch/queries/chooser/chooser.html` | `wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/queries/chooser/chooser.html` |
| Template | `wagtail/search/templates/wagtailsearch/queries/chooser/results.html` | `wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/queries/chooser/results.html` |
| Template | `wagtail/search/templates/wagtailsearch/queries/chooser_field.html` | `wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/queries/chooser_field.html` |
### `Block.get_template` now accepts a `value` argument
The `get_template` method on StreamField blocks now accepts a `value` argument in addition to `context`. Code using the old signature should be updated:
```python
# Old
def get_template(self, context=None):
...
# New
def get_template(self, value=None, context=None):
...
```
# 6.0.1.html.md
# Wagtail 6.0.1 release notes
*February 15, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure `BooleanRadioSelect` uses the same styles as `RadioSelect` (Thibaud Colas)
* Prevent failure on `collectstatic` when `ManifestStaticFilesStorage` is in use (Matt Westcott)
* Prevent error on submitting an empty search in the admin under Elasticsearch (Maikel Martens)
# 6.0.2.html.md
# Wagtail 6.0.2 release notes
*April 3, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure that modal tabs width are not impacted by side panel opening (LB (Ben) Johnston)
* Resolve issue local development of docs when running `make livehtml` (Sage Abdullah)
* Resolve issue with unwanted padding in chooser modal listings (Sage Abdullah)
* Ensure `get_add_url()` is always used to re-render the add button when the listing is refreshed in viewsets (Sage Abdullah)
* Move `modal-workflow.js` script usage to base admin template instead of ad-hoc imports so that choosers work in `ModelViewSet`s (Elhussein Almasri)
* Ensure JavaScript for common widgets such as `InlinePanel` is included by default in `ModelViewSet`’s create and edit views (Sage Abdullah)
* Reinstate styles for customizations of `extra_footer_actions` block in page create/edit templates (LB (Ben) Johnston, Sage Abdullah)
* Prevent crash when loading an empty table block in the editor (Sage Abdullah)
### Documentation
* Update Sphinx theme to `6.3.0` with a fix for the missing favicon (Sage Abdullah)
# 6.0.3.html.md
# Wagtail 6.0.3 release notes
*May 1, 2024*
> * [What’s new](#what-s-new)
> * [Upgrade considerations](#upgrade-considerations)
## What’s new
### 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`](../reference/contrib/settings.md) module or [ModelViewSet](../extending/generic_views.md#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](https://github.com/wagtail/wagtail/security/advisories/GHSA-w2v8-php4-p8hc).
### Bug fixes
* 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`](../reference/management_commands.md#convert-mariadb-uuids) management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
* Fix generic CopyView for models with primary keys that need to be quoted (Sage Abdullah)
## Upgrade considerations
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
Django 5.0 introduces support for MariaDB’s native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](../reference/management_commands.md#convert-mariadb-uuids) management command (available as of Wagtail 6.0.3) after upgrading:
```sh
./manage.py convert_mariadb_uuids
```
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
# 6.0.4.html.md
# Wagtail 6.0.4 release notes
*May 21, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix snippet copy view not prefilling form data (Sage Abdullah)
# 6.0.5.html.md
# Wagtail 6.0.5 release notes
*May 30, 2024*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2024-35228: Improper handling of insufficient permissions in `wagtail.contrib.settings`
This release addresses a permission vulnerability in the Wagtail admin interface. Due to an improperly applied permission check in the `wagtail.contrib.settings` module, a user with access to the Wagtail admin and knowledge of the URL of the edit view for a settings model can access and update that setting, even when they have not been granted permission over the model. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to Victor Miti for reporting this issue, and Matt Westcott and Jake Howard for the fix. For further details, please see [the CVE-2024-35228 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-xxfm-vmcf-g33f).
# 6.0.6.html.md
# Wagtail 6.0.6 release notes
*July 11, 2024*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2024-39317: Regular expression denial-of-service via search query parsing
This release addresses a denial-of-service vulnerability in Wagtail. A bug in Wagtail’s [`parse_query_string`](../topics/search/searching.md#wagtailsearch-query-string-parsing) would result in it taking a long time to process suitably crafted inputs. When used to parse sufficiently long strings of characters without a space, `parse_query_string` would take an unexpectedly large amount of time to process, resulting in a denial of service.
In an initial Wagtail installation, the vulnerability can be exploited by any Wagtail admin user. It cannot be exploited by end users. If your Wagtail site has a custom search implementation which uses parse_query_string, it may be exploitable by other users (e.g. unauthenticated users).
Many thanks to Jake Howard for reporting and fixing this issue. For further details, please see [the CVE-2024-39317 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-jmp3-39vp-fwg8).
# 6.0.html.md
# Wagtail 6.0 release notes
*February 7, 2024*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - removal of deprecated features from Wagtail 4.2 - 5.1](#upgrade-considerations-removal-of-deprecated-features-from-wagtail-4-2-5-1)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - deprecation of old functionality](#upgrade-considerations-deprecation-of-old-functionality)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
## What’s new
### Django 5.0 support
This release adds support for Django 5.0. The support has also been backported to Wagtail 5.2 LTS.
### New developer tutorial
A new [developer tutorial](../tutorial/index.md) series has been added to the documentation. This series builds upon the pre-existing [Your first Wagtail site](../getting_started/tutorial.md), going through the creation and deployment of a portfolio website.
This tutorial series was created by Damilola Oladele as part of the Google Season of Docs program, with support from Meagen Voss, and Thibaud Colas. We also thank Storm Heg, Kalob Taulien, Kátia Nakamura, Mariusz Felisiak, and Rachel Smith for their support and feedback as part of the project.
### Universal listings
Following design improvements to the page listing view, Wagtail now provides a unified search and filtering interface for all listings. This will improve navigation capabilities, particularly for sites with a large number of pages or where content tends to use a flat structure.
In this release, the universal listing interface is available for Pages, Snippets, and Forms. For pages, the UI includes the following filters out of the box:
* Page type
* Date updated
* Owner
* Edited by
* Site
* Has child pages
* Locale
This feature was developed by Ben Enright, Matt Westcott, Nick Lee, Thibaud Colas, and Sage Abdullah.
### Right-to-left language support
The admin interface now supports right-to-left languages, such as Persian, Arabic, and Hebrew. Though there are still some areas that need improvement, all admin views will now be displayed in the correct direction. Review our [UI guidelines](../contributing/ui_guidelines.md) for guidance on supporting right-to-left languages in admin interface customizations.
Thank you to Thibaud Colas, Badr Fourane, and Sage Abdullah for their work on this long-requested improvement.
### Accessibility checker in page editor
The [built-in accessibility checker](../advanced_topics/accessibility_considerations.md#authoring-accessible-content) now displays as a side panel within page and snippet editors supporting preview. The new “Checks” side panel only shows accessibility-related issues for pages with the userbar enabled in this release, but will be updated to support [any content checks](https://github.com/wagtail/wagtail/discussions/11063) in the future.
This feature was implemented by Nick Lee, Thibaud Colas, and Sage Abdullah.
### Page types usage report
The new Page types report provides a breakdown of the number of pages for each type. It helps answer questions such as:
* Which page types do we have on our CMS?
* How many pages of that page type do we have?
* When was a page of that type last edited? By whom? Which page was that?
This feature was developed by Jhonatan Lopes, as part of a sponsorship by the Mozilla Foundation.
### Accessibility improvements
This release comes with a high number of accessibility improvements across the admin interface.
* Improve layout and accessibility of the image URL generator page, reduce reliance on JavaScript (Temidayo Azeez)
* Remove overly verbose image captions in image listings for screen readers (Sage Abdullah)
* Ensure screen readers and dictation tools can more easily navigate bulk actions in images, documents and page listings by streamlining labels and descriptions (Sage Abdullah)
* Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser)
* Switch the `TableBlock` header controls to a field that requires user input (Bhuvnesh Sharma, Aman Pandey, Cynthia Kiser)
* Add support for `caption` on admin UI Table component (Aman Pandey)
* Replace legacy dropdown component with new Tippy dropdown-button (Thibaud Colas)
* Ensure the sidebar account toggle has no duplicate accessible labels (Nandini Arora)
* Ensure that page listing re-ordering messages and accessible labels can be translated (Aman Pandey, LB (Ben) Johnston)
* Resolve multiple issues with page listing re-ordering using keyboard and screen readers (Aman Pandey)
* When using an empty table header (`th`) for visual spacing, ensure this is ignored by accessibility tooling (V Rohitansh)
* Ensure that TableBlock cells are accessible when using keyboard control only (Elhussein Almasri)
### Other features
* Added [`search_index` option to StreamField](../topics/streamfield.md#streamfield-search) blocks to control whether the block is indexed for searching (Vedant Pandey)
* Remember previous location on returning from page add/edit actions (Robert Rollins)
* Update settings file in project settings to address Django 4.2 deprecations (Sage Abdullah)
* Allow `UniqueConstraint` in place of `unique_together` for [`TranslatableMixin`](../reference/models.md#wagtail.models.TranslatableMixin)’s system check (Temidayo Azeez, Sage Abdullah)
* Make use of `IndexView.get_add_url()` in snippets index view template (Christer Jensen, Sage Abdullah)
* Allow `Page.permissions_for_user()` to be overridden by specific page types (Sébastien Corbin)
* Improve visual alignment of explore icon in Page listings for longer content (Krzysztof Jeziorny)
* Add `extra_actions` blocks to Snippets and generic index templates (Bhuvnesh Sharma)
* Add support for defining `panels` / `edit_handler` on `ModelViewSet` (Sage Abdullah)
* Use a single instance of `PagePermissionPolicy` in `wagtail.permissions` module (Sage Abdullah)
* Add max tag length validation for multiple uploads (documents/images) (Temidayo Azeez)
* Ensure expanded side panel does not overlap form content for most viewports (Chiemezuo Akujobi)
* Add ability to [modify the default ordering](../reference/models.md#page-model-ref) for the page explorer view (Shlomo Markowitz)
* Remove support for Safari 14 (Thibaud Colas)
* Add ability to click to copy the URL in the image URL generator page (Sai Srikar Dumpeti)
* Show edit as a main action in generic history and usage views (Sage Abdullah)
* Make styles for header buttons consistent (Sage Abdullah)
* Improve styles of slim header’s search and filters (Sage Abdullah)
* Change page listing’s add button to icon-only (Sage Abdullah)
* Add sublabel to breadcrumbs, including history, usage, and inspect views (Sage Abdullah)
* Standardise search form placeholder to ‘Search…’ (Sage Abdullah)
* Use SlugInput on all SlugFields by default (LB (Ben) Johnston)
* Show character counts on RichTextBlock with `max_length` (Elhussein Almasri)
* Move locale selector in generic IndexView to a filter (Sage Abdullah)
* Add ability to [customize a page’s copy form](../advanced_topics/customization/page_editing_interface.md#custom-page-copy-form) including an auto-incrementing slug example (Neeraj Yetheendran)
* Add [`WAGTAILADMIN_LOGIN_URL` setting](../reference/settings.md#wagtailadmin-login-url) to allow customizing the login URL (Neeraj Yetheendran)
* Polish dark theme styles and update color tokens (Thibaud Colas, Rohit Sharma)
* Keep database state of pages and snippets updated while in draft state (Stefan Hammer)
* Add `DrilldownController` and `w-drilldown` component to support drilldown menus (Thibaud Colas)
* Add API support for a [redirects (contrib)](../reference/contrib/redirects.md#redirects-api-endpoint) endpoint (Rohit Sharma, Jaap Roes, Andreas Donig)
* Add the default ability for all `SnippetViewSet` & `ModelViewSet` to support [being copied](../extending/generic_views.md#modelviewset-copy), this can be disabled by `copy_view_enabled = False` (Shlomo Markowitz)
* Support dynamic Wagtail guide links in the admin that are based on the running version of Wagtail (Tidiane Dia)
### Bug fixes
* Update system check for overwriting storage backends to recognize the `STORAGES` setting introduced in Django 4.2 (phijma-leukeleu)
* Prevent password change form from raising a validation error when browser autocomplete fills in the “Old password” field (Chiemezuo Akujobi)
* Ensure that the legacy dropdown options, when closed, do not get accidentally clicked by other interactions on wide viewports (CheesyPhoenix, Christer Jensen)
* Add a fallback background for the editing preview iframe for sites without a background (Ian Price)
* Preserve whitespace in rendered comments (Elhussein Almasri)
* Remove search logging from project template so that new projects without the search promotions module will not error (Matt Westcott)
* Ensure text only email notifications for updated comments do not escape HTML characters (Rohit Sharma)
* Use the latest draft when copying an unpublished page for translation (Andrey Nehaychik)
* Make Workflow and Aging Pages reports only available to users with page-related permissions (Rohit Sharma)
* Make searching on specific fields work correctly on Elasticsearch when boost is in use (Matt Westcott)
* Use a visible border and background color to highlight active formatting in the rich text toolbar (Cassidy Pittman)
* Ensure image focal point box can be removed (Gunnar Scherf)
* Ensure that Snippets search results correctly use the `index_results.html` or `index_results_template_name` override on initial load (Stefan Hammer)
* Avoid error when attempting to moderate a page drafted by a now deleted user (Dan Braghis)
* Do not show multiple error messages when editing a Site to use existing hostname and port (Rohit Sharma)
* Avoid error when exporting Aging Pages report where a page has an empty `last_published_by_user` (Chiemezuo Akujobi)
* Ensure Page querysets support using `alias` and `specific` (Tomasz Knapik)
* Ensure workflow dashboard panels work when the page/snippet is missing (Sage Abdullah)
* Ensure `ActionController` explicitly checks for elements that allow select functionality (Nandini Arora)
* Prevent a ValueError with `FormSubmissionsPanel` on Django 5.0 when creating a new form page (Matt Westcott)
* Avoid duplicate entries in “Recent edits” panel when copying pages (Matt Westcott)
* Prevent TitleFieldPanel from raising an error when the slug field is missing or read-only (Rohit Sharma)
* Ensure that the close button on the new dialog designs is visible in the non-message variant (Nandini Arora)
* Avoid text overflow issues in comment replies and scroll position issues for long comments (Rohit Sharma)
* Remove ‘Page’ from page types filter on aging pages report (Matt Westcott)
* Prevent page types filter from showing other non-Page models that match by name (Matt Westcott)
* Ensure `MultipleChooserPanel` modal works correctly when `USE_THOUSAND_SEPARATOR` is `True` for pages with ids over 1,000 (Sankalp, Rohit Sharma)
* Ensure the panel anchor button sizes meet accessibility guidelines for minimum dimensions (Nandini Arora)
* Raise a 404 for bulk actions for models which don’t exist instead of throwing a 500 error (Alex Tomkins)
* Raise a `SiteSetting.DoesNotExist` error when retrieving settings for an unrecognized site (Nick Smith)
* Ensure that defaulted or unique values declared in `exclude_fields_in_copy` are correctly excluded in new copies, resolving to the default value (Elhussein Almasri)
* Ensure that `default_ordering` set on IndexView is preserved if ModelViewSet does not specify an explicit ordering (Cynthia Kiser)
* Resolve issue where clicking Publish for a Page that was in workflow in Safari would block publishing and not trigger the workflow confirmation modal (Alex Morega)
* Fix pagination links on model history and usage views (Matt Westcott)
* Fix crash when accessing workflow reports with a deleted snippet (Sage Abdullah)
### Documentation
* Document, for contributors, the use of translate string literals passed as arguments to tags and filters using `_()` within templates (Chiemezuo Akujobi)
* Document all features for the Documents app in one location, see [Documents](../advanced_topics/documents/index.md) (Neeraj Yetheendran)
* Add section to [testing docs](../advanced_topics/testing.md) about creating pages and working with page content (Mariana Bedran Lesche)
* Add more nuance to the database recommendations in [Performance](../advanced_topics/performance.md#performance-overview) (Jadesola Kareem)
* Add clarity that [`MultipleChooserPanel`](../reference/panels.md#multiple-chooser-panel) may require a chooser viewset and how the functionality is expected to work (Andy Chosak)
* Clarify where documentation build commands should be run (Nikhil S Kalburgi)
* Add missing import to tutorial BlogPage example (Salvo Polizzi)
* Update contributing guide documentation and GitHub templates to better support new contributors (Thibaud Colas)
* Add more CSS authoring guidelines (Thibaud Colas)
* Update MyST documentation parser library to 2.0.0 (Neeraj Yetheendran)
* Add documentation writing guidelines for intersphinx / external links (LB (Ben) Johnston)
* Add `Page` model reference `get_children` documentation (Salvo Polizzi)
* Enforce CI build checks for documentation so that malformed links or missing images will not be allowed (Neeraj Yetheendran)
* Update spelling on customizing admin template and page model section from British to American English (Victoria Poromon)
* Add documentation for how to override the file locations for custom image models [Overriding the upload location](../advanced_topics/images/custom_image_model.md#custom-image-model-upload-location) (Osaf AliSayed, Dharmik Gangani)
* Update documentation theme (Sphinx Wagtail Theme) to 6.2.0, fixing the incorrect favicon (LB (Ben) Johnston, Sahil Jangra)
* Refactor promotion banner without jQuery and use sameSite cookies when storing if cleared (LB (Ben) Johnston)
* Use cross-reference for compatible Python versions in tutorial instead of the out of date listing (mirusu400)
### Maintenance
#### Generic class-based views adoption
As part of ongoing refactorings, we have migrated several views to use generic class-based views. This allows for easier extensibility and better code reuse.
* Migrate the contrib styleguide index view to a class-based view (Chiemezuo Akujobi)
* Migrate the contrib settings edit view to a class-based view (Chiemezuo Akujobi, Sage Abdullah)
* Migrate account editing view to a class-based view (Kehinde Bobade)
* Refactor page explorer index template to extend generic index template (Sage Abdullah)
* Refactor snippets index view and template to make better use of generic IndexView (Sage Abdullah)
* Refactor documents listing view to use generic IndexView (Sage Abdullah)
* Refactor images listing view to use generic IndexView (Sage Abdullah)
* Refactor form pages listing view to use generic IndexView (Sage Abdullah)
* Reduce gap between snippets and generic views/templates (Sage Abdullah)
#### Other maintenance
* Update BeautifulSoup upper bound to 4.12.x (scott-8)
* Migrate initialization of classes (such as `body.ready`) from multiple JavaScript implementations to one Stimulus controller `w-init` (Chiemezuo Akujobi)
* Adopt the usage of translate string literals using `arg=_('...')` in all `wagtailadmin` module templates (Chiemezuo Akujobi)
* Update djhtml to 3.0.6 (Matt Westcott)
* Remove django-pattern-library upper bound in testing dependencies (Sage Abdullah)
* Split up functions in Elasticsearch backend for easier extensibility (Marcel Kornblum, Cameron Lamb, Sam Dudley)
* Relax draftjs_exporter dependency to allow using version 5.x (Sylvain Fankhauser)
* Refine styling of listings, account settings panels and the block chooser (Meli Imelda)
* Remove icon font support (Matt Westcott)
* Remove deprecated SVG icons (Matt Westcott)
* Remove icon font styles (Thibaud Colas)
* Upgrade frontend tooling to use Node 20 (LB (Ben) Johnston)
* Upgrade `ruff` and replace `black` with `ruff format` (John-Scott Atlakson)
* Update Willow upper bound to 2.x (Dan Braghis)
* Removed support for Django < 4.2 (Dan Braghis)
* Replace template components implementation with [standalone `laces` library](https://pypi.org/project/laces/) (Tibor Leupold)
* Introduce an internal `{% formattedfield %}` tag to replace direct use of `wagtailadmin/shared/field.html` (Matt Westcott)
* Update Telepath dependency to 0.3.1 (Matt Westcott)
* Allow `ActionController` to have a `noop` method to more easily leverage standalone Stimulus action options (Nandini Arora)
* Upgrade to latest TypeScript and Storybook (Thibaud Colas, Sage Abdullah)
* Turn on `skipLibCheck` for TypeScript (LB (Ben) Johnston)
* Support for the Stimulus `CloneController` to auto clear the added content after a set duration (LB (Ben) Johnston)
* Update Stylelint, our linting configuration, Sass, and related code changes (LB (Ben) Johnston)
* Simplify browserslist and browser support documentation for maintainers (Thibaud Colas)
* Relax django-taggit dependency to allow 5.0 (Sylvain Fankhauser)
* Fix various warnings when building docs (Cynthia Kiser)
* Upgrade sphinxcontrib-spelling to 7.x for Python 3.12 compatibility (Matt Westcott)
* Move logic for django-filters filtering into `BaseListingView` (Matt Westcott)
* Remove or replace legacy CSS classes: visuallyhidden, visuallyvisible, divider-after, divider-before, inline, inline-block, block, u-hidden, clearfix, reordering, overflow (Thibaud Colas)
* Prevent future issues with icon.html end-of-file newlines (Thibaud Colas)
* Rewrite styles using legacy `c-`, `o-`, `u-`, `t-`, `is-` prefixes (Thibaud Colas)
* Remove invalid CSS styles / Sass selector concatenation (Thibaud Colas)
* Refactor listing views to share more queryset ordering logic (Matt Westcott)
* Remove `initTooltips` in favor of Stimulus controller (LB (Ben) Johnston)
* Enhance the Stimulus `InitController` to allow for custom event dispatching when ready (Aditya, LB (Ben) Johnston)
* Remove inline script usage for comment initialization and adopt an event listener/dispatch approach for better CSP compliance (Aditya, LB (Ben) Johnston)
* Migrate styleguide ad-hoc JavaScript to use styles only to avoid CSP issues (LB (Ben) Johnston)
* Update Jest version - frontend tooling (Nandini Arora)
* Remove non-functional and inaccessible auto-focus on first field in page create forms (LB (Ben) Johnston)
* Migrate the unsaved form checks & confirmation trigger to Stimulus `UnsavedController` (Sai Srikar Dumpeti, LB (Ben) Johnston)
* Migrate page listing menu re-ordering (drag & drop) from jQuery inline scripts to `OrderableController` with a more accessible solution (Aman Pandey, LB (Ben) Johnston)
* Clean up scss variable usage, remove unused variables and mixins, adopt more core token variables (Jai Vignesh J, Nandini Arora, LB (Ben) Johnston)
* Migrate Image URL generator views to class-based views (Rohit Sharma)
* Use Django’s `FileResponse` when serving files such as Images or Documents (Jake Howard)
* Deprecated `WidgetWithScript` base widget class (LB (Ben) Johnston)
* Remove support for Django 4.1 and below (Sage Abdullah)
## Upgrade considerations - removal of deprecated features from Wagtail 4.2 - 5.1
Features previously deprecated in Wagtail 4.2, 5.0 and 5.1 have been fully removed. For additional details on these changes, see:
* [Wagtail 4.2 release notes](4.2.md)
* [Wagtail 5.0 release notes](5.0.md)
* [Wagtail 5.1 release notes](5.1.md)
The most significant changes are highlighted below.
### Removal of ModelAdmin app
The `wagtail.contrib.modeladmin` app has been removed. If you wish to continue using it, it is available as the external package [`wagtail-modeladmin`](https://github.com/wagtail-nest/wagtail-modeladmin).
### `Query` model moved to `wagtail.contrib.search_promotions`
The `Query` model (used to log search queries performed by users, to identify commonly searched terms) is no longer part of the `wagtail.search` module; it can now be found in the optional `wagtail.contrib.search_promotions` app. When updating code to import the model from the new location, ensure that you have added `wagtail.contrib.search_promotions` to your `INSTALLED_APPS` setting - failing to do this may result in a spurious migration being created within the core `wagtail` app.
### Support for Elasticsearch 5 and 6 dropped
The Elasticsearch 5 and 6 backends have been removed. If you are using one of these backends, you will need to upgrade to Elasticsearch 7 or 8 before upgrading to Wagtail 6.0.
### StreamField no longer requires `use_json_field=True`
The `use_json_field` argument to `StreamField` is no longer required, and can be removed. StreamField now consistently uses JSONField for its database representation, and Wagtail 5.0 required older TextField-based streams to be migrated. As such, `use_json_field` no longer has any effect.
### Other removals
* The `WAGTAILADMIN_GLOBAL_PAGE_EDIT_LOCK` setting is no longer recognized and should be replaced with `WAGTAILADMIN_GLOBAL_EDIT_LOCK`.
* The `wagtail.models.UserPagePermissionsProxy` class and `get_pages_with_direct_explore_permission`, `get_explorable_root_page` and `users_with_page_permission` functions have been removed; equivalent functionality exists in the `wagtail.permission_policies.pages.PagePermissionPolicy` class.
* The `permission_type` field of the `GroupPagePermission` model has been removed; the `permission` field (a foreign key to Django’s `Permission` model) should be used instead.
* The legacy moderation system used before the introduction of workflows in Wagtail 2.10 has been removed. Any moderation requests still in the queue from before this time will be lost.
* The Wagtail icon font has been removed; any direct usage of this needs to be converted to SVG icons.
* Various unused icons deprecated in Wagtail 5.0 have been removed.
* The `partial_match` argument on `SearchField` and on `search` methods has been removed. `AutocompleteField` and the `autocomplete` method should be used instead.
* The `insert_editor_css` hook has been removed; the `insert_global_admin_css` hook should be used instead.
* The `wagtail.contrib.frontend_cache` module now supports `azure-mgmt-cdn` version 10 and `azure-mgmt-frontdoor` version 1 as its minimum supported versions.
* The `Task.page_locked_for_user` method has been removed; `Task.locked_for_user` should be used instead.
* The `{% icon %}` template tag no longer accepts `class_name` as an argument; `classname` should be used instead.
* The `wagtail.tests.utils` module has been removed and can now be found at `wagtail.test.utils`.
* The template `wagtailadmin/shared/field_as_li.html` has been removed, and should be replaced with `wagtailadmin/shared/field.html` enclosed in an `` tag.
* The custom client-side events `wagtail:show` and `wagtail:hide` on showing and hiding dialogs have been removed; `w-dialog:show` and `w-dialog:hide` should be used instead.
* The global Javascript definitions `headerSearch`, `initTagField`, `cancelSpinner` and `unicodeSlugsEnabled` have been removed; these should be replaced with Stimulus controllers.
## Upgrade considerations - changes affecting all projects
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
Django 5.0 introduces support for MariaDB’s native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](../reference/management_commands.md#convert-mariadb-uuids) management command (available as of Wagtail 6.0.3) after upgrading:
```sh
./manage.py convert_mariadb_uuids
```
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
### `SnippetViewSet` & `ModelViewSet` copy view enabled by default
The newly introduced copy view will be enabled by default for all `ModelViewSet` and `SnippetViewSet` classes.
This can be disabled by setting `copy_view_enabled = False`, for example.
```python
class PersonViewSet(SnippetViewSet):
model = Person
#...
copy_view_enabled = False
class PersonViewSet(ModelViewSet):
model = Person
#...
copy_view_enabled = False
```
See [Copy view](../extending/generic_views.md#modelviewset-copy) for additional details about this feature.
## Upgrade considerations - deprecation of old functionality
### Removed support for Django < 4.2
Django versions before 4.2 are no longer supported as of this release; please upgrade to Django 4.2 or above before upgrading Wagtail.
## Upgrade considerations - changes affecting Wagtail customizations
### `SlugInput` widget is now the default for `SlugField` fields
In Wagtail 5.0 a new `SlugInput` admin widget was added to support slug behavior in Page and Page copy forms. This widget was included by default if the `promote_panels` fields layout was customized, causing confusion.
As of this release, any forms that inherit from `WagtailAdminModelForm` (includes page and snippet model editing) will now use the `SlugInput` by default on all models with `SlugField` fields.
Previously, the widget had to be explicitly added.
```python
from wagtail.admin.widgets.slug import SlugInput
# ... other imports
class MyPage(Page):
promote_panels = [
FieldPanel("slug", widget=SlugInput),
# ... other panels
]
```
Keeping the widget as above is fine, but will no longer be required. The JavaScript field behavior will be included by default.
```python
# ... imports
class MyPage(Page):
promote_panels = [
FieldPanel("slug"),
# ... other panels
]
```
If you do not want this for some reason, you will now need to declare a different widget.
```python
from django.forms.widgets import TextInput
# ... other imports
class MyPage(Page):
promote_panels = [
FieldPanel("slug", widget=TextInput), # use a plain text field
# ... other panels
]
```
### Changed handling of database updates of non-live `Page` objects or subclasses of `DraftStateMixin`
Before this release, the database record of a `Page` or any subclass of `DraftStateMixin` either contained the live data (if published), the state of the last published version (if unpublished), or the state of the first revision (if never published). Subsequent draft edits would create new `Revision` records, but the main database record would not be updated. As a result, the database record could lag substantially behind the current state of the object, causing unexpected behavior particularly when unique constraints are in use.
As of this release, the database record of a non-live object will be updated to reflect the draft state of the object. This is unlikely to have a visible effect on existing sites, since the admin backend works with the `Revision` records while the site front-end typically filters out non-live objects. However, any code that relies on the database record being untouched by draft edits (for example, using it to store a specific approved / archived state of the page) may need to be updated.
## Upgrade considerations - changes to undocumented internals
### `filter_queryset` and `get_filtered_queryset` methods no longer return filters
The undocumented internal methods `filter_queryset(queryset)` on `wagtail.admin.views.generic.IndexView`, and `get_filtered_queryset()` on `wagtail.admin.views.reports.ReportView`, now return just the filtered queryset; previously they returned a tuple of `(filters, queryset)`. The filterset instance is always available as the cached property `self.filters`.
### Deprecation of the undocumented `window.enableDirtyFormCheck` function
The admin frontend `window.enableDirtyFormCheck` will be removed in a future release and as of this release only supports the basic initialization.
The previous approach was to call a window global function as follows.
```javascript
window.enableDirtyFormCheck('.my-form', { alwaysDirty: true, confirmationMessage: 'You have unsaved changes'});
```
The new approach will be data attribute driven as follows.
```text
```
### `data-tippy-content` attribute support will be removed
The implementation of the JS tooltips have been fully migrated to the Stimulus `w-tooltip`/`TooltipController` implementation.
Dynamic support for any `data-tippy-content="..."` usage will be removed this release, for example, within chooser modals or dynamic html response data.
Some minimal backwards compatibility support for `data-tippy-content` will work until a future release, but only in the initial HTML response on a page.
#### Data attributes
These HTML data attributes were not documented, but if any custom code implemented custom tooltips, these will need to be changed.
| Old | New | Notes |
|----------------------------------------------|--------------------------------------------------------|--------------------------------------|
| | `data-controller="w-tooltip"` | Required, new addition for any usage |
| `data-tippy-content="{% trans 'History' %}"` | `data-w-tooltip-content-value="{% trans 'History' %}"` | Required |
| `data-tippy-offset="[12, 24]"` | `data-w-tooltip-offset-value="[12, 24]"` | Optional, default is no offset |
| `data-tippy-placement="top"` | `data-w-tooltip-placement-value="top"` | Optional, default is ‘bottom’ |
### Deprecated `WidgetWithScript` base widget class
The undocumented `WidgetWithScript` class that used inline scripts to attach JavaScript to widgets will be removed in a future release.
This approach creates security risks and will not be compliant with CSP support. Instead, it’s recommended that all similar requirements migrate to use the recommended Stimulus JS integration approach.
A full example of how to build this has been documented on [extending client-side behavior](../extending/extending_client_side.md#extending-client-side-stimulus-widget), a basic example is below.
#### Old
```py
from django.forms import Media, widgets
class CustomRichTextArea(WidgetWithScript, widgets.Textarea):
def render_js_init(self, id_, name, value):
return f"window.customEditorInitScript({json.dumps(id_)});"
@property
def media(self):
return Media(js=["vendor/custom-editor.js"])
```
#### New
```py
from django.forms import Media, widgets
class CustomRichTextArea(widgets.Textarea):
def build_attrs(self, *args, **kwargs):
attrs = super().build_attrs(*args, **kwargs)
attrs['data-controller'] = 'custom-editor'
@property
def media(self):
return Media(js=["vendor/custom-editor.js","js/custom-editor-controller.js"])
```
```javascript
// myapp/static/js/custom-editor-controller.js
class CustomEditorController extends window.StimulusModule.Controller {
connect() {
window.customEditorInitScript(this.element.id);
}
}
window.wagtail.app.register('custom-editor', CustomEditorController);
```
# 6.1.1.html.md
# Wagtail 6.1.1 release notes
*May 21, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix form action URL in user edit and delete views for custom user models (Sage Abdullah)
* Fix snippet copy view not prefilling form data (Sage Abdullah)
* Address layout issues in the title cell of universal listings (Sage Abdullah)
* Fix incorrect rich text to HTML conversion when multiple link / embed types are present (Andy Chosak, Matt Westcott)
* Restore ability for custom widgets in StreamField blocks to have multiple top-level nodes (Sage Abdullah, Matt Westcott)
# 6.1.2.html.md
# Wagtail 6.1.2 release notes
*May 30, 2024*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2024-35228: Improper handling of insufficient permissions in `wagtail.contrib.settings`
This release addresses a permission vulnerability in the Wagtail admin interface. Due to an improperly applied permission check in the `wagtail.contrib.settings` module, a user with access to the Wagtail admin and knowledge of the URL of the edit view for a settings model can access and update that setting, even when they have not been granted permission over the model. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
Many thanks to Victor Miti for reporting this issue, and Matt Westcott and Jake Howard for the fix. For further details, please see [the CVE-2024-35228 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-xxfm-vmcf-g33f).
### Bug fixes
* Fix client-side handling of select inputs within `ChoiceBlock` (Matt Westcott)
* Support SVG icon id attributes with single quotes in the styleguide (Sage Abdullah)
# 6.1.3.html.md
# Wagtail 6.1.3 release notes
*July 11, 2024*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2024-39317: Regular expression denial-of-service via search query parsing
This release addresses a denial-of-service vulnerability in Wagtail. A bug in Wagtail’s [`parse_query_string`](../topics/search/searching.md#wagtailsearch-query-string-parsing) would result in it taking a long time to process suitably crafted inputs. When used to parse sufficiently long strings of characters without a space, `parse_query_string` would take an unexpectedly large amount of time to process, resulting in a denial of service.
In an initial Wagtail installation, the vulnerability can be exploited by any Wagtail admin user. It cannot be exploited by end users. If your Wagtail site has a custom search implementation which uses parse_query_string, it may be exploitable by other users (e.g. unauthenticated users).
Many thanks to Jake Howard for reporting and fixing this issue. For further details, please see [the CVE-2024-39317 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-jmp3-39vp-fwg8).
### Bug fixes
* Allow renditions of `.ico` images (Julie Rymer)
* Handle choice groups as dictionaries in active filters (Sébastien Corbin)
* Fix image preview when Willow optimizers are enabled (Alex Tomkins)
* Fix dynamic image serve view with certain backends (Sébastien Corbin)
# 6.1.html.md
# Wagtail 6.1 release notes
*May 1, 2024*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
## 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](../advanced_topics/customization/admin_templates.md#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](../advanced_topics/customization/custom_page_listings.md#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](../advanced_topics/privacy.md#private-pages) and [private collections (documents)](../advanced_topics/privacy.md#private-collections) 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](../topics/images.md#favicon-generation), with `format-ico`:
```html+django
```
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`](../reference/contrib/settings.md) module or [ModelViewSet](../extending/generic_views.md#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](https://github.com/wagtail/wagtail/security/advisories/GHSA-w2v8-php4-p8hc).
### 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](../reference/settings.md#wagtail-date-time-formats) (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](../extending/custom_tasks.md) (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](../reference/models.md#page-model-ref). Results are cached on `request._wagtail_route_for_request` (Gordon Pendleton)
* Optimize 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`](../reference/settings.md#wagtailimages-rendition-storage) (Alec Baron)
* Update `PASSWORD_REQUIRED_TEMPLATE` setting to [`WAGTAIL_PASSWORD_REQUIRED_TEMPLATE`](../reference/settings.md#frontend-authentication) with deprecation of previous naming (Saksham Misra, LB (Ben) Johnston)
* Update `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` setting to [`WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE`](../reference/settings.md#frontend-authentication) 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`](../reference/management_commands.md#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](../contributing/developing.md#developing-using-a-fork) (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`](../reference/management_commands.md#wagtail-update-image-renditions) management command section (Pranith Beeram)
* Update [template components](../extending/template_components.md#creating-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](../reference/settings.md#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](../advanced_topics/streamfield_validation.md#streamfield-validation) works (Tibor Leupold)
* Add additional reference to the [`wagtail_update_image_renditions`](../reference/management_commands.md#wagtail-update-image-renditions) management command on the [using images](../advanced_topics/images/renditions.md#regenerate-image-renditions) 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/create_footer_for_all_pages.md) tutorial section (Drikus Roor)
* Update list of third-party tutorials (LB (Ben) Johnston)
* Update [Integrating into Django](../getting_started/integrating_into_django.md) documentation to emphasise creating page models (Matt Westcott)
### Maintenance
* Move RichText HTML whitelist parser to use the faster, built in `html.parser` rather than `html5lib` (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`](../reference/hooks.md#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](../reference/settings.md#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](../reference/settings.md#frontend-authentication).
## Upgrade considerations - changes to undocumented internals
### Removal of `html5lib` dependency
Wagtail now uses `html.parser` for its rich text processing, and no longer depends on `html5lib`. If your project relies on `html5lib`, update rich text code to use Wagtail’s documented rich text APIs like [rewrite handlers](../extending/rich_text_internals.md#rich-text-rewrite-handlers) and [format converters](../extending/rich_text_internals.md#rich-text-format-converters). Or update project dependencies to include `html5lib`.
### 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:
```python
reverse('wagtailusers_groups:users', args=[group.id])
```
should be updated to:
```python
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
```python
# .../wagtail_hooks.py
@hooks.register("insert_editor_js")
def editor_js():
return format_html(
"""
""",
reverse("myapp_chooser:choose"),
)
```
##### New
Remove the `insert_editor_js` hook usage and instead pass the data needed via the Entity’s data.
```python
# .../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](../extending/extending_draftail.md#creating-new-draftail-editor-entities) to be registered as a replacement feature for Draftail customizations as per the documentation.
```py
# .../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:
```html
```
### 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:
```html
```
### 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](../reference/streamfield/widget_api.md#streamfield-widget-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`.
```python
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](https://raw.githubusercontent.com/wagtail/wagtail/stable/6.0.x/wagtail/admin/static_src/wagtailadmin/js/vendor/urlify.js). Alternatively, the [`slugify`](https://www.npmjs.com/package/slugify) or [`parameterize` (a Django URLify port)](https://www.npmjs.com/package/parameterize) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions).
```javascript
// 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, '')
```
# 6.2.1.html.md
# Wagtail 6.2.1 release notes
*August 20, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Handle `child_block` being passed as a kwarg in ListBlock migrations (Matt Westcott)
* Fix broken task type filter in workflow task chooser modal (Sage Abdullah)
* Prevent circular imports between `wagtail.admin.models` and custom user models (Matt Westcott)
* Ensure that concurrent editing check works for users who only have edit access via workflows (Matt Westcott)
# 6.2.2.html.md
# Wagtail 6.2.2 release notes
*September 24, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix various instances of `USE_THOUSAND_SEPARATOR` formatting numbers where formatting is invalid (Sébastien Corbin, Matt Westcott)
* Fix broken link to user search (Shlomo Markowitz)
* Make sure content metrics falls back to body element only when intended (Sage Abdullah)
* Remove wrongly-added filters from redirects index (Matt Westcott)
* Prevent popular tags filter from generating overly complex queries when not filtering (Matt Westcott)
### Documentation
* Clarify process for [UserViewSet customization](../advanced_topics/customization/custom_user_models.md#custom-userviewset) (Sage Abdullah)
# 6.2.3.html.md
# Wagtail 6.2.3 release notes
*November 1, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent multiple URLs from being combined into one when pasting links into a rich text input (Thibaud Colas)
* Fix error on workflow settings view with multiple snippet types assigned to the same workflow on Postgres (Sage Abdullah)
* Prevent history view from breaking if a log entry’s revision is missing (Matt Westcott)
### Documentation
* Upgrade sphinx-wagtail-theme to v6.4.0, with a new search integration and Read the Docs Addons bug fixes (Thibaud Colas)
# 6.2.4.html.md
# Wagtail 6.2.4 release notes
*June 12, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix broken migration when ListBlock is defined with a `child_block` kwarg (Matt Westcott)
# 6.2.html.md
# Wagtail 6.2 release notes
*August 1, 2024*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
## What’s new
### Content metrics
The page editor’s Checks panel now displays two content metrics: word count, and reading time.
They are calculated based on the contents of the page preview, with a new mechanism to extract content from the previewed page for processing within the page editor. The Checks panel has also been redesigned to accommodate a wider breadth of types of checks, and interactive checks, in future releases.
This feature was developed by Albina Starykova and sponsored by The Motley Fool.
### Concurrent editing notifications
When multiple users concurrently work on the same content, Wagtail now displays notifications to inform them of potential editing conflicts. When a user saves their work, other users are informed and presented with options: they can refresh the page to view the latest changes, or proceed with their own changes, overwriting the other user’s work.
Concurrent editing notifications are available for pages, and snippets. Specific messaging about conflicting versions is only available for pages and snippets with [support for saving revisions](../topics/snippets/features.md#wagtailsnippets-saving-revisions-of-snippets). To configure how often notifications are updated, use [`WAGTAIL_EDITING_SESSION_PING_INTERVAL`](../reference/settings.md#wagtail-editing-session-ping-interval).
This feature was implemented by Matt Westcott and Sage Abdullah.
### Alt text accessibility check
The [built-in accessibility checker](../advanced_topics/accessibility_considerations.md#authoring-accessible-content) now enforces a new `alt-text-quality` rule, which tests alt text for the presence of known bad patterns such as file extensions and underscores. This rule is enabled by default, but can be disabled if necessary.
This feature was implemented by Albina Starykova, with support from the Wagtail accessibility team.
### Universal listings designs for report views
All built-in and custom report views now use the Universal Listings visual design and filtering features introduced in all other listings in the admin interface over past releases. Thank you to Sage Abdullah for implementing this feature and continuing the rollout of the new designs.
### Compact StreamField representation for migrations
StreamField definitions within migrations are now represented in a more compact form, where blocks that appear in multiple places within a StreamField structure are only defined once. For complex and deeply-nested StreamFields, this considerably reduces the size of migration files, and the memory consumption when loading them. This feature was developed by Matt Westcott.
### Other features
* Optimize and consolidate redirects report view into the index view (Jake Howard, Dan Braghis)
* Support a [`HOSTNAMES` parameter on `WAGTAILFRONTENDCACHE`](../reference/contrib/frontendcache.md#frontendcache-multiple-backends) to define which hostnames a backend should respond to (Jake Howard, sponsored by Oxfam America)
* Refactor redirects edit view to use the generic `EditView` and breadcrumbs (Rohit Sharma)
* Allow custom permission policies on snippets to prevent superusers from creating or editing them (Sage Abdullah)
* Do not link to edit view from listing views if user has no permission to edit (Sage Abdullah)
* Allow access to snippets and other model viewsets to users with “View” permission (Sage Abdullah)
* Skip `ChooseParentView` if only one possible valid parent page is available (Matthias Brück)
* Add `copy_for_translation_done` signal when a page is copied for translation (Arnar Tumi Þorsteinsson)
* Remove reduced opacity for draft page title in listings (Inju Michorius)
* Implement a new design for locale labels in listings (Albina Starykova)
* Add a `deactivate()` method to `ProgressController` (Alex Morega)
* Allow manually specifying credentials for CloudFront frontend cache backend (Jake Howard)
* Automatically register permissions for models registered with a `ModelViewSet` (Sage Abdullah)
* Make `routable_resolver_match` attribute available on RoutablePageMixin responses (Andy Chosak)
* Support customizations to `UserViewSet` via the app config (Sage Abdullah)
* Allow changing available privacy options per page model (Shlomo Markowitz)
* Add “soft” client-side validation for `StreamBlock` / `ListBlock` `min_num` / `max_num` (Matt Westcott)
* Log accessibility checker results in the console to help developers with troubleshooting (Thibaud Colas)
* Disable pointer events on checker highlights to simplify DevTools inspections (Thibaud Colas)
### Bug fixes
* Make `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` setting functional again (Rohit Sharma)
* Enable `richtext` template tag to convert lazy translation values (Benjamin Bach)
* Ensure permission labels on group permissions page are translated where available (Matt Westcott)
* Preserve whitespace in comment replies (Elhussein Almasri)
* Address layout issues in the title cell of universal listings (Sage Abdullah)
* Support SVG icon id attributes with single quotes in the styleguide (Sage Abdullah)
* Do not show delete button on model edit views if per-instance permissions prevent deletion (Matt Westcott)
* Remove duplicate header in privacy dialog when a privacy setting is set on a parent page or collection (Matthias Brück)
* Allow renditions of `.ico` images (Julie Rymer)
* Fix the rendering of grouped choices when using ChoiceFilter in combination with choices (Sébastien Corbin)
* Add separators when displaying multiple error messages on a StructBlock (Kyle Bayliss)
* Specify `verbose_name` on `TranslatableMixin.locale` so that it is translated when used as a label (Romein van Buren)
* Disallow null characters in API filter values (Jochen Wersdörfer)
* Fix image preview when Willow optimizers are enabled (Alex Tomkins)
* Ensure external-to-internal link conversion works when the `wagtail_serve` view is on a non-root path (Sage Abdullah)
* Add missing `for_instance` method to `PageLogEntryManager` (Matt Westcott)
* Ensure that “User” column on history view is translatable (Romein van Buren)
* Handle StreamField migrations where the field value is null (Joshua Munn)
* Prevent incorrect menu ordering when order value is 0 (Ben Dickinson)
* Fix dynamic image serve view with certain backends (Sébastien Corbin)
* Show not allowed extension in error message (Sahil Jangra)
* Fix focal point chooser when localization enabled (Sébastien Corbin)
* Ensure that system checks for `WAGTAIL_DATE_FORMAT`, `WAGTAIL_DATETIME_FORMAT` and `WAGTAIL_TIME_FORMAT` take `FORMAT_MODULE_PATH` into account (Sébastien Corbin)
* Prevent rich text fields inside choosers from being duplicated when opened repeatedly (Sage Abdullah)
### Documentation
* Remove duplicate section on frontend caching proxies from performance page (Jake Howard)
* Document `restriction_type` field on [PageViewRestriction](../reference/models.md) (Shlomo Markowitz)
* Document Wagtail’s bug bounty policy (Jake Howard)
* Fix incorrect Sphinx-style code references to use MyST style (Byron Peebles)
* Document the fact that `Orderable` is not required for inline panels (Bojan Mihelac)
* Add note about `prefers-reduced-motion` to the [accessibility documentation](../advanced_topics/accessibility_considerations.md#accessibility-resources) (Roel Koper)
* Update deployment instructions for Fly.io (Jeroen de Vries)
* Add better docs for generating URLs on [creating admin views](../extending/admin_views.md) (Shlomo Markowitz)
* Document the `vary_fields` property for [custom image filters](../extending/custom_image_filters.md#custom-image-filters) (Daniel Kirkham)
* Fix documentation build errors (Himanshu Garg, Chris Shenton)
* Fix PDF export (Nathanaël Jourdane)
### Maintenance
* Use `DjangoJSONEncoder` instead of custom `LazyStringEncoder` to serialize Draftail config (Sage Abdullah)
* Refactor image chooser pagination to check `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` at runtime (Matt Westcott)
* Exclude the `client/scss` directory in Tailwind content config to speed up CSS compilation (Sage Abdullah)
* Split `contrib.frontend_cache.backends` into dedicated sub-modules (Andy Babic)
* Remove unused `docs/autobuild.sh` script (Sævar Öfjörð Magnússon)
* Replace `urlparse` with `urlsplit` to improve performance (Jake Howard)
* Optimize embed finder lookups (Jake Howard)
* Improve performance of initial admin loading by moving sprite hashing out of module import time (Jake Howard)
* Remove workaround and inline scripts for activating workflow actions (Sage Abdullah)
* Prevent `'BlockWidget' object has no attribute '_block_json'` from masking errors during StreamField serialization (Matt Westcott)
## Upgrade considerations - changes affecting all projects
### Specifying a dict of distribution IDs for CloudFront cache invalidation is deprecated
Previous versions allowed passing a dict for `DISTRIBUTION_ID` within the `WAGTAILFRONTENDCACHE` configuration for a CloudFront backend, to allow specifying different distribution IDs for different hostnames. This is now deprecated; instead, multiple distribution IDs should be defined as [multiple backends](../reference/contrib/frontendcache.md#frontendcache-multiple-backends), with a `HOSTNAMES` parameter to define the hostnames associated with each one. For example, a configuration such as:
```python
WAGTAILFRONTENDCACHE = {
'cloudfront': {
'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudfrontBackend',
'DISTRIBUTION_ID': {
'www.wagtail.org': 'your-distribution-id',
'www.madewithwagtail.org': 'other-distribution-id',
},
},
}
```
should now be rewritten as:
```python
WAGTAILFRONTENDCACHE = {
'mainsite': {
'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudfrontBackend',
'DISTRIBUTION_ID': 'your-distribution-id',
'HOSTNAMES': ['www.wagtail.org'],
},
'madewithwagtail': {
'BACKEND': 'wagtail.contrib.frontend_cache.backends.CloudfrontBackend',
'DISTRIBUTION_ID': 'other-distribution-id',
'HOSTNAMES': ['www.madewithwagtail.org'],
},
}
```
### Changes to permissions registration for models with `ModelViewSet` and `SnippetViewSet`
Models registered with a `ModelViewSet` will now automatically have their [`Permission`](https://docs.djangoproject.com/en/stable/ref/contrib/auth/#django.contrib.auth.models.Permission) objects registered in the Groups administration area. Previously, you need to use the [`register_permissions`](../reference/hooks.md#register-permissions) hook to register them.
If you have a model registered with a `ModelViewSet` and you registered the model’s permissions using the `register_permissions` hook, you can now safely remove the hook.
If the viewset has [`inspect_view_enabled`](../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet.inspect_view_enabled) set to `True`, all permissions for the model are registered. Otherwise, the “view” permission is excluded from the registration.
To customize which permissions get registered for the model, you can override the [`get_permissions_to_register()`](../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet.get_permissions_to_register) method.
This behavior now applies to snippets as well. Previously, the “view” permission for snippets is always registered regardless of `inspect_view_enabled`. If you wish to register the “view” permission, you can enable the inspect view:
```py
class FooViewSet(SnippetViewSet):
...
inspect_view_enabled = True
```
Alternatively, if you wish to register the “view” permission without enabling the inspect view (i.e. the previous behavior), you can override `get_permissions_to_register` like the following:
```py
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
class FooViewSet(SnippetViewSet):
def get_permissions_to_register(self):
content_type = ContentType.objects.get_for_model(self.model)
return Permission.objects.filter(content_type=content_type)
```
### Deprecation of `WAGTAIL_USER_EDIT_FORM`, `WAGTAIL_USER_CREATION_FORM`, and `WAGTAIL_USER_CUSTOM_FIELDS` settings
This release introduces a customizable `UserViewSet` class, which can be used to customize various aspects of Wagtail’s admin views for managing users, including the form classes for creating and editing users. As a result, the `WAGTAIL_USER_EDIT_FORM`, `WAGTAIL_USER_CREATION_FORM`, and `WAGTAIL_USER_CUSTOM_FIELDS` settings have been deprecated in favor of customizing the form classes via `UserViewSet.get_form_class()`.
If you use the aforementioned settings, you can migrate your code by making the following changes.
#### Before
Given the following custom user model:
```py
class User(AbstractUser):
country = models.CharField(verbose_name='country', max_length=255)
status = models.ForeignKey(MembershipStatus, on_delete=models.SET_NULL, null=True, default=1)
```
The following custom forms:
```py
class CustomUserEditForm(UserEditForm):
status = forms.ModelChoiceField(queryset=MembershipStatus.objects, required=True, label=_("Status"))
class CustomUserCreationForm(UserCreationForm):
status = forms.ModelChoiceField(queryset=MembershipStatus.objects, required=True, label=_("Status"))
```
And the following settings:
```py
WAGTAIL_USER_EDIT_FORM = "myapp.forms.CustomUserEditForm"
WAGTAIL_USER_CREATION_FORM = "myapp.forms.CustomUserCreationForm"
WAGTAIL_USER_CUSTOM_FIELDS = ["country", "status"]
```
#### After
Change the custom forms to the following:
```py
class CustomUserEditForm(UserEditForm):
status = forms.ModelChoiceField(queryset=MembershipStatus.objects, required=True, label=_("Status"))
# Use ModelForm's automatic form fields generation for the model's `country` field,
# but use an explicit custom form field for `status`.
# This replaces the `WAGTAIL_USER_CUSTOM_FIELDS` setting.
class Meta(UserEditForm.Meta):
fields = UserEditForm.Meta.fields | {"country", "status"}
class CustomUserCreationForm(UserCreationForm):
status = forms.ModelChoiceField(queryset=MembershipStatus.objects, required=True, label=_("Status"))
# Use ModelForm's automatic form fields generation for the model's `country` field,
# but use an explicit custom form field for `status`.
# This replaces the `WAGTAIL_USER_CUSTOM_FIELDS` setting.
class Meta(UserCreationForm.Meta):
fields = UserEditForm.Meta.fields | {"country", "status"}
```
Create a custom `UserViewSet` subclass in e.g. `myapp/viewsets.py`:
```py
# myapp/viewsets.py
from wagtail.users.views.users import UserViewSet as WagtailUserViewSet
from .forms import CustomUserCreationForm, CustomUserEditForm
class UserViewSet(WagtailUserViewSet):
# This replaces the WAGTAIL_USER_EDIT_FORM and WAGTAIL_USER_CREATION_FORM settings
def get_form_class(self, for_update=False):
if for_update:
return CustomUserEditForm
return CustomUserCreationForm
```
If you already have a custom `GroupViewSet` as described in [Customizing group edit/create views](../extending/customizing_group_views.md#customizing-group-views), you can reuse the custom `WagtailUsersAppConfig` subclass. Otherwise, create an `apps.py` file within your project folder (the one containing the top-level settings and urls modules) e.g. `myproject/apps.py`. Then, create a custom `WagtailUsersAppConfig` subclass in that file, with a `user_viewset` attribute pointing to the custom `UserViewSet` subclass:
```py
# myproject/apps.py
from wagtail.users.apps import WagtailUsersAppConfig
class CustomUsersAppConfig(WagtailUsersAppConfig):
user_viewset = "myapp.viewsets.UserViewSet"
# If you have customized the GroupViewSet before
group_viewset = "myapp.viewsets.GroupViewSet"
```
Replace `wagtail.users` in `settings.INSTALLED_APPS` with the path to `CustomUsersAppConfig`:
```python
INSTALLED_APPS = [
...,
# Make sure you have two separate entries for the custom user model's app
# and the custom app config for the wagtail.users app
"myapp", # an app that contains the custom user model
"myproject.apps.CustomUsersAppConfig", # a custom app config for the wagtail.users app
# "wagtail.users", # this should be removed in favour of the custom app config
...,
]
```
#### WARNING
You can also place the `WagtailUsersAppConfig` subclass inside the same `apps.py` file of your custom user model’s app (instead of in a `myproject/apps.py` file), but you need to be careful. Make sure to use two separate config classes instead of turning your existing `AppConfig` subclass into a `WagtailUsersAppConfig` subclass, as that would cause Django to pick up your custom user model as being part of `wagtail.users`. You may also need to set [`default`](https://docs.djangoproject.com/en/stable/ref/applications/#django.apps.AppConfig.default) to `True` in your own app’s `AppConfig`, unless you already use a dotted path to the app’s `AppConfig` subclass in `INSTALLED_APPS`.
For more details, see [Creating a custom UserViewSet](../advanced_topics/customization/custom_user_models.md#custom-userviewset).
## Upgrade considerations - changes affecting Wagtail customizations
### Changes to report views with the new Universal Listings UI
The report views have been reimplemented to use the new Universal Listings UI, which introduces AJAX-based filtering and support for the `wagtail.admin.ui.tables` framework.
As a result, a number of changes have been made to the `ReportView` and `PageReportView` classes, as well as their templates.
If you have custom report views as documented in [Adding reports](../extending/adding_reports.md#adding-reports), you will need to make the following changes.
#### Change `title` to `page_title`
The `title` attribute on the view class should be renamed to `page_title`:
```diff
class UnpublishedChangesReportView(PageReportView):
- title = "Pages with unpublished changes"
+ page_title = "Pages with unpublished changes"
```
#### Set up the results-only view
Set the `index_url_name` and `index_results_url_name` attributes on the view class:
```diff
class UnpublishedChangesReportView(PageReportView):
+ index_url_name = "unpublished_changes_report"
+ index_results_url_name = "unpublished_changes_report_results"
```
and register the results-only view:
```diff
@hooks.register("register_admin_urls")
def register_unpublished_changes_report_url():
return [
path("reports/unpublished-changes/", UnpublishedChangesReportView.as_view(), name="unpublished_changes_report"),
+ # Add a results-only view to add support for AJAX-based filtering
+ path("reports/unpublished-changes/results/", UnpublishedChangesReportView.as_view(results_only=True), name="unpublished_changes_report_results"),
]
```
#### Adjust the templates
If you are only extending the templates to add your own markup for the listing table (and not other parts of the view template), you need to change the `template_name` into `results_template_name` on the view class.
For a page report, the following changes are needed:
- Change `template_name` to `results_template_name`, and optionally rename the template (e.g. `reports/unpublished_changes_report.html` to `reports/unpublished_changes_report_results.html`).
- The template should extend from `wagtailadmin/reports/base_page_report_results.html`.
- The `listing` and `no_results` blocks should be renamed to `results` and `no_results_message`, respectively.
```diff
class UnpublishedChangesReportView(PageReportView):
- template_name = "reports/unpublished_changes_report.html"
+ results_template_name = "reports/unpublished_changes_report_results.html"
```
```diff
{# /templates/reports/unpublished_changes_report_results.html #}
-{% extends "wagtailadmin/reports/base_page_report.html" %}
+{% extends "wagtailadmin/reports/base_page_report_results.html" %}
-{% block listing %}
+{% block results %}
{% include "reports/include/_list_unpublished_changes.html" %}
{% endblock %}
-{% block no_results %}
+{% block no_results_message %}
No pages with unpublished changes.
{% endblock %}
```
For a non-page report, the following changes are needed:
- Change `template_name` to `results_template_name`, and optionally rename the template (e.g. `reports/custom_non_page_report.html` to `reports/custom_non_page_report_results.html`).
- The template should extend from `wagtailadmin/reports/base_report_results.html`.
- Existing templates will typically define a `results` block containing both the results listing and the “no results” message; these should now become separate blocks named `results` and `no_results_message`.
**Before:**
```py
class CustomNonPageReportView(ReportView):
template_name = "reports/custom_non_page_report.html"
```
```html+django
{# /templates/reports/custom_non_page_report.html #}
{% extends "wagtailadmin/reports/base_report.html" %}
{% block results %}
{% if object_list %}
{% else %}
No results found.
{% endif %}
{% endblock %}
```
**After:**
```py
class CustomNonPageReportView(ReportView):
results_template_name = "reports/custom_non_page_report_results.html"
```
```html+django
{# /templates/reports/custom_non_page_report_results.html #}
{% extends "wagtailadmin/reports/base_report_results.html" %}
{% block results %}
{% endblock %}
{% block no_results_message %}
No results found.
{% endblock %}
```
If you need to completely customize the view’s template, you can still override the `template_name` attribute on the view class. Note that both `ReportView` and `PageReportView` now use the `wagtailadmin/reports/base_report.html` template, which now extends the `wagtailadmin/generic/listing.html` template. The `wagtailadmin/reports/base_page_report.html` template is now unused and should be replaced with `wagtailadmin/reports/base_report.html`.
If you override `template_name`, it is still necessary to set `results_template_name` to a template that extends `wagtailadmin/reports/base_report_results.html` (or `wagtailadmin/reports/base_page_report_results.html` for page reports), so the view can correctly update the listing and show the active filters as you apply or remove any filters.
## Upgrade considerations - changes to undocumented internals
### Deprecation of `window.ActivateWorkflowActionsForDashboard` and `window.ActivateWorkflowActionsForEditView`
The undocumented usage of the JavaScript `window.ActivateWorkflowActionsForDashboard` and `window.ActivateWorkflowActionsForEditView` functions will be removed in a future release.
These functions are only used by Wagtail to initialize event listeners to workflow action buttons via inline scripts and are never intended to be used in custom code. The inline scripts have been removed in favour of initializing the event listeners directly in the included `workflow-action.js` script. As a result, the functions no longer need to be globally-accessible.
Any custom workflow actions should be done using the [documented approach for customizing the behavior of custom task types](../extending/custom_tasks.md#custom-tasks-behavior), such as by overriding `Task.get_actions()`.
# 6.3.1.html.md
# Wagtail 6.3.1 release notes
*November 19, 2024*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Restore ability to upload profile picture through account settings (Sage Abdullah)
* Correctly handle `ImageChooserBlock` to `ImageBlock` data conversions where all inputs to `bulk_to_python` are null (Storm Heg, Matt Westcott)
* Improve spacing of page / collection permissions table in Group settings (Sage Abdullah)
* Remove forced capitalization of site name on admin dashboard (Thibaud Colas)
### Documentation
* Reword `BlogTagIndexPage` example for clarity and several other tweaks (Clifford Gama)
* Change title of blog index page in tutorial to avoid slug issues (Thibaud Colas)
* Fix `wagtailcache` and `wagtailpagecache` examples to not use quotes for the `fragment_name` (Shiv)
* Lower search result ranking for release notes on readthedocs search (Sage Abdullah)
# 6.3.2.html.md
# Wagtail 6.3.2 release notes
*January 2, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Ensure CloudFront cache invalidation is called with a list, for compatibility with current botocore versions (Jake Howard)
* Ensure Draftail features wrap when a large amount of features are added (Bart Cieliński)
* Implement `get_block_by_content_path` on `ImageBlock` to prevent errors on commenting (Matt Westcott)
### Documentation
* Update tutorial to reflect the move of the “Add child page” action to a top-level button in the header as a ‘+’ icon (Clifford Gama)
# 6.3.3.html.md
# Wagtail 6.3.3 release notes
*February 3, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Correctly place comment buttons next to date / datetime / time fields. (Srishti Jaiswal)
* Reduce confusing spacing below StreamField blocks help text (Rishabh Sharma)
* Make sure alt text quality check is on by default as documented (Thibaud Colas)
* Prevent `StreamChildrenToListBlockOperation` from duplicating data across multiple StreamField instances (Joshua Munn)
* Prevent database error when calling permission_order.register on app ready (Daniel Kirkham, Matt Westcott)
* Prevent error on lazily loading StreamField blocks after the stream has been modified (Stefan Hammer)
* Prevent syntax error on MySQL search when query includes symbols (Matt Westcott)
### Documentation
* Update example for customizing “p-as-heading” accessibility check without overriding built-in checks (Cynthia Kiser)
* Update accessibility considerations on alt text in light of contextual alt text improvements (Cynthia Kiser)
* Revert incorrect example of appending a `RichTextBlock` to a `StreamField` (Matt Westcott)
# 6.3.4.html.md
# Wagtail 6.3.4 release notes
*April 24, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Django 5.2 support
This release adds support for Django 5.2.
### Bug fixes
* Add missing “Close” label to the upgrade notification dismiss button (Sage Abdullah)
* Fix white text on white background in previews for sites that use color-scheme without a background-color (Sage Abdullah)
### Maintenance
* Remove upper version boundary for django-filter (Dan Braghis)
# 6.3.5.html.md
# Wagtail 6.3.5 release notes
*June 12, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Use correct URL when redirecting back to the listing after filtering and deleting form submissions (Sage Abdullah)
* Fix broken migration when ListBlock is defined with a `child_block` kwarg (Matt Westcott)
### Maintenance
* Use `utf8mb4` charset and collation for MySQL test database (Sage Abdullah)
# 6.3.6.html.md
# Wagtail 6.3.6 release notes
*February 3, 2026*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2026-25517: Improper permission handling on admin preview endpoints
This release addresses a permission vulnerability in the Wagtail admin interface. Due to a missing permission check on the preview endpoints, a user with access to the Wagtail admin and knowledge of a model’s fields can craft a form submission to obtain a preview rendering of any page or snippet object for which previews are enabled, consisting of any data of the user’s choosing. The existing data of the object itself is not exposed, but depending on the nature of the template being rendered, this may expose other database contents that would otherwise only be accessible to users with edit access over the model. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
### Bug fixes
* Remove ngram parser on MySQL that prevented autocomplete search from returning results (Vince Salvino)
# 6.3.7.html.md
# Wagtail 6.3.7 release notes
*February 12, 2026*
> * [What’s new](#what-s-new)
## What’s new
* Remove upper bound on Pillow dependency
# 6.3.html.md
# Wagtail 6.3 (LTS) release notes
*November 1, 2024*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - deprecation of old functionality](#upgrade-considerations-deprecation-of-old-functionality)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
Wagtail 6.3 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### Python 3.13 support
This release adds formal support for Python 3.13.
### Django 5.1 support
This release adds formal support for Django 5.1.
### `ImageBlock` with alt text support
This release introduces a new block type [`ImageBlock`](../reference/streamfield/blocks.md#streamfield-imageblock), which improves upon `ImageChooserBlock` by allowing editors to specify alt text tailored to the context in which the image is used. This is the new recommended block type for all images that are not purely decorative, and existing instances of `ImageChooserBlock` can be directly replaced with `ImageBlock` (with no data migration or template changes required) to benefit from contextual alt text. This feature was developed by Chiemezuo Akujobi as part of the Google Summer of Code program with mentoring support from Storm Heg, Saptak Sengupta, Thibaud Colas and Matt Westcott.
### Incremental dashboard enhancements
The Wagtail dashboard design evolves towards providing more information and navigation features. Mobile support is much improved. Upgrade banners are now dismissible.
This feature was developed by Albina Starykova and Sage Abdullah, based on designs by Ben Enright.
### Enhanced contrast admin theme
CMS users can now control the level of contrast of UI elements in the admin interface.
This new customization is designed for partially sighted users, complementing existing support for a dark theme and Windows Contrast Themes.
The new “More contrast” theming can be enabled in account preferences, or will otherwise be derived from operating system preferences.
This feature was designed thanks to feedback from our blind and partially sighted users, and was developed by Albina Starykova based on design input from Victoria Ottah.
### Universal design
This release follows through with “universal listings” user experience and design consistency improvements earlier in 2024, with the following features.
* All create/edit admin forms now use a sticky submit button, for consistency and to speed up edits
* Secondary form actions such as “Delete” are now in the header actions menu, for consistency and to make the actions more easily reachable for keyboard users
* Documents and Images views now use universal listings styles
* Page type usage, workflow usage, and workflow history views also use universal listings styles
* The forms pages listing now supports search and filtering
These features were developed by Sage Abdullah.
### HEIC / HEIF image upload support
The `WAGTAILIMAGES_EXTENSIONS` setting now accepts the `heic` extension, which allows users to upload and use HEIC / HEIF images in Wagtail. These images are automatically converted to JPEG format when rendered. For more details, see [HEIC / HEIF images](../topics/images.md#heic-heif-images).
This feature was developed by Matt Westcott.
### Custom preview sizes support
You can now customize the preview device sizes available in the live preview panel by overriding [`preview_sizes`](../reference/models.md#wagtail.models.PreviewableMixin.preview_sizes). The default size can also be set by overriding [`default_preview_size`](../reference/models.md#wagtail.models.PreviewableMixin.default_preview_size).
This feature was developed by Bart Cieliński, alexkiro, and Sage Abdullah.
### Other features
* Formalize support for MariaDB (Sage Abdullah, Daniel Black)
* Redirect to the last viewed listing page after deleting form submissions (Matthias Brück)
* Provide `getTextLabel` method on date / time StreamField blocks (Vaughn Dickson)
* Purge frontend cache when modifying redirects (Jake Howard)
* Add search and filters to form pages listing (Sage Abdullah)
* Deprecate the `WAGTAIL_AUTO_UPDATE_PREVIEW` setting, use `WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL = 0` instead (Sage Abdullah)
* Consistently use `capfirst` for title-casing model verbose names (Sébastien Corbin)
* Fire `copy_for_translation_done` signal when copying translatable models as well as pages (Coen van der Kamp)
* Add support for an image `description` field across all images, to better support accessible image descriptions (Chiemezuo Akujobi)
* Prompt the user about unsaved changes when editing snippets (Sage Abdullah)
* Add support for specifying different preview modes to the “View draft” URL for pages (Robin Varghese)
* Implement new designs for the footer actions dropdown with more contrast and larger text (Sage Abdullah)
* `StaticBlock` now renders nothing by default when no template is specified (Sævar Öfjörð Magnússon)
* Allow setting page privacy rules when a restriction already exists on an ancestor page (Bojan Mihelac)
* Automatically create links when pasting content that contains URLs into a rich text input (Thibaud Colas)
* Add Uyghur language support
### Bug fixes
* Prevent page type business rules from blocking reordering of pages (Andy Babic, Sage Abdullah)
* Improve layout of object permissions table (Sage Abdullah)
* Fix typo in aria-label attribute of page explorer navigation link (Sébastien Corbin)
* Reinstate transparency indicator on image chooser widgets (Sébastien Corbin)
* Remove table headers that have no text (Matt Westcott)
* Fix broken link to user search (Shlomo Markowitz)
* Ensure that JS slugify function strips Unicode characters disallowed by Django slug validation (Atif Khan)
* Do not show notices about root / unroutable pages when searching or filtering in the page explorer (Matt Westcott)
* Resolve contrast issue for page deletion warning (Sanjeev Holla S)
* Make sure content metrics falls back to body element only when intended (Sage Abdullah)
* Remove wrongly-added filters from redirects index (Matt Westcott)
* Prevent popular tags filter from generating overly complex queries when not filtering (Matt Westcott)
* Fix content path links in usage view to scroll to the correct element (Sage Abdullah)
* Always show the minimap toggle button (Albina Starykova)
* Ensure invalid submissions are marked as dirty edits on load to trigger UI and browser warnings for unsaved changes, restoring previous behavior from Wagtail 5.2 (Sage Abdullah)
* Update polldaddy oEmbed provider to use the crowdsignal URL (Matthew Scouten)
* Remove polleverywhere oEmbed provider as this application longer supports oEmbed (Matthew Scouten)
* Ensure that dropdown button toggles show with a border in high contrast mode (Ishwari8104, LB (Ben) Johnston)
* Update email notification header to the new logo design (rahulsamant37)
* Change `file_size` field on document model to avoid artificial 2Gb limit (Gabriel Getzie)
* Ensure that `TypedTableBlock` uses the correct API representations of child blocks (Matt Westcott)
* Footer action buttons now include their `media` definitions (Sage Abdullah)
* Improve the text contrast of the bulk actions “Select all” button (Sage Abdullah)
* Fix error on workflow settings view with multiple snippet types assigned to the same workflow on Postgres (Sage Abdullah)
* Prevent history view from breaking if a log entry’s revision is missing (Matt Westcott)
* Prevent long filenames from breaking layout on document chooser listings (Frank Yiu, Shaurya Panchal)
* Fix datetime fields overflowing its parent wrapper in listing filters (Rohit Singh)
* Prevent multiple URLs from being combined into one when pasting links into a rich text input (Thibaud Colas)
* Improve layout of report listing tables (Sage Abdullah)
* Fix regression from creation of `AbstractGroupApprovalTask` to ensure `can_handle` checks for the abstract class correctly (Sumana Sree Angajala)
### Documentation
* Upgrade Sphinx to 7.3 (Matt Westcott)
* Upgrade sphinx-wagtail-theme to v6.4.0, with a new search integration and Read the Docs Addons bug fixes (Thibaud Colas)
* Document how to [customize date/time format settings](../reference/settings.md#wagtail-date-time-formats) (Vince Salvino)
* Create a new documentation section for [deployment](../deployment/index.md#deployment-guide) and move `fly.io` deployment from the tutorial to this section (Vince Salvino)
* Clarify process for [UserViewSet customization](../advanced_topics/customization/custom_user_models.md#custom-userviewset) (Sage Abdullah)
* Correct [`WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDIT`](../reference/settings.md#workflow-settings) documentation to state that it defaults to `False` (Matt Westcott)
* Add an example of customizing a default accessibility check in the [Authoring accessible content](../advanced_topics/accessibility_considerations.md#authoring-accessible-content) section (Cynthia Kiser)
* Demonstrate access protection with `TokenAuthentication` in the [Wagtail API v2 Configuration Guide](../advanced_topics/api/v2/configuration.md) (Krzysztof Jeziorny)
* Replace X links with Mastodon in the README (Alex Morega)
* Re-enable building offline formats in online documentation (Read the docs) for EPUB/PDF/HTML downloads (Joel William, Sage Abdullah)
* Resolve multiple output errors in the documentation ePub format (Sage Abdullah)
* Update social media examples to use LinkedIn, Reddit, Facebook (Ayaan Qadri)
### Maintenance
* Removed support for Python 3.8 (Matt Westcott)
* Drop `pytz` dependency in favour of `zoneinfo.available_timezones` (Sage Abdullah)
* Relax `django-taggit` dependency to allow 6.0 (Matt Westcott)
* Improve page listing performance (Sage Abdullah)
* Phase out usage of `SECRET_KEY` in version and icon hashes (Jake Howard)
* Audit all use of localized and non-localized numbers in templates (Matt Westcott)
* Refactor StreamField `get_prep_value` for closer alignment with JSONField (Sage Abdullah)
* Move search implementation logic from generic `IndexView` to `BaseListingView` (Sage Abdullah)
* Upgrade Puppeteer integration tests for reliability (Matt Westcott)
* Restore ability to use `.in_bulk()` on specific querysets under Django 5.2a0 (Sage Abdullah)
* Add generated `test-media` to .gitignore (Shlomo Markowitz)
* Improve `debounce` util’s return type for better TypeScript usage (Sage Abdullah)
* Ensure the side panel’s show event is dispatched after any hide events (Sage Abdullah)
* Migrate preview-panel JavaScript to Stimulus & TypeScript, add full unit testing (Sage Abdullah)
* Move `wagtailConfig` values from inline scripts to the `wagtail_config` template tag (LB (Ben) Johnston, Sage Abdullah)
* Deprecate the `{% locales %}` and `{% js_translation_strings %}` template tags (LB (Ben) Johnston, Sage Abdullah)
* Adopt the modern best practice for `beforeunload` usage in `UnsavedController` to trigger a leave page warning when edits have been made (Shubham Mukati, Sage Abdullah)
* Ensure multi-line comments are cleaned from custom icons in addition to just single line comments (Jake Howard)
* Deprecate `window.wagtailConfig.BULK_ACTION_ITEM_TYPE` usage in JavaScript to reduce reliance on inline scripts (LB (Ben) Johnston)
* Remove `window.fileupload_opts` usage in JavaScript, use data attributes on fields instead to reduce reliance on inline scripts (LB (Ben) Johnston)
* Remove `image_format_name_to_content_type` helper function that duplicates Willow functionality (Matt Westcott)
* Improve code reuse for footer actions markup across generic views (Sage Abdullah)
* Deprecate internal `DeleteMenuItem` API for footer actions (Sage Abdullah)
* Update Pillow dependency to allow 11.x (Storm Heg)
## Upgrade considerations - deprecation of old functionality
### Removed support for Python 3.8
Python 3.8 is no longer supported as of this release; please upgrade to Python 3.9 or above before upgrading Wagtail.
### Deprecation of the `WAGTAIL_AUTO_UPDATE_PREVIEW` setting
The `WAGTAIL_AUTO_UPDATE_PREVIEW` setting has been deprecated and will be removed in a future release.
To disable the automatic preview update feature, set [`WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL = 0`](../reference/settings.md#wagtail-auto-update-preview-interval) in your Django settings instead.
## Upgrade considerations - changes to undocumented internals
### Deprecation of `window.wagtailConfig.BULK_ACTION_ITEM_TYPE`
As part of migrating away from inline scripts, the undocumented use of `window.wagtailConfig.BULK_ACTION_ITEM_TYPE` as a global has been deprecated and will be removed in a future release.
**Old**
```html+django
{% block extra_js %}
{{ block.super }}
{% endblock %}
```
**New**
Update usage of the `wagtailadmin/bulk_actions/footer.html` template include to declare the `item_type`.
```html+django
{% block bulk_actions %}
{% include 'wagtailadmin/bulk_actions/footer.html' ... item_type="SOME_ITEM" %}
{% endblock %}
```
#### NOTE
Custom item types for bulk actions are not officially supported yet and this approach is likely to get further changes in the future.
### Deprecation of the `{% locales %}` template tag
The undocumented `locales` template tag will be removed in a future release.
If access to JSON locales within JavaScript is needed, use `window.wagtailConfig.LOCALES` instead.
### Deprecation of the `{% js_translation_strings %}` template tag
The undocumented `js_translation_strings` template tag will be removed in a future release.
If access to JSON translation strings within JavaScript is needed, use `window.wagtailConfig.STRINGS` instead.
### `UpgradeNotificationPanel` is no longer removable with `construct_homepage_panels` hook
The upgrade notification panel can still be removed with the [`WAGTAIL_ENABLE_UPDATE_CHECK = False`](../reference/settings.md#update-notifications) setting.
### `SiteSummaryPanel` is no longer removable with `construct_homepage_panels` hook
The summary items can still be removed with the [`construct_homepage_summary_items`](../reference/hooks.md#construct-homepage-summary-items) hook.
### Deprecation of `DeleteMenuItem`
The undocumented `DeleteMenuItem` API will be removed in a future release.
The delete option is now provided via `EditView.get_header_more_buttons()`, though this is still an internal-only API.
# 6.4.1.html.md
# Wagtail 6.4.1 release notes
*February 21, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent error when filtering by locale and searching with Elasticsearch (Sage Abdullah)
* Support searching `none()` querysets (Matt Westcott)
* Correctly handle UUID primary keys when invoking background tasks (Matt Westcott)
* Fix regression where nested sub-menu items would not be visible (Sage Abdullah)
* Ensure the top of the minimap correctly adjusts when resizing the browser viewport (Thibaud Colas)
* Remove obsolete SubqueryConstraint check in search backends, for provisional Django 5.2 compatibility (Sage Abdullah)
* Add missing “Close” label to the upgrade notification dismiss button (Sage Abdullah)
* Fix white text on white background in previews for sites that use color-scheme without a background-color (Sage Abdullah)
### Documentation
* Fix typo in the headless documentation page (Mahmoud Nasser)
* Fix typo in `Page.get_route_paths` docstring (Baptiste Mispelon)
* Upgrade sphinx-wagtail-theme to v6.5.0 (Sage Abdullah)
### Maintenance
* Remove upper version boundary for django-filter (Dan Braghis)
* Relax upper version boundaries for django-taggit and beautifulsoup4 (Matt Westcott)
# 6.4.2.html.md
# Wagtail 6.4.2 release notes
*June 12, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Do not show upgrade notification if the installed version is the latest (Sage Abdullah)
* Use correct URL when redirecting back to the listing after filtering and deleting form submissions (Sage Abdullah)
* Fix broken migration when ListBlock is defined with a `child_block` kwarg (Matt Westcott)
* Sanitize request data when logging method not allowed (Jake Howard)
### Maintenance
* Use `utf8mb4` charset and collation for MySQL test database (Sage Abdullah)
# 6.4.html.md
# Wagtail 6.4 release notes
*February 3, 2025*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - changes affecting Wagtail customizations](#upgrade-considerations-changes-affecting-wagtail-customizations)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
## What’s new
### Support for background tasks using `django-tasks`
Wagtail now integrates with the [django-tasks](https://github.com/realOrangeOne/django-tasks) library to allow moving computationally-intensive tasks out of the request-response cycle, and improving the performance of the Wagtail admin interface. These tasks include:
* Updating the [search index](../topics/search/indexing.md#wagtailsearch-indexing)
* Updating or creating entries for the [reference index](../advanced_topics/reference_index.md#managing-the-reference-index)
* Calculating [image focal points](../advanced_topics/images/focal_points.md#image-focal-points)
* Purging [frontend caching](../reference/contrib/frontendcache.md#frontend-cache-purging) URLs
* Deleting image and document files when the model is deleted
In the default configuration, these tasks are executed immediately within the request-response cycle, as they were in previous versions of Wagtail. However, by configuring the `TASKS` setting with an appropriate backend [as per the django-tasks documentation](https://github.com/realOrangeOne/django-tasks?tab=readme-ov-file#installation), these tasks can be deferred to a background worker process.
This feature was developed by Jake Howard.
### Previews for StreamField blocks
You can now set up previews for StreamField blocks. The preview will be shown in the block picker, along with a description for the block. This feature can help users choose the right block when writing content inside a StreamField. To enable this feature, see [Configuring block previews](../topics/streamfield.md#configuring-block-previews).
This feature was developed by Sage Abdullah and Thibaud Colas.
### Headless documentation
The new [Headless support](../advanced_topics/headless.md#headless) documentation page curates important information about Wagtail’s headless capabilities, directly within the developer documentation. This is the foundation for a [headless improvements roadmap](https://github.com/wagtail/rfcs/pull/100) for Wagtail.
Thank you to Sævar Öfjörð Magnússon and Alex Fulcher for creating this new page at the Wagtail Space NL 2024 sprint.
### Alt text improvements
Building upon improvements in past versions, this release comes with further enhancements to alt text management:
* The [content modeling accessibility considerations](../advanced_topics/accessibility_considerations.md#content-modeling) now reflect the latest capabilities for alt text.
* The new `ImageBlock` alt text is now populated from the image’s default alt text when selecting a new image.
* The `ImageBlock` alt text field is also populated from the image’s default alt text when converting from an ImageChooserBlock.
* Alt text quality is now checked by default, as was intended with the `alt-text-quality` content check.
Thank you to Matt Westcott, Thibaud Colas, and Cynthia Kiser for their work on these improvements.
### Shorthand for `FieldPanel` and `InlinePanel`
Plain strings can now be used in panel definitions as a substitute for `FieldPanel` and `InlinePanel`, avoiding the need to import these classes from `wagtail.admin.panels`:
```python
class MyPage(Page):
body = RichTextField()
content_panels = [
'body',
]
```
This feature was developed by Matt Westcott.
### Drag-and-drop support for StreamField and InlinePanel
The StreamField and InlinePanel interfaces now support drag-and-drop reordering of items within a field. This makes it faster to rearrange content within these fields, particularly when there is a lot of content.
This feature was developed by Thibaud Colas and Sage Abdullah, thanks to a sponsorship by Lyst.
### Search terms report
For users of [promoted search results](../reference/contrib/searchpromotions.md#editors-picks), a new search terms report is available in the admin interface.
This report shows terms that website users have searched for, and how many times they have been searched.
This can help you understand what your users are looking for, and adjust your search promotions accordingly.
This feature was developed by Noah van der Meer and Sage Abdullah.
### Performance optimizations
Following from a recent audit, this release comes with performance improvements focused on the user interface.
* Prevent main menu from re-rendering when clicking outside while the menu is closed (Sage Abdullah)
* Skip loading of unused JavaScript to speed up 404 page rendering (Sage Abdullah)
* Remove support for Safari 15 (Thibaud Colas)
* Limit tags autocompletion to 10 items and add delay to avoid performance issues with large number of matching tags (Aayushman Singh)
### Other features
* Add the ability to apply basic Page QuerySet optimizations to `specific()` sub-queries using `select_related` & `prefetch_related`, see [Page QuerySet reference](../reference/pages/queryset_reference.md) (Andy Babic)
* Increase `DATA_UPLOAD_MAX_NUMBER_FIELDS` in project template (Matt Westcott)
* Stop invalid Site hostname records from breaking preview (Matt Westcott)
* Set sensible defaults for InlinePanel heading and label (Matt Westcott)
* Add the ability to restrict what types of requests a Page supports via [`allowed_http_methods`](../reference/models.md#wagtail.models.Page.allowed_http_methods) (Andy Babic)
* Only allow selection of valid new parents within the copy Page view (Mauro Soche)
* Add [`on_serve_page`](../reference/hooks.md#on-serve-page) hook to modify the serving chain of pages (Krystian Magdziarz, Dawid Bugajewski)
* Add support for [`WAGTAIL_GRAVATAR_PROVIDER_URL`](../reference/settings.md#wagtail-gravatar-provider-url) URLs with query string parameters (Ayaan Qadri, Guilhem Saurel)
* Add [`get_avatar_url`](../reference/hooks.md#get-avatar-url) hook to customise user avatars (James Harrington)
* Set content security policy (CSP) headers to block embedded content when serving images and documents (Jake Howard, with thanks to Ali İltizar for the initial report)
* Add `page` as a third parameter to the [`construct_wagtail_userbar`](../reference/hooks.md#construct-wagtail-userbar) hook (claudobahn)
* Enable breadcrumbs in revisions compare view (Sage Abdullah)
* Replace l18n library with JavaScript Intl API for time zone options in Account view (Sage Abdullah)
* Use explicit label for defaulting to server language in account settings (Sage Abdullah)
* Add support for specifying an operator on `Fuzzy` queries (Tom Usher)
* Make sure typing text at the bottom of the page editor always scrolls enough to keep the text into view (Jatin Bhardwaj)
### Bug fixes
* Improve handling of translations for bulk page action confirmation messages (Matt Westcott)
* Ensure custom rich text feature icons are correctly handled when provided as a list of SVG paths (Temidayo Azeez, Joel William, LB (Ben) Johnston)
* Prevent error on lazily loading StreamField blocks after the stream has been modified (Stefan Hammer)
* Fix sub-menus within the main menu cannot be closed on mobile (Bojan Mihelac)
* Fix animation overflow transition when navigating through subpages in the sidebar page explorer (manu)
* Ensure form builder supports custom admin form validation (John-Scott Atlakson, LB (Ben) Johnston)
* Ensure form builder correctly checks for duplicate field names when using a custom related name (John-Scott Atlakson, LB (Ben) Johnston)
* Normalize `StreamField.get_default()` to prevent creation forms from breaking (Matt Westcott)
* Prevent out-of-order migrations from skipping creation of image/document choose permissions (Matt Westcott)
* Use correct connections on multi-database setups in database search backends (Jake Howard)
* Ensure CloudFront cache invalidation is called with a list, for compatibility with current botocore versions (Jake Howard)
* Show the correct privacy status in the sidebar when creating a new page (Joel William)
* Prevent generic model edit view from unquoting non-integer primary keys multiple times (Matt Westcott)
* Ensure comments are functional when editing Page models with `read_only` `FieldPanel`s in use (Strapchay)
* Ensure the accessible labels and tooltips reflect the correct private/public status on the live link button within pages after changing the privacy (Ayaan Qadri)
* Fix empty `th` (table heading) elements that are not compliant with accessibility standards (Jai Vignesh J)
* Ensure `MultipleChooserPanel` using images or documents work when nested within an `InlinePanel` when no other choosers are in use within the model (Elhussein Almasri)
* Ensure `MultipleChooserPanel` works after doing a search in the page chooser modal (Matt Westcott)
* Ensure new `ListBlock` instances get created with unique IDs in the admin client for accessibility and mini-map element references (Srishti Jaiswal)
* Return never-cache HTTP headers when serving pages and documents with view restrictions (Krystian Magdziarz, Dawid Bugajewski)
* Implement `get_block_by_content_path` on `ImageBlock` to prevent errors on commenting (Matt Westcott)
* Add `aria-expanded` attribute to new column button on `TypedTableBlock` to reflect menu state (Ayaan Qadri, Scott Cranfill)
* Allow page models to extend base `Page` panel definitions without importing `wagtail.admin` (Matt Westcott)
* Fix crash when loading the dashboard with only the “unlock” or “bulk delete” page permissions (Unyime Emmanuel Udoh, Sage Abdullah)
* Improve deprecation warning for `WidgetWithScript` by raising it with `stacklevel=3` (Joren Hammudoglu)
* Correctly place comment buttons next to date / datetime / time fields. (Srishti Jaiswal)
* Add missing `FilterField("created_at")` to `AbstractDocument` to fix ordering by `created_at` after searching in the documents index view (Srishti Jaiswal)
* Add missing heading and breadcrumbs in Account view (Sage Abdullah)
* Reduce confusing spacing below StreamField blocks help text (Rishabh Sharma)
* Prevent redundant calls to `Site.find_for_request()` from `Page.get_url_parts()` (Andy Babic)
* Prevent error on listings when searching and filtering by locale (Matt Westcott, Sage Abdullah)
* Add missing space in panels check warning message (Stéphane Blondon)
* Prevent `StreamChildrenToListBlockOperation` from duplicating data across multiple StreamField instances (Joshua Munn)
* Prevent database error when calling permission_order.register on app ready (Daniel Kirkham, Matt Westcott)
* Prevent syntax error on MySQL search when query includes symbols (Matt Westcott)
### Documentation
* Move the [model reference page](../reference/models.md) from reference/pages to the references section as it covers all Wagtail core models (Srishti Jaiswal)
* Move the [panels reference page](../reference/panels.md) from references/pages to the references section as panels are available for any model editing, merge panels API into this page (Srishti Jaiswal)
* Move the tags documentation to standalone [advanced topic for tagging](../advanced_topics/tags.md), instead of being inside the reference/pages section (Srishti Jaiswal)
* Refine the [Adding reports](../extending/adding_reports.md#adding-reports) page so that common (page/non-page) class references are at the top and the full page only example has correct heading nesting (Alessandro Chitarrini)
* Add the [`wagtail start`](../reference/management_commands.md#wagtail-start) command to the management commands reference page (Damilola Oladele)
* Refine the [The project template](../reference/project_template.md#project-templates-reference) page sections and document common issues encountered when creating custom templates (Damilola Oladele)
* Refine titles, references and URLs to better align with the documentation style guide, including US spelling (Srishti Jaiswal)
* Recommend a larger `DATA_UPLOAD_MAX_NUMBER_FIELDS` when [integrating Wagtail into Django](../getting_started/integrating_into_django.md) (Matt Westcott)
* Improve code highlighting and formatting for Python docstrings in core models (Srishti Jaiswal)
* Update all JavaScript inline scripts & some CSS inline style tags to a CSP compliant approach by using external scripts/styles (Aayushman Singh)
* Update usage of `mark_safe` to `format_html` for any script inclusions, to better avoid XSS issues from example code (Aayushman Singh)
* Update documentation writing guidelines to [encourage better considerations](../contributing/documentation_guidelines.md#documentation-code-example-considerations) of security, accessibility and good practice when writing code examples (Aayushman Singh, LB (Ben) Johnston)
* Update documentation guidelines on writing links and API reference (Sage Abdullah)
* Replace absolute URLs with intersphinx links where possible to avoid broken links (Sage Abdullah)
* Update upgrading guide and release process to better reflect the current practices (Sage Abdullah)
* Document usage of [Custom validation for admin form pages](../reference/contrib/forms/customization.md#form-builder-custom-admin-validation) when using `AbstractEmailForm` or `AbstractForm` pages (John-Scott Atlakson, LB (Ben) Johnston)
* Reword `BlogTagIndexPage` example for clarity (Clifford Gama)
* Change title of blog index page in tutorial to avoid slug issues (Thibaud Colas)
* Fix `wagtailcache` and `wagtailpagecache` examples to not use quotes for the `fragment_name` (Shiv)
* Update tutorial to reflect the move of the “Add child page” action to a top-level button in the header as a ‘+’ icon (Clifford Gama)
* Fix link to `HTTPMethod` in `Page.handle_options_request()` docs (Sage Abdullah)
* Improve [Theory](../reference/pages/theory.md#pages-theory) with added & more consistent section headings and admonitions to aid in readability (Clifford Gama)
* Fix non-functional link to the community guidelines in the [Your first contribution](../contributing/first_contribution_guide.md) page (Ankit Kumar)
* Introduce tags and filters by name in the [Writing templates](../topics/writing_templates.md#writing-templates) docs (Clifford Gama)
* Fix Django HTML syntax formatting issue on the [documents overview](../advanced_topics/documents/overview.md#documents-overview) page (LB (Ben) Johnston)
* Separate virtual environment creation and activation steps in tutorial (Ankit Kumar)
* Update tutorial to use plain strings in place of `FieldPanel` / `InlinePanel` where appropriate (Unyime Emmanuel Udoh)
* Update example for customizing [“p-as-heading” accessibility check](../advanced_topics/accessibility_considerations.md#built-in-accessibility-checker) without overriding built-in checks (Cynthia Kiser)
* Document [`get_template` method on StreamField blocks](../topics/streamfield.md#streamfield-get-template) (Matt Westcott)
* Revert incorrect example of appending a `RichTextBlock` to a `StreamField` (Matt Westcott)
### Maintenance
* Close open files when reading within utils/setup.py (Ataf Fazledin Ahamed)
* Avoid redundant `ALLOWED_HOSTS` check in `Site.find_for_request` (Jake Howard)
* Update `CloneController` to ensure that `added`/`cleared` events are not dispatched as cancelable (LB (Ben) Johnston)
* Remove unused `uuid` UMD module as all code is now using the NPM module (LB (Ben) Johnston)
* Clean up JS comments throughout codebase to be aligned to JSDoc where practical (LB (Ben) Johnston)
* Replace `eslint-disable no-undef` linter directives with `global` comments (LB (Ben) Johnston)
* Upgrade Node tooling to active LTS version 22 (LB (Ben) Johnston)
* Remove defunct oEmbed providers (Rahul Samant)
* Remove obsolete non-upsert-based code for Postgres search indexing (Jake Howard)
* Remove unused Rangy JS library (LB (Ben) Johnston)
* Update `PreviewController` usage to leverage Stimulus actions instead of calling `preventDefault` manually (Ayaan Qadri)
* Various performance optimizations to page publishing (Jake Howard)
* Remove unnecessary DOM canvas.toBlob polyfill (LB (Ben) Johnston)
* Ensure Storybook core files are correctly running through Eslint (LB (Ben) Johnston)
* Add a new Stimulus `ZoneController` (`w-zone`) to support dynamic class name changes & event handling on container elements (Ayaan Qadri)
* Migrate jQuery class toggling & drag/drop event handling within the multiple upload views to the Stimulus `ZoneController` usage (Ayaan Qadri)
* Test project template for warnings when run against Django pre-release versions (Sage Abdullah)
* Refactor redirects create/delete views to use generic views (Sage Abdullah)
* Add JSDoc description, adopt linting recommendations, and add more unit tests for `ModalWorkflow` (LB (Ben) Johnston)
* Add support for a `delay` value in `TagController` to debounce async autocomplete tag fetch requests (Aayushman Singh)
* Add unit tests, Storybook stories & JSDoc items for the Stimulus `DrilldownController` (Srishti Jaiswal)
* Enhance sidebar preview performance by eliminating duplicate asset loads on preview error (Sage Abdullah)
* Remove unused `LinkController` (`w-link`) (Sage Abdullah)
* Refactor settings `EditView` to make better use of generic `EditView` (Sage Abdullah)
* Add a new Stimulus `RulesController` (`w-rules`) to support declarative conditional field enabling from other field values in a form (LB (Ben) Johnston)
* Migrate the conditional enabling of fields in the image URL builder view away from ad-hoc jQuery to use the `RulesController` (`w-rules`) approach (LB (Ben) Johnston)
* Enhance the Stimulus `ZoneController` (`w-zone`) to support inactive class and a mechanism to switch the mode based on data within events (Ayaan Qadri)
* Use the Stimulus `ZoneController` (`w-zone`) to remove ad-hoc jQuery for the privacy switch when toggling visibility of private/public elements (Ayaan Qadri)
* Remove unused `is_active` & `active_menu_items` from `wagtail.admin.menu.MenuItem` (Srishti Jaiswal)
* Only call `openpyxl` at runtime to improve performance for projects that do not use `ReportView`, `SpreadsheetExportMixin` and `wagtail.contrib.redirects` (Sébastien Corbin)
* Adopt the update value `mp` instead of `mm` for ‘mystery person’ as the default Gravatar if no avatar found (Harsh Dange)
* Refactor search promotions views to use generic views and templates (Sage Abdullah, Rohit Sharma)
* Use built-in `venv` instead of `pipenv` in CircleCI (Sage Abdullah)
* Add a new Stimulus `FormsetController` (`w-formset`) to support dynamic formset insertion/deletion behavior (LB (Ben) Johnston)
* Enable breadcrumbs by default on admin views using generic templates (Sage Abdullah)
* Refactor pages `revisions_revert` view to be a subclass of `EditView` (Sage Abdullah)
* Move images and documents `get_usage().count()` call to view code (Sage Abdullah)
* Upgrade sass-loader to resolve Sass deprecation warnings (Ayaan Qadri)
## Upgrade considerations - changes affecting all projects
### `DATA_UPLOAD_MAX_NUMBER_FIELDS` update
It’s recommended that all projects set the `DATA_UPLOAD_MAX_NUMBER_FIELDS` setting to 10000 or higher.
This specifies the maximum number of fields allowed in a form submission, and it is recommended to increase this from Django’s default of 1000, as particularly complex page models can exceed this limit within Wagtail’s page editor:
```python
# settings.py
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
```
### Form builder - validation of fields with custom `related_name`
On previous releases, form page models (defined through `AbstractEmailForm`, `AbstractForm`, `FormMixin` or `EmailFormMixin`) did not validate form fields where the `related_name` was different to the default value of `form_fields`. As a result, it was possible to create forms with duplicate field names - in this case, only a single field is displayed and captured in the resulting form.
In this new release, validation is now applied on these fields, existing forms will continue to behave as before and no data will be lost. However, editing them will now raise a validation error and users may need to delete any duplicated fields that they had previously missed.
### Background tasks run at end of current transaction
#### NOTE
The behaviour described here no longer applies as of `django-tasks` 0.10. Tasks are now enqueued immediately (and executed immediately if `ImmediateBackend` is in use), and the `ENQUEUE_ON_COMMIT` setting is no longer available.
In the default configuration, tasks managed by `django-tasks` (see above) run during the request-response cycle, as before. However, they are now deferred until the current transaction (if any) is committed. If [`ATOMIC_REQUESTS`](https://docs.djangoproject.com/en/stable/ref/settings/#std-setting-DATABASE-ATOMIC_REQUESTS) is set to `True`, this will be at the end of the request. This may lead to a change of behaviour on views that expect to see the results of these tasks immediately, such as a view that creates a page and then performs a search query to retrieve it. To restore the previous behaviour, set `"ENQUEUE_ON_COMMIT": False` in the `TASKS` setting:
```python
# settings.py
TASKS = {
"default": {
"BACKEND": "django_tasks.backends.immediate.ImmediateBackend",
"ENQUEUE_ON_COMMIT": False,
}
}
```
Django’s test framework typically runs tests inside transactions, and so this situation is also likely to arise in tests that perform database updates and then - within the same test - expect these changes to be immediately reflected in search queries, object usage counts, and other processes that are now handled with background tasks. This can be addressed by setting `ENQUEUE_ON_COMMIT` to `False` as above in the test settings, or by wrapping the database updates in `with self.captureOnCommitCallbacks(execute=True)` to ensure that these tasks are completed before the test continues:
```python
def test_search(self):
home_page = Page.objects.get(slug="home")
with self.captureOnCommitCallbacks(execute=True): # Added
home_page.add_child(instance=EventPage(title="Christmas party"))
response = self.client.get("/search/?q=Christmas")
self.assertContains(response, "Christmas party")
```
## Upgrade considerations - changes affecting Wagtail customizations
### Added `page` as a third parameter to the `construct_wagtail_userbar` hook
In previous releases, implementations of the [`construct_wagtail_userbar`](../reference/hooks.md#construct-wagtail-userbar) hook were expected to accept two arguments, whereas now `page` is passed in as a third argument.
**Old**
```python
@hooks.register('construct_wagtail_userbar')
def construct_wagtail_userbar(request, items):
pass
```
**New**
```python
@hooks.register('construct_wagtail_userbar')
def construct_wagtail_userbar(request, items, page):
pass
```
The old style will now produce a deprecation warning.
## Upgrade considerations - changes to undocumented internals
### Changes to `content_panels`, `promote_panels` and `settings_panels` values on base `Page` model
Previously, the `content_panels`, `promote_panels` and `settings_panels` attributes on the base `Page` model were defined as lists of `Panel` instances. These lists now contain instances of `wagtail.models.PanelPlaceholder` instead, which are resolved to `Panel` instances at runtime. Panel definitions that simply extend these lists (such as `content_panels = Page.content_panels + [...]`) are unaffected; however, any logic that inspects these lists (for example, finding a panel in the list to insert a new one immediately after it) will need to be updated to handle the new object types.
### Removal of unused Rangy JS library
The unused JavaScript include `wagtailadmin/js/vendor/rangy-core.js` has been removed from the editor interface, and functions such as `window.rangy.getSelection()` are no longer available. Any code relying on this should now either supply its own copy of the [Rangy library](https://github.com/timdown/rangy), or be migrated to the official [`Document.createRange()`](https://developer.mozilla.org/en-US/docs/Web/API/Range) browser API.
### Deprecation of `window.buildExpandingFormset` global function
The undocumented global function `window.buildExpandingFormset` to attach JavaScript insertion / deletion behavior for Django formsets has been deprecated and will be removed in a future release.
Within the Wagtail admin this only impacts a small set of basic expanding formsets in use across Workflow and Group view editing. `InlinePanel` is not affected.
Previously these expanding formsets required a mix of specific id attribute structures and inline scripts to instantiate with callbacks for handling deletion. User code implementing this functionality through `buildExpandingFormset` should be updated - the following data attributes can be used to emulate the same behavior. These are likely to change and should not be considered official documentation.
| Element | Attribute(s) |
|----------------------------------------------|-------------------------------------------|
| Containing element | `data-controller="w-formset"` |
| Element to append new child forms | `data-w-formset-target="forms"` |
| Child form element | `data-w-formset-target="child"` |
| Deleted form element | `data-w-formset-target="deleted" hidden` |
| `template` element for blank form | `data-w-formset-target="template"` |
| Management field (total forms) | `data-w-formset-target="totalFormsInput"` |
| Management field (min forms) | `data-w-formset-target="minFormsInput"` |
| Management field (max forms) | `data-w-formset-target="maxFormsInput"` |
| Management field (Delete, within child form) | `data-w-formset-target="deleteInput"` |
| Add child `button` | `data-action="w-formset#add"` |
| Delete child `button` (within child form) | `data-action="w-formset#delete"` |
Usage of nested id structures are no longer required but can be left in place for easier debugging.
# 7.0.1.html.md
# Wagtail 7.0.1 release notes
*June 12, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Fix type hints for `register_filter_adapter_class` parameters (Sébastien Corbin)
* Use correct URL when redirecting back to the listing after filtering and deleting form submissions (Sage Abdullah)
* Fix broken migration when ListBlock is defined with a `child_block` kwarg (Matt Westcott)
* Fix saving of empty values in EmbedBlock (Matt Westcott)
* Sanitize request data when logging method not allowed (Jake Howard)
### Documentation
* Use tuple instead of set in `UniqueConstraint` examples for a custom rendition model to avoid spurious migrations (Alec Baron)
* Document how to [turn off StreamField block previews](../topics/streamfield.md#turning-off-block-previews) (Shlomo Markowitz)
### Maintenance
* Use `utf8mb4` charset and collation for MySQL test database (Sage Abdullah)
# 7.0.2.html.md
# Wagtail 7.0.2 release notes
*July 24, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent error when restoring scroll position for cross-domain preview iframe (Sage Abdullah)
* Remove ngram parser on MySQL that prevented autocomplete search from returning results (Vince Salvino)
* Ensure the editing of translation alias pages correctly shows links to the source page if the alias was created from a draft (Dan Braghis)
# 7.0.3.html.md
# Wagtail 7.0.3 release notes
*August 28, 2025*
> * [What’s new](#what-s-new)
## What’s new
### Bug fixes
* Prevent crash when previewing a form page with an empty field type (Sage Abdullah)
# 7.0.4.html.md
# Wagtail 7.0.4 release notes
*February 3, 2026*
> * [What’s new](#what-s-new)
## What’s new
### CVE-2026-25517: Improper permission handling on admin preview endpoints
This release addresses a permission vulnerability in the Wagtail admin interface. Due to a missing permission check on the preview endpoints, a user with access to the Wagtail admin and knowledge of a model’s fields can craft a form submission to obtain a preview rendering of any page or snippet object for which previews are enabled, consisting of any data of the user’s choosing. The existing data of the object itself is not exposed, but depending on the nature of the template being rendered, this may expose other database contents that would otherwise only be accessible to users with edit access over the model. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin.
### Bug fixes
* Prevent error on custom generic create and edit views without a header icon (Sage Abdullah)
# 7.0.5.html.md
# Wagtail 7.0.5 release notes
*February 12, 2026*
> * [What’s new](#what-s-new)
## What’s new
* Remove upper bound on Pillow dependency (Kunal Hemnani)
# 7.0.html.md
# Wagtail 7.0 (LTS) release notes
*May 6, 2025*
> * [What’s new](#what-s-new)
> * [Upgrade considerations - removal of deprecated features from Wagtail 5.2 - 6.3](#upgrade-considerations-removal-of-deprecated-features-from-wagtail-5-2-6-3)
> * [Upgrade considerations - changes affecting all projects](#upgrade-considerations-changes-affecting-all-projects)
> * [Upgrade considerations - deprecation of old functionality](#upgrade-considerations-deprecation-of-old-functionality)
> * [Upgrade considerations - changes to undocumented internals](#upgrade-considerations-changes-to-undocumented-internals)
Wagtail 7.0 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (typically a period of 12 months).
## What’s new
### Django 5.2 support
This version adds formal support for Django 5.2.
### Deferring validation on saving drafts
This release introduces a change to the validation behavior when saving pages (or snippets using [`DraftStateMixin`](../topics/snippets/features.md#wagtailsnippets-saving-draft-changes-of-snippets)) as drafts. In most cases, required fields will not be enforced when saving as draft, allowing users to save work-in-progress versions without filling in all fields. Validation is applied as normal when the page or snippet is published, scheduled, or submitted to a workflow. The new behavior is enabled by default, but see the notes below on [Configuring deferred validation of required fields](#configuring-deferred-validation).
This feature was developed by Matt Westcott and Sage Abdullah.
### New and improved pagination
We have a new pagination interface for all listing views and most choosers, including page numbers. This simplifies navigation for listings with tens or hundreds of pages, so users can jump directly to the last pages. Thank you to Jordan Teichmann for implementing the new designs, with guidance from Sage Abdullah.
### Locale in listings and choosers
This release adds a new “Locale” column to the listings and choosers of translatable models, making it easier to filter and sort by locale. The current content’s locale is applied in choosers by default, with the ability to clear the locale filter. This feature was developed by Dan Braghis and Sage Abdullah.
### How-to guide for Django Ninja
While Wagtail provides a [built-in API module](../advanced_topics/api/index.md#api) based on the popular Django REST Framework, it is possible to use other approaches.
This release adds a new [How to set up Django Ninja](../advanced_topics/api/django-ninja.md#api-ninja) guide, to demonstrate basic usage of Wagtail with [Django Ninja](https://django-ninja.dev/), which leverages type hints and Pydantic for data validation.
The guide covers common requirements beyond initial setup, like rich text, image renditions, and generation of API documentation.
This documentation was contributed by Thibaud Colas with support from Sage Abdullah.
### Other features
* Add `WAGTAIL_` prefix to Wagtail-specific tag settings (Aayushman Singh)
* Implement `normalize` on `TypedTableBlock` to assist with setting `default` and `preview_value` (Sage Abdullah)
* Apply normalization when modifying a `StreamBlock`’s value to assist with programmatic changes to `StreamField` (Matt Westcott)
* Allow a custom image rendition model to define its unique constraint with `models.UniqueConstraint` instead of `unique_together` (Oliver Parker, Cynthia Kiser, Sage Abdullah)
* Default to the `standard` tokenizer on Elasticsearch, to correctly handle numbers as tokens (Matt Westcott)
* Add color-scheme meta tag to Wagtail admin (Ashish Nagmoti)
* Add the ability to set the [default privacy restriction for new pages](../advanced_topics/privacy.md#set-default-page-privacy) using `get_default_privacy_setting` (Shlomo Markowitz)
* Improve performance of batch purging page urls in the frontend cache, avoiding n+1 query issues (Andy Babic)
* Add better support and documentation for overriding or extending [icons used in the in the userbar](../advanced_topics/icons.md#custom-icons-userbar) (Sébastien Corbin)
* List the comments action, if comments are enabled, within the admin keyboard shortcuts dialog (Dhruvi Patel)
* Add better support and documentation for [overriding the default field widgets](../reference/contrib/forms/customization.md#custom-form-field-type-widgets) used within form pages (Baptiste Mispelon)
* Allow workflow tasks to specify a template for the action modal via `get_template_for_action` (Sage Abdullah)
* Change ‘Publish’ button label to ‘Schedule to publish’ if go-live schedule is set (Sage Abdullah)
* Hide add locale button when no more languages are available (Dan Braghis)
* Allow customizing `InspectView` field display value via methods on the view (Dan Braghis)
* Make rendering of active listing filters extensible, to support additional filter types (Sage Abdullah)
### Bug fixes
* Take preferred language into account for translatable strings in client-side code (Bernhard Bliem, Sage Abdullah)
* Support translating with the preferred language for rich text formatting labels (Bernhard Bliem, Sage Abdullah)
* Make “Actions” label translatable within the rich text toolbar (Bernhard Bliem, Sage Abdullah)
* Do not show the content type column as sortable when searching pages (Srishti Jaiswal, Sage Abdullah)
* Support simple subqueries for `in` and `exact` lookup on Elasticsearch (Sage Abdullah)
* Force preview panel scroll behavior to instant to avoid flickering (Sage Abdullah)
* Fix incorrect “Views (past week)” heading on promoted search results listing (Baptiste Mispelon)
* Ensure `InlinePanel` will be correctly ordered after the first save when `min_num` is used (Elhussein Almasri, Joel William)
* Avoid deprecation warnings about URLField `assume_scheme` on Django 5.x (Sage Abdullah)
* Fix setup.cfg syntax for setuptools v78 (Sage Abdullah)
* Ensure `ImproperlyConfigured` is thrown from `db_field` on unbound `FieldPanel`s as intended (Matt Westcott)
* Refine the positioning of the add comment button next to select, radio, checkbox fields and between field row columns (Srishti Jaiswal)
* Show the correct privacy status for child collections of private collections (Shlomo Markowitz)
* Ensure reference index correctly handles models with primary keys not named `id` (Sage Abdullah)
* On “move page” bulk action, do not prefill the destination with the root page (Stefan Hammer)
* Ensure the default preferred language respects the `WAGTAILADMIN_PERMITTED_LANGUAGES` setting (Sébastien Corbin)
* Ensure there is consistent padding in homepage panels table headers (Aditya (megatrron))
* Prevent redundant calls to context processors when rendering userbar (Ihor Marhitych)
* Fix validation of duplicate field names in form builder when fields have been deleted (Ian Meigh)
* Fix font size of footer action buttons when there is only one item (Sage Abdullah)
### Documentation
* Add missing `django.contrib.admin` to list of apps in [“add to Django project” guide](../advanced_topics/add_to_django_project.md) (Mohamed Rabiaa)
* Add tutorial on deploying on Ubuntu to [third-party tutorials](../advanced_topics/third_party_tutorials.md) (Mohammad Fathi Rahman)
* Document that `request_or_site` is optional on `BaseGenericSetting.load` (Matt Westcott)
* Mention third-party `StreamField` based form builder packages in the [form builder](../reference/contrib/forms/index.md#form-builder) documentation (Matt Westcott)
* Clarify that [`insert_editor_js` hook](../reference/hooks.md#insert-editor-js) applies to all core editing/creation views (LB (Ben) Johnston)
* Clarify requirement for non-page models using model mixins to be registered as snippets (Sage Abdullah)
* Document the `page.move` method from django-treebeard (Shlomo Markowitz)
* Clarify that `Page.get_url_parts` will return a tuple, not `None` for `NoReverseMatch` errors (Arthur Tripp)
* Document the [`expand_db_html` utility function](../extending/rich_text_internals.md#rich-text-manual-conversion) to create HTML ready for display (Thibaud Colas)
* Mention `expand_db_html` usage for rich text in [REST framework API](../advanced_topics/api/v2/configuration.md#api-v2-configuration) (Thibaud Colas)
* Docs: Further document usage of API `?limit` with `WAGTAILAPI_LIMIT_MAX` (Thibaud Colas)
### Maintenance
* Migrate away from deprecated Sass import rules to module system (Srishti Jaiswal)
* Apply Sass mixed declarations migration in preparation for CSS nesting (Prabhpreet Kaur)
* Refactor styles for Draftail, minimap, and comments to fix remaining Sass migration warnings (Thibaud Colas)
* npm package updates; `downshift`, `focus-trap-react`, `immer`, `redux`, `uuid` (LB (Ben) Johnston)
* Validate against invalid characters in Lexeme values (Matt Westcott)
* Split up `wagtail.models` module into submodules (Matt Westcott)
* Update `ruff` to 0.9.6 (Sage Abdullah)
* Fix up `stubs` & `adapter` contents to better support Jest testing (LB (Ben) Johnston)
* Cleanup Stimulus controller imports, JSDoc & linting (LB (Ben) Johnston)
* Rename `SkipLinkController` to `FocusController` with improved reusability, updated unit tests, and added story (LB (Ben) Johnston)
* Fix CI testing issues with the Stimulus `LocaleController` time zones & non-deterministic page ordering tests (Sage Abdullah)
* Make GitHub highlight `.html` files as Django templates (Jake Howard)
* Remove admin JavaScript imports from shared template includes, moving the imports to the appropriate core admin inclusion locations (Sai Srikar Dumpeti)
* Remove non-editing view inclusions of the `insert_editor_js` hook output, deprecate the wrapper template tag `_editor_js.html` (Sai Srikar Dumpeti, LB (Ben) Johnston)
* Remove upper bound on Django dependency (Matt Westcott)
* Add `default_auto_field` setting to home app in project template (Sylvain Boissel)
* Migrate setuptools configuration from `setup.py` and `setup.cfg` to `pyproject.toml` (Sage Abdullah)
* Refactor `move_choose_destination` to a class-based view (Chiemezuo Akujobi)
* Update `django-tasks` to 0.7.x (Jake Howard)
## Upgrade considerations - removal of deprecated features from Wagtail 5.2 - 6.3
Features previously deprecated in Wagtail 5.2, 6.0, 6.1, 6.2 and 6.3 have been fully removed. For additional details on these changes, see:
* [Wagtail 5.2 release notes](5.2.md)
* [Wagtail 6.0 release notes](6.0.md)
* [Wagtail 6.1 release notes](6.1.md)
* [Wagtail 6.2 release notes](6.2.md)
* [Wagtail 6.3 release notes](6.3.md)
The most significant changes are highlighted below.
### Removal of `classnames` attribute on menu item and image format classes
The `classnames` keyword argument on the following classes is no longer supported and should be replaced with `classname`:
* `admin.menu.MenuItem`
* `admin.ui.sidebar.ActionMenuItem`
* `admin.ui.sidebar.LinkMenuItem`
* `admin.ui.sidebar.PageExplorerMenuItem`
* `contrib.settings.registry.SettingMenuItem`
* `wagtail.images.formats.Format`
### Renamed settings
* The `WAGTAIL_AUTO_UPDATE_PREVIEW` setting is removed and should be replaced with [`WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL = 0`](../reference/settings.md#wagtail-auto-update-preview-interval) to disable auto-update.
* The `PASSWORD_REQUIRED_TEMPLATE` setting is no longer recognized and should be replaced with [`WAGTAIL_PASSWORD_REQUIRED_TEMPLATE`](../reference/settings.md#frontend-authentication).
* The `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` setting is no longer recognized and should be replaced with [`WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE`](../reference/settings.md#frontend-authentication).
### Replaced mechanism for customizing user form classes
The settings `WAGTAIL_USER_EDIT_FORM`, `WAGTAIL_USER_CREATION_FORM` and `WAGTAIL_USER_CUSTOM_FIELDS` have been removed in favor of customizing the form classes via [`UserViewSet.get_form_class()`](../advanced_topics/customization/custom_user_models.md#custom-userviewset).
### Updates to button rendering hooks
* The hooks [`register_page_header_buttons`](../reference/hooks.md#register-page-header-buttons), [`register_page_listing_buttons`](../reference/hooks.md#register-page-listing-buttons), [`construct_page_listing_buttons`](../reference/hooks.md#construct-page-listing-buttons) and [`register_page_listing_more_buttons`](../reference/hooks.md#register-page-listing-more-buttons) now receive a `user` argument instead of `page_perms`.
* The [`register_page_header_buttons`](../reference/hooks.md#register-page-header-buttons) receives a fourth argument `view_name`.
* The [`construct_snippet_listing_buttons`](../reference/hooks.md#construct-snippet-listing-buttons) hook no longer accepts a `context` argument.
* The [`register_user_listing_buttons`](../reference/hooks.md#register-user-listing-buttons) hook now accepts a `request_user` argument instead of `context`.
### Other removals
* The `get_template` method on StreamField blocks now accepts a `value` argument as its first argument.
* Passing a dict as `DISTRIBUTION_ID` within the `WAGTAILFRONTENDCACHE` configuration setting is no longer supported, and should be replaced by [multiple backends](../reference/contrib/frontendcache.md#frontendcache-multiple-backends) with a `HOSTNAMES` parameter.
* `ModelViewSet` no longer provides the URL patterns `/` and `/delete/` for editing and deleting; these have been replaced by `edit//` and `delete//`.
* The undocumented usage of the JavaScript `window.chooserUrls` within Draftail choosers is removed.
* The undocumented `coreutils.escape_script` function and `escapescript` template tag, and handling of `
{% block extra_js %}
{# Override this in templates to add extra javascript #}
{% endblock %}
```
Now, reload your [homepage](http://127.0.0.1:8000). You’ll see your social media links at the bottom of your homepage.
# Create editable footer text with Wagtail Snippets
Having only your social media links in your portfolio footer isn’t ideal. You can add other items, like site credits and copyright notices, to your footer. One way to do this is to use the Wagtail [snippet](../topics/snippets/index.md) feature to create an editable footer text in your admin interface and display it in your site’s footer.
To add a footer text snippet to your admin interface, modify your `base/models.py` file as follows:
```python
from django.db import models
from wagtail.admin.panels import (
FieldPanel,
MultiFieldPanel,
# import PublishingPanel:
PublishingPanel,
)
# import RichTextField:
from wagtail.fields import RichTextField
# import DraftStateMixin, PreviewableMixin, RevisionMixin, TranslatableMixin:
from wagtail.models import (
DraftStateMixin,
PreviewableMixin,
RevisionMixin,
TranslatableMixin,
)
from wagtail.contrib.settings.models import (
BaseGenericSetting,
register_setting,
)
# import register_snippet:
from wagtail.snippets.models import register_snippet
# ...keep the definition of the NavigationSettings model and add the FooterText model:
@register_snippet
class FooterText(
DraftStateMixin,
RevisionMixin,
PreviewableMixin,
TranslatableMixin,
models.Model,
):
body = RichTextField()
panels = [
FieldPanel("body"),
PublishingPanel(),
]
def __str__(self):
return "Footer text"
def get_preview_template(self, request, mode_name):
return "base.html"
def get_preview_context(self, request, mode_name):
return {"footer_text": self.body}
class Meta(TranslatableMixin.Meta):
verbose_name_plural = "Footer Text"
```
In the preceding code, the `FooterText` class inherits from several `Mixins`, the `DraftStateMixin`, `RevisionMixin`, `PreviewableMixin`, and `TranslatableMixin`. In Django, `Mixins` are reusable pieces of code that define additional functionality. They are implemented as Python classes, so you can inherit their methods and properties.
Since your `FooterText` model is a Wagtail snippet, you must manually add `Mixins` to your model. This is because snippets aren’t Wagtail `Pages` in their own right. Wagtail `Pages` don’t require `Mixins` because they already have them.
`DraftStateMixin` is an abstract model that you can add to any non-page Django model. You can use it for drafts or unpublished changes. The `DraftStateMixin` requires `RevisionMixin`.
`RevisionMixin` is an abstract model that you can add to any non-page Django model to save revisions of its instances. Every time you edit a page, Wagtail creates a new `Revision` and saves it in your database. You can use `Revision` to find the history of all the changes that you make. `Revision` also provides a place to keep new changes before they go live.
`PreviewableMixin` is a `Mixin` class that you can add to any non-page Django model to preview any changes made.
`TranslatableMixin` is an abstract model you can add to any non-page Django model to make it translatable.
Also, with Wagtail, you can set publishing schedules for changes you made to a Snippet. You can use a `PublishingPanel` to schedule revisions in your `FooterText`.
The `__str__` method defines a human-readable string representation of an instance of the `FooterText` class. It returns the string “Footer text”.
The `get_preview_template` method determines the template for rendering the preview. It returns the template name *“base.html”*.
The `get_preview_context` method defines the context data that you can use to render the preview template. It returns a key “footer_text” with the content of the body field as its value.
The `Meta` class holds metadata about the model. It inherits from the `TranslatableMixin.Meta` class and sets the `verbose_name_plural` attribute to *“Footer Text”*.
Now, migrate your database by running `python manage.py makemigrations` and `python manage.py migrate`. After migrating, restart your server and then reload your [admin interface](https://guide.wagtail.org/en-latest/concepts/wagtail-interfaces/#admin-interface). You can now find **Snippets** in your [Sidebar](https://guide.wagtail.org/en-latest/how-to-guides/find-your-way-around/).
## Add footer text
To add your footer text, go to your [admin interface](https://guide.wagtail.org/en-latest/concepts/wagtail-interfaces/#admin-interface). Click **Snippets** in your [Sidebar](https://guide.wagtail.org/en-latest/how-to-guides/find-your-way-around/#the-sidebar) and add your footer text.
## Display your footer text
In this tutorial, you’ll use a custom template tag to display your footer text.
In your `base` folder, create a `templatetags` folder. Within your new `templatetags` folder, create the following files:
- `__init__.py`
- `navigation_tags.py`
Leave your `base/templatetags/__init__.py` file blank and add the following to your `base/templatetags/navigation_tags.py` file:
```python
from django import template
from base.models import FooterText
register = template.Library()
@register.inclusion_tag("base/includes/footer_text.html", takes_context=True)
def get_footer_text(context):
footer_text = context.get("footer_text", "")
if not footer_text:
instance = FooterText.objects.filter(live=True).first()
footer_text = instance.body if instance else ""
return {
"footer_text": footer_text,
}
```
In the preceding code, you imported the `template` module. You can use it to create and render template tags and filters. Also, you imported the `FooterText` model from your `base/models.py` file.
`register = template.Library()` creates an instance of the `Library` class from the template module. You can use this instance to register custom template tags and filters.
`@register.inclusion_tag("base/includes/footer_text.html", takes_context=True)` is a decorator that registers an inclusion tag named `get_footer_text`. `"base/includes/footer_text.html"` is the template path that you’ll use to render the inclusion tag. `takes_context=True ` indicates that the context of your `footer_text.html` template will be passed as an argument to your inclusion tag function.
The `get_footer_text` inclusion tag function takes a single argument named `context`. `context` represents the template context where you’ll use the tag.
`footer_text = context.get("footer_text", "")` tries to retrieve a value from the context using the key `footer_text`. The `footer_text` variable stores any retrieved value. If there is no `footer_text` value within the context, then the variable stores an empty string `""`.
The `if` statement in the `get_footer_text` inclusion tag function checks whether the `footer_text` exists within the context. If it doesn’t, the `if` statement proceeds to retrieve the first published instance of the `FooterText` from the database. If a published instance is found, the statement extracts the `body` content from it. However, if there’s no published instance available, it defaults to an empty string.
Finally, the function returns a dictionary containing the `"footer_text"` key with the value of the retrieved `footer_text` content.
You’ll use this dictionary as context data when rendering your `footer_text` template.
To use the returned dictionary, create a `templates/base/includes` folder in your `base` folder. Then create a `footer_text.html` file in your `base/templates/base/includes/` folder and add the following to it:
```html+django
{% load wagtailcore_tags %}
{{ footer_text|richtext }}
```
Add your `footer_text` template to your footer by modifying your `mysite/templates/includes/footer.html` file:
```html+django
{# Load navigation_tags at the top of the file: #}
{% load navigation_tags %}
```
Now, restart your server and reload your [homepage](http://127.0.0.1:8000/). For more information on how to render your Wagtail snippets, read [Rendering snippets](../topics/snippets/rendering.md).
Well done! 👏 You now have a footer across all pages of your portfolio site. In the next section of this tutorial, you’ll learn how to set up a site menu for linking to your homepage and other pages as you add them.
# create_portfolio_page.html.md
# Create a portfolio page
A portfolio page is a web page that has your resume or Curriculum Vitae (CV). The page will give potential employers a chance to review your work experience.
This tutorial shows you how to add a portfolio page to your portfolio site using the Wagtail StreamField.
First, let’s explain what StreamField is.
## What is StreamField?
StreamField is a feature that was created to balance the need for developers to have well-structured data and the need for content creators to have editorial flexibility in how they create and organize their content.
In traditional content management systems, there’s often a compromise between structured content and giving editors the freedom to create flexible layouts. Typically, Rich Text fields are used to give content creators the tools they need to make flexible and versatile content. Rich Text fields can provide a WYSIWYG editor for formatting. However, Rich Text fields have limitations.
One of the limitations of Rich Text fields is the loss of semantic value. Semantic value in content denotes the underlying meaning or information conveyed by the structure and markup of content. When content lacks semantic value, it becomes more difficult to determine its intended meaning or purpose. For example, when editors use Rich Text fields to style text or insert multimedia, the content might not be semantically marked as such.
So, StreamField gives editors more flexibility and addresses the limitations of Rich Text fields. StreamField is a versatile content management solution that treats content as a sequence of blocks. Each block represents different content types like paragraphs, images, and maps. Editors can arrange and customize these blocks to create complex and flexible layouts. Also, StreamField can capture the semantic meaning of different content types.
## Create reusable custom blocks
Now that you know what StreamField is, let’s guide you through using it to add a portfolio page to your site.
Start by adding a new app to your portfolio site by running the following command:
```sh
python manage.py startapp portfolio
```
Install your new portfolio app to your site by adding *“portfolio”* to the `INSTALLED_APPS` list in your `mysite/settings/base.py` file.
Now create a `base/blocks.py` file and add the following lines of code to it:
```python
from wagtail.blocks import (
CharBlock,
ChoiceBlock,
RichTextBlock,
StreamBlock,
StructBlock,
)
from wagtail.embeds.blocks import EmbedBlock
from wagtail.images.blocks import ImageBlock
class CaptionedImageBlock(StructBlock):
image = ImageBlock(required=True)
caption = CharBlock(required=False)
attribution = CharBlock(required=False)
class Meta:
icon = "image"
template = "base/blocks/captioned_image_block.html"
class HeadingBlock(StructBlock):
heading_text = CharBlock(classname="title", required=True)
size = ChoiceBlock(
choices=[
("", "Select a heading size"),
("h2", "H2"),
("h3", "H3"),
("h4", "H4"),
],
blank=True,
required=False,
)
class Meta:
icon = "title"
template = "base/blocks/heading_block.html"
class BaseStreamBlock(StreamBlock):
heading_block = HeadingBlock()
paragraph_block = RichTextBlock(icon="pilcrow")
image_block = CaptionedImageBlock()
embed_block = EmbedBlock(
help_text="Insert a URL to embed. For example, https://www.youtube.com/watch?v=SGJFWirQ3ks",
icon="media",
)
```
In the preceding code, you created reusable Wagtail custom blocks for different content types in your general-purpose app. You can use these blocks across your site in any order. Let’s take a closer look at each of these blocks.
First, `CaptionedImageBlock` is a block that editors can use to add images to a StreamField section.
```python
class CaptionedImageBlock(StructBlock):
image = ImageBlock(required=True)
caption = CharBlock(required=False)
attribution = CharBlock(required=False)
class Meta:
icon = "image"
template = "base/blocks/captioned_image_block.html"
```
`CaptionedImageBlock` inherits from `StructBlock`. With `StructBlock`, you can group several child blocks together under a single parent block. Your `CaptionedImageBlock` has three child blocks. The first child block, `Image`, uses the `ImageBlock` field block type. With `ImageBlock`, editors can select an existing image or upload a new one. Its `required` argument has a value of `true`, which means that you must provide an image for the block to work. The `caption` and `attribution` child blocks use the `CharBlock` field block type, which provides single-line text inputs for adding captions and attributions to your images. Your `caption` and `attribution` child blocks have their `required` attributes set to `false`. That means you can leave them empty in your [admin interface](https://guide.wagtail.org/en-latest/concepts/wagtail-interfaces/#admin-interface) if you want to.
Just like `CaptionedImageBlock`, your `HeadingBlock` also inherits from `StructBlock`. It has two child blocks. Let’s look at those.
```python
class HeadingBlock(StructBlock):
heading_text = CharBlock(classname="title", required=True)
size = ChoiceBlock(
choices=[
("", "Select a heading size"),
("h2", "H2"),
("h3", "H3"),
("h4", "H4"),
],
blank=True,
required=False,
)
class Meta:
icon = "title"
template = "base/blocks/heading_block.html"
```
The first child block, `heading_text`, uses `CharBlock` for specifying the heading text, and it’s required. The second child block, `size`, uses `ChoiceBlock` for selecting the heading size. It provides options for **h2**, **h3**, and **h4**. Both `blank=True` and `required=False` make the heading text optional in your [admin interface](https://guide.wagtail.org/en-latest/concepts/wagtail-interfaces/#admin-interface).
Your `BaseStreamBlock` class inherits from `StreamBlock`. `StreamBlock` defines a set of child block types that you would like to include in all of the StreamField sections across a project. This class gives you a baseline collection of common blocks that you can reuse and customize for all the different page types where you use StreamField. For example, you will definitely want editors to be able to add images and paragraph text to all their pages, but you might want to create a special pull quote block that is only used on blog pages.
```python
class BaseStreamBlock(StreamBlock):
heading_block = HeadingBlock()
paragraph_block = RichTextBlock(icon="pilcrow")
image_block = CaptionedImageBlock()
embed_block = EmbedBlock(
help_text="Insert a URL to embed. For example, https://www.youtube.com/watch?v=SGJFWirQ3ks",
icon="media",
)
```
Your `BaseStreamBlock` has four child blocks. The `heading_block` uses the previously defined `HeadingBlock`. `paragraph_block` uses `RichTextBlock`, which provides a WYSIWYG editor for creating formatted text. `image_block` uses the previously defined `CaptionedImageBlock` class. `embed_block` is a block for embedding external content like videos. It uses the Wagtail `EmbedBlock`. To discover more field block types that you can use, read the [documentation on Field block types](../reference/streamfield/blocks.md#field-block-types).
Also, you defined a `Meta` class within your `CaptionedImageBlock` and `HeadingBlock` blocks. The `Meta` classes provide metadata for the blocks, including icons to visually represent them in the admin interface. The `Meta` classes also include custom templates for rendering your `CaptionedImageBlock` and `HeadingBlock` blocks.
#### NOTE
Wagtail provides built-in templates to render each block. However, you can override the built-in template with a custom template.
Finally, you must add the custom templates that you defined in the `Meta` classes of your `CaptionedImageBlock` and `HeadingBlock` blocks.
To add the custom template of your `CaptionedImageBlock`, create a `base/templates/base/blocks/captioned_image_block.html` file and add the following to it:
```html+django
{% load wagtailimages_tags %}
{% image self.image fill-600x338 loading="lazy" %}
{{ self.caption }} - {{ self.attribution }}
```
To add the custom template of your `HeadingBlock` block, create a `base/templates/base/blocks/heading_block.html` file and add the following to it:
```html+django
{% if self.size == 'h2' %}
{{ self.heading_text }}
{% elif self.size == 'h3' %}
{{ self.heading_text }}
{% elif self.size == 'h4' %}
{{ self.heading_text }}
{% endif %}
```
#### NOTE
You can also create a custom template for a child block. For example, to create a custom template for `embed_block`, create a `base/templates/base/blocks/embed_block.html` file and add the following to it:
`{{ self }}`
## Use the blocks you created in your portfolio app
You can use the reusable custom blocks you created in your general-purpose `base` app across your site. However, it’s conventional to define the blocks you want to use in a `blocks.py` file of the app you intend to use them in. Then you can import the blocks from your app’s `blocks.py` file to use them in your `models.py` file.
Now create a `portfolio/blocks.py` file and import the block you intend to use as follows:
```python
from base.blocks import BaseStreamBlock
class PortfolioStreamBlock(BaseStreamBlock):
pass
```
The preceding code defines a custom block named `PortfolioStreamBlock`, which inherits from `BaseStreamBlock`. The pass statement indicates a starting point. Later in the tutorial, you’ll add custom block definitions and configurations to the `PortfolioStreamBlock`.
Now add the following to your
`portfolio/models.py` file:
```python
from wagtail.models import Page
from wagtail.fields import StreamField
from wagtail.admin.panels import FieldPanel
from portfolio.blocks import PortfolioStreamBlock
class PortfolioPage(Page):
parent_page_types = ["home.HomePage"]
body = StreamField(
PortfolioStreamBlock(),
blank=True,
use_json_field=True,
help_text="Use this section to list your projects and skills.",
)
content_panels = Page.content_panels + [
FieldPanel("body"),
]
```
In the preceding code, you defined a Wagtail `Page` named `PortfolioPage`. `parent_page_types = ["home.HomePage"]` specifies that your Portfolio page can only be a child page of Home Page. Your `body` field is a `StreamField`, which uses the `PortfolioStreamBlock` custom block that you imported from your `portfolio/blocks.py` file. `blank=True` indicates that you can leave this field empty in your admin interface. `help_text` provides a brief description of the field to guide editors.
Your next step is to create a template for your `PortfolioPage`. To do this, create a `portfolio/templates/portfolio/portfolio_page.html` file and add the following to it:
```html+django
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block body_class %}template-portfolio{% endblock %}
{% block content %}
{{ page.title }}
{{ page.body }}
{% endblock %}
```
Now migrate your database by running `python manage.py makemigrations` and then `python manage.py migrate`.
## Add more custom blocks
To add more custom blocks to your `PortfolioPage`’s body, modify your `portfolio/blocks.py` file:
```python
# import CharBlock, ListBlock, PageChooserBlock, PageChooserBlock, RichTextBlock, and StructBlock:
from wagtail.blocks import (
CharBlock,
ListBlock,
PageChooserBlock,
RichTextBlock,
StructBlock,
)
# import ImageBlock:
from wagtail.images.blocks import ImageBlock
from base.blocks import BaseStreamBlock
# add CardBlock:
class CardBlock(StructBlock):
heading = CharBlock()
text = RichTextBlock(features=["bold", "italic", "link"])
image = ImageBlock(required=False)
class Meta:
icon = "form"
template = "portfolio/blocks/card_block.html"
# add FeaturedPostsBlock:
class FeaturedPostsBlock(StructBlock):
heading = CharBlock()
text = RichTextBlock(features=["bold", "italic", "link"], required=False)
posts = ListBlock(PageChooserBlock(page_type="blog.BlogPage"))
class Meta:
icon = "folder-open-inverse"
template = "portfolio/blocks/featured_posts_block.html"
class PortfolioStreamBlock(BaseStreamBlock):
# delete the pass statement
card = CardBlock(group="Sections")
featured_posts = FeaturedPostsBlock(group="Sections")
```
In the preceding code, `CardBlock` has three child blocks, `heading`, `text` and `image`. You are already familiar with the field block types used by the child pages.
However, in your `FeaturedPostsBlock`, one of the child blocks, `posts`, uses `ListBlock`. `ListBlock` is a structural block type that you can use for multiple sub-blocks of the same type. You used it with `PageChooserBlock` to select only the Blog Page type pages. To better understand structural block types, read the [Structural block types documentation](../reference/streamfield/blocks.md#streamfield-staticblock).
Furthermore, `icon = "form"` and `icon = "folder-open-inverse"` define custom block icons to set your blocks apart in the admin interface. For more information about block icons, read the [documentation on block icons](../topics/streamfield.md#block-icons).
You used `group="Sections"` in `card = CardBlock(group="Sections")` and `featured_posts = FeaturedPostsBlock(group="Sections")` to categorize your `card` and `featured_posts` child blocks together within a category named `section`.
You probably know what your next step is. You have to create templates for your `CardBlock` and `FeaturedPostsBlock`.
To create a template for `CardBlock`, create a `portfolio/templates/portfolio/blocks/card_block.html` file and add the following to it:
```html+django
{% load wagtailcore_tags wagtailimages_tags %}
{{ self.heading }}
{{ self.text|richtext }}
{% if self.image %}
{% image self.image width-480 %}
{% endif %}
```
To create a template for `featured_posts_block`, create a `portfolio/templates/portfolio/blocks/featured_posts_block.html` file and add the following to it:
```html+django
{% load wagtailcore_tags %}
{{ self.heading }}
{% if self.text %}
{{ self.text|richtext }}
{% endif %}
{% for page in self.posts %}
{% endfor %}
```
Finally, migrate your changes by running `python manage.py makemigrations` and then `python manage.py migrate`.
## Add your resume
To add your resume to your portfolio site, follow these steps:
1. Create a **Portfolio Page** as a child page of **Home** by following these steps:
a. Restart your server.
b. Go to your admin interface.
c. Click `Pages` in your [Sidebar](https://guide.wagtail.org/en-latest/how-to-guides/find-your-way-around/#the-sidebar).
d. Click `Home`.
e. Click the `+` icon (Add child page) at the top of the resulting page.
f. Click `Portfolio Page`.
2. Add your resume data by following these steps:
\\\\
a. Use “Resume” as your page title.
b. Click **+** to expand your body section.
c. Click **Paragraph block**.
d. Copy and paste the following text in your new **Paragraph block**:
```text
I'm a Wagtail Developer with a proven track record of developing and maintaining complex web applications. I have experience writing custom code to extend Wagtail applications, collaborating with other developers, and integrating third-party services and APIs.
```
e. Click **+** below your preceding Paragraph block, and then click **Paragraph block** to add a new Paragraph Block.
f. Type “/” in the input field of your new Paragraph block and then click **H2 Heading 2**.
g. Use “Work Experience” as your Heading 2.
h. Type “/” below your Heading 2 and click **H3 Heading 3**.
i. Use the following as your Heading 3:
```default
Wagtail developer at Birdwatchers Inc, United Kingdom
```
j. Type the following after your Heading 3:
```text
January 2022 to November 2023
- Developed and maintained a complex web application using Wagtail, resulting in a 25% increase in user engagement and a 20% increase in revenue within the first year.
- Wrote custom code to extend Wagtail applications, resulting in a 30% reduction in development time and a 15% increase in overall code quality.
- Collaborated with other developers, designers, and stakeholders to integrate third-party services and APIs, resulting in a 40% increase in application functionality and user satisfaction.
- Wrote technical documentation and participated in code reviews, providing feedback to other developers and improving overall code quality by 20%.
```
#### NOTE
By starting your sentences with “-”, you’re writing out your work experience as a Bulletted list. You can achieve the same result by typing “/” in the input field of your Paragraph block and then clicking **Bulleted list**.
k. Click **+** below your Work experience.
l. Click **Paragraph block** to add another Paragraph block.
m. Type “/” in the input field of your new Paragraph block and then click **H2 Heading 2**.
n. Use “Skills” as the Heading 2 of your new Paragraph block.
o. Copy and paste the following after your Heading 2:
```text
Python, Django, Wagtail, HTML, CSS, Markdown, Open-source management, Trello, Git, GitHub
```
3. Publish your `Portfolio Page`.
Congratulations! 🎉 You now understand how to create complex flexible layouts with Wagtail StreamField. In the next section of this tutorial, you’ll learn how to add search functionality to your site.
# custom_account_settings.html.md
# Customizing the user account settings form
This document describes how to customize the user account settings form which can be found by clicking “Account settings”
at the bottom of the main menu.
## Adding new panels
Each panel on this form is a separate model form that can operate on an instance of either the user model, or the `wagtail.users.models.UserProfile`.
### Basic example
Here is an example of how to add a new form that operates on the user model:
```python
# forms.py
from django import forms
from django.contrib.auth import get_user_model
class CustomSettingsForm(forms.ModelForm):
class Meta:
model = get_user_model()
fields = [...]
```
```python
# wagtail_hooks.py
from wagtail.admin.views.account import BaseSettingsPanel
from wagtail import hooks
from .forms import CustomSettingsForm
@hooks.register('register_account_settings_panel')
class CustomSettingsPanel(BaseSettingsPanel):
name = 'custom'
title = "My custom settings"
order = 500
form_class = CustomSettingsForm
form_object = 'user'
```
The attributes are as follows:
- `name` - A unique name for the panel. All form fields are prefixed with this name, so it must be lowercase and cannot contain symbols -
- `title` - The heading that is displayed to the user
- `order` - Used to order panels on a tab. The builtin Wagtail panels start at `100` and increase by `100` for each panel.
- `form_class` - A `ModelForm` subclass that operates on a user or a profile
- `form_object` - Set to `user` to operate on the user, and `profile` to operate on the profile
- `tab` (optional) - Set which tab the panel appears on.
- `template_name` (optional) - Override the default template used for rendering the panel
## Operating on the `UserProfile` model
To add a panel that alters data on the user’s `wagtail.users.models.UserProfile` instance, set `form_object` to `'profile'`:
```python
# forms.py
from django import forms
from wagtail.users.models import UserProfile
class CustomProfileSettingsForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = [...]
```
```python
# wagtail_hooks.py
from wagtail.admin.views.account import BaseSettingsPanel
from wagtail import hooks
from .forms import CustomProfileSettingsForm
@hooks.register('register_account_settings_panel')
class CustomSettingsPanel(BaseSettingsPanel):
name = 'custom'
title = "My custom settings"
order = 500
form_class = CustomProfileSettingsForm
form_object = 'profile'
```
## Creating new tabs
You can define a new tab using the `SettingsTab` class:
```python
# wagtail_hooks.py
from wagtail.admin.views.account import BaseSettingsPanel, SettingsTab
from wagtail import hooks
from .forms import CustomSettingsForm
custom_tab = SettingsTab('custom', "Custom settings", order=300)
@hooks.register('register_account_settings_panel')
class CustomSettingsPanel(BaseSettingsPanel):
name = 'custom'
title = "My custom settings"
tab = custom_tab
order = 100
form_class = CustomSettingsForm
```
`SettingsTab` takes three arguments:
- `name` - A slug to use for the tab (this is placed after the `#` when linking to a tab)
- `title` - The display name of the title
- `order` - The order of the tab. The builtin Wagtail tabs start at `100` and increase by `100` for each tab
## Customizing the template
You can provide a custom template for the panel by specifying a template name:
```python
# wagtail_hooks.py
from wagtail.admin.views.account import BaseSettingsPanel
from wagtail import hooks
from .forms import CustomSettingsForm
@hooks.register('register_account_settings_panel')
class CustomSettingsPanel(BaseSettingsPanel):
name = 'custom'
title = "My custom settings"
order = 500
form_class = CustomSettingsForm
template_name = 'myapp/admin/custom_settings.html'
```
```html+django
{# templates/myapp/admin/custom_settings.html #}
{# This is the default template Wagtail uses, which just renders the form #}
{% block content %}
{% for field in form %}
{% include "wagtailadmin/shared/field.html" with field=field %}
{% endfor %}
{% endblock %}
```
# custom_bulk_actions.html.md
# Adding custom bulk actions
This document describes how to add custom bulk actions to different listings.
## Registering a custom bulk action
```python
from wagtail.admin.views.bulk_action import BulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomDeleteBulkAction(BulkAction):
display_name = _("Delete")
aria_label = _("Delete selected objects")
action_type = "delete"
template_name = "/path/to/confirm_bulk_delete.html"
models = [...]
@classmethod
def execute_action(cls, objects, **kwargs):
for obj in objects:
do_something(obj)
return num_parent_objects, num_child_objects # return the count of updated objects
```
The attributes are as follows:
- `display_name` - The label that will be displayed on the button in the user interface
- `aria_label` - The `aria-label` attribute that will be applied to the button in the user interface
- `action_type` - A unique identifier for the action (required in the URL for bulk actions)
- `template_name` - The path to the confirmation template
- `models` - A list of models on which the bulk action can act
- `action_priority` (optional) - A number that is used to determine the placement of the button in the list of buttons
- `classes` (optional) - A set of CSS class names that will be used on the button in the user interface
An example of a confirmation template is as follows:
```html+django
{% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %}
{% load i18n wagtailadmin_tags %}
{% block titletag %}{% blocktranslate trimmed count counter=items|length %}Delete 1 item{% plural %}Delete {{ counter }} items{% endblocktranslate %}{% endblock %}
{% block header %}
{% trans "Delete" as del_str %}
{% include "wagtailadmin/shared/header.html" with title=del_str icon="doc-empty-inverse" %}
{% endblock header %}
{% block items_with_access %}
{% if items %}
{% trans "Are you sure you want to delete these items?" %}
{% endif %}
{% endblock items_with_access %}
{% block items_with_no_access %}
{% blocktranslate trimmed asvar no_access_msg count counter=items_with_no_access|length %}You don't have permission to delete this item{% plural %}You don't have permission to delete these items{% endblocktranslate %}
{% include './list_items_with_no_access.html' with items=items_with_no_access no_access_msg=no_access_msg %}
{% endblock items_with_no_access %}
{% block form_section %}
{% if items %}
{% trans 'Yes, delete' as action_button_text %}
{% trans "No, don't delete" as no_action_button_text %}
{% include 'wagtailadmin/bulk_actions/confirmation/form.html' with action_button_class="serious" %}
{% else %}
{% include 'wagtailadmin/bulk_actions/confirmation/go_back.html' %}
{% endif %}
{% endblock form_section %}
```
```html+django
{% extends 'wagtailadmin/bulk_actions/confirmation/list_items_with_no_access.html' %}
{% load i18n %}
{% block per_item %}
{% if item.can_edit %}
{{ item.item.title }}
{% else %}
{{ item.item.title }}
{% endif %}
{% endblock per_item %}
```
The `execute_action` classmethod is the only method that must be overridden for the bulk action to work properly. It takes a list of objects as the only required argument, and a bunch of keyword arguments that can be supplied by overriding the `get_execution_context` method. For example.
```python
@classmethod
def execute_action(cls, objects, **kwargs):
# the kwargs here is the output of the get_execution_context method
user = kwargs.get('user', None)
num_parent_objects, num_child_objects = 0, 0
# you could run the action per object or run them in bulk using django's bulk update and delete methods
for obj in objects:
num_child_objects += obj.get_children().count()
num_parent_objects += 1
obj.delete(user=user)
num_parent_objects += 1
return num_parent_objects, num_child_objects
```
The `get_execution_context` method can be overridden to provide context to the `execute_action`
```python
def get_execution_context(self):
return { 'user': self.request.user }
```
The `get_context_data` method can be overridden to pass additional context to the confirmation template.
```python
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['new_key'] = some_value
return context
```
The `check_perm` method can be overridden to check if an object has some permission or not. Objects for which the `check_perm` returns `False` will be available in the context under the key `'items_with_no_access'`.
```python
def check_perm(self, obj):
return obj.has_perm('some_perm') # returns True or False
```
The success message shown on the admin can be customized by overriding the `get_success_message` method.
```python
def get_success_message(self, num_parent_objects, num_child_objects):
return _("{} objects, including {} child objects have been updated".format(num_parent_objects, num_child_objects))
```
## Adding bulk actions to the page explorer
When creating a custom bulk action class for pages, subclass from `wagtail.admin.views.pages.bulk_actions.page_bulk_action.PageBulkAction` instead of `wagtail.admin.views.bulk_action.BulkAction`
### Basic example
```python
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomPageBulkAction(PageBulkAction):
...
```
## Adding bulk actions to the Images listing
When creating a custom bulk action class for images, subclass from `wagtail.images.views.bulk_actions.image_bulk_action.ImageBulkAction` instead of `wagtail.admin.views.bulk_action.BulkAction`
### Basic example
```python
from wagtail.images.views.bulk_actions.image_bulk_action import ImageBulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomImageBulkAction(ImageBulkAction):
...
```
## Adding bulk actions to the documents listing
When creating a custom bulk action class for documents, subclass from `wagtail.documents.views.bulk_actions.document_bulk_action.DocumentBulkAction` instead of `wagtail.admin.views.bulk_action.BulkAction`
### Basic example
```python
from wagtail.documents.views.bulk_actions.document_bulk_action import DocumentBulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomDocumentBulkAction(DocumentBulkAction):
...
```
## Adding bulk actions to the user listing
When creating a custom bulk action class for users, subclass from `wagtail.users.views.bulk_actions.user_bulk_action.UserBulkAction` instead of `wagtail.admin.views.bulk_action.BulkAction`
### Basic example
```python
from wagtail.users.views.bulk_actions.user_bulk_action import UserBulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomUserBulkAction(UserBulkAction):
...
```
## Adding bulk actions to the snippets listing
When creating a custom bulk action class for snippets, subclass from `wagtail.snippets.bulk_actions.snippet_bulk_action.SnippetBulkAction`
instead of `wagtail.admin.views.bulk_action.BulkAction`
### Basic example
```python
from wagtail.snippets.bulk_actions.snippet_bulk_action import SnippetBulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomSnippetBulkAction(SnippetBulkAction):
# ...
```
If you want to apply an action only to certain snippets, override the `models` list in the action class
```python
from wagtail.snippets.bulk_actions.snippet_bulk_action import SnippetBulkAction
from wagtail import hooks
@hooks.register('register_bulk_action')
class CustomSnippetBulkAction(SnippetBulkAction):
models = [SnippetA, SnippetB]
# ...
```
# custom_document_model.html.md
# Custom document model
An alternate `Document` model can be used to add custom behavior and
additional fields.
You need to complete the following steps in your project to do this:
- Create a new document model that inherits from `wagtail.documents.models.AbstractDocument`. This is where you would add additional fields.
- Point `WAGTAILDOCS_DOCUMENT_MODEL` to the new model.
Here’s an example:
```python
# models.py
from django.db import models
from wagtail.documents.models import Document, AbstractDocument
class CustomDocument(AbstractDocument):
# Custom field example:
source = models.CharField(
max_length=255,
blank=True,
null=True
)
admin_form_fields = Document.admin_form_fields + (
# Add all custom fields names to make them appear in the form:
'source',
)
```
Then in your settings module:
```python
# Ensure that you replace app_label with the app you placed your custom
# model in.
WAGTAILDOCS_DOCUMENT_MODEL = 'app_label.CustomDocument'
```
#### NOTE
Migrating from the built-in document model:
When changing an existing site to use a custom document model, no documents
will be copied to the new model automatically. Copying old documents to the
new model would need to be done manually with a
[data migration](https://docs.djangoproject.com/en/stable/topics/migrations/#data-migrations).
Templates that reference the built-in document model will continue
to work as before
## Referring to the document model
### wagtail.documents.get_document_model()
Get the document model from the `WAGTAILDOCS_DOCUMENT_MODEL` setting.
Defaults to the standard `wagtail.documents.models.Document` model
if no custom model is defined.
### wagtail.documents.get_document_model_string()
Get the dotted `app.Model` name for the document model as a string.
Useful for developers making Wagtail plugins that need to refer to the
document model, such as in foreign keys, but the model itself is not required.
# custom_document_upload_form.html.md
# Custom document upload form
Wagtail provides a way to use a custom document form by modifying the [`WAGTAILDOCS_DOCUMENT_FORM_BASE`](../../reference/settings.md#wagtaildocs-document-form-base) setting. This setting allows you to extend the default document form with your custom fields and logic.
Here’s an example:
```python
# settings.py
WAGTAILDOCS_DOCUMENT_FORM_BASE = 'myapp.forms.CustomDocumentForm'
```
```python
# myapp/forms.py
from django import forms
from wagtail.documents.forms import BaseDocumentForm
class CustomDocumentForm(BaseDocumentForm):
terms_and_conditions = forms.BooleanField(
label="I confirm that this document was not created by AI.",
required=True,
)
def clean(self):
cleaned_data = super().clean()
if not cleaned_data.get("terms_and_conditions"):
raise forms.ValidationError(
"You must confirm the document was not created by AI."
)
return cleaned_data
```
#### NOTE
Any custom document form should extend the built-in `BaseDocumentForm` class.
# custom_image_filters.html.md
# Custom image filters
Wagtail comes with [various image operations](../topics/images.md#image-tag). To add custom image operation, add `register_image_operations` hook to your `wagtail_hooks.py` file.
In this example, the `willow.image` is a Pillow Image instance. If you use another image library, or like to support multiple image libraries, you need to update the filter code accordingly. See the [Willow documentation](https://willow.wagtail.org/stable/) for more information.
```python
from PIL import ImageFilter
from wagtail import hooks
from wagtail.images.image_operations import FilterOperation
class BlurOperation(FilterOperation):
def construct(self, radius):
self.radius = int(radius)
def run(self, willow, image, env):
willow.image = willow.image.filter(ImageFilter.GaussianBlur(radius=self.radius))
return willow
@hooks.register("register_image_operations")
def register_image_operations():
return [
("blur", BlurOperation),
]
```
Use the filter in a template, like so:
```html+Django
{% load wagtailimages_tags %}
{% image page.photo width-400 blur-7 %}
```
If your custom image filter depends on fields within the `Image`, for instance those defining the focal point, add a `vary_fields` property listing those field names to the subclassed `FilterOperation`. This ensures that a new rendition is created whenever the focal point is changed:
```python
class BlurOutsideFocusPointOperation(FilterOperation):
vary_fields = (
"focal_point_width",
"focal_point_height",
"focal_point_x",
"focal_point_y",
)
# ...
```
# custom_image_model.html.md
# Custom image models
The `Image` model can be customized, allowing additional fields to be added
to images.
To do this, you need to add two models to your project:
- The image model itself that inherits from `wagtail.images.models.AbstractImage`. This is where you would add your additional fields
- The renditions model that inherits from `wagtail.images.models.AbstractRendition`. This is used to store renditions for the new model.
Here’s an example:
```python
# models.py
from django.db import models
from wagtail.images.models import Image, AbstractImage, AbstractRendition
class CustomImage(AbstractImage):
# Add any extra fields to image here
# To add a caption field:
# caption = models.CharField(max_length=255, blank=True)
admin_form_fields = Image.admin_form_fields + (
# Then add the field names here to make them appear in the form:
# 'caption',
)
@property
def default_alt_text(self):
# Force editors to add specific alt text if description is empty.
# Do not use image title which is typically derived from file name.
return getattr(self, "description", None)
class CustomRendition(AbstractRendition):
image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')
class Meta:
constraints = [
models.UniqueConstraint(
fields=("image", "filter_spec", "focal_point_key"),
name="unique_rendition",
)
]
```
Then set the `WAGTAILIMAGES_IMAGE_MODEL` setting to point to it:
```python
WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
```
## Migrating from the builtin image model
When changing an existing site to use a custom image model, no images will
be copied to the new model automatically. Copying old images to the new
model would need to be done manually with a
[data migration](https://docs.djangoproject.com/en/stable/topics/migrations/#data-migrations).
Any templates that reference the builtin image model will still continue to
work as before but would need to be updated in order to see any new images.
## Referring to the image model
### wagtail.images.get_image_model()
Get the image model from the `WAGTAILIMAGES_IMAGE_MODEL` setting.
Useful for developers making Wagtail plugins that need the image model.
Defaults to the standard `wagtail.images.models.Image` model
if no custom model is defined.
### wagtail.images.get_image_model_string()
Get the dotted `app.Model` name for the image model as a string.
Useful for developers making Wagtail plugins that need to refer to the
image model, such as in foreign keys, but the model itself is not required.
## Overriding the upload location
The following methods can be overridden on your custom `Image` or `Rendition` models to customize how the original and rendition image files get stored.
### *class* wagtail.images.models.AbstractImage
#### get_upload_to(filename)
Generates a file path in the “original_images” folder.
Ensuring ASCII characters and limiting length to prevent filesystem issues during uploads.
### *class* wagtail.images.models.AbstractRendition
#### get_upload_to(filename)
Generates a file path within the “images” folder by combining the folder name and the validated filename.
Refer to the Django [`FileField.upload_to`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.FileField.upload_to) function to further understand how the function works.
# custom_page_listings.html.md
# Custom page listings
Normally, editors navigate through the Wagtail admin interface by following the structure of the page tree. However, this can make it slow to locate a specific page for editing, especially on large sites where pages are organised into a deep hierarchy.
Custom page listings are a way to present a flat list of all pages of a given type, accessed from a menu item in the Wagtail admin menu, with the ability for editors to search and filter this list to find the pages they are interested in. To define a custom page listing, create a subclass of [`PageListingViewSet`](../../reference/viewsets.md#wagtail.admin.viewsets.pages.PageListingViewSet) and register it using the [`register_admin_viewset`](../../reference/hooks.md#register-admin-viewset) hook.
For example, if your site implemented the page type `BlogPage`, you could provide a “Blog pages” listing in the Wagtail admin by adding the following definitions to a `wagtail_hooks.py` file within the app:
```python
# myapp/wagtail_hooks.py
from wagtail import hooks
from wagtail.admin.viewsets.pages import PageListingViewSet
from myapp.models import BlogPage
class BlogPageListingViewSet(PageListingViewSet):
icon = "globe"
menu_label = "Blog Pages"
add_to_admin_menu = True
model = BlogPage
blog_page_listing_viewset = BlogPageListingViewSet("blog_pages")
@hooks.register("register_admin_viewset")
def register_blog_page_listing_viewset():
return blog_page_listing_viewset
```
The columns of the listing can be customized by overriding the `columns` attribute on the viewset. This should be a list of `wagtail.admin.ui.tables.Column` instances:
```python
from wagtail import hooks
from wagtail.admin.ui.tables import Column
from wagtail.admin.viewsets.pages import PageListingViewSet
from myapp.models import BlogPage
class BlogPageListingViewSet(PageListingViewSet):
# ...
columns = PageListingViewSet.columns + [
Column("blog_category", label="Category", sort_key="blog_category"),
]
```
The filtering options for the listing can be customized by overriding the `filterset_class` attribute on the viewset:
```python
from wagtail import hooks
from wagtail.admin.viewsets.pages import PageListingViewSet
from myapp.models import BlogPage
class BlogPageFilterSet(PageListingViewSet.filterset_class):
class Meta:
model = BlogPage
fields = ["blog_category"]
class BlogPageListingViewSet(PageListingViewSet):
# ...
filterset_class = BlogPageFilterSet
```
# custom_tasks.html.md
# Adding new Task types
The Workflow system allows users to create tasks, which represent stages of moderation.
Wagtail provides one built-in task type: `GroupApprovalTask`, which allows any user in specific groups to approve or reject moderation.
However, it is possible to implement your own task types. Instances of your custom task can then be created in the Workflow tasks section of the Wagtail Admin.
## Task models
All custom tasks must be models inheriting from `wagtailcore.Task`.
If you need to customize the behavior of the built-in `GroupApprovalTask`, create a custom task which inherits from `AbstractGroupApprovalTask` and add your customizations there.
See below for more details on how to customize behavior.
In this set of examples, we’ll set up a task that can be approved by only one specific user.
```python
# /models.py
from wagtail.models import Task
class UserApprovalTask(Task):
pass
```
Subclassed Tasks follow the same approach as Pages: they are concrete models, with the specific subclass instance accessible by calling `Task.specific()`.
You can now add any custom fields. To make these editable in the admin, add the names of the fields into the `admin_form_fields` attribute:
For example:
```python
# /models.py
from django.conf import settings
from django.db import models
from wagtail.models import Task
class UserApprovalTask(Task):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)
admin_form_fields = Task.admin_form_fields + ['user']
```
Any fields that shouldn’t be edited after task creation - for example, anything that would fundamentally change the meaning of the task in any history logs - can be added to `admin_form_readonly_on_edit_fields`. For example:
```python
# /models.py
from django.conf import settings
from django.db import models
from wagtail.models import Task
class UserApprovalTask(Task):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)
admin_form_fields = Task.admin_form_fields + ['user']
# prevent editing of `user` after the task is created
# by default, this attribute contains the 'name' field to prevent tasks from being renamed
admin_form_readonly_on_edit_fields = Task.admin_form_readonly_on_edit_fields + ['user']
```
Wagtail will choose a default form widget to use based on the field type. But you can override the form widget using the `admin_form_widgets` attribute:
```python
# /models.py
from django.conf import settings
from django.db import models
from wagtail.models import Task
from .widgets import CustomUserChooserWidget
class UserApprovalTask(Task):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)
admin_form_fields = Task.admin_form_fields + ['user']
admin_form_widgets = {
'user': CustomUserChooserWidget,
}
```
## Custom TaskState models
You might also need to store custom state information for the task: for example, a rating left by an approving user.
Normally, this is done on an instance of `TaskState`, which is created when an object starts the task. However, this can
also be subclassed equivalently to `Task`:
```python
# /models.py
from wagtail.models import TaskState
class UserApprovalTaskState(TaskState):
pass
```
Your custom task must then be instructed to generate an instance of your custom task state on start instead of a plain `TaskState` instance:
```python
# /models.py
from django.conf import settings
from django.db import models
from wagtail.models import Task, TaskState
class UserApprovalTaskState(TaskState):
pass
class UserApprovalTask(Task):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)
admin_form_fields = Task.admin_form_fields + ['user']
task_state_class = UserApprovalTaskState
```
## Customizing behavior
Both `Task` and `TaskState` have a number of methods that can be overridden to implement custom behavior. Here are some of the most useful:
`Task.user_can_access_editor(obj, user)`, `Task.user_can_lock(obj, user)`, `Task.user_can_unlock(obj, user)`:
These methods determine if users usually without permission can access the editor, and lock, or unlock the object, by returning True or False.
Note that returning `False` will not prevent users who would normally be able to perform those actions. For example, for our `UserApprovalTask`:
```python
def user_can_access_editor(self, obj, user):
return user == self.user
```
`Task.locked_for_user(obj, user)`:
This returns `True` if the object should be locked and uneditable by the user. It is used by `GroupApprovalTask` to lock the object to any users not in the approval group.
```python
def locked_for_user(self, obj, user):
return user != self.user
```
`Task.lock_class`:
An attribute that defines the lock class used when the task is locked for the user. Defaults to `wagtail.locks.WorkflowLock`.
Note that your task’s custom lock class must inherit from `wagtail.locks.WorkflowLock`.
```python
from wagtail.locks import WorkflowLock
from wagtail.models import Task
class MyWorkflowLock(WorkflowLock):
def get_message(self, user):
return f"{user}, you shall not pass!"
class UserApprovalTask(Task):
lock_class = MyWorkflowLock
```
`Task.get_actions(obj, user)`:
This returns a list of `(action_name, action_verbose_name, action_requires_additional_data_from_modal)` tuples, corresponding to the actions available for the task in the edit view menu.
`action_requires_additional_data_from_modal` should be a boolean, returning `True` if choosing the action should open a modal for additional data input - for example, entering a comment.
For example:
```python
def get_actions(self, obj, user):
if user == self.user:
return [
('approve', "Approve", False),
('reject', "Reject", False),
('cancel', "Cancel", False),
]
else:
return []
```
`Task.get_form_for_action(action)`:
Returns a form to be used for additional data input for the given action modal. By default, returns `TaskStateCommentForm`, with a single comment field. The form data returned in `form.cleaned_data` must be fully serializable as JSON.
`Task.get_template_for_action(action)`:
Returns the name of a custom template to be used in rendering the data entry modal for that action.
`Task.on_action(task_state, user, action_name, **kwargs)`:
This performs the actions specified in `Task.get_actions(obj, user)`: it is passed an action name, for example, `approve`, and the relevant task state. By default, it calls `approve` and `reject` methods on the task state when the corresponding action names are passed through. Any additional data entered in a modal (see `get_form_for_action` and `get_actions`) is supplied as kwargs.
For example, let’s say we wanted to add an additional option: canceling the entire workflow:
```python
def on_action(self, task_state, user, action_name):
if action_name == 'cancel':
return task_state.workflow_state.cancel(user=user)
else:
return super().on_action(task_state, user, workflow_state)
```
`Task.get_task_states_user_can_moderate(user, **kwargs)`:
This returns a QuerySet of `TaskStates` (or subclasses) that the given user can moderate - this is currently used to select objects to display on the user’s dashboard.
For example:
```python
def get_task_states_user_can_moderate(self, user, **kwargs):
if user == self.user:
# get all task states linked to the (base class of) current task
return TaskState.objects.filter(status=TaskState.STATUS_IN_PROGRESS, task=self.task_ptr)
else:
return TaskState.objects.none()
```
`Task.get_description()`
A class method that returns the human-readable description for the task.
For example:
```python
@classmethod
def get_description(cls):
return _("Members of the chosen Wagtail Groups can approve this task")
```
## Adding notifications
Wagtail’s notifications are sent by `wagtail.admin.mail.Notifier` subclasses: callables intended to be connected to a signal.
By default, email notifications are sent upon workflow submission, approval, and rejection, and upon submission to a group approval task.
As an example, we’ll add email notifications for when our new task is started.
```python
# /mail.py
from wagtail.admin.mail import EmailNotificationMixin, Notifier
from wagtail.models import TaskState
from .models import UserApprovalTaskState
class BaseUserApprovalTaskStateEmailNotifier(EmailNotificationMixin, Notifier):
"""A base notifier to send updates for UserApprovalTask events"""
def __init__(self):
# Allow UserApprovalTaskState and TaskState to send notifications
super().__init__((UserApprovalTaskState, TaskState))
def can_handle(self, instance, **kwargs):
if super().can_handle(instance, **kwargs) and isinstance(instance.task.specific, UserApprovalTask):
# Don't send notifications if a Task has been canceled and then resumed - when object was updated to a new revision
return not TaskState.objects.filter(workflow_state=instance.workflow_state, task=instance.task, status=TaskState.STATUS_CANCELLED).exists()
return False
def get_context(self, task_state, **kwargs):
context = super().get_context(task_state, **kwargs)
context['object'] = task_state.workflow_state.content_object
context['task'] = task_state.task.specific
return context
def get_recipient_users(self, task_state, **kwargs):
# Send emails to the user assigned to the task
approving_user = task_state.task.specific.user
recipients = {approving_user}
return recipients
class UserApprovalTaskStateSubmissionEmailNotifier(BaseUserApprovalTaskStateEmailNotifier):
"""A notifier to send updates for UserApprovalTask submission events"""
notification = 'submitted'
```
Similarly, you could define notifier subclasses for approval and rejection notifications.
Next, you need to instantiate the notifier and connect it to the `task_submitted` signal.
```python
# /signal_handlers.py
from wagtail.signals import task_submitted
from .mail import UserApprovalTaskStateSubmissionEmailNotifier
task_submission_email_notifier = UserApprovalTaskStateSubmissionEmailNotifier()
def register_signal_handlers():
task_submitted.connect(user_approval_task_submission_email_notifier, dispatch_uid='user_approval_task_submitted_email_notification')
```
`register_signal_handlers()` should then be run on loading the app: for example, by adding it to the `ready()` method in your `AppConfig`.
```python
# /apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myappname'
label = 'myapplabel'
verbose_name = 'My verbose app name'
def ready(self):
from .signal_handlers import register_signal_handlers
register_signal_handlers()
```
# custom_user_models.html.md
# Custom user models
This page shows how to configure Wagtail to accommodate a custom user model.
## Creating a custom user model
This example uses a custom user model that adds a text field and foreign key field.
The custom user model must at minimum inherit from [`AbstractBaseUser`](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser) and [`PermissionsMixin`](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#django.contrib.auth.models.PermissionsMixin). In this case, we extend the [`AbstractUser`](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#django.contrib.auth.models.AbstractUser) class and add two fields. The foreign key references another model (not shown).
```python
# myapp/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
country = models.CharField(verbose_name='country', max_length=255)
status = models.ForeignKey(MembershipStatus, on_delete=models.SET_NULL, null=True, default=1)
```
Add the app containing your user model to `INSTALLED_APPS` - it must be above the `'wagtail.users'` line,
in order to override Wagtail’s built-in templates - and set [`AUTH_USER_MODEL`](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#auth-custom-user) to reference
your model. In this example the app is called `myapp` and the model is `User`.
```python
AUTH_USER_MODEL = 'myapp.User'
```
## Creating custom user forms
Now we need to configure Wagtail’s user forms to allow the custom fields’ values to be updated.
Create your custom user ‘create’ and ‘edit’ forms in your app:
```python
# myapp/forms.py
from django import forms
from django.utils.translation import gettext_lazy as _
from wagtail.users.forms import UserEditForm, UserCreationForm
from myapp.models import MembershipStatus
class CustomUserEditForm(UserEditForm):
status = forms.ModelChoiceField(queryset=MembershipStatus.objects, required=True, label=_("Status"))
# Use ModelForm's automatic form fields generation for the model's `country` field,
# but use an explicit custom form field for `status`.
class Meta(UserEditForm.Meta):
fields = UserEditForm.Meta.fields | {"country", "status"}
class CustomUserCreationForm(UserCreationForm):
status = forms.ModelChoiceField(queryset=MembershipStatus.objects, required=True, label=_("Status"))
# Use ModelForm's automatic form fields generation for the model's `country` field,
# but use an explicit custom form field for `status`.
class Meta(UserCreationForm.Meta):
fields = UserCreationForm.Meta.fields | {"country", "status"}
```
## Extending the create and edit templates
Extend the Wagtail user ‘create’ and ‘edit’ templates. These extended templates should be placed in `wagtailusers/users/` within any valid template location - for example, `myapp/templates/wagtailusers/users/`.
myapp/templates/wagtailusers/users/create.html:
```html+django
{% extends "wagtailusers/users/create.html" %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field.html" with field=form.country %}
{% include "wagtailadmin/shared/field.html" with field=form.status %}
{% endblock extra_fields %}
```
myapp/templates/wagtailusers/users/edit.html:
```html+django
{% extends "wagtailusers/users/edit.html" %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field.html" with field=form.country %}
{% include "wagtailadmin/shared/field.html" with field=form.status %}
{% endblock extra_fields %}
```
The `extra_fields` block allows fields to be inserted below the `last_name` field
in the default templates. There is a `fields` block that allows appending
fields to the end or beginning of the existing fields or to allow all the fields to
be redefined.
## Creating a custom `UserViewSet`
To make use of the custom forms, create a `UserViewSet` subclass.
```python
# myapp/viewsets.py
from wagtail.users.views.users import UserViewSet as WagtailUserViewSet
from .forms import CustomUserCreationForm, CustomUserEditForm
class UserViewSet(WagtailUserViewSet):
def get_form_class(self, for_update=False):
if for_update:
return CustomUserEditForm
return CustomUserCreationForm
```
Then, configure the `wagtail.users` application to use the custom viewset, by setting up a custom `AppConfig` class. Within your project folder (which will be the package containing the top-level settings and urls modules), create `apps.py` (if it does not exist already) and add:
```python
# myproject/apps.py
from wagtail.users.apps import WagtailUsersAppConfig
class CustomUsersAppConfig(WagtailUsersAppConfig):
user_viewset = "myapp.viewsets.UserViewSet"
```
Replace `wagtail.users` in `settings.INSTALLED_APPS` with the path to `CustomUsersAppConfig`.
```python
INSTALLED_APPS = [
...,
# Make sure you have two separate entries for the following:
"myapp", # an app that contains the custom user model
"myproject.apps.CustomUsersAppConfig", # a custom app config for the wagtail.users app
# "wagtail.users", # this should be removed in favour of the custom app config
...,
]
```
#### WARNING
You can also place the `WagtailUsersAppConfig` subclass inside the same `apps.py` file of your custom user model’s app (instead of in a `myproject/apps.py` file), but you need to be careful. Make sure to use two separate config classes instead of turning your existing `AppConfig` subclass into a `WagtailUsersAppConfig` subclass, as that would cause Django to pick up your custom user model as being part of `wagtail.users`. You may also need to set [`default`](https://docs.djangoproject.com/en/stable/ref/applications/#django.apps.AppConfig.default) to `True` in your own app’s `AppConfig`, unless you already use a dotted path to the app’s `AppConfig` subclass in `INSTALLED_APPS`.
The `UserViewSet` class is a subclass of [`ModelViewSet`](../../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet) and thus it supports most of [the customizations available for `ModelViewSet`](../../extending/generic_views.md#generic-views). For example, you can use a custom directory for the templates by setting [`template_prefix`](../../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet.template_prefix):
```py
class UserViewSet(WagtailUserViewSet):
template_prefix = "myapp/users/"
```
or customize the create and edit templates specifically:
```py
class UserViewSet(WagtailUserViewSet):
create_template_name = "myapp/users/create.html"
edit_template_name = "myapp/users/edit.html"
```
The group forms and views can be customized in a similar way – see [Customizing group edit/create views](../../extending/customizing_group_views.md#customizing-group-views).
# customization.html.md
# Form builder customization
For a basic usage example see [form builder usage](index.md#form-builder-usage).
## Custom `related_name` for form fields
If you want to change `related_name` for form fields
(by default `AbstractForm` and `AbstractEmailForm` expect `form_fields` to be defined),
you will need to override the `get_form_fields` method.
You can do this as shown below.
```python
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
from wagtail.fields import RichTextField
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='custom_form_fields')
class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('custom_form_fields'),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def get_form_fields(self):
return self.custom_form_fields.all()
```
## Custom form submission model
If you need to save additional data, you can use a custom form submission model.
To do this, you need to:
- Define a model that extends `wagtail.contrib.forms.models.AbstractFormSubmission`.
- Override the `get_submission_class` and `process_form_submission` methods in your page model.
Example:
```python
import json
from django.conf import settings
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
from wagtail.fields import RichTextField
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField, AbstractFormSubmission
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')
class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields'),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def get_submission_class(self):
return CustomFormSubmission
def process_form_submission(self, form):
return self.get_submission_class().objects.create(
form_data=form.cleaned_data,
page=self, user=form.user
)
class CustomFormSubmission(AbstractFormSubmission):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
```
## Add custom data to CSV export
If you want to add custom data to the CSV export, you will need to:
- Override the `get_data_fields` method in page model.
- Override `get_data` in the submission model.
The example below shows how to add a username to the CSV export.
Note that this code also changes the submissions list view.
```python
import json
from django.conf import settings
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
from wagtail.fields import RichTextField
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField, AbstractFormSubmission
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')
class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields'),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def get_data_fields(self):
data_fields = [
('username', 'Username'),
]
data_fields += super().get_data_fields()
return data_fields
def get_submission_class(self):
return CustomFormSubmission
def process_form_submission(self, form):
return self.get_submission_class().objects.create(
form_data=form.cleaned_data,
page=self, user=form.user
)
class CustomFormSubmission(AbstractFormSubmission):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def get_data(self):
form_data = super().get_data()
form_data.update({
'username': self.user.username,
})
return form_data
```
## Check that a submission already exists for a user
If you want to prevent users from filling in a form more than once,
you need to override the `serve` method in your page model.
Example:
```python
import json
from django.conf import settings
from django.db import models
from django.shortcuts import render
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
from wagtail.fields import RichTextField
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField, AbstractFormSubmission
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')
class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields'),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def serve(self, request, *args, **kwargs):
if self.get_submission_class().objects.filter(page=self, user__pk=request.user.pk).exists():
return render(
request,
self.template,
self.get_context(request)
)
return super().serve(request, *args, **kwargs)
def get_submission_class(self):
return CustomFormSubmission
def process_form_submission(self, form):
return self.get_submission_class().objects.create(
form_data=form.cleaned_data,
page=self, user=form.user
)
class CustomFormSubmission(AbstractFormSubmission):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Meta:
unique_together = ('page', 'user')
```
Your template should look like this:
```html+django
{% load wagtailcore_tags %}
{{ page.title }}
{{ page.title }}
{% if user.is_authenticated and user.is_active or request.is_preview %}
{% if form %}
{{ page.intro|richtext }}
{% else %}
You can fill in the form only one time.
{% endif %}
{% else %}
To fill in the form, you must log in.
{% endif %}
```
## Multi-step form
The following example shows how to create a multi-step form.
```python
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.shortcuts import render
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
from wagtail.fields import RichTextField
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')
class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields'),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def get_form_class_for_step(self, step):
return self.form_builder(step.object_list).get_form_class()
def serve(self, request, *args, **kwargs):
"""
Implements a simple multi-step form.
Stores each step into a session.
When the last step is submitted correctly, saves the whole form into a DB.
"""
session_key_data = 'form_data-%s' % self.pk
is_last_step = False
step_number = request.GET.get('p', 1)
paginator = Paginator(self.get_form_fields(), per_page=1)
try:
step = paginator.page(step_number)
except PageNotAnInteger:
step = paginator.page(1)
except EmptyPage:
step = paginator.page(paginator.num_pages)
is_last_step = True
if request.method == 'POST':
# The first step will be submitted with step_number == 2,
# so we need to get a form from the previous step
# Edge case - submission of the last step
prev_step = step if is_last_step else paginator.page(step.previous_page_number())
# Create a form only for submitted step
prev_form_class = self.get_form_class_for_step(prev_step)
prev_form = prev_form_class(request.POST, page=self, user=request.user)
if prev_form.is_valid():
# If data for step is valid, update the session
form_data = request.session.get(session_key_data, {})
form_data.update(prev_form.cleaned_data)
request.session[session_key_data] = form_data
if prev_step.has_next():
# Create a new form for a following step, if the following step is present
form_class = self.get_form_class_for_step(step)
form = form_class(page=self, user=request.user)
else:
# If there is no next step, create form for all fields
form = self.get_form(
request.session[session_key_data],
page=self, user=request.user
)
if form.is_valid():
# Perform validation again for whole form.
# After successful validation, save data into DB,
# and remove from the session.
form_submission = self.process_form_submission(form)
del request.session[session_key_data]
# render the landing page
return self.render_landing_page(request, form_submission, *args, **kwargs)
else:
# If data for step is invalid
# we will need to display form again with errors,
# so restore previous state.
form = prev_form
step = prev_step
else:
# Create empty form for non-POST requests
form_class = self.get_form_class_for_step(step)
form = form_class(page=self, user=request.user)
context = self.get_context(request)
context['form'] = form
context['fields_step'] = step
return render(
request,
self.template,
context
)
```
Your template for this form page should look like this:
```html+django
{% load wagtailcore_tags %}
{{ page.title }}
{{ page.title }}
{{ page.intro|richtext }}
```
Note that the example shown before allows the user to return to a previous step,
or to open a second step without submitting the first step.
Depending on your requirements, you may need to add extra checks.
## Show results
If you are implementing polls or surveys, you may want to show results after submission.
The following example demonstrates how to do this.
First, you need to collect results as shown below:
```python
from modelcluster.fields import ParentalKey
from wagtail.admin.panels import (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
from wagtail.fields import RichTextField
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')
class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields'),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
# If you need to show results only on landing page,
# You may need to check request.method
results = dict()
# Get information about form fields
data_fields = [
(field.clean_name, field.label)
for field in self.get_form_fields()
]
# Get all submissions for current page
submissions = self.get_submission_class().objects.filter(page=self)
for submission in submissions:
data = submission.get_data()
# Count results for each question
for name, label in data_fields:
answer = data.get(name)
if answer is None:
# Something wrong with data.
# Probably you have changed questions
# and now we are receiving answers for old questions.
# Just skip them.
continue
if type(answer) is list:
# Answer is a list if the field type is 'Checkboxes'
answer = u', '.join(answer)
question_stats = results.get(label, {})
question_stats[answer] = question_stats.get(answer, 0) + 1
results[label] = question_stats
context.update({
'results': results,
})
return context
```
Next, you need to transform your template to display the results:
```html+django
{% load wagtailcore_tags %}
{{ page.title }}
{{ page.title }}
Results
{% for question, answers in results.items %}
{{ question }}
{% for answer, count in answers.items %}
{{ answer }}: {{ count }}
{% endfor %}
{% endfor %}
{{ page.intro|richtext }}
```
You can also show the results on the landing page.
## Custom landing page redirect
You can override the `render_landing_page` method on your `FormPage` to change what is rendered when a form submits.
In the example below we have added a `thank_you_page` field that enables custom redirects after a form submits to the selected page.
When overriding the `render_landing_page` method, we check if there is a linked `thank_you_page` and then redirect to it if it exists.
Finally, we add a URL param of `id` based on the `form_submission` if it exists.
```python
from django.shortcuts import redirect
from wagtail.admin.panels import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel
from wagtail.contrib.forms.models import AbstractEmailForm
class FormPage(AbstractEmailForm):
# intro, thank_you_text, ...
thank_you_page = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
)
def render_landing_page(self, request, form_submission=None, *args, **kwargs):
if self.thank_you_page:
url = self.thank_you_page.url
# if a form_submission instance is available, append the id to URL
# when previewing landing page, there will not be a form_submission instance
if form_submission:
url += '?id=%s' % form_submission.id
return redirect(url, permanent=False)
# if no thank_you_page is set, render default landing page
return super().render_landing_page(request, form_submission, *args, **kwargs)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields'),
FieldPanel('thank_you_text'),
FieldPanel('thank_you_page'),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname='col6'),
FieldPanel('to_address', classname='col6'),
]),
FieldPanel('subject'),
], 'Email'),
]
```
## Customize form submissions listing in Wagtail Admin
The Admin listing of form submissions can be customized by setting the attribute `submissions_list_view_class` on your FormPage model.
The list view class must be a subclass of `SubmissionsListView` from `wagtail.contrib.forms.views`, which is a subclass of `wagtail.admin.views.generic.base.BaseListingView` and Django’s class based [`ListView`](https://docs.djangoproject.com/en/stable/ref/class-based-views/generic-display/#django.views.generic.list.ListView).
Example:
```python
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
from wagtail.contrib.forms.views import SubmissionsListView
class CustomSubmissionsListView(SubmissionsListView):
paginate_by = 50 # show more submissions per page, default is 20
default_ordering = ('submit_time',) # order submissions by oldest first, normally newest first
ordering_csv = ('-submit_time',) # order csv export by newest first, normally oldest first
# override the method to generate csv filename
def get_csv_filename(self):
""" Returns the filename for CSV file with page slug at start"""
filename = super().get_csv_filename()
return self.form_page.slug + '-' + filename
class FormField(AbstractFormField):
page = ParentalKey('FormPage', related_name='form_fields')
class FormPage(AbstractEmailForm):
"""Form Page with customized submissions listing view"""
# set custom view class as class attribute
submissions_list_view_class = CustomSubmissionsListView
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
# content_panels = ...
```
## Customizing the widget of built-in field types
When rendering a form, each field type will be rendered using its default widget, the widget used can be overridden as follows.
Define your custom widget, in this example we will override the email type with an enhanced `EmailInput` widget.
```py
# myapp/widgets.py
from django import forms
from django.utils.translation import gettext as _
#... other imports
class CustomEmailInputWidget(forms.EmailInput):
"""
This is a custom input type for the email field, with refined
extra attributes for cross-browser compatibility.
"""
def __init__(self, attrs={}):
attrs = {
"autocapitalize": "off",
"autocomplete": "email",
"autocorrect": "off",
"placeholder": _("email@example.com"),
"spellcheck": "false",
**attrs, # let supplied attrs override the new defaults
}
super().__init__(attrs=attrs)
```
Override the `create_TYPE_field` method for the `email` type, assigning the widget to the field’s options that have been defined by user entry.
```python
from wagtail.contrib.forms.forms import FormBuilder
from myapp.widgets import CustomEmailInputWidget
class CustomFormBuilder(FormBuilder):
# Override any of the `create_TYPE_field` to apply to different field types
# e.g., create_singleline_field, create_checkboxes_field, create_url_field
def create_email_field(self, field, options):
options["widget"] = CustomEmailInputWidget
return super().create_email_field(field, options)
# Alternatively, you can instantiate the widget directly with custom attributes
def create_singleline_field(self, field, options):
options["widget"] = forms.TextInput(attrs={"class": "custom-class"})
return super().create_singleline_field(field, options)
```
As per other customizations, ensure you have set the `form_builder` attribute on your form page model.
```python
from wagtail.contrib.forms.models import AbstractEmailForm
class FormPage(AbstractEmailForm):
# intro, thank_you_text, edit_handlers, etc...
# use custom form builder defined above
form_builder = CustomFormBuilder
```
## Adding a custom field type
First, make the new field type available in the page editor by changing your `FormField` model.
- Create a new set of choices which includes the original `FORM_FIELD_CHOICES` along with new field types you want to make available.
- Each choice must contain a unique key and a human-readable name of the field, for example `('slug', 'URL Slug')`
- Override the `field_type` field in your `FormField` model with `choices` attribute using these choices.
- You will need to run `./manage.py makemigrations` and `./manage.py migrate` after this step.
Then, create and use a new form builder class.
- Define a new form builder class that extends the `FormBuilder` class.
- Add a method that will return a created Django form field for the new field type.
- Its name must be in the format: `create__field`, for example `create_slug_field`
- Override the `form_builder` attribute in your form page model to use your new form builder class.
Example:
```python
from django import forms
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.models import (
AbstractEmailForm, AbstractFormField, FORM_FIELD_CHOICES)
class FormField(AbstractFormField):
# extend the built-in field type choices
# our field type key will be 'ipaddress'
CHOICES = FORM_FIELD_CHOICES + (('ipaddress', 'IP Address'),)
page = ParentalKey('FormPage', related_name='form_fields')
# override the field_type field with extended choices
field_type = models.CharField(
verbose_name='field type',
max_length=16,
# use the choices tuple defined above
choices=CHOICES
)
class CustomFormBuilder(FormBuilder):
# create a function that returns an instanced Django form field
# function name must match create__field
def create_ipaddress_field(self, field, options):
# return `forms.GenericIPAddressField(**options)` not `forms.SlugField`
# returns created a form field with the options passed in
return forms.GenericIPAddressField(**options)
class FormPage(AbstractEmailForm):
# intro, thank_you_text, edit_handlers, etc...
# use custom form builder defined above
form_builder = CustomFormBuilder
```
## Custom `render_email` method
If you want to change the content of the email that is sent when a form submits you can override the `render_email` method.
To do this, you need to:
- Ensure you have your form model defined that extends `wagtail.contrib.forms.models.AbstractEmailForm`.
- Override the `render_email` method in your page model.
Example:
```python
from datetime import date
# ... additional wagtail imports
from wagtail.contrib.forms.models import AbstractEmailForm
class FormPage(AbstractEmailForm):
# ... fields, content_panels, etc
def render_email(self, form):
# Get the original content (string)
email_content = super().render_email(form)
# Add a title (not part of the original method)
title = '{}: {}'.format('Form', self.title)
content = [title, '', email_content, '']
# Add a link to the form page
content.append('{}: {}'.format('Submitted Via', self.full_url))
# Add the date the form was submitted
submitted_date_str = date.today().strftime('%x')
content.append('{}: {}'.format('Submitted on', submitted_date_str))
# Content is joined with a new line to separate each text line
content = '\n'.join(content)
return content
```
## Custom `send_mail` method
If you want to change the subject or some other part of how an email is sent when a form submits you can override the `send_mail` method.
To do this, you need to:
- Ensure you have your form model defined that extends `wagtail.contrib.forms.models.AbstractEmailForm`.
- In your models.py file, import the `wagtail.admin.mail.send_mail` function.
- Override the `send_mail` method in your page model.
Example:
```python
from datetime import date
# ... additional wagtail imports
from wagtail.admin.mail import send_mail
from wagtail.contrib.forms.models import AbstractEmailForm
class FormPage(AbstractEmailForm):
# ... fields, content_panels, etc
def send_mail(self, form):
# `self` is the FormPage, `form` is the form's POST data on submit
# Email addresses are parsed from the FormPage's addresses field
addresses = [x.strip() for x in self.to_address.split(',')]
# Subject can be adjusted (adding submitted date), be sure to include the form's defined subject field
submitted_date_str = date.today().strftime('%x')
subject = f"{self.subject} - {submitted_date_str}"
send_mail(subject, self.render_email(form), addresses, self.from_address,)
```
## Custom `clean_name` generation
- Each time a new `FormField` is added a `clean_name` also gets generated based on the user-entered `label`.
- `AbstractFormField` has a method `get_field_clean_name` to convert the label into an HTML-valid `lower_snake_case` ASCII string using the [AnyAscii](https://pypi.org/project/anyascii/) library which can be overridden to generate a custom conversion.
- The resolved `clean_name` is also used as the form field name in rendered HTML forms.
- Ensure that any conversion will be unique enough to not create conflicts within your `FormPage` instance.
- This method gets called on the creation of new fields only and as such will not have access to its own `Page` or `pk`. This does not get called when labels are edited as modifying the `clean_name` after any form responses are submitted will mean those field responses will not be retrieved.
- This method gets called for form previews and also validation of duplicate labels.
```python
import uuid
from django.db import models
from modelcluster.fields import ParentalKey
# ... other field and edit_handler imports
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
class FormField(AbstractFormField):
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')
def get_field_clean_name(self):
clean_name = super().get_field_clean_name()
id = str(uuid.uuid4())[:8] # short uuid
return f"{id}_{clean_name}"
class FormPage(AbstractEmailForm):
# ... page definitions
```
## Using `FormMixin` or `EmailFormMixin` to use with other `Page` subclasses
If you need to add form behavior while extending an additional class, you can use the base mixins instead of the abstract models.
```python
from wagtail.models import Page
from wagtail.contrib.forms.models import EmailFormMixin, FormMixin
class BasePage(Page):
"""
A shared base page used throughout the project.
"""
# ...
class FormPage(FormMixin, BasePage):
intro = RichTextField(blank=True)
# ...
class EmailFormPage(EmailFormMixin, FormMixin, BasePage):
intro = RichTextField(blank=True)
# ...
```
## Custom validation for admin form pages
By default, pages that inherit from `FormMixin` will validate that each field added by an editor has a unique `clean_name`.
If you need to add custom validation, create a subclass of `WagtailAdminFormPageForm` and add your own `clean` definition and set the `base_form_class` on your `Page` model.
#### NOTE
Validation only applies when editors use the form builder to add fields in the Wagtail admin,
not when the form is submitted by end users.
```python
from wagtail.models import Page
from wagtail.contrib.forms.models import FormMixin, WagtailAdminFormPageForm
class CustomWagtailAdminFormPageForm(WagtailAdminFormPageForm):
def clean(self):
cleaned_data = super().clean()
# Insert custom validation here, see `WagtailAdminFormPageForm.clean` for an example
return cleaned_data
class FormPage(AbstractForm):
base_form_class = CustomWagtailAdminFormPageForm
```
# customize_homepage.html.md
# Customize your home page
Heads’up! Make sure you have completed [Your first Wagtail site](../getting_started/tutorial.md) before going through this extended tutorial.
When building your portfolio website, the first step is to set up and personalize your homepage. The homepage is your chance to make an excellent first impression and convey the core message of your portfolio. So your homepage should include the following features:
1. **Introduction:** A concise introduction captures visitors’ attention.
2. **Biography:** Include a brief biography that introduces yourself. This section should mention your name, role, expertise, and unique qualities.
3. **Hero Image:** This may be a professional headshot or other image that showcases your work and adds visual appeal.
4. **Call to Action (CTA):** Incorporate a CTA that guides visitors to take a specific action, such as “View Portfolio,” “Hire Me,” or “Learn More”.
5. **Resume:** This is a document that provides a summary of your education, work experience, achievements, and qualifications.
In this section, you’ll learn how to add features **1** through **4** to your homepage. You’ll add your resume or CV later in the tutorial.
Now, modify your `home/models.py` file to include the following:
```python
from django.db import models
from wagtail.models import Page
from wagtail.fields import RichTextField
# import MultiFieldPanel:
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
class HomePage(Page):
# add the Hero section of HomePage:
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
help_text="Homepage image",
)
hero_text = models.CharField(
blank=True,
max_length=255, help_text="Write an introduction for the site"
)
hero_cta = models.CharField(
blank=True,
verbose_name="Hero CTA",
max_length=255,
help_text="Text to display on Call to Action",
)
hero_cta_link = models.ForeignKey(
"wagtailcore.Page",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
verbose_name="Hero CTA link",
help_text="Choose a page to link to for the Call to Action",
)
body = RichTextField(blank=True)
# modify your content_panels:
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("image"),
FieldPanel("hero_text"),
FieldPanel("hero_cta"),
FieldPanel("hero_cta_link"),
],
heading="Hero section",
),
FieldPanel('body'),
]
```
You might already be familiar with the different parts of your `HomePage` model. The `image` field is a `ForeignKey` referencing Wagtail’s built-in Image model for storing images. Similarly, `hero_cta_link` is a `ForeignKey` to `wagtailcore.Page`. The `wagtailcore.Page` is the base class for all other page types in Wagtail. This means all Wagtail pages inherit from `wagtailcore.Page`. For instance, your `class HomePage(Page)` inherits from `wagtailcore.Page`.
Using `on_delete=models.SET_NULL` ensures that if you remove an image or hero link from your admin interface, the `image` or `hero_cta_link` fields on your Homepage will be set to null, but the rest of the data will be preserved. Read the [Django documentation on the `on_delete` attribute](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.ForeignKey.on_delete) for more details.
By default, Django creates a reverse relation between the models when you have a `ForeignKey` field within your model. Django also generates a name for this reverse relation using the model name and the `_set` suffix. You can use the default name of the reverse relation to access the model with the `ForeignKey` field from the referenced model.
You can override this default naming behavior and provide a custom name for the reverse relationship by using the `related_name` attribute. For example, if you want to access your `HomePage` from `wagtailimages.Image`, you can use the value you provided for your `related_name` attribute.
However, when you use `related_name="+"`, you create a connection between models without creating a reverse relation. In other words, you’re instructing Django to create a way to access `wagtailimages.Image` from your `Homepage` but not a way to access `HomePage` from `wagtailimages.Image`.
While `body` is a `RichTextField`, `hero_text` and `hero_cta` are `CharField`, a Django string field for storing short text.
The [Your First Wagtail Tutorial](../getting_started/tutorial.md) already explained `content_panels`. [FieldPanel](../reference/panels.md#field-panel) and [MultiPanel](../reference/panels.md#multifieldpanel) are types of Wagtail built-in [Panels](../reference/panels.md#editing-api). They’re both subclasses of the base Panel class and accept all of Wagtail’s `Panel` parameters in addition to their own. While the `FieldPanel` provides a widget for basic Django model fields, `MultiFieldPanel` helps you decide the structure of the editing form. For example, you can group related fields.
Now that you understand the different parts of your `HomePage` model, migrate your database by running `python manage.py makemigrations` and
then `python manage.py migrate`
After migrating your database, start your server by running
`python manage.py runserver`.
## Add content to your homepage
To add content to your homepage through the admin interface, follow these steps:
1. Log in to your [admin interface](http://127.0.0.1:8000/admin/), with your admin username and password.
2. Click Pages.
3. Click the **pencil** icon beside **Home**.
4. Choose an image, choose a page, and add data to the input fields.
#### NOTE
You can choose your home page or blog index page to link to your Call to Action. You can choose a more suitable page later in the tutorial.
1. Publish your Home page.
You have all the necessary data for your Home page now. You can visit your Home page by going to `http://127.0.0.1:8000` in your browser. You can’t see all your data, right? That’s because you must modify your Homepage template to display the data.
Replace the content of your `home/templates/home/home_page.html` file with the following:
```html+django
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block body_class %}template-homepage{% endblock %}
{% block content %}
{{ page.body|richtext }}
{% endblock content %}
```
In your Homepage template, notice the use of `firstof` in line 13. It’s helpful to use this tag when you have created a series of fallback options, and you want to display the first one that has a value. So, in your template, the `firstof` template tag displays `page.hero_cta` if it has a value. If `page.hero_cta` doesn’t have a value, then it displays `page.hero_cta_link.title`.
Congratulations! You’ve completed the first stage of your Portfolio website 🎉🎉🎉.
# customizing.html.md
# Customizing admin views for snippets
Additional customizations to the admin views for each snippet model can be achieved through a custom [`SnippetViewSet`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet) class. The `SnippetViewSet` is a subclass of [`ModelViewSet`](../../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet), with snippets-specific properties provided by default. Hence, it supports the same customizations provided by `ModelViewSet` such as [customizing the listing view](../../extending/generic_views.md#modelviewset-listing) (e.g. adding custom columns, and filters), creating a custom menu item, and more.
Before proceeding, ensure that you register the snippet model using `register_snippet` as a function instead of a decorator, as described in [Registering snippets](registering.md#wagtailsnippets-registering).
For demonstration, consider the following `Member` model and a `MemberFilterSet` class:
```python
# models.py
from django.db import models
from wagtail.admin.filters import WagtailFilterSet
class Member(models.Model):
class ShirtSize(models.TextChoices):
SMALL = "S", "Small"
MEDIUM = "M", "Medium"
LARGE = "L", "Large"
EXTRA_LARGE = "XL", "Extra Large"
name = models.CharField(max_length=255)
shirt_size = models.CharField(max_length=5, choices=ShirtSize.choices, default=ShirtSize.MEDIUM)
def get_shirt_size_display(self):
return self.ShirtSize(self.shirt_size).label
get_shirt_size_display.admin_order_field = "shirt_size"
get_shirt_size_display.short_description = "Size description"
class MemberFilterSet(WagtailFilterSet):
class Meta:
model = Member
fields = ["shirt_size"]
```
And the following is the snippet’s corresponding `SnippetViewSet` subclass:
```python
# wagtail_hooks.py
from wagtail.admin.panels import FieldPanel, ObjectList, TabbedInterface
from wagtail.admin.ui.tables import UpdatedAtColumn
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import SnippetViewSet
from myapp.models import Member, MemberFilterSet
class MemberViewSet(SnippetViewSet):
model = Member
icon = "user"
list_display = ["name", "shirt_size", "get_shirt_size_display", UpdatedAtColumn()]
list_per_page = 50
copy_view_enabled = False
inspect_view_enabled = True
admin_url_namespace = "member_views"
base_url_path = "internal/member"
filterset_class = MemberFilterSet
# alternatively, you can use the following instead of filterset_class
# list_filter = ["shirt_size"]
# or
# list_filter = {"shirt_size": ["exact"], "name": ["icontains"]}
edit_handler = TabbedInterface([
ObjectList([FieldPanel("name")], heading="Details"),
ObjectList([FieldPanel("shirt_size")], heading="Preferences"),
])
register_snippet(MemberViewSet)
```
## Icon
You can define an [`icon`](../../reference/viewsets.md#wagtail.admin.viewsets.base.ViewSet.icon) attribute on the `SnippetViewSet` to specify the icon that is used across the admin for this snippet type. The `icon` needs to be [registered in the Wagtail icon library](../../advanced_topics/icons.md). If `icon` is not set, the default `"snippet"` icon is used.
## URL namespace and base URL path
The [`url_namespace`](../../reference/viewsets.md#wagtail.admin.viewsets.base.ViewSet.url_namespace) property can be overridden to use a custom URL namespace for the URL patterns of the views. If unset, it defaults to `wagtailsnippets_{app_label}_{model_name}`. Meanwhile, overriding [`url_prefix`](../../reference/viewsets.md#wagtail.admin.viewsets.base.ViewSet.url_prefix) allows you to customize the base URL path relative to the Wagtail admin URL. If unset, it defaults to `snippets/app_label/model_name`.
Similar URL customizations are also possible for the snippet chooser views through [`chooser_admin_url_namespace`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.chooser_admin_url_namespace), [`chooser_base_url_path`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.chooser_base_url_path), [`get_chooser_admin_url_namespace()`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.get_chooser_admin_url_namespace), and [`get_chooser_admin_base_path()`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.get_chooser_admin_base_path).
## Listing view
You can customize the listing view to add custom columns, filters, pagination, etc. via various attributes available on the `SnippetViewSet`. Refer to [the listing view customizations for `ModelViewSet`](../../extending/generic_views.md#modelviewset-listing) for more details.
Additionally, you can customize the base queryset for the listing view by overriding the [`get_queryset()`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.get_queryset) method.
## Copy view
The copy view is enabled by default and will be accessible by users with the ‘add’ permission on the model. To disable it, set [`copy_view_enabled`](../../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet.copy_view_enabled) to `False`. Refer to [the copy view customizations for `ModelViewSet`](../../extending/generic_views.md#modelviewset-copy) for more details.
## Inspect view
The inspect view is disabled by default, as it’s not often useful for most models. To enable it, set [`inspect_view_enabled`](../../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet.inspect_view_enabled) to `True`. Refer to [the inspect view customizations for `ModelViewSet`](../../extending/generic_views.md#modelviewset-inspect) for more details.
## Templates
Template customizations work the same way as for `ModelViewSet`, except that the [`template_prefix`](../../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet.template_prefix) defaults to `wagtailsnippets/snippets/`. Refer to [the template customizations for `ModelViewSet`](../../extending/generic_views.md#modelviewset-templates) for more details.
## Menu item
By default, registering a snippet model will add a “Snippets” menu item to the sidebar menu. However, you can configure a snippet model to have its own top-level menu item in the sidebar menu by setting [`add_to_admin_menu`](../../reference/viewsets.md#wagtail.admin.viewsets.base.ViewSet.add_to_admin_menu) to `True`. Refer to [the menu customizations for `ModelViewSet`](../../extending/generic_views.md#modelviewset-menu) for more details.
An example of a custom `SnippetViewSet` subclass with `add_to_admin_menu` set to `True`:
```python
from wagtail.snippets.views.snippets import SnippetViewSet
class AdvertViewSet(SnippetViewSet):
model = Advert
icon = "crosshairs"
menu_label = "Advertisements"
menu_name = "adverts"
menu_order = 300
add_to_admin_menu = True
```
Multiple snippet models can also be grouped under a single menu item using a [`SnippetViewSetGroup`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSetGroup). You can do this by setting the [`model`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet.model) attribute on the `SnippetViewSet` classes and then registering the `SnippetViewSetGroup` subclass instead of each individual model or viewset:
```python
from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup
class AdvertViewSet(SnippetViewSet):
model = Advert
icon = "crosshairs"
menu_label = "Advertisements"
menu_name = "adverts"
class ProductViewSet(SnippetViewSet):
model = Product
icon = "desktop"
menu_label = "Products"
menu_name = "banners"
class MarketingViewSetGroup(SnippetViewSetGroup):
items = (AdvertViewSet, ProductViewSet)
menu_icon = "folder-inverse"
menu_label = "Marketing"
menu_name = "marketing"
# When using a SnippetViewSetGroup class to group several SnippetViewSet classes together,
# only register the SnippetViewSetGroup class. You do not need to register each snippet
# model or viewset separately.
register_snippet(MarketingViewSetGroup)
```
By default, the sidebar “Snippets” menu item will only show snippet models that haven’t been configured with their own menu items.
If all snippet models have their own menu items, the “Snippets” menu item will not be shown.
This behaviour can be changed using the [WAGTAILSNIPPETS_MENU_SHOW_ALL](../../reference/settings.md#wagtailsnippets-menu-show-all) setting.
Various additional attributes are available to customize the viewset - see [`SnippetViewSet`](../../reference/viewsets.md#wagtail.snippets.views.snippets.SnippetViewSet).
# customizing_group_views.html.md
# Customizing group edit/create views
The views for managing groups within the app are collected into a ‘viewset’ class, which acts as a single point of reference for all shared components of those views, such as forms. By subclassing the viewset, it is possible to override those components and customize the behavior of the group management interface.
## Custom edit/create forms
This example shows how to customize forms on the ‘edit group’ and ‘create group’ views in the Wagtail admin.
Let’s say you need to connect Active Directory groups with Django groups.
We create a model for Active Directory groups as follows:
```python
# myapp/models.py
from django.contrib.auth.models import Group
from django.db import models
class ADGroup(models.Model):
guid = models.CharField(verbose_name="GUID", max_length=64, db_index=True, unique=True)
name = models.CharField(verbose_name="Group", max_length=255)
domain = models.CharField(verbose_name="Domain", max_length=255, db_index=True)
description = models.TextField(verbose_name="Description", blank=True, null=True)
roles = models.ManyToManyField(Group, verbose_name="Role", related_name="adgroups", blank=True)
class Meta:
verbose_name = "AD group"
verbose_name_plural = "AD groups"
```
However, there is no role field on the Wagtail group ‘edit’ or ‘create’ view.
To add it, inherit from `wagtail.users.forms.GroupForm` and add a new field:
```python
# myapp/forms.py
from django import forms
from wagtail.users.forms import GroupForm as WagtailGroupForm
from .models import ADGroup
class GroupForm(WagtailGroupForm):
adgroups = forms.ModelMultipleChoiceField(
label="AD groups",
required=False,
queryset=ADGroup.objects.order_by("name"),
)
class Meta(WagtailGroupForm.Meta):
fields = WagtailGroupForm.Meta.fields + ("adgroups",)
def __init__(self, initial=None, instance=None, **kwargs):
if instance is not None:
if initial is None:
initial = {}
initial["adgroups"] = instance.adgroups.all()
super().__init__(initial=initial, instance=instance, **kwargs)
def save(self, commit=True):
instance = super().save()
instance.adgroups.set(self.cleaned_data["adgroups"])
return instance
```
Now add your custom form into the group viewset by inheriting the default Wagtail `GroupViewSet` class and overriding the `get_form_class` method.
```python
# myapp/viewsets.py
from wagtail.users.views.groups import GroupViewSet as WagtailGroupViewSet
from .forms import GroupForm
class GroupViewSet(WagtailGroupViewSet):
def get_form_class(self, for_update=False):
return GroupForm
```
Add the field to the group ‘edit’/’create’ templates:
```html+django
{% extends "wagtailusers/groups/edit.html" %}
{% load wagtailusers_tags wagtailadmin_tags i18n %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field.html" with field=form.adgroups %}
{% endblock extra_fields %}
```
Finally, we configure the `wagtail.users` application to use the custom viewset, by setting up a custom `AppConfig` class. Within your project folder (which will be the package containing the top-level settings and urls modules), create `apps.py` (if it does not exist already) and add:
```python
# myproject/apps.py
from wagtail.users.apps import WagtailUsersAppConfig
class CustomUsersAppConfig(WagtailUsersAppConfig):
group_viewset = "myapp.viewsets.GroupViewSet"
```
Replace `wagtail.users` in `settings.INSTALLED_APPS` with the path to `CustomUsersAppConfig`.
```python
INSTALLED_APPS = [
...,
"myproject.apps.CustomUsersAppConfig",
# "wagtail.users",
...,
]
```
The `GroupViewSet` class is a subclass of [`ModelViewSet`](../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet) and thus it supports most of [the customizations available for `ModelViewSet`](generic_views.md).
The user forms and views can be customized in a similar way - see [Creating a custom UserViewSet](../advanced_topics/customization/custom_user_models.md#custom-userviewset).
## Customizing the group editor permissions ordering
The order in which object types appear in the group editor’s “Object permissions” and “Other permissions” sections can be configured by registering that order in one or more `AppConfig` definitions. The order value is typically an integer between 0 and 999, although this is not enforced.
```python
from django.apps import AppConfig
class MyProjectAdminAppConfig(AppConfig):
name = "myproject_admin"
verbose_name = "My Project Admin"
def ready(self):
from wagtail.users.permission_order import register
register("gadgets.SprocketType", order=150)
register("gadgets.ChainType", order=151)
register("site_settings.Settings", order=160)
```
A model class can also be passed to `register()`.
Any object types that are not explicitly given an order will be sorted in alphabetical order by `app_label` and `model`, and listed after all of the object types *with* a configured order.
# data_migrations.html.md
# StreamField data migration reference
## wagtail.blocks.migrations.migrate_operation
### MigrateStreamData
```python
class MigrateStreamData(RunPython)
```
Subclass of RunPython for `StreamField` data migration operations
#### \_\_init_\_
```python
def __init__(app_name,
model_name,
field_name,
operations_and_block_paths,
revisions_from=None,
chunk_size=1024,
**kwargs)
```
MigrateStreamData constructor
**Arguments**:
- `app_name` *str* - Name of the app.
- `model_name` *str* - Name of the model.
- `field_name` *str* - Name of the `StreamField`.
- `operations_and_block_paths` *List[Tuple[operation, str]]* - List of operations and the block paths to apply them to.
- `revisions_from` *datetime, optional* - Only revisions created from this date onwards will be updated. Passing `None` updates all revisions. Defaults to `None`. Note that live and latest revisions will be updated regardless of what value this takes.
- `chunk_size` *int, optional* - chunk size for `queryset.iterator` and `bulk_update`.
Defaults to 1024.
- `**kwargs` - atomic, elidable, hints for superclass `RunPython` can be given
**Example**:
Renaming a block named `field1` to `block1`:
```python
MigrateStreamData(
app_name="blog",
model_name="BlogPage",
field_name="content",
operations_and_block_paths=[
(RenameStreamChildrenOperation(old_name="field1", new_name="block1"), ""),
],
revisions_from=datetime.datetime(2022, 7, 25)
)
```
## wagtail.blocks.migrations.operations
### RenameStreamChildrenOperation
```python
class RenameStreamChildrenOperation(BaseBlockOperation)
```
Renames all `StreamBlock` children of the given type
**Notes**:
The `block_path_str` when using this operation should point to the parent `StreamBlock` which contains the blocks to be renamed, not the block being renamed.
**Attributes**:
- `old_name` *str* - name of the child block type to be renamed
- `new_name` *str* - new name to rename to
### RenameStructChildrenOperation
```python
class RenameStructChildrenOperation(BaseBlockOperation)
```
Renames all `StructBlock` children of the given type
**Notes**:
The `block_path_str` when using this operation should point to the parent `StructBlock` which contains the blocks to be renamed, not the block being renamed.
**Attributes**:
- `old_name` *str* - name of the child block type to be renamed
- `new_name` *str* - new name to rename to
### RemoveStreamChildrenOperation
```python
class RemoveStreamChildrenOperation(BaseBlockOperation)
```
Removes all `StreamBlock` children of the given type
**Notes**:
The `block_path_str` when using this operation should point to the parent `StreamBlock` which contains the blocks to be removed, not the block being removed.
**Attributes**:
- `name` *str* - name of the child block type to be removed
### RemoveStructChildrenOperation
```python
class RemoveStructChildrenOperation(BaseBlockOperation)
```
Removes all `StructBlock` children of the given type
**Notes**:
The `block_path_str` when using this operation should point to the parent `StructBlock` which contains the blocks to be removed, not the block being removed.
**Attributes**:
- `name` *str* - name of the child block type to be removed
### StreamChildrenToListBlockOperation
```python
class StreamChildrenToListBlockOperation(BaseBlockOperation)
```
Combines `StreamBlock` children of the given type into a new `ListBlock`
**Notes**:
The `block_path_str` when using this operation should point to the parent `StreamBlock` which contains the blocks to be combined, not the child block itself.
**Attributes**:
- `block_name` *str* - name of the child block type to be combined
- `list_block_name` *str* - name of the new `ListBlock` type
### StreamChildrenToStreamBlockOperation
```python
class StreamChildrenToStreamBlockOperation(BaseBlockOperation)
```
Combines `StreamBlock` children of the given types into a new `StreamBlock`
**Notes**:
The `block_path_str` when using this operation should point to the parent `StreamBlock` which contains the blocks to be combined, not the child block itself.
**Attributes**:
- `block_names` *[str]* - names of the child block types to be combined
- `stream_block_name` *str* - name of the new `StreamBlock` type
### AlterBlockValueOperation
```python
class AlterBlockValueOperation(BaseBlockOperation)
```
Alters the value of each block to the given value
**Attributes**:
- `new_value`: new value to change to
### StreamChildrenToStructBlockOperation
```python
class StreamChildrenToStructBlockOperation(BaseBlockOperation)
```
Move each `StreamBlock` child of the given type inside a new `StructBlock`
A new `StructBlock` will be created as a child of the parent `StreamBlock` for each child block of the given type, and then that child block will be moved from the parent StreamBlocks children inside the new `StructBlock` as a child of that `StructBlock`.
**Example**:
Consider the following `StreamField` definition:
```python
mystream = StreamField([("char1", CharBlock()), ...], ...)
```
Then the stream data would look like the following:
```python
[
...,
{ "type": "char1", "value": "Value1", ... },
{ "type": "char1", "value": "Value2", ... },
...
]
```
And if we define the operation like this:
```python
StreamChildrenToStructBlockOperation("char1", "struct1")
```
Our altered stream data would look like this:
```python
[
...,
{ "type": "struct1", "value": { "char1": "Value1" } },
{ "type": "struct1", "value": { "char1": "Value2" } },
...,
]
```
**Notes**:
- The `block_path_str` when using this operation should point to the parent `StreamBlock` which contains the blocks to be combined, not the child block itself.
- Block ids are not preserved here since the new blocks are structurally different than the previous blocks.
**Attributes**:
- `block_names` *str* - names of the child block types to be combined
- `struct_block_name` *str* - name of the new `StructBlock` type
## wagtail.blocks.migrations.utils
### InvalidBlockDefError
```python
class InvalidBlockDefError(Exception)
```
Exception for invalid block definitions
#### map_block_value
```python
def map_block_value(block_value, block_def, block_path, operation, **kwargs)
```
Maps the value of a block.
**Arguments**:
- `block_value`: The value of the block. This would be a list or dict of children for structural blocks.
- `block_def`: The definition of the block.
- `block_path`: A `"."` separated list of names of the blocks from the current block (not included) to the nested block of which the value will be passed to the operation.
- `operation`: An Operation class instance (extends `BaseBlockOperation`), which has an `apply` method for mapping values.
**Returns**:
Transformed value
#### map_struct_block_value
```python
def map_struct_block_value(struct_block_value, block_def, block_path,
**kwargs)
```
Maps each child block in a `StructBlock` value.
**Arguments**:
- `stream_block_value`: The value of the `StructBlock`, a dict of child blocks
- `block_def`: The definition of the `StructBlock`
- `block_path`: A `"."` separated list of names of the blocks from the current block (not included) to the nested block of which the value will be passed to the operation.
**Returns**:
- mapped_value: The value of the `StructBlock` after transforming its children.
#### map_list_block_value
```python
def map_list_block_value(list_block_value, block_def, block_path, **kwargs)
```
Maps each child block in a `ListBlock` value.
**Arguments**:
- `stream_block_value`: The value of the `ListBlock`, a list of child blocks
- `block_def`: The definition of the `ListBlock`
- `block_path`: A `"."` separated list of names of the blocks from the current block (not included) to the nested block of which the value will be passed to the operation.
**Returns**:
- mapped_value: The value of the `ListBlock` after transforming all the children.
#### apply_changes_to_raw_data
```python
def apply_changes_to_raw_data(raw_data, block_path_str, operation, streamfield,
**kwargs)
```
Applies changes to raw stream data
**Arguments**:
- `raw_data`: The current stream data (a list of top level blocks)
- `block_path_str`: A `"."` separated list of names of the blocks from the top level block to the nested block of which the value will be passed to the operation.
- `operation`: A subclass of `operations.BaseBlockOperation`. It will have the `apply` method for applying changes to the matching block values.
- `streamfield`: The `StreamField` for which data is being migrated. This is used to get the definitions of the blocks.
**Returns**:
altered_raw_data:
## Block paths
Operations for `StreamField` data migrations defined in `wagtail.blocks.migrations` require a “block path” to determine which blocks they should be applied to.
```default
block_path = "" | block_name ("." block_name)*
block_name = str
```
A block path is either:
- the empty string, in which case the operation should be applied to the top-level stream; or
- a `"."` (period) separated sequence of block names, where block names are the names given to the blocks in the `StreamField` definition.
Block names are the values associated with the `"type"` keys in the stream data’s dictionary structures. As such, traversing or selecting `ListBlock` members requires the use of the `"item"` block name.
The value that an operation’s `apply` method receives is the `"value"` member of the dict associated with the terminal block name in the block path.
For examples see [the tutorial](../../advanced_topics/streamfield_migrations.md#using-streamfield-migration-block-paths).
# demo_site.html.md
# Demo site
To create a new site on Wagtail, we recommend the `wagtail start` command in [Getting started](index.md). We also have a demo site, The Wagtail Bakery, which contains example page types and models. We recommend you use the demo site for testing during the development of Wagtail itself.
The source code and installation instructions can be found at [https://github.com/wagtail/bakerydemo](https://github.com/wagtail/bakerydemo).
# deployment.html.md
# Deploy your site
So far, you’ve been accessing your site locally. Now, it’s time to deploy it. Deployment makes your site publicly accessible on the Internet by hosting it on a production server.
To deploy your site, you’ll first need to choose a [hosting provider](../deployment/index.md#deployment-guide), then follow their Wagtail deployment guide.
If you’re unsure, we have a tutorial for using [Fly.io with Backblaze](../deployment/flyio.md).
---
Congratulations! You made it to the end of the tutorial!
## Where next
- Read the Wagtail [topics](../topics/index.md) and [reference](../reference/index.md) documentation
- Learn how to implement [StreamField](../topics/streamfield.md) for freeform page content
- Browse through the [advanced topics](../advanced_topics/index.md) section and read [third-party tutorials](../advanced_topics/third_party_tutorials.md)
# developing.html.md
# Setting up a development environment
Setting up a local copy of [the Wagtail git repository](https://github.com/wagtail/wagtail) is slightly more involved than running a release package of Wagtail, as it requires [Node.js](https://nodejs.org/) and npm for building JavaScript and CSS assets. (This is not required when running a release version, as the compiled assets are included in the release package.)
If you’re happy to develop on a local virtual machine, the [docker-wagtail-develop](https://github.com/wagtail/docker-wagtail-develop) and [vagrant-wagtail-develop](https://github.com/wagtail/vagrant-wagtail-develop) setup scripts are the fastest way to get up and running. They will provide you with a running instance of the [Wagtail Bakery demo site](https://github.com/wagtail/bakerydemo/), with the Wagtail and bakerydemo codebases available as shared folders for editing on your host machine.
You can also set up a cloud development environment that you can work with in a browser-based IDE using the [gitpod-wagtail-develop](https://github.com/wagtail/gitpod-wagtail-develop) project.
(Build scripts for other platforms would be very much welcomed - if you create one, please let us know via the [Slack workspace](https://github.com/wagtail/wagtail/wiki/Slack)!)
If you’d prefer to set up all the components manually, read on. These instructions assume that you’re familiar with using pip and [virtual environments](https://docs.python.org/3/tutorial/venv.html) to manage Python packages.
## Setting up the Wagtail codebase
The preferred way to install the correct version of Node is to use [Fast Node Manager (fnm)](https://github.com/Schniz/fnm), which will always align the version with the supplied `.nvmrc` file in the root of the project. To ensure you are running the correct version of Node, run `fnm install` from the project root.
Alternatively, you can install [Node.js](https://nodejs.org/) directly, ensure you install the version as declared in the project’s root `.nvmrc` file.
You will also need to install the **libjpeg** and **zlib** libraries, if you haven’t done so already - see Pillow’s [platform-specific installation instructions](https://pillow.readthedocs.io/en/stable/installation/building-from-source.html#external-libraries).
Fork [the Wagtail codebase](https://github.com/wagtail/wagtail) and clone the forked copy:
```sh
git clone https://github.com/username/wagtail.git
cd wagtail
```
**With your preferred [virtualenv activated](../getting_started/tutorial.md#virtual-environment-creation),** install the Wagtail package in development mode with the included testing and documentation dependencies:
```sh
pip install -e ."[testing,docs]" --config-settings editable-mode=strict -U
```
Or, on Windows
```doscon
pip install -e .[testing,docs] --config-settings editable-mode=strict -U
```
Install the tool chain for building static assets:
```sh
npm ci
```
Compile the assets:
```sh
npm run build
```
Any Wagtail sites you start up in this virtualenv will now run against this development instance of Wagtail. We recommend using the [Wagtail Bakery demo site](https://github.com/wagtail/bakerydemo/) as a basis for developing Wagtail. Keep in mind that the setup steps for a Wagtail site may include installing a release version of Wagtail, which will override the development version you’ve just set up. In this case, to install the local Wagtail development instance in your virtualenv for your Wagtail site:
```sh
pip install -e path/to/wagtail"[testing,docs]" --config-settings editable-mode=strict -U
```
Or, on Windows
```doscon
pip install -e path/to/wagtail[testing,docs] --config-settings editable-mode=strict -U
```
Here, `path/to/wagtail` is the path to your local Wagtail copy.
## Development on Windows
Documentation for development on Windows has some gaps and should be considered a work in progress. We recommend setting up on a local virtual machine using our already available scripts, [docker-wagtail-develop](https://github.com/wagtail/docker-wagtail-develop) or [vagrant-wagtail-develop](https://github.com/wagtail/vagrant-wagtail-develop)
If you are confident with Python and Node development on Windows and wish to proceed here are some helpful tips.
We recommend [Chocolatey](https://chocolatey.org/install) for managing packages in Windows. Once Chocolatey is installed you can then install the [`make`](https://community.chocolatey.org/packages/make) utility in order to run common build and development commands.
We use LF for our line endings. To effectively collaborate with other developers on different operating systems, use Git’s automatic CRLF handling by setting the `core.autocrlf` config to `true`:
```doscon
git config --global core.autocrlf true
```
## Testing
From the root of the Wagtail codebase, run the following command to run all the Python tests:
```sh
python runtests.py
```
### Running only some of the tests
At the time of writing, Wagtail has well over 5000 tests, which takes a while to
run. You can run tests for only one part of Wagtail by passing in the path as
an argument to `runtests.py` or `tox`:
```sh
# Running in the current environment
python runtests.py wagtail
# Running in a specified Tox environment
tox -e py39-dj32-sqlite-noelasticsearch -- wagtail
# See a list of available Tox environments
tox -l
```
You can also run tests for individual TestCases by passing in the path as
an argument to `runtests.py`
```sh
# Running in the current environment
python runtests.py wagtail.tests.test_blocks.TestIntegerBlock
# Running in a specified Tox environment
tox -e py39-dj32-sqlite-noelasticsearch -- wagtail.tests.test_blocks.TestIntegerBlock
```
### Running migrations for the test app models
You can create migrations for the test app by running the following from the Wagtail root.
```sh
django-admin makemigrations --settings=wagtail.test.settings
```
### Testing against PostgreSQL
#### NOTE
In order to run these tests, you must install the required modules for PostgreSQL as described in Django’s [Databases documentation](https://docs.djangoproject.com/en/stable/ref/databases/).
By default, Wagtail tests against SQLite. You can switch to using PostgreSQL by
using the `--postgres` argument:
```sh
python runtests.py --postgres
```
If you need to use a different user, password, host, or port, use the `PGUSER`, `PGPASSWORD`, `PGHOST`, and `PGPORT` environment variables respectively.
### Testing against a different database
#### NOTE
In order to run these tests, you must install the required client libraries and modules for the given database as described in Django’s [Databases documentation](https://docs.djangoproject.com/en/stable/ref/databases/) or the 3rd-party database backend’s documentation.
If you need to test against a different database, set the `DATABASE_ENGINE`
environment variable to the name of the Django database backend to test against:
```sh
DATABASE_ENGINE=django.db.backends.mysql python runtests.py
```
This will create a new database called `test_wagtail` in MySQL and run
the tests against it.
If you need to use different connection settings, use the following environment variables which correspond to the respective keys within Django’s [`DATABASES`](https://docs.djangoproject.com/en/stable/ref/settings/#std-setting-DATABASES) settings dictionary:
- `DATABASE_ENGINE`
- `DATABASE_NAME`
- `DATABASE_PASSWORD`
- `DATABASE_HOST`
- Note that for MySQL, this must be `127.0.0.1` rather than `localhost` if you need to connect using a TCP socket
- `DATABASE_PORT`
It is also possible to set `DATABASE_DRIVER`, which corresponds to the `driver` value within `OPTIONS` if an SQL Server engine is used.
### Testing Elasticsearch and OpenSearch
You can test Wagtail against Elasticsearch or OpenSearch by passing one of the arguments `--elasticsearch7`, `--elasticsearch8`, `--elasticsearch9`, `--opensearch2`, `--opensearch3` (corresponding to the version of Elasticsearch or OpenSearch you want to test against):
```sh
python runtests.py --elasticsearch8
```
Wagtail will attempt to connect to a local instance of Elasticsearch or OpenSearch
(`http://localhost:9200`) and use the index `test_wagtail`.
If your instance is located somewhere else, you can set the
`ELASTICSEARCH_URL` environment variable to point to its location:
```sh
ELASTICSEARCH_URL=https://my-elasticsearch-instance:9200 python runtests.py --elasticsearch8
```
Note that the environment variable `ELASTICSEARCH_URL` is used for both Elasticsearch and OpenSearch.
### Unit tests for JavaScript
We use [Jest](https://jestjs.io/) for unit tests of client-side business logic or UI components. From the root of the Wagtail codebase, run the following command to run all the front-end unit tests:
```sh
npm run test:unit
```
### Integration tests
Our end-to-end browser testing suite also uses [Jest](https://jestjs.io/), combined with [Puppeteer](https://pptr.dev/). We set this up to be installed separately so as not to increase the installation size of the existing Node tooling. To run the tests, you will need to install the dependencies and, in a separate terminal, run the test suite’s Django development server:
```sh
export DJANGO_SETTINGS_MODULE=wagtail.test.settings_ui
# Assumes the current environment contains a valid installation of Wagtail for local development.
./wagtail/test/manage.py migrate
./wagtail/test/manage.py createcachetable
DJANGO_SUPERUSER_EMAIL=admin@example.com DJANGO_SUPERUSER_USERNAME=admin DJANGO_SUPERUSER_PASSWORD=changeme ./wagtail/test/manage.py createsuperuser --noinput
./wagtail/test/manage.py runserver 0:8000
# In a separate terminal:
npm --prefix client/tests/integration install
npm run test:integration
```
Integration tests target `http://127.0.0.1:8000` by default. Use the `TEST_ORIGIN` environment variable to use a different port, or test a remote Wagtail instance: `TEST_ORIGIN=http://127.0.0.1:9000 npm run test:integration`.
## Compiling static assets
All static assets such as JavaScript, CSS, images, and fonts for the Wagtail admin are compiled from their respective sources by Webpack. The compiled assets are not committed to the repository, and are compiled before packaging each new release. Compiled assets should not be submitted as part of a pull request.
To compile the assets, run:
```sh
npm run build
```
This must be done after every change to the source files. To watch the source files for changes and then automatically recompile the assets, run:
```sh
npm start
```
## Compiling the documentation
The Wagtail documentation is built by Sphinx. To install Sphinx and compile the documentation, run:
```sh
# Starting from the wagtail root directory:
# Install the documentation dependencies
pip install -e .[docs] --config-settings editable-mode=strict
# or if using zsh as your shell:
# pip install -e '.[docs]' -U
# Compile the docs
cd docs/
make html
```
The compiled documentation will now be in `docs/_build/html`.
Open this directory in a web browser to see it.
Python comes with a module that makes it very easy to preview static files in a web browser.
To start this simple server, run the following commands:
```sh
# Starting from the wagtail root directory:
cd docs/_build/html/
python -m http.server 8080
```
Now you can open [http://localhost:8080/](http://localhost:8080/) in your web browser to see the compiled documentation.
Sphinx caches the built documentation to speed up subsequent compilations.
Unfortunately, this cache also hides any warnings thrown by unmodified documentation source files.
To clear the built HTML and start fresh, so you can see all warnings thrown when building the documentation, run:
```sh
# Starting from the wagtail root directory:
cd docs/
make clean
make html
```
Wagtail also provides a way for documentation to be compiled automatically on each change.
To do this, you can run the following command to see the changes automatically at `localhost:4000`:
```sh
# Starting from the wagtail root directory:
cd docs/
make livehtml
```
## Linting and formatting
Wagtail makes use of various tools to ensure consistency and readability across the codebase:
- [Ruff](https://github.com/astral-sh/ruff) for formatting and linting Python code, including enforcing [PEP8](https://peps.python.org/pep-0008/) and [isort](https://pycqa.github.io/isort/) rules
- [djhtml](https://github.com/rtts/djhtml) and [Curlylint](https://www.curlylint.org/) for formatting and linting HTML templates
- [Prettier](https://prettier.io/), [Stylelint](https://stylelint.io/) and [ESLint](https://eslint.org/) for formatting and linting JavaScript and CSS code
All contributions should follow these standards, and you are encouraged to run these tools locally to avoid delays in your contributions being accepted. Here are the available commands:
- `make lint` will run all linting, `make lint-server` lints Python and template code, and `make lint-client` lints JS/CSS.
- `make format` will run all formatting and fixing of linting issues. There is also `make format-server` and `make format-client`.
Have a look at our `Makefile` tasks and `package.json` scripts if you prefer more granular options.
### Automatically lint and code format on commits
[pre-commit](https://pre-commit.com/) is configured to automatically run code linting and formatting checks with every commit. To install pre-commit into your git hooks run:
```sh
pre-commit install
```
pre-commit should now run on every commit you make.
## Using forks for installation
Sometimes it may be necessary to install Wagtail from a fork. For example your site depends on a bug fix that is currently waiting for review, and you cannot afford waiting for a new release.
The Wagtail release process includes steps for static asset building and translations updated which means you cannot update your requirements file to point a particular git commit in the main repository.
To install from your fork, ensure you have installed `build` (`python -m pip install build`) and the tooling for building the static assets (`npm install`). Then, from the root of your Wagtail git checkout, run:
```sh
python -m build
```
This will create a `.tar.gz` and `.whl` packages within `dist/,` which can be installed with `pip`.
For remote deployments, it’s usually most convenient to upload this to a public URL somewhere and place that URL in your project’s requirements in place of the standard `wagtail` line.
# django-ninja.html.md
# How to set up Django Ninja
While Wagtail provides a [built-in API module](index.md#api) based on Django REST Framework, it is possible to use other API frameworks.
Here is information on usage with [Django Ninja](https://django-ninja.dev/), an API framework built on Python type hints and [Pydantic](https://docs.pydantic.dev/latest/), which includes built-in support for OpenAPI schemas.
## Basic configuration
Install `django-ninja`. Optionally you can also add `ninja` to your `INSTALLED_APPS` to avoid loading static files externally when using the OpenAPI documentation viewer.
### Create the API
We will create a new `api.py` module next to the existing `urls.py` file in the project root, instantiate the router.
```python
# api.py
from typing import Literal
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from ninja import Field, ModelSchema, NinjaAPI
from wagtail.models import Page
api = NinjaAPI()
```
Next, register the URLs so Django can route requests into the API. To test this is working, navigate to `/api/docs`, which displays the OpenAPI documentation (with no available endpoints yet).
```python
# urls.py
from .api import api
urlpatterns = [
...
path("api/", api.urls),
...
# Ensure that the api line appears above the default Wagtail page serving route
path("", include(wagtail_urls)),
]
```
### Our first endpoint
We will create a simple endpoint that returns a list of all pages in the site. We use the `@api.get` operation decorator to define what route the endpoint is available at, and how to format the response: here, using a custom schema we create.
```python
# api.py
class BasePageSchema(ModelSchema):
url: str = Field(None, alias="get_url")
class Config:
model = Page
model_fields = [
"id",
"title",
"slug",
]
@api.get("/pages/", response=list[BasePageSchema])
def list_pages(request: "HttpRequest"):
return Page.objects.live().public().exclude(id=1)
```
Our custom `BasePageSchema` combines two techniques: [schema generation from Django models](https://django-ninja.dev/guides/response/django-pydantic/), and calculated fields with [aliases](https://django-ninja.dev/guides/response/#aliases). Here, we use an alias to retrieve the page URL.
We can also add an extra `child_of: int = None` parameter to our endpoint to filter the pages by their parent:
```python
@api.get("/pages/", response=list[BasePageSchema])
def list_pages(request: "HttpRequest", child_of: int = None):
if child_of:
return get_object_or_404(Page, id=child_of).get_children().live().public()
# Exclude the page tree root.
return Page.objects.live().public().exclude(id=1)
```
Ninja treats every parameter of the `list_pages` function as a query parameter. It uses the provided type hint to parse the value, validate it, and generate the OpenAPI schema.
### Adding custom page fields
Next, let’s add a “detail” API endpoint to return a single page of a specific type. We can use the [path parameters](https://django-ninja.dev/guides/input/path-params/) from Ninja to retrieve our `page_id`.
We also create a new schema for a specific page type: here, `BlogPage`, with `BasePageSchema` as a base.
```python
from blog.models import BlogPage
class BlogPageSchema(BasePageSchema, ModelSchema):
class Config(BasePageSchema.Config):
model = BlogPage
model_fields = [
"intro",
]
@api.get("/pages/{page_id}/", response=BlogPageSchema)
def get_page(request: "HttpRequest", page_id: int):
return get_object_or_404(Page, id=page_id).specific
```
This works well, with the endpoint now returning generic `Page` fields and the `BlogPage` introduction.
But for sites where all page content is served via an API, it could become tedious to create new endpoints for every page type.
### Combining multiple schemas
To reflect that our response may return multiple page types, we use the type hint union syntax to combine multiple schemas.
This allows us to return different page types from the same endpoint.
Here is an example with an additional schema for our `HomePage` type:
```python
from home.models import HomePage
class HomePageSchema(BasePageSchema, ModelSchema):
class Config(BasePageSchema.Config):
model = HomePage
@api.get("/pages/{page_id}/", response=BlogPageSchema | HomePageSchema)
def get_page(request: "HttpRequest", page_id: int):
return get_object_or_404(Page, id=page_id).specific
```
With this in place, we are still missing a way to determine which of the schemas to use for a given page.
We want to do this by page type, adding an extra `content_type` class attribute annotation to our schemas.
- For `BasePageSchema`, we define `content_type: str`, as any page type can use this base.
- For `HomePageSchema`, we set `content_type: Literal["homepage"]`.
- And for `BlogPageSchema`, we set `content_type: Literal["blogpage"]`.
All we need now is to add a [resolver](https://django-ninja.dev/guides/response/#resolvers) calculated field to the `BasePageSchema`, to return the correct content type for each page. Here is the final version of `BasePageSchema`:
```python
class BasePageSchema(ModelSchema):
url: str = Field(None, alias="get_url")
content_type: str
@staticmethod
def resolve_content_type(page: Page) -> str:
return page.specific_class._meta.model_name
class Config:
model = Page
model_fields = [
"id",
"title",
"slug",
]
```
With this in place, Pydantic is able to validate the page data returned in `get_page` according to one of the schemas in the `response` union.
It then serializes the data according to the specific schema.
### Nested data
Where the page schema references data in separate models, rather than creating new endpoints, we can add the data directly to the page schema.
Here is an example, adding blog page authors (a snippet with a `ParentalManyToManyField`):
```python
class BlogPageSchema(BasePageSchema, ModelSchema):
content_type: Literal["blogpage"]
authors: list[str] = []
class Config(BasePageSchema.Config):
model = BlogPage
model_fields = [
"intro",
]
@staticmethod
def resolve_authors(page: BlogPage, context) -> list[str]:
return [author.name for author in page.authors.all()]
```
This could also be done with the `Field` class if the `BlogPage` class had a method to retrieve author names directly: `authors: list[str] = Field([], alias="get_author_names")`.
### Rich text in the API
Rich text fields in Wagtail use a specific internal format, described in [Rich text internals](../../extending/rich_text_internals.md). They can be added to the schema as `str`, but it’s often more useful for the API to provide a “display” representation, where references to pages and images are replaced with URLs.
This can also be done with [Ninja resolvers](https://django-ninja.dev/guides/response/#resolvers). Here is an example with the `HomePageSchema`:
```python
from wagtail.rich_text import expand_db_html
class HomePageSchema(BasePageSchema, ModelSchema):
content_type: Literal["homepage"]
body: str
class Config(BasePageSchema.Config):
model = HomePage
@staticmethod
def resolve_body(page: HomePage, context) -> str:
return expand_db_html(page.body)
```
Here, `body` is defined as a `str`, and the resolver uses the `expand_db_html` function to convert the internal representation to HTML.
### Images in the API
We can use a similar technique for images, combining resolvers and aliases to generate the data.
We use the [`get_renditions()` method](../images/renditions.md#image-renditions-multiple) to retrieve the formatted images, and a custom `RenditionSchema` to define their API representation.
```python
from wagtail.images.models import AbstractRendition
class RenditionSchema(ModelSchema):
# We need to use the Field / alias API for properties
url: str = Field(None, alias="file.url")
alt: str = Field(None, alias="alt")
class Config:
model = AbstractRendition
model_fields = [
"width",
"height",
]
```
On the `BlogPageSchema`, we define our image field as: `main_image: list[RenditionSchema] = []`. Then add the resolver for it:
```python
@staticmethod
def resolve_main_image(page: BlogPage) -> list[AbstractRendition]:
filters = [
"fill-800x600|format-webp",
"fill-800x600",
]
if image := page.main_image():
return image.get_renditions(*filters).values()
return []
```
In JSON, our `main_image` is now represented as an array, where each item is an object with `url`, `alt`, `width`, and `height` properties.
## OpenAPI documentation
Django Ninja generates [OpenAPI](https://swagger.io/specification/) documentation automatically, based on the defined operations and schemas.
This also includes a documentation viewer, with support to try out the API directly from the browser. With the above example, you can try accessing the docs at `/api/docs`.
To make the most of this capability, consider the supported [operations parameters](https://django-ninja.dev/reference/operations-parameters/).
# documentation_guidelines.html.md
# Documentation guidelines
> * [Writing style guide](#writing-style-guide)
> * [Formatting recommendations](#formatting-recommendations)
> * [Formatting to avoid](#formatting-to-avoid)
> * [Code example considerations](#code-example-considerations)
## Writing style guide
To ensure consistency in tone and language, follow the [Google developer documentation style guide](https://developers.google.com/style) when writing the Wagtail documentation.
## Formatting recommendations
Wagtail’s documentation uses a mixture of [Markdown (with MyST)](https://myst-parser.readthedocs.io/en/stable/syntax/typography.html#syntax-core) and [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-primer). We encourage writing documentation in Markdown first, and only reaching for more advanced reStructuredText formatting if there is a compelling reason. Docstrings in Python code must be written in reStructuredText, as using Markdown is not yet supported.
Here are formats we encourage using when writing documentation for Wagtail.
### Paragraphs
It all starts here.
Keep your sentences short, varied in length.
Separate text with an empty line to create a new paragraph.
### Latin phrases and abbreviations
Try to avoid Latin phrases (such as `ergo` or `de facto`) and abbreviations (such as `i.e.` or `e.g.`), and use common English phrases instead. Alternatively, find a simpler way to communicate the concept or idea to the reader. The exception is `etc.` which can be used when space is limited.
Examples:
| Don’t use this | Use this instead |
|------------------|----------------------|
| e.g. | for example, such as |
| i.e. | that is |
| viz. | namely |
| ergo | therefore |
### Heading levels
Use heading levels to create sections, and allow users to link straight to a specific section. Start documents with an `# h1`, and proceed with `## h2` and further sub-sections without skipping levels.
```md
# Heading level 1
## Heading level 2
### Heading level 3
```
### Lists
Use bullets for unordered lists, numbers when ordered. Prefer dashes `-` for bullets. Nest by indenting with 4 spaces.
```md
- Bullet 1
- Bullet 2
- Nested bullet 2
- Bullet 3
1. Numbered list 1
2. Numbered list 2
3. Numbered list 3
```
Rendered output
- Bullet 1
- Bullet 2
- Nested bullet 2
- Bullet 3
1. Numbered list 1
2. Numbered list 2
3. Numbered list 3
### Inline styles
Use **bold** and *italic* sparingly and inline `code` when relevant.
```md
Use **bold** and _italic_ sparingly and inline `code` when relevant.
```
Keep in mind that in reStructuredText, italic is written with `*`, and inline code must be written with double backticks, like ```code```.
```rst
Use **bold** and *italic* sparingly and inline ``code`` when relevant.
```
### Code blocks
Make sure to include the correct language code for syntax highlighting, and to format your code according to our coding guidelines. Frequently used: `python`, `css`, `html`, `html+django`, `javascript`, `sh`.
```md
```python
INSTALLED_APPS = [
...
"wagtail",
...
]
```
```
Rendered output
```python
INSTALLED_APPS = [
...
"wagtail",
...
]
```
#### When using console (terminal) code blocks
#### NOTE
`$` or `>` prompts are not needed, this makes it harder to copy and paste the lines and can be difficult to consistently add in every single code snippet.
Use `sh` as it has better support for comment and code syntax highlighting in MyST’s parser, plus is more compatible with GitHub and VSCode.
```md
```sh
# some comment
some command
```
```
Rendered output
```sh
# some comment
some command
```
Use `doscon` (DOS Console) only if explicitly calling out Windows commands alongside their bash equivalent.
```md
```doscon
# some comment
some command
```
```
Rendered output
```doscon
# some comment
some command
```
#### Code blocks that contain triple backticks
You can use three or more backticks to create code blocks. If you need to include triple backticks in a code block, you can use a different number of backticks to wrap the code block. This is useful when you need to demonstrate a Markdown code block, such as in the examples above.
```md
````md
```python
print("Hello, world!")
```
````
```
Rendered output
```md
```python
print("Hello, world!")
```
```
### Links
Links are fundamental in documentation. Use internal links to tie your content to other docs, and external links as needed. Pick relevant text for links, so readers know where they will land.
Do not let external links hide critical context for the reader. Instead, provide the core information on the page and use links for added context.
```md
An [external link](https://www.example.com).
An [internal link to another document](/reference/contrib/legacy_richtext).
An auto generated link label to a page [](/getting_started/tutorial).
A [link to a target](register_reports_menu_item).
Do not use [click here](https://www.example.com) as the link's text, use a more descriptive label.
Do not rely on links for critical context, like [why it is important](https://www.example.com).
```
Rendered output
An [external link](https://www.example.com).
An [internal link to another document](../reference/contrib/legacy_richtext.md).
An auto generated link label to a page [Your first Wagtail site](../getting_started/tutorial.md).
A [link to a target](../reference/hooks.md#register-reports-menu-item).
Do not use [click here](https://www.example.com) as the link’s text, use a more descriptive label.
Do not rely on links for critical context, like [why it is important](https://www.example.com).
#### Anchor links
Anchor links point to a specific target on a page. They rely on the page having the target created. Each target must have a unique name and should use the `lower_snake_case` format. A target can be added as follows:
```md
(my_awesome_section)=
##### Some awesome section title
...
```
The target can be linked to, with an optional label, using the Markdown link syntax as follows:
```md
- Auto generated label (preferred) [](my_awesome_section)
- [label for section](my_awesome_section)
```
Rendered output:
##### Some awesome section title
…
- Auto generated label (preferred) [Some awesome section title](#my-awesome-section)
- [label for section](#my-awesome-section)
You can read more about other methods of linking to, and creating references in the MyST-Parser docs section on [Cross-references](https://myst-parser.readthedocs.io/en/stable/syntax/cross-referencing.html).
#### Intersphinx links (external docs)
Due to the large amount of links to external documentation (especially Django), we have added the integration via intersphinx references. This is configured via [`intersphinx_mapping`](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping) in the `docs/conf.py` file. This allows you to link to specific sections of a project’s documentation and catch warnings when the target is no longer available.
Markdown example:
```md
You can select widgets from [Django's form widgets](inv:django#ref/forms/widgets).
```
reStructuredText example:
```rst
You can select widgets from :doc:`Django form widget `.
```
The format for a Sphinx link in Markdown is `inv:key:domain:type#name`. The `key`, `domain`, and `type` are optional, but are recommended to avoid ambiguity when there are multiple targets with the same `name`.
If the `name` contains a space, you need to wrap the whole link in angle brackets `<>`.
```md
See Django's docs on []().
```
Rendered output
See Django’s docs on [Template fragment caching](https://docs.djangoproject.com/en/stable/topics/cache/#template-fragment-caching).
##### Find the right intersphinx target
The intersphinx target for a specific anchor you want to link to may not be obvious. You can use the `myst-inv` command line tool from MyST-Parser and save the output as a JSON or YAML file to get a visual representation of the available targets.
```sh
myst-inv https://docs.djangoproject.com/en/stable/_objects/ --format=json > django-inv.json
```
Using the output from `myst-inv`, you can follow the tree structure under the `objects` key to build the link target. Some text editors such as VSCode can show you the breadcrumbs to the target as you navigate the file.
Other tools are also available to help you explore Sphinx inventories, such as [sphobjinv](https://github.com/bskinn/sphobjinv) and Sphinx’s built-in `sphinx.ext.intersphinx` extension.
```sh
sphobjinv suggest "https://docs.djangoproject.com/en/stable/_objects/" 'template fragment caching' -su
python -m sphinx.ext.intersphinx https://docs.djangoproject.com/en/stable/_objects/
```
In some cases, a more specific target may be available in the documentation. However, you may need to inspect the source code of the page to find it.
For example, the above section on Django’s docs can also be linked via the type `templatetag` and the name `cache`.
```md
See Django's docs on the [](inv:django:std:templatetag#cache) tag.
```
Rendered output
See Django’s docs on the [`cache`](https://docs.djangoproject.com/en/stable/topics/cache/#std-templatetag-cache) tag.
Note that while the link takes you to the same section, the URL hash and the default text will be different. If you use a custom text, this may not make a difference to the reader. However, they are semantically different.
Use the first approach (with the `label` type) when you are linking in the context of documentation in general, such as a guide on how to do caching. Use the second approach (with the `templatetag` type) when you are linking in the context of writing code, such as the use of the `{% cache %}` template tag. The second approach is generally preferred when writing docstrings.
#### Absolute links
Sometimes, there are sections in external docs that do not have a Sphinx target attached at all. Before linking to such sections, consider linking to the nearest target before that section. If there is one available that is close enough such that your intended section is immediately visible upon clicking the link, use that. Otherwise, you can write it as a full URL. Remember to use the `stable` URL and not a specific version.
A common example of using full URLs over intersphinx links is when linking to sections in Django’s release notes.
```md
`DeleteView` has been updated to align with [Django 4.0's `DeleteView` implementation](https://docs.djangoproject.com/en/stable/releases/4.0/#deleteview-changes).
```
Rendered output
`DeleteView` has been updated to align with [Django 4.0’s `DeleteView` implementation](https://docs.djangoproject.com/en/stable/releases/4.0/#deleteview-changes).
For external links to websites with no intersphinx mapping, always use the `https://` scheme.
Absolute links are also preferred for one-off links to external docs, even if they have a Sphinx object inventory. Once there are three or more links to the same project, consider adding an intersphinx mapping if possible.
#### Code references
When linking to code references, you can use Sphinx’s [reference roles](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html).
Markdown example:
```md
The {class}`~django.db.models.JSONField` class lives in the {mod}`django.db.models.fields.json` module,
but it can be imported from the {mod}`models ` module directly.
For more info, see {ref}`querying-jsonfield`.
```
reStructuredText example:
```rst
The :class:`~django.db.models.JSONField` class lives in the :mod:`django.db.models.fields.json` module,
but it can be imported from the :mod:`models ` module directly.
For more info, see :ref:`querying-jsonfield`.
```
Rendered output
The [`JSONField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.JSONField) class lives in the [`django.db.models.fields.json`](https://docs.djangoproject.com/en/stable/topics/db/queries/#module-django.db.models.fields.json) module,
but it can be imported from the [`models`](https://docs.djangoproject.com/en/stable/topics/db/models/#module-django.db.models) module directly.
For more info, see [Querying JSONField](https://docs.djangoproject.com/en/stable/topics/db/queries/#querying-jsonfield).
Adding `~` before the dotted path will shorten the link text to just the final part (the object name). This can be useful when the full path is already mentioned in the text. You can also set the current scope of the documentation with a [`module`](https://www.sphinx-doc.org/en/master/usage/domains/python.html#directive-py-module) or [`currentmodule`](https://www.sphinx-doc.org/en/master/usage/domains/python.html#directive-py-currentmodule) directive to avoid writing the full path to every object.
```md
```{currentmodule} wagtail.admin.viewsets.model
```
The {class}`ModelViewSet` class extends the {class}`~wagtail.admin.viewsets.base.ViewSet` class.
```
Rendered output
The [`ModelViewSet`](../reference/viewsets.md#wagtail.admin.viewsets.model.ModelViewSet) class extends the [`ViewSet`](../reference/viewsets.md#wagtail.admin.viewsets.base.ViewSet) class.
A reference role can also define how it renders itself. In the above examples, the [`class`](https://www.sphinx-doc.org/en/master/usage/domains/python.html#role-py-class) and [`mod`](https://www.sphinx-doc.org/en/master/usage/domains/python.html#role-py-mod) roles are rendered as an inline code with link, but the [`ref`](https://www.sphinx-doc.org/en/master/usage/referencing.html#role-ref) role is rendered as a plain link.
These features make reference roles particularly useful when writing reference-type documentation and docstrings.
Aside from using reference roles, you can also use the link syntax. Unlike reference roles, the link syntax requires the full path to the object and it allows you to customize the link label. This can be useful when you want to avoid the reference role’s default rendering, for example to mix inline code and plain text as the link label.
```md
For more details on how to query the [`JSONField` model field](django.db.models.JSONField),
see [the section about querying `JSONField`](inv:django#querying-jsonfield).
```
Rendered output
For more details on how to query the [`JSONField` model field](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.JSONField),
see [the section about querying `JSONField`](https://docs.djangoproject.com/en/stable/topics/db/queries/#querying-jsonfield).
### Note and warning call-outs
Use notes and warnings sparingly to get the reader’s attention when needed. These can be used to provide additional context or to warn about potential issues.
```none
```{note}
Notes can provide complementary information.
```
```{warning}
Warnings can be scary.
```
```
Rendered output
#### NOTE
Notes can provide complementary information.
#### WARNING
Warnings can be scary.
These call-outs do not support titles, so be careful not to include them, titles will just be moved to the body of the call-out.
```none
```{note} Title's here will not work correctly
Notes can provide complementary information.
```
```
### Images
Images are hard to keep up-to-date as documentation evolves, but can be worthwhile nonetheless. Here are guidelines when adding images:
- All images should have meaningful [alt text](https://axesslab.com/alt-texts/) unless they are decorative.
- Images are served as-is – pick the correct format, and losslessly compress all images.
- Use absolute paths for image files so they are more portable.
```md

```
Rendered output

### Docstrings and API reference (autodoc)
With its [autodoc](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#ext-autodoc) feature, Sphinx supports writing documentation in Python docstrings for subsequent integration in the project’s documentation pages. This is a very powerful feature that we highly recommend using to document Wagtail’s APIs.
Modules, classes, and functions can be documented with docstrings. Class and instance attributes can be documented with docstrings (with triple quotes `"""`) or doc comments (with hash-colon `#:`). Docstrings are preferred, as they have better integration with code editors. Docstrings in Python code must be written in reStructuredText syntax.
```py
SLUG_REGEX = r'^[-a-zA-Z0-9_]+$'
"""Docstring for module-level variable ``SLUG_REGEX``."""
class Foo:
"""Docstring for class ``Foo``."""
bar = 1
"""Docstring for class attribute ``Foo.bar``."""
#: Doc comment for class attribute ``Foo.baz``.
#: It can have multiple lines, and each line must start with ``#:``.
#: Note that it is written before the attribute.
#: While Sphinx supports this, it is not recommended.
baz = 2
def __init__(self):
self.spam = 4
"""Docstring for instance attribute ``spam``."""
```
The autodoc extension provides many directives to document Python code, such as [`autoclass`](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoclass), [`autofunction`](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autofunction), [`automodule`](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-automodule), along with different options to customize the output. In Markdown files, these directives need to be wrapped in an `eval-rst` directive. As with docstrings, everything inside the `eval-rst` block must be written in reStructuredText syntax.
You can mix automatic and non-automatic documentation. For example, you can use [`module`](https://www.sphinx-doc.org/en/master/usage/domains/python.html#directive-py-module) instead of `automodule` and write the module’s documentation in the `eval-rst` block, but still use `autoclass` and `autofunction` for classes and functions. Using automatic documentation is recommended, as it reduces the risk of inconsistencies between the code and the documentation, and it provides better integration with code editors.
```none
```{eval-rst}
.. module:: wagtail.coreutils
Wagtail's core utilities.
.. autofunction:: cautious_slugify
:no-index:
```
```
Rendered output
Wagtail’s core utilities.
### wagtail.coreutils.cautious_slugify(value)
Convert a string to ASCII exactly as Django’s slugify does, with the exception
that any non-ASCII alphanumeric characters (that cannot be ASCIIfied under Unicode
normalisation) are escaped into codes like ‘u0421’ instead of being deleted entirely.
This ensures that the result of slugifying (for example - Cyrillic) text will not be an empty
string, and can thus be safely used as an identifier (albeit not a human-readable one).
For more details on the available directives and options, refer to [Directives](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#autodoc-directives) and [The Python Domain](https://www.sphinx-doc.org/en/master/usage/domains/python.html) in Sphinx’s documentation.
### Tables
Only use tables when needed, using the [GitHub Flavored Markdown table syntax](https://github.github.com/gfm/#tables-extension-).
```md
| Browser | Device/OS |
| ------------- | --------- |
| Stock browser | Android |
| IE | Desktop |
| Safari | Windows |
```
Rendered output
| Browser | Device/OS |
|---------------|-------------|
| Stock browser | Android |
| IE | Desktop |
| Safari | Windows |
### Tables of contents
The [`toctree` and `contents` directives](https://myst-parser.readthedocs.io/en/stable/syntax/organising_content.html#organising-content) can be used to render tables of contents.
```none
```{toctree}
---
maxdepth: 2
titlesonly:
---
getting_started/index
topics/index
```
```{contents}
---
local:
depth: 1
---
```
```
### Version added, changed, deprecations
Sphinx offers release-metadata directives to present information about new or updated features in a consistent manner.
```none
```{versionadded} 2.15
The `WAGTAIL_NEW_SETTING` setting was added.
```
```{versionchanged} 2.15
The `WAGTAIL_OLD_SETTING` setting was deprecated.
```
```
Rendered output
#### Versionadded
Added in version 2.15: The `WAGTAIL_NEW_SETTING` setting was added.
#### Versionchanged
Changed in version 2.15: The `WAGTAIL_OLD_SETTING` setting was deprecated.
These directives will typically be removed two releases after they are added, so they should only be used for short-lived information, such as “The `WAGTAILIMAGES_CACHE_DURATION` setting was added”. Detailed documentation about the feature should be in the main body of the text, outside of the directive.
### Progressive disclosure
We can add supplementary information in documentation with the HTML `` element. This relies on HTML syntax, which can be hard to author consistently, so keep this type of formatting to a minimum.
```html
Supplementary information
This will be visible when expanding the content.
```
Example:
Supplementary information
This will be visible when expanding the content.
## Formatting to avoid
There is some formatting in the documentation which is technically supported, but we recommend avoiding unless there is a clear necessity.
### Call-outs
We only use `{note}` and `{warning}` call-outs. Avoid `{admonition}`, `{important}`, `{topic}`, and `{tip}`. If you find one of these, please replace it with `{note}`.
### Glossary
Sphinx glossaries (`.. glossary::`) generate definition lists. Use plain bullet or number lists instead, or sections with headings, or a table.
### Comments
Avoid source comments in committed documentation.
### Figure
reStructuredText figures (`.. figure::`) only offer very marginal improvements over vanilla images. If your figure has a caption, add it as an italicized paragraph underneath the image.
### Other reStructuredText syntax and Sphinx directives
We generally want to favor Markdown over reStructuredText, to make it as simple as possible for newcomers to make documentation contributions to Wagtail. Always prefer Markdown, unless the document’s formatting highly depends on reStructuredText syntax.
If you want to use a specific Sphinx directive, consult with core contributors to see whether its usage is justified, and document its expected usage on this page.
### Markdown in reStructuredText
Conversely, do not use Markdown syntax in places where reStructuredText is required. A common mistake is writing Markdown-style inline ``code`` (with single backticks) inside Python code docstrings and inside `eval-rst` directives. This is not supported and will not render correctly.
### Arbitrary HTML
While our documentation tooling offers some support for embedding arbitrary HTML, this is frowned upon. Only do so if there is a necessity, and if the formatting is unlikely to need updates.
## Code example considerations
When including code examples, particularly JavaScript or embedded HTML, it’s important to follow best practices for security, accessibility and approaches that make it easier to understand the example.
These are not hard rules but rather considerations to make when writing example code.
### Reference example filename
At the start of a code snippet, it can be helpful to reference an example filename at the top. For example: `# wagtail_hooks.py` or `// js/my-custom.js`.
### CSP (Content Security Policy) compliance
When adding JavaScript from external sources or custom scripts, ensure CSP compliance to prevent security vulnerabilities like cross-site scripting (XSS).
Avoid `mark_safe` where possible, and use `format_html` and use examples that load external files to manage scripts securely instead of inline `',
((static(filename),) for filename in js_files),
)
# Replace the default `FieldPanel` for `search_description`
# with a custom one that uses the `summarize` controller.
Page.promote_panels[0].args[0][-1] = FieldPanel(
"search_description",
attrs={
"data-controller": "summarize",
"data-summarize-input-value": "[name='search_description']",
},
)
```
# embeds.html.md
# Embedded content
Wagtail supports generating embed code from URLs to content on external
providers such as YouTube or Reddit. By default, Wagtail will fetch the embed
code directly from the relevant provider’s site using the oEmbed protocol.
Wagtail has a built-in list of the most common providers and this list can be
changed [with a setting](#customizing-embed-providers). Wagtail also supports
fetching embed code using [Embedly](#embedly) and [custom embed finders](#custom-embed-finders).
## Embedding content on your site
Wagtail’s embeds module should work straight out of the box for most providers.
You can use any of the following methods to call the module:
### Rich text
Wagtail’s default rich text editor has a “media” icon that allows embeds to be
placed into rich text. You don’t have to do anything to enable this; just make
sure the rich text field’s content is being passed through the `|richtext`
filter in the template as this is what calls the embeds module to fetch and
nest the embed code.
### `EmbedBlock` StreamField block type
The `EmbedBlock` block type allows embeds
to be placed into a `StreamField`.
The `max_width` and `max_height` arguments are sent to the provider when fetching the embed code.
For example:
```python
from wagtail.embeds.blocks import EmbedBlock
class MyStreamField(blocks.StreamBlock):
...
embed = EmbedBlock(max_width=800, max_height=400)
```
### `{% embed %}` tag
Syntax: `{% embed [max_width=] %}`
You can nest embeds into a template by passing the URL and an optional
`max_width` argument to the `{% embed %}` tag.
The `max_width` argument is sent to the provider when fetching the embed code.
```html+django
{% load wagtailembeds_tags %}
{# Embed a YouTube video #}
{% embed 'https://www.youtube.com/watch?v=Ffu-2jEdLPw' %}
{# This tag can also take the URL from a variable #}
{% embed page.video_url %}
```
### From Python
You can also call the internal `get_embed` function that takes a URL string
and returns an `Embed` object (see model documentation below). This also
takes a `max_width` keyword argument that is sent to the provider when
fetching the embed code.
```python
from wagtail.embeds.embeds import get_embed
from wagtail.embeds.exceptions import EmbedException
try:
embed = get_embed('https://www.youtube.com/watch?v=Ffu-2jEdLPw')
print(embed.html)
except EmbedException:
# Cannot find embed
pass
```
## Configuring embed “finders”
Embed finders are the modules within Wagtail that are responsible for producing
embed code from a URL.
Embed finders are configured using the `WAGTAILEMBEDS_FINDERS` setting. This
is a list of finder configurations that are each run in order until one of them
successfully returns an embed:
The default configuration is:
```python
WAGTAILEMBEDS_FINDERS = [
{
'class': 'wagtail.embeds.finders.oembed'
}
]
```
### oEmbed (default)
The default embed finder fetches the embed code directly from the content
provider using the oEmbed protocol. Wagtail has a built-in list of providers
which are all enabled by default. You can find that provider list at the
following link:
[https://github.com/wagtail/wagtail/blob/main/wagtail/embeds/oembed_providers.py](https://github.com/wagtail/wagtail/blob/main/wagtail/embeds/oembed_providers.py)
#### Customizing the provider list
You can limit which providers may be used by specifying the list of providers
in the finder configuration.
For example, this configuration will only allow content to be nested from Vimeo
and Youtube. It also adds a custom provider:
```python
from wagtail.embeds.oembed_providers import youtube, vimeo
# Add a custom provider
# Your custom provider must support oEmbed for this to work. You should be
# able to find these details in the provider's documentation.
# - 'endpoint' is the URL of the oEmbed endpoint that Wagtail will call
# - 'urls' specifies which patterns
my_custom_provider = {
'endpoint': 'https://customvideosite.com/oembed',
'urls': [
'^http(?:s)?://(?:www\\.)?customvideosite\\.com/[^#?/]+/videos/.+$',
]
}
WAGTAILEMBEDS_FINDERS = [
{
'class': 'wagtail.embeds.finders.oembed',
'providers': [youtube, vimeo, my_custom_provider],
}
]
```
#### Customizing an individual provider
Multiple finders can be chained together. This can be used for customizing the
configuration for one provider without affecting the others.
For example, this is how you can instruct YouTube to return videos in HTTPS
(which must be done explicitly for YouTube):
```python
from wagtail.embeds.oembed_providers import youtube
WAGTAILEMBEDS_FINDERS = [
# Fetches YouTube videos but puts ``?scheme=https`` in the GET parameters
# when calling YouTube's oEmbed endpoint
{
'class': 'wagtail.embeds.finders.oembed',
'providers': [youtube],
'options': {'scheme': 'https'}
},
# Handles all other oEmbed providers the default way
{
'class': 'wagtail.embeds.finders.oembed',
}
]
```
#### How Wagtail uses multiple finders
If multiple providers can handle a URL (for example, a YouTube video was
requested using the configuration above), the topmost finder is chosen to
perform the request.
Wagtail will not try to run any other finder, even if the chosen one doesn’t
return an embed.
### Facebook and Instagram
As of October 2020, Meta deprecated their public oEmbed APIs. If you would
like to embed Facebook or Instagram posts in your site, you will need to
use the new authenticated APIs. This requires you to set up a Meta
Developer Account and create a Facebook App that includes the *oEmbed Product*.
Instructions for creating the necessary app are in the requirements sections of the
[Facebook](https://developers.facebook.com/docs/plugins/oembed)
and [Instagram](https://developers.facebook.com/docs/instagram/oembed) documentation.
As of June 2021, the *oEmbed Product* has been replaced with the *oEmbed Read*
feature. In order to embed Facebook and Instagram posts your app must activate
the *oEmbed Read* feature. Furthermore, the app must be reviewed and accepted
by Meta. You can find the announcement in the
[API changelog](https://developers.facebook.com/docs/graph-api/changelog/version11.0/#oembed).
Apps that activated the oEmbed Product before June 8, 2021 need to activate
the oEmbed Read feature and review their app before September 7, 2021.
Once you have your app access tokens (App ID and App Secret), add the Facebook and/or
Instagram finders to your `WAGTAILEMBEDS_FINDERS` setting and configure them with
the App ID and App Secret from your app:
```python
WAGTAILEMBEDS_FINDERS = [
{
'class': 'wagtail.embeds.finders.facebook',
'app_id': 'YOUR FACEBOOK APP_ID HERE',
'app_secret': 'YOUR FACEBOOK APP_SECRET HERE',
},
{
'class': 'wagtail.embeds.finders.instagram',
'app_id': 'YOUR INSTAGRAM APP_ID HERE',
'app_secret': 'YOUR INSTAGRAM APP_SECRET HERE',
},
# Handles all other oEmbed providers the default way
{
'class': 'wagtail.embeds.finders.oembed',
}
]
```
By default, Facebook and Instagram embeds include some JavaScript that is necessary to
fully render the embed. In certain cases, this might not be something you want - for
example, if you have multiple Facebook embeds, this would result in multiple script tags.
By passing `'omitscript': True` in the configuration, you can indicate that these script
tags should be omitted from the embed HTML. Note that you will then have to take care of
loading this script yourself.
### Embed.ly
[Embed.ly](https://embed.ly) is a paid-for service that can also provide
embeds for sites that do not implement the oEmbed protocol.
They also provide some helpful features such as giving embeds a consistent look
and a common video playback API which is useful if your site allows videos to
be hosted on different providers and you need to implement custom controls for
them.
Wagtail has built in support for fetching embeds from Embed.ly. To use it,
first pip install the `Embedly` [python package](https://pypi.org/project/Embedly/).
Now add an embed finder to your `WAGTAILEMBEDS_FINDERS` setting that uses the
`wagtail.embeds.finders.oembed` class and pass it your API key:
```python
WAGTAILEMBEDS_FINDERS = [
{
'class': 'wagtail.embeds.finders.embedly',
'key': 'YOUR EMBED.LY KEY HERE'
}
]
```
### Custom embed finder classes
For complete control, you can create a custom finder class.
Here’s a stub finder class that could be used as a skeleton; please read the
docstrings for details of what each method does:
```python
from wagtail.embeds.finders.base import EmbedFinder
class ExampleFinder(EmbedFinder):
def __init__(self, **options):
pass
def accept(self, url):
"""
Returns True if this finder knows how to fetch an embed for the URL.
This should not have any side effects (no requests to external servers)
"""
pass
def find_embed(self, url, max_width=None):
"""
Takes a URL and max width and returns a dictionary of information about the
content to be used for embedding it on the site.
This is the part that may make requests to external APIs.
"""
# TODO: Perform the request to your embed provider's API
# Parse the response and return the embed information
return {
'title': "Title of the content",
'author_name': "Author name",
'provider_name': "Provider name (such as YouTube, Vimeo, etc)",
'type': "Either 'photo', 'video', 'link' or 'rich'",
'thumbnail_url': "URL to thumbnail image",
'width': width_in_pixels,
'height': height_in_pixels,
'html': "The Embed HTML ",
}
```
Once you’ve implemented all of those methods, you just need to add it to your
`WAGTAILEMBEDS_FINDERS` setting:
```python
WAGTAILEMBEDS_FINDERS = [
{
'class': 'path.to.your.finder.class.here',
# Any other options will be passed as kwargs to the __init__ method
}
]
```
## The `Embed` model
#### *class* wagtail.embeds.models.Embed
Embeds are fetched only once and stored in the database so subsequent requests
for an individual embed do not hit the embed finders again.
#### url
(text)
The URL of the original content of this embed.
#### max_width
(integer, nullable)
The max width that was requested.
#### type
(text)
The type of the embed. This can be either ‘video’, ‘photo’, ‘link’ or ‘rich’.
#### html
(text)
The HTML content of the embed that should be placed on the page
#### title
(text)
The title of the content that is being embedded.
#### author_name
(text)
The author’s name of the content that is being embedded.
#### provider_name
(text)
The provider name of the content that is being embedded.
For example: YouTube, Vimeo
#### thumbnail_url
(text)
a URL to a thumbnail image of the content that is being embedded.
#### width
(integer, nullable)
The width of the embed (images and videos only).
#### height
(integer, nullable)
The height of the embed (images and videos only).
#### last_updated
(datetime)
The Date/time when this embed was last fetched.
### Deleting embeds
As long as your embeds configuration is not broken, deleting items in the
`Embed` model should be perfectly safe to do. Wagtail will automatically
repopulate the records that are being used on the site.
You may want to do this if you’ve changed from oEmbed to Embedly or vice-versa
as the embed code they generate may be slightly different and lead to
inconsistency on your site.
In general, whenever you make changes to embed settings you are recommended to clear out Embed objects using [`purge_embeds` command](../reference/management_commands.md#purge-embeds).
# extending_client_side.html.md
# Extending client-side behavior
#### NOTE
This document provides an overview of how to extend Wagtail’s client-side behavior. For a more detailed API reference of Wagtail’s JavaScript components, see [JavaScript components](../reference/ui/client.md#javascript-components).
Many kinds of common customizations can be done without reaching into JavaScript, but depending on what parts of the client-side interaction you want to leverage or customize, you may need to employ React, Stimulus, or plain (vanilla) JS.
[React](https://reactjs.org/) is used for more complex parts of Wagtail, such as the sidebar, commenting system, and the Draftail rich-text editor.
For basic JavaScript-driven interaction, Wagtail is migrating towards [Stimulus](https://stimulus.hotwired.dev/).
You don’t need to know or use these libraries to add your custom behavior to elements, and in many cases, simple JavaScript will work fine, but Stimulus is the recommended approach for more complex use cases.
You don’t need to have Node.js tooling running for your custom Wagtail installation for many customizations built on these libraries, but in some cases, such as building packages, it may make more complex development easier.
#### NOTE
Avoid using jQuery and undocumented jQuery plugins, as they will be removed in a future version of Wagtail.
## Adding custom JavaScript
Within Wagtail’s admin interface, there are a few ways to add JavaScript.
The simplest way is to add global JavaScript files via hooks, see [insert_editor_js](../reference/hooks.md#insert-editor-js) and [insert_global_admin_js](../reference/hooks.md#insert-global-admin-js).
For JavaScript added when a specific Widget is used you can add an inner `Media` class to ensure that the file is loaded when the widget is used, see [Django’s docs on their form `Media` class](https://docs.djangoproject.com/en/stable/topics/forms/media/#assets-as-a-static-definition).
In a similar way, Wagtail’s [Template components](template_components.md) provide a `media` property or `Media` class to add scripts when rendered.
These will ensure the added files are used in the admin after the core JavaScript admin files are already loaded.
## Extending with DOM events
When approaching client-side customizations or adopting new components, try to keep the implementation simple first, you may not need any knowledge of Stimulus, React, JavaScript Modules, or a build system to achieve your goals.
The simplest way to attach behavior to the browser is via [DOM Events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) and plain (vanilla) JavaScript.
### Wagtail’s custom DOM events
Wagtail supports some custom behavior via listening or dispatching custom DOM events.
- See [Images title generation on upload](../advanced_topics/images/title_generation_on_upload.md#images-title-generation-on-upload).
- See [Documents title generation on upload](../advanced_topics/documents/title_generation_on_upload.md#docs-title-generation-on-upload).
- See [`InlinePanel` DOM events](../reference/panels.md#inline-panel-events).
## Extending with Stimulus
Wagtail uses [Stimulus](https://stimulus.hotwired.dev/) as a way to provide lightweight client-side interactivity or custom JavaScript widgets within the admin interface.
The key benefit of using Stimulus is that your code can avoid the need for manual initialization when widgets appear dynamically, such as within modals, `InlinePanel`, or `StreamField` panels.
The [Stimulus handbook](https://stimulus.hotwired.dev/handbook/introduction) is the best source on how to work with and understand Stimulus.
### Adding a custom Stimulus controller
Wagtail exposes two client-side globals for using Stimulus.
1. `window.wagtail.app` the core admin Stimulus [`WagtailApplication`](../reference/ui/client/classes/includes_initStimulus.WagtailApplication) instance.
2. `window.StimulusModule` Stimulus module as exported from `@hotwired/stimulus`.
First, create a custom [Stimulus controller](https://stimulus.hotwired.dev/reference/controllers) that extends the base `window.StimulusModule.Controller` using [JavaScript class inheritance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). If you are using a build tool you can import your base controller via `import { Controller } from '@hotwired/stimulus';`.
Once you have created your custom controller, you will need to [register your Stimulus controllers manually](https://stimulus.hotwired.dev/reference/controllers#registering-controllers-manually) via the `window.wagtail.app.register` method.
#### A simple controller example
First, create your HTML so that appears somewhere within the Wagtail admin.
```html
Hi
Hello
```
Second, create a JavaScript file that will contain your controller code. This controller logs a simple message on `connect`, which is once the controller has been created and connected to an HTML element with the matching `data-controller` attribute.
```javascript
// myapp/static/js/example.js
class MyController extends window.StimulusModule.Controller {
static targets = ['label'];
connect() {
console.log(
'My controller has connected:',
this.element.innerText,
this.labelTargets,
);
}
}
window.wagtail.app.register('my-controller', MyController);
```
Finally, load the JavaScript file into Wagtail’s admin with a hook.
```python
# myapp/wagtail_hooks.py
from django.templatetags.static import static
from django.utils.html import format_html
from wagtail import hooks
@hooks.register('insert_global_admin_js')
def global_admin_js():
return format_html(
f'',
)
```
You should now be able to refresh your admin that was showing the HTML and see two logs in the console.
#### A more complex controller example
Now we will create a `WordCountController` that adds a small `output` element next to the controlled `input` element that shows a count of how many words have been entered.
```javascript
// myapp/static/js/word-count-controller.js
class WordCountController extends window.StimulusModule.Controller {
static values = { max: { default: 10, type: Number } };
connect() {
this.setupOutput();
this.updateCount();
}
setupOutput() {
if (this.output) return;
const template = document.createElement('template');
template.innerHTML = ` `;
const output = template.content.firstChild;
this.element.insertAdjacentElement('beforebegin', output);
this.output = output;
}
updateCount(event) {
const value = event ? event.target.value : this.element.value;
const words = (value || '').split(' ');
this.output.textContent = `${words.length} / ${this.maxValue} words`;
}
disconnect() {
this.output && this.output.remove();
}
}
window.wagtail.app.register('word-count', WordCountController);
```
This lets the data attribute `data-word-count-max-value` determine the ‘configuration’ of this controller and the data attribute actions to determine the ‘triggers’ for the updates to the output element.
```python
# models.py
from django import forms
from wagtail.admin.panels import FieldPanel
from wagtail.models import Page
class BlogPage(Page):
# ...
content_panels = Page.content_panels + [
FieldPanel('subtitle', classname="full"),
FieldPanel(
'introduction',
classname="full",
widget=forms.TextInput(
attrs={
'data-controller': 'word-count',
# allow the max number to be determined with attributes
# note we can use Python values here, Django will handle the string conversion (including escaping if applicable)
'data-word-count-max-value': 5,
# decide when you want the count to update with data-action
# (e.g. 'blur->word-count#updateCount' will only update when field loses focus)
'data-action': 'word-count#updateCount paste->word-count#updateCount',
}
)
),
#...
```
This next code snippet shows a more advanced version of the `insert_editor_js` hook usage which is set up to append additional scripts for future controllers.
```python
# wagtail_hooks.py
from django.utils.html import format_html_join
from django.templatetags.static import static
from wagtail import hooks
@hooks.register('insert_editor_js')
def editor_js():
# add more controller code as needed
js_files = ['js/word-count-controller.js',]
return format_html_join('\n', '',
((static(filename),) for filename in js_files)
)
```
You should be able to see that on your Blog Pages, the introduction field will now have a small `output` element showing the count and max words being used.
#### A more complex widget example
For more complex widgets we can now integrate additional libraries whenever the widget appears in the rendered HTML, either on initial load or dynamically without the need for any inline `script` elements.
In this example, we will build a color picker widget using the [Coloris](https://coloris.js.org/) JavaScript library with support for custom widget options.
First, let’s start with the HTML, building on the [Django widgets](https://docs.djangoproject.com/en/stable/ref/forms/widgets/) system that Wagtail supports for `FieldPanel` and `FieldBlock`. Using the `build_attrs` method, we build up the appropriate Stimulus data attributes to support common data structures being passed into the controller.
Observe that we are using `json.dumps` for complex values (a list of strings in this case), Django will automatically escape these values when rendered to avoid common causes of insecure client-side code.
```py
# myapp/widgets.py
import json
from django.forms import Media, TextInput
from django.utils.translation import gettext as _
class ColorWidget(TextInput):
"""
See https://coloris.js.org/
"""
def __init__(self, attrs=None, swatches=[], theme='large'):
self.swatches = swatches
self.theme = theme
super().__init__(attrs=attrs);
def build_attrs(self, *args, **kwargs):
attrs = super().build_attrs(*args, **kwargs)
attrs['data-controller'] = 'color'
attrs['data-color-theme-value'] = self.theme
attrs['data-color-swatches-value'] = json.dumps(swatches)
return attrs
@property
def media(self):
return Media(
js=[
# load the UI library
"https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.js",
# load controller JS
"js/color-controller.js",
],
css={"all": ["https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.css"]},
)
```
For the Stimulus controller, we pass the values through to the JavaScript library, including a reference to the controlled element via `this.element.id`.
```javascript
// myapp/static/js/color-controller.js
class ColorController extends window.StimulusModule.Controller {
static values = { swatches: Array, theme: String };
connect() {
// create
Coloris({ el: `#${this.element.id}` });
// set options after initial creation
setTimeout(() => {
Coloris({ swatches: this.swatchesValue, theme: this.themeValue });
});
}
}
window.wagtail.app.register('color', ColorController);
```
Now we can use this widget in any `FieldPanel` or any `FieldBlock` for StreamFields, it will automatically instantiate the JavaScript to the field’s element.
```py
# blocks.py
# ... other imports
from django import forms
from wagtail.blocks import FieldBlock
from .widgets import ColorWidget
class ColorBlock(FieldBlock):
def __init__(self, *args, **kwargs):
swatches = kwargs.pop('swatches', [])
theme = kwargs.pop('theme', 'large')
self.field = forms.CharField(widget=ColorWidget(swatches=swatches, theme=theme))
super().__init__(*args, **kwargs)
```
```py
# models.py
# ... other imports
from django import forms
from wagtail.admin.panels import FieldPanel
from .blocks import ColorBlock
from .widgets import ColorWidget
BREAD_COLOR_PALETTE = ["#CFAC89", "#C68C5F", "#C47647", "#98644F", "#42332E"]
class BreadPage(Page):
body = StreamField([
# ...
('color', ColorBlock(swatches=BREAD_COLOR_PALETTE)),
# ...
], use_json_field=True)
color = models.CharField(blank=True, max_length=50)
# ... other fields
content_panels = Page.content_panels + [
# ... other panels
FieldPanel("body"),
FieldPanel("color", widget=ColorWidget(swatches=BREAD_COLOR_PALETTE)),
]
```
#### Using a build system
You will need ensure your build output is ES6/ES2015 or higher. You can use the exposed global module at `window.StimulusModule` or provide your own using the npm module `@hotwired/stimulus`.
```javascript
// myapp/static/js/word-count-controller.js
import { Controller } from '@hotwired/stimulus';
class WordCountController extends Controller {
// ... the same as above
}
window.wagtail.app.register('word-count', WordCountController);
```
You may want to avoid bundling Stimulus with your JavaScript output and treat the global as an external/alias module, refer to your build system documentation for instructions on how to do this.
## Extending with React
To customize or extend the [React](https://reactjs.org/) components, you may need to use React too, as well as other related libraries.
To make this easier, Wagtail exposes its React-related dependencies as global variables within the admin. Here are the available packages:
```javascript
// 'focus-trap-react'
window.FocusTrapReact;
// 'react'
window.React;
// 'react-dom'
window.ReactDOM;
// 'react-transition-group/CSSTransitionGroup'
window.CSSTransitionGroup;
```
Wagtail also exposes some of its own React components. You can reuse:
```javascript
window.wagtail.components.Icon;
window.wagtail.components.Portal;
```
Pages containing rich text editors also have access to:
```javascript
// 'draft-js'
window.DraftJS;
// 'draftail'
window.Draftail;
// Wagtail’s Draftail-related APIs and components.
window.draftail;
window.draftail.DraftUtils;
window.draftail.ModalWorkflowSource;
window.draftail.ImageModalWorkflowSource;
window.draftail.EmbedModalWorkflowSource;
window.draftail.LinkModalWorkflowSource;
window.draftail.DocumentModalWorkflowSource;
window.draftail.Tooltip;
window.draftail.TooltipEntity;
```
## Extending Draftail
- [Extending the Draftail editor](extending_draftail.md#extending-the-draftail-editor)
## Extending StreamField
- [Form widget client-side API](../reference/streamfield/widget_api.md#streamfield-widget-api)
- [Additional JavaScript on StructBlock forms](../advanced_topics/customization/streamfield_blocks.md#custom-streamfield-blocks-media)
## Extending the editor
- [Accessing the editor programmatically](editor_api.md)
# extending_draftail.html.md
# Extending the Draftail editor
Wagtail’s rich text editor is built with [Draftail](https://www.draftail.org/), which supports different types of extensions.
## Formatting extensions
Draftail supports three types of formatting:
- **Inline styles** – To format a portion of a line, for example `bold`, `italic` or `monospace`. Text can have as many inline styles as needed – for example bold *and* italic at the same time.
- **Blocks** – To indicate the structure of the content, for example, `blockquote`, `ol`. Any given text can only be of one block type.
- **Entities** – To enter additional data/metadata, for example, `link` (with a URL) or `image` (with a file). Text can only have one entity applied at a time.
All of these extensions are created with a similar baseline, which we can demonstrate with one of the simplest examples – a custom feature for an inline style of `mark`. Place the following in a `wagtail_hooks.py` file in any installed app:
```python
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineStyleElementHandler
from wagtail import hooks
# 1. Use the register_rich_text_features hook.
@hooks.register('register_rich_text_features')
def register_mark_feature(features):
"""
Registering the `mark` feature, which uses the `MARK` Draft.js inline style type,
and is stored as HTML with a `` tag.
"""
feature_name = 'mark'
type_ = 'MARK'
tag = 'mark'
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': '☆',
'description': 'Mark',
# This isn’t even required – Draftail has predefined styles for MARK.
# 'style': {'textDecoration': 'line-through'},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append('mark')
```
These steps will always be the same for all Draftail plugins. The important parts are to:
- Consistently use the feature’s Draft.js type or Wagtail feature names where appropriate.
- Give enough information to Draftail so it knows how to make a button for the feature, and how to render it (more on this later).
- Configure the conversion to use the right HTML element (as they are stored in the DB).
For detailed configuration options, head over to the [Draftail documentation](https://www.draftail.org/docs/formatting-options) to see all of the details. Here are some parts worth highlighting about controls:
- The `type` is the only mandatory piece of information.
- To display the control in the toolbar, combine `icon`, `label`, and `description`.
- `icon` is an icon name [registered in the Wagtail icon library](../advanced_topics/icons.md) - for example, `'icon': 'user',`. It can also be an array of strings, to use SVG paths, or SVG symbol references for example `'icon': ['M100 100 H 900 V 900 H 100 Z'],`. The paths need to be set for a 1024x1024 viewbox.
### Creating new inline styles
As noted in the initial example, the configuration passed to `InlineStyleFeature` can include a `style` property to define what CSS rules will be applied to text in the editor. Be sure to read the [Draftail documentation](https://www.draftail.org/docs/formatting-options) on inline styles.
Finally, the DB to/from conversion uses an `InlineStyleElementHandler` to map from a given tag (`` in the example above) to a Draftail type, and the inverse mapping is done with [Draft.js exporter configuration](https://github.com/springload/draftjs_exporter) of the `style_map`.
### Creating new blocks
Blocks are nearly as simple as inline styles:
```python
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import BlockElementHandler
from wagtail import hooks
@hooks.register('register_rich_text_features')
def register_help_text_feature(features):
"""
Registering the `help-text` feature, which uses the `help-text` Draft.js block type,
and is stored as HTML with a `` tag.
"""
feature_name = 'help-text'
type_ = 'help-text'
control = {
'type': type_,
'label': '?',
'description': 'Help text',
# Optionally, we can tell Draftail what element to use when displaying those blocks in the editor.
'element': 'div',
}
features.register_editor_plugin(
'draftail', feature_name, draftail_features.BlockFeature(control, css={'all': ['help-text.css']})
)
features.register_converter_rule('contentstate', feature_name, {
'from_database_format': {'div[class=help-text]': BlockElementHandler(type_)},
'to_database_format': {'block_map': {type_: {'element': 'div', 'props': {'class': 'help-text'}}}},
})
```
Here are the main differences:
- We can configure an `element` to tell Draftail how to render those blocks in the editor.
- We register the plugin with `BlockFeature`.
- We set up the conversion with `BlockElementHandler` and `block_map`.
Optionally, we can also define styles for the blocks with the `Draftail-block--help-text` (`Draftail-block--
`) CSS class.
That’s it! The extra complexity is that you may need to write CSS to style the blocks in the editor.
### Creating new entities
#### WARNING
This is an advanced feature. Please carefully consider whether you really need this.
Entities aren’t simply formatting buttons in the toolbar. They usually need to be much more versatile, communicating to APIs or requesting further user input. As such,
- You will most likely need to write a **hefty dose of JavaScript**, some of it with React.
- The API is very **low-level**. You will most likely need some **Draft.js knowledge**.
- Custom UIs in rich text can be brittle. Be ready to spend time **testing in multiple browsers**.
The good news is that having such a low-level API will enable third-party Wagtail plugins to innovate on rich text features, proposing new kinds of experiences.
But in the meantime, consider implementing your UI through [StreamField](../topics/streamfield.md) instead, which has a battle-tested API meant for Django developers.
Here are the main requirements to create a new entity feature:
- As for inline styles and blocks, register an editor plugin.
- The editor plugin must define a `source`: a React component responsible for creating new entity instances in the editor, using the Draft.js API.
- The editor plugin also needs a `decorator` (for inline entities) or `block` (for block entities): a React component responsible for displaying entity instances within the editor.
- Like for inline styles and blocks, set up the to/from DB conversion.
- The conversion usually is more involved, since entities contain data that needs to be serialized to HTML.
To write the React components, Wagtail exposes its own React, Draft.js, and Draftail dependencies as global variables. Read more about this in [extending client-side React components](extending_client_side.md#extending-client-side-react).
To go further, please look at the [Draftail documentation](https://www.draftail.org/docs/formatting-options) as well as the [Draft.js exporter documentation](https://github.com/springload/draftjs_exporter).
Here is a detailed example to showcase how those tools are used in the context of Wagtail.
For the sake of our example, we can imagine a news team working at a financial newspaper.
They want to write articles about the stock market, refer to specific stocks anywhere inside of their content (for example “$NEE” tokens in a sentence), and then have their article automatically enriched with the stock’s information (a link, a number, a sparkline).
The editor toolbar could contain a “stock chooser” that displays a list of available stocks, then inserts the user’s selection as a textual token. For our example, we will just pick a stock at random:

Those tokens are then saved in the rich text on publish. When the news article is displayed on the site, we then insert live market data coming from an API next to each token:

In order to achieve this, we start with registering the rich text feature like for inline styles and blocks:
```python
@hooks.register('register_rich_text_features')
def register_stock_feature(features):
features.default_features.append('stock')
"""
Registering the `stock` feature, which uses the `STOCK` Draft.js entity type,
and is stored as HTML with a `` tag.
"""
feature_name = 'stock'
type_ = 'STOCK'
control = {
'type': type_,
'label': '$',
'description': 'Stock',
}
features.register_editor_plugin(
'draftail', feature_name, draftail_features.EntityFeature(
control,
js=['stock.js'],
css={'all': ['stock.css']}
)
)
features.register_converter_rule('contentstate', feature_name, {
# Note here that the conversion is more complicated than for blocks and inline styles.
'from_database_format': {'span[data-stock]': StockEntityElementHandler(type_)},
'to_database_format': {'entity_decorators': {type_: stock_entity_decorator}},
})
```
The `js` and `css` keyword arguments on `EntityFeature` can be used to specify additional JS and CSS files to load when this feature is active. Both are optional. Their values are added to a `Media` object, more documentation on these objects is available in the [Django Form Assets documentation](https://docs.djangoproject.com/en/stable/topics/forms/media/).
Since entities hold data, the conversion to/from database format is more complicated. We have to create two handlers:
```python
from draftjs_exporter.dom import DOM
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineEntityElementHandler
def stock_entity_decorator(props):
"""
Draft.js ContentState to database HTML.
Converts the STOCK entities into a span tag.
"""
return DOM.create_element('span', {
'data-stock': props['stock'],
}, props['children'])
class StockEntityElementHandler(InlineEntityElementHandler):
"""
Database HTML to Draft.js ContentState.
Converts the span tag into a STOCK entity, with the right data.
"""
mutability = 'IMMUTABLE'
def get_attribute_data(self, attrs):
"""
Take the `stock` value from the `data-stock` HTML attribute.
"""
return { 'stock': attrs['data-stock'] }
```
Note how they both do similar conversions, but use different APIs. `to_database_format` is built with the [Draft.js exporter](https://github.com/springload/draftjs_exporter) components API, whereas `from_database_format` uses a Wagtail API.
The next step is to add JavaScript to define how the entities are created (the `source`), and how they are displayed (the `decorator`). Within `stock.js`, we define the source component:
```javascript
// Not a real React component – just creates the entities as soon as it is rendered.
class StockSource extends window.React.Component {
componentDidMount() {
const { editorState, entityType, onComplete } = this.props;
const content = editorState.getCurrentContent();
const selection = editorState.getSelection();
const demoStocks = ['AMD', 'AAPL', 'NEE', 'FSLR'];
const randomStock = demoStocks[Math.floor(Math.random() * demoStocks.length)];
// Uses the Draft.js API to create a new entity with the right data.
const contentWithEntity = content.createEntity(
entityType.type,
'IMMUTABLE',
{ stock: randomStock },
);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
// We also add some text for the entity to be activated on.
const text = `$${randomStock}`;
const newContent = window.DraftJS.Modifier.replaceText(
content,
selection,
text,
null,
entityKey,
);
const nextState = window.DraftJS.EditorState.push(
editorState,
newContent,
'insert-characters',
);
onComplete(nextState);
}
render() {
return null;
}
}
```
This source component uses data and callbacks provided by [Draftail](https://www.draftail.org/docs/api).
It also uses dependencies from global variables – see [Extending client-side React components](extending_client_side.md#extending-client-side-react).
We then create the decorator component:
```javascript
const Stock = (props) => {
const { entityKey, contentState } = props;
const data = contentState.getEntity(entityKey).getData();
return window.React.createElement(
'a',
{
role: 'button',
onMouseUp: () => {
window.open(`https://finance.yahoo.com/quote/${data.stock}`);
},
},
props.children,
);
};
```
This is a straightforward React component. It does not use JSX since we do not want to have to use a build step for our JavaScript.
Finally, we call `registerPlugin` on the Draftail global to register the JS components of our plugin:
```javascript
// Register the plugin directly on script execution so the editor loads it when initializing.
window.draftail.registerPlugin({
type: 'STOCK',
source: StockSource,
decorator: Stock,
}, 'entityTypes');
```
And that’s it! All of this setup will finally produce the following HTML on the site’s front-end:
```html
Anyone following NextEra technology $NEE should
also look into $FSLR .
```
To fully complete the demo, we can add a bit of JavaScript to the front-end in order to decorate those tokens with links and a little sparkline.
```javascript
document.querySelectorAll('[data-stock]').forEach((elt) => {
const link = document.createElement('a');
link.href = `https://finance.yahoo.com/quote/${elt.dataset.stock}`;
link.innerHTML = `${elt.innerHTML} `;
elt.innerHTML = '';
elt.appendChild(link);
});
```
Custom block entities can also be created (have a look at the separate [Draftail documentation](https://www.draftail.org/docs/blocks)), but these are not detailed here since [StreamField](../topics/streamfield.md#streamfield-topic) is the go-to way to create block-level rich text in Wagtail.
## Other editor extensions
Draftail has additional APIs for more complex customizations:
- **Controls** – To add arbitrary UI elements to editor toolbars.
- **Decorators** – For arbitrary text decorations/highlighting.
- **Plugins** – For direct access to all Draft.js APIs.
### Custom toolbar controls
To add an arbitrary new UI element to editor toolbars, Draftail comes with a [controls API](https://www.draftail.org/docs/arbitrary-controls). Controls can be arbitrary React components, which can get and set the editor state. Note controls update on *every keystroke* in the editor – make sure they render fast!
Here is an example with a simple sentence counter – first, registering the editor feature in a `wagtail_hooks.py`:
```python
from wagtail.admin.rich_text.editors.draftail.features import ControlFeature
from wagtail import hooks
@hooks.register('register_rich_text_features')
def register_sentences_counter(features):
feature_name = 'sentences'
features.default_features.append(feature_name)
features.register_editor_plugin(
'draftail',
feature_name,
ControlFeature({
'type': feature_name,
},
js=['draftail_sentences.js'],
),
)
```
Then, `draftail_sentences.js` declares a React component that will be rendered in the “meta” bottom toolbar of the editor:
```javascript
const countSentences = (str) =>
str ? (str.match(/[.?!…]+./g) || []).length + 1 : 0;
const SentenceCounter = ({ getEditorState }) => {
const editorState = getEditorState();
const content = editorState.getCurrentContent();
const text = content.getPlainText();
return window.React.createElement('div', {
className: 'w-inline-block w-tabular-nums w-help-text w-mr-4',
}, `Sentences: ${countSentences(text)}`);
}
window.draftail.registerPlugin({
type: 'sentences',
meta: SentenceCounter,
}, 'controls');
```
#### NOTE
Remember to include this feature in any custom Draft configs set up in the `WAGTAILADMIN_RICH_TEXT_EDITORS` setting. So that this new ‘sentences’ feature is available.
For example:
```python
WAGTAILADMIN_RICH_TEXT_EDITORS = {
'default': {
'WIDGET': 'wagtail.admin.rich_text.DraftailRichTextArea',
'OPTIONS': {
'features': ['bold', 'italic', 'link', 'sentences'], # Add 'sentences' here
},
},
}
```
### Text decorators
The [decorators API](https://www.draftail.org/docs/decorators) is how Draftail / Draft.js supports highlighting text with special formatting in the editor. It uses the [CompositeDecorator](https://draftjs.org/docs/advanced-topics-decorators/#compositedecorator) API, with each entry having a `strategy` function to determine what text to target, and a `component` function to render the decoration.
There are two important considerations when using this API:
- Order matters: only one decorator can render per character in the editor. This includes any entities that are rendered as decorations.
- For performance reasons, Draft.js only re-renders decorators that are on the currently focused line of text.
Here is an example with highlighting of problematic punctuation – first, registering the editor feature in a `wagtail_hooks.py`:
```python
from wagtail.admin.rich_text.editors.draftail.features import DecoratorFeature
from wagtail import hooks
@hooks.register('register_rich_text_features')
def register_punctuation_highlighter(features):
feature_name = 'punctuation'
features.default_features.append(feature_name)
features.register_editor_plugin(
'draftail',
feature_name,
DecoratorFeature({
'type': feature_name,
},
js=['draftail_punctuation.js'],
),
)
```
Then, `draftail_punctuation.js` defines the strategy and the highlighting component:
```javascript
const PUNCTUATION = /(\.\.\.|!!|\?!)/g;
const punctuationStrategy = (block, callback) => {
const text = block.getText();
let matches;
while ((matches = PUNCTUATION.exec(text)) !== null) {
callback(matches.index, matches.index + matches[0].length);
}
};
const errorHighlight = {
color: 'var(--w-color-text-error)',
outline: '1px solid currentColor',
}
const PunctuationHighlighter = ({ children }) => (
window.React.createElement('span', { style: errorHighlight, title: 'refer to our styleguide' }, children)
);
window.draftail.registerPlugin({
type: 'punctuation',
strategy: punctuationStrategy,
component: PunctuationHighlighter,
}, 'decorators');
```
### Arbitrary plugins
#### WARNING
This is an advanced feature. Please carefully consider whether you really need this.
Draftail supports plugins following the [Draft.js Plugins](https://www.draft-js-plugins.com/) architecture. Such plugins are the most advanced and powerful type of extension for the editor, offering customization capabilities equal to what would be possible with a custom Draft.js editor.
A common scenario where this API can help is to add bespoke copy-paste processing. Here is a simple example, automatically converting URL anchor hash references to links. First, let’s register the extension in Python:
```python
@hooks.register('register_rich_text_features')
def register_anchorify(features):
feature_name = 'anchorify'
features.default_features.append(feature_name)
features.register_editor_plugin(
'draftail',
feature_name,
PluginFeature({
'type': feature_name,
},
js=['draftail_anchorify.js'],
),
)
```
Then, in `draftail_anchorify.js`:
```javascript
const anchorifyPlugin = {
type: 'anchorify',
handlePastedText(text, html, editorState, { setEditorState }) {
let nextState = editorState;
if (text.match(/^#[a-zA-Z0-9_-]+$/ig)) {
const selection = nextState.getSelection();
let content = nextState.getCurrentContent();
content = content.createEntity("LINK", "MUTABLE", { url: text });
const entityKey = content.getLastCreatedEntityKey();
if (selection.isCollapsed()) {
content = window.DraftJS.Modifier.insertText(
content,
selection,
text,
undefined,
entityKey,
)
nextState = window.DraftJS.EditorState.push(
nextState,
content,
"insert-fragment",
);
} else {
nextState = window.DraftJS.RichUtils.toggleLink(nextState, selection, entityKey);
}
setEditorState(nextState);
return "handled";
}
return "not-handled";
},
};
window.draftail.registerPlugin(anchorifyPlugin, 'plugins');
```
## Integration of the Draftail widgets
To further customize how the Draftail widgets are integrated into the UI, there are additional extension points for CSS and JS:
- In JavaScript, use the `[data-draftail-input]` attribute selector to target the input that contains the data, and `[data-draftail-editor-wrapper]` for the element that wraps the editor.
- The editor instance is bound to the input field for imperative access. Use `document.querySelector('[data-draftail-input]').draftailEditor`.
- In CSS, use the classes prefixed with `Draftail-`.
# feature_detection.html.md
# Feature detection
Wagtail has the ability to automatically detect faces and features inside your images and crop the images to those features.
Feature detection uses third-party tools to detect faces/features in an image when the image is uploaded. The detected features are stored internally as a focal point in the `focal_point_{x, y, width, height}` fields on the `Image` model. These fields are used by the `fill` image filter when an image is rendered in a template to crop the image.
## Installation
Two third-party tools are known to work with Wagtail: One based on [OpenCV](https://opencv.org/) for general feature detection and one based on [Rustface](https://github.com/torchbox/rustface-py/) for face detection.
### OpenCV on Debian/Ubuntu
Feature detection requires [OpenCV](https://opencv.org/) which can be a bit tricky to install as it’s not currently pip-installable.
There is more than one way to install these components, but in each case you will need to test that both OpenCV itself *and* the Python interface have been correctly installed.
#### Install `opencv-python`
[opencv-python](https://pypi.org/project/opencv-python/) is available on PyPI.
It includes a Python interface to OpenCV, as well as the statically-built OpenCV binaries themselves.
To install:
```sh
pip install opencv-python
```
Depending on what else is installed on your system, this may be all that is required. On lighter-weight Linux systems, you may need to identify and install missing system libraries (for example, a slim version of Debian Stretch requires `libsm6 libxrender1 libxext6` to be installed with `apt`).
#### Install a system-level package
A system-level package can take care of all of the required components. Check what is available for your operating system. For example, [python-opencv](https://packages.debian.org/stretch/python-opencv) is available for Debian; it installs OpenCV itself, and sets up Python bindings.
However, it may make incorrect assumptions about how you’re using Python (for example, which version you’re using) - test as described below.
#### Testing the installation
Test the installation:
```python
python3
>>> import cv2
```
An error such as:
```python
ImportError: libSM.so.6: cannot open shared object file: No such file or directory
```
indicates that a required system library (in this case `libsm6`) has not been installed.
On the other hand,
```python
ModuleNotFoundError: No module named 'cv2'
```
means that the Python components have not been set up correctly in your Python environment.
If you don’t get an import error, installation has probably been successful.
### Rustface
[Rustface](https://github.com/torchbox/rustface-py/) is Python library with prebuilt wheel files provided for Linux and macOS. Although implemented in Rust it is pip-installable:
```sh
pip install wheel
pip install rustface
```
#### Registering with Willow
Rustface provides a plug-in that needs to be registered with [Willow](https://github.com/wagtail/Willow).
This should be done somewhere that gets run on application startup:
```python
from willow.registry import registry
import rustface.willow
registry.register_plugin(rustface.willow)
```
For example, in an app’s [`AppConfig.ready`](https://docs.djangoproject.com/en/stable/ref/applications/#django.apps.AppConfig.ready).
## Cropping
The face detection algorithm produces a focal area that is tightly cropped to the face rather than the whole head.
For images with a single face, this can be okay in some cases (thumbnails for example), however, it might be overly tight for “headshots”.
Image renditions can encompass more of the head by reducing the crop percentage (`-c`), at the end of the resize-rule, down to as low as 0%:
```html+django
{% image page.photo fill-200x200-c0 %}
```
## Switching on feature detection in Wagtail
Once installed, you need to set the `WAGTAILIMAGES_FEATURE_DETECTION_ENABLED` setting to `True` to automatically detect faces/features whenever a new image is uploaded in to Wagtail or when an image without a focal point is saved (this is done via a pre-save signal handler):
```python
# settings.py
WAGTAILIMAGES_FEATURE_DETECTION_ENABLED = True
```
## Manually running feature detection
If you already have images in your Wagtail site and would like to run feature detection on them, or you want to apply feature detection selectively when the `WAGTAILIMAGES_FEATURE_DETECTION_ENABLED` is set to `False` you can run it manually using the `get_suggested_focal_point()` method on the `Image` model.
For example, you can manually run feature detection on all images by running the following code in the python shell:
```python
from wagtail.images import get_image_model
Image = get_image_model()
for image in Image.objects.all():
if not image.has_focal_point():
image.set_focal_point(image.get_suggested_focal_point())
image.save()
```
# features.html.md
# Optional features
By default, snippets lack many of the features of pages, such as previews, revisions, and workflows. These features can individually be added to each snippet model by inheriting from the appropriate mixin classes.
## Making snippets previewable
If a snippet model inherits from [`PreviewableMixin`](../../reference/models.md#wagtail.models.PreviewableMixin), Wagtail will automatically add a live preview panel in the editor. In addition to inheriting the mixin, the model must also override [`get_preview_template()`](../../reference/models.md#wagtail.models.PreviewableMixin.get_preview_template) or [`serve_preview()`](../../reference/models.md#wagtail.models.PreviewableMixin.serve_preview). For example, the `Advert` snippet could be made previewable as follows:
```python
# ...
from wagtail.models import PreviewableMixin
# ...
class Advert(PreviewableMixin, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
def get_preview_template(self, request, mode_name):
return "demo/previews/advert.html"
```
With the following `demo/previews/advert.html` template:
```html+django
{{ object.text }}
{{ object.text }}
```
The variables available in the default context are `request` (a fake [`HttpRequest`](https://docs.djangoproject.com/en/stable/ref/request-response/#django.http.HttpRequest) object) and `object` (the snippet instance). To customize the context, you can override the [`get_preview_context()`](../../reference/models.md#wagtail.models.PreviewableMixin.get_preview_context) method.
By default, the `serve_preview` method returns a [`TemplateResponse`](https://docs.djangoproject.com/en/stable/ref/template-response/#django.template.response.TemplateResponse) that is rendered using the request object, the template returned by `get_preview_template`, and the context object returned by `get_preview_context`. You can override the `serve_preview` method to customize the rendering and/or routing logic.
Similar to pages, you can define multiple preview modes by overriding the [`preview_modes`](../../reference/models.md#wagtail.models.PreviewableMixin.preview_modes) property. For example, the following `Advert` snippet has two preview modes:
```python
# ...
from wagtail.models import PreviewableMixin
# ...
class Advert(PreviewableMixin, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
@property
def preview_modes(self):
return PreviewableMixin.DEFAULT_PREVIEW_MODES + [("alt", "Alternate")]
def get_preview_template(self, request, mode_name):
templates = {
"": "demo/previews/advert.html", # Default preview mode
"alt": "demo/previews/advert_alt.html", # Alternate preview mode
}
return templates.get(mode_name, templates[""])
def get_preview_context(self, request, mode_name):
context = super().get_preview_context(request, mode_name)
if mode_name == "alt":
context["extra_context"] = "Alternate preview mode"
return context
```
## Making snippets searchable
If a snippet model inherits from `wagtail.search.index.Indexed`, as described in [Indexing custom models](../search/indexing.md#wagtailsearch-indexing-models), Wagtail will automatically add a search box to the chooser interface for that snippet type. For example, the `Advert` snippet could be made searchable as follows:
```python
# ...
from wagtail.search import index
# ...
class Advert(index.Indexed, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
search_fields = [
index.SearchField('text'),
index.AutocompleteField('text'),
]
```
## Saving revisions of snippets
If a snippet model inherits from [`RevisionMixin`](../../reference/models.md#wagtail.models.RevisionMixin), Wagtail will automatically save revisions when you save any changes in the snippets admin.
The mixin defines a `revisions` property that gives you a queryset of all revisions for the snippet instance. It also comes with a default [`GenericRelation`](https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericRelation) to the [`Revision`](../../reference/models.md#wagtail.models.Revision) model so that the revisions are properly cleaned up when the snippet instance is deleted.
The default `GenericRelation` does not have a [`related_query_name`](https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericRelation.related_query_name), so it does not give you the ability to query and filter from the `Revision` model back to the snippet model. If you would like this feature, you can define your own `GenericRelation` with a custom `related_query_name`.
For more details, see the default `GenericRelation` [`_revisions`](../../reference/models.md#wagtail.models.RevisionMixin._revisions) and the property [`revisions`](../../reference/models.md#wagtail.models.RevisionMixin.revisions).
For example, the `Advert` snippet could be made revisable as follows:
```python
# ...
from django.contrib.contenttypes.fields import GenericRelation
from wagtail.models import RevisionMixin
# ...
class Advert(RevisionMixin, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
# If no custom logic is required, this can be defined as `revisions` directly
_revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
@property
def revisions(self):
# Some custom logic here if necessary, e.g. to handle multi-table inheritance.
# The mixin already handles inheritance, so this is optional.
return self._revisions.all()
```
If your snippet model defines relations using Django’s [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.ManyToManyField), you need to change the model class to inherit from `modelcluster.models.ClusterableModel` instead of `django.models.Model` and replace the `ManyToManyField` with `ParentalManyToManyField`. Inline models should continue to use `ParentalKey` instead of `ForeignKey`. This is necessary in order to allow the relations to be stored in the revisions. See the [Authors](../../getting_started/tutorial.md#tutorial-categories) section of the tutorial for more details. For example:
```python
# ...
from django.db import models
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.models import ClusterableModel
from wagtail.models import RevisionMixin
# ...
class ShirtColour(models.Model):
name = models.CharField(max_length=255)
panels = [FieldPanel("name")]
class ShirtCategory(models.Model):
name = models.CharField(max_length=255)
panels = [FieldPanel("name")]
class Shirt(RevisionMixin, ClusterableModel):
name = models.CharField(max_length=255)
colour = models.ForeignKey("shirts.ShirtColour", on_delete=models.SET_NULL, blank=True, null=True)
categories = ParentalManyToManyField("shirts.ShirtCategory", blank=True)
revisions = GenericRelation("wagtailcore.Revision", related_query_name="shirt")
panels = [
FieldPanel("name"),
FieldPanel("colour"),
FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
InlinePanel("images"),
]
class ShirtImage(models.Model):
shirt = ParentalKey("shirts.Shirt", related_name="images")
image = models.ForeignKey("wagtailimages.Image", on_delete=models.CASCADE, related_name="+")
caption = models.CharField(max_length=255, blank=True)
panels = [
FieldPanel("image"),
FieldPanel("caption"),
]
```
The `RevisionMixin` includes a `latest_revision` field that needs to be added to your database table. Make sure to run the `makemigrations` and `migrate` management commands after making the above changes to apply the changes to your database.
With the `RevisionMixin` applied, any changes made from the snippets admin will create an instance of the `Revision` model that contains the state of the snippet instance. The revision instance is attached to the [audit log](../../extending/audit_log.md#audit-log) entry of the edit action, allowing you to revert to a previous revision or compare the changes between revisions from the snippet history page.
You can also save revisions programmatically by calling the [`save_revision()`](../../reference/models.md#wagtail.models.RevisionMixin.save_revision) method. After applying the mixin, it is recommended to call this method (or save the snippet in the admin) at least once for each instance of the snippet that already exists (if any), so that the `latest_revision` field is populated in the database table.
## Saving draft changes of snippets
If a snippet model inherits from [`DraftStateMixin`](../../reference/models.md#wagtail.models.DraftStateMixin), Wagtail will automatically add a live/draft status column to the listing view, change the “Save” action menu to “Save draft”, and add a new “Publish” action menu in the editor. Any changes you save in the snippets admin will be saved as revisions and will not be reflected in the “live” snippet instance until you publish the changes.
As the `DraftStateMixin` works by saving draft changes as revisions, inheriting from this mixin also requires inheriting from `RevisionMixin`. See [Saving revisions of snippets](#wagtailsnippets-saving-revisions-of-snippets) above for more details.
Wagtail will also allow you to set publishing schedules for instances of the model if there is a `PublishingPanel` in the model’s panels definition.
For example, the `Advert` snippet could save draft changes and publishing schedules by defining it as follows:
```python
# ...
from django.contrib.contenttypes.fields import GenericRelation
from wagtail.admin.panels import PublishingPanel
from wagtail.models import DraftStateMixin, RevisionMixin
# ...
class Advert(DraftStateMixin, RevisionMixin, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
_revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")
panels = [
FieldPanel('url'),
FieldPanel('text'),
PublishingPanel(),
]
@property
def revisions(self):
return self._revisions
```
The `DraftStateMixin` includes additional fields that need to be added to your database table. Make sure to run the `makemigrations` and `migrate` management commands after making the above changes to apply the changes to your database.
You can publish revisions programmatically by calling [`instance.publish(revision)`](../../reference/models.md#wagtail.models.DraftStateMixin.publish) or by calling [`revision.publish()`](../../reference/models.md#wagtail.models.Revision.publish). After applying the mixin, it is recommended to publish at least one revision for each instance of the snippet that already exists (if any), so that the `latest_revision` and `live_revision` fields are populated in the database table.
If you use the scheduled publishing feature, make sure that you run the [`publish_scheduled`](../../reference/management_commands.md#publish-scheduled) management command periodically. For more details, see [Scheduled publishing](../../reference/pages/theory.md#scheduled-publishing).
Publishing a snippet instance requires `publish` permission on the snippet model. For models with `DraftStateMixin` applied, Wagtail automatically creates the corresponding `publish` permissions and displays them in the ‘Groups’ area of the Wagtail admin interface. For more details on how to configure the permission, see [Permissions](../permissions.md#permissions-overview).
#### WARNING
Wagtail does not yet have a mechanism to prevent editors from including unpublished (“draft”) snippets in pages. When including a `DraftStateMixin`-enabled snippet in pages, make sure that you add necessary checks to handle how a draft snippet should be rendered (for example, by checking its `live` field). We are planning to improve this in the future.
## Locking snippets
If a snippet model inherits from [`LockableMixin`](../../reference/models.md#wagtail.models.LockableMixin), Wagtail will automatically add the ability to lock instances of the model. When editing, Wagtail will show the locking information in the “Status” side panel, and a button to lock/unlock the instance if the user has the permission to do so.
If the model is also configured to have scheduled publishing (as shown in [Saving draft changes of snippets](#wagtailsnippets-saving-draft-changes-of-snippets) above), Wagtail will lock any instances that are scheduled for publishing.
Similar to pages, users who locked a snippet can still edit it, unless [`WAGTAILADMIN_GLOBAL_EDIT_LOCK`](../../reference/settings.md#wagtailadmin-global-edit-lock) is set to `True`.
For example, instances of the `Advert` snippet could be locked by defining it as follows:
```python
# ...
from wagtail.models import LockableMixin
# ...
class Advert(LockableMixin, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
```
If you use the other mixins, make sure to apply `LockableMixin` after the other mixins, but before the `RevisionMixin` (in left-to-right order). For example, with `DraftStateMixin` and `RevisionMixin`, the correct inheritance of the model would be `class MyModel(DraftStateMixin, LockableMixin, RevisionMixin)`. There is a system check to enforce the ordering of the mixins.
The `LockableMixin` includes additional fields that need to be added to your database table. Make sure to run the `makemigrations` and `migrate` management commands after making the above changes to apply the changes to your database.
Locking and unlocking a snippet instance requires `lock` and `unlock` permissions on the snippet model, respectively. For models with `LockableMixin` applied, Wagtail automatically creates the corresponding `lock` and `unlock` permissions and displays them in the ‘Groups’ area of the Wagtail admin interface. For more details on how to configure the permission, see [Permissions](../permissions.md#permissions-overview).
## Enabling workflows for snippets
If a snippet model inherits from [`WorkflowMixin`](../../reference/models.md#wagtail.models.WorkflowMixin), Wagtail will automatically add the ability to assign a workflow to the model. With a workflow assigned to the snippet model, a “Submit for moderation” and other workflow action menu items will be shown in the editor. The status side panel will also show the information on the current workflow.
Since the `WorkflowMixin` utilizes revisions and publishing mechanisms in Wagtail, inheriting from this mixin also requires inheriting from `RevisionMixin` and `DraftStateMixin`. It is also recommended to enable locking by inheriting from `LockableMixin`, so that the snippet instance can be locked and only editable by reviewers when it is in a workflow. See the above sections for more details.
The mixin defines a `workflow_states` property that gives you a queryset of all workflow states for the snippet instance. It also comes with a default [`GenericRelation`](https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericRelation) to the [`WorkflowState`](../../reference/models.md#wagtail.models.WorkflowState) model so that the workflow states are properly cleaned up when the snippet instance is deleted.
The default `GenericRelation` does not have a [`related_query_name`](https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericRelation.related_query_name), so it does not give you the ability to query and filter from the `WorkflowState` model back to the snippet model. If you would like this feature, you can define your own `GenericRelation` with a custom `related_query_name`.
For more details, see the default `GenericRelation` [`_workflow_states`](../../reference/models.md#wagtail.models.WorkflowMixin._workflow_states) and the property [`workflow_states`](../../reference/models.md#wagtail.models.WorkflowMixin.workflow_states).
For example, workflows (with locking) can be enabled for the `Advert` snippet by defining it as follows:
```python
# ...
from wagtail.models import DraftStateMixin, LockableMixin, RevisionMixin, WorkflowMixin
# ...
class Advert(WorkflowMixin, DraftStateMixin, LockableMixin, RevisionMixin, models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
_revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")
workflow_states = GenericRelation(
"wagtailcore.WorkflowState",
content_type_field="base_content_type",
object_id_field="object_id",
related_query_name="advert",
for_concrete_model=False,
)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
@property
def revisions(self):
return self._revisions
```
The other mixins required by `WorkflowMixin` includes additional fields that need to be added to your database table. Make sure to run the `makemigrations` and `migrate` management commands after making the above changes to apply the changes to your database.
After enabling the mixin, you can assign a workflow to the snippet models through the workflow settings. For more information, see how to [configure workflows for moderation](https://guide.wagtail.org/en-latest/how-to-guides/configure-workflows-for-moderation/).
The admin dashboard and workflow reports will also show you snippets (alongside pages) that have been submitted to workflows.
## Tagging snippets
Adding tags to snippets is very similar to adding tags to pages. The only difference is that if `RevisionMixin` is not applied, then `taggit.manager.TaggableManager` should be used in the place of `modelcluster.contrib.taggit.ClusterTaggableManager`.
```python
# ...
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from taggit.models import TaggedItemBase
from taggit.managers import TaggableManager
# ...
class AdvertTag(TaggedItemBase):
content_object = ParentalKey('demo.Advert', on_delete=models.CASCADE, related_name='tagged_items')
class Advert(ClusterableModel):
# ...
tags = TaggableManager(through=AdvertTag, blank=True)
panels = [
# ...
FieldPanel('tags'),
]
```
The [documentation on tagging pages](../../advanced_topics/tags.md#tagging) has more information on how to use tags in views.
## Inline models within snippets
Similar to pages, you can nest other models within a snippet. This requires the snippet model to inherit from `modelcluster.models.ClusterableModel` instead of `django.models.Model`.
```python
from django.db import models
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.models import Orderable
class BandMember(Orderable):
band = ParentalKey("music.Band", related_name="members", on_delete=models.CASCADE)
name = models.CharField(max_length=255)
@register_snippet
class Band(ClusterableModel):
name = models.CharField(max_length=255)
panels = [
FieldPanel("name"),
InlinePanel("members")
]
```
The [documentation on how to use inline models with pages](../pages.md#inline-models) provides more information that is also applicable to snippets.
# first_contribution_guide.html.md
# Your first contribution
> * [Guide](#guide)
> * [Common questions](#common-questions)
> * [Helpful links](#helpful-links)
This page has a step by step guide for how to get started making contributions to Wagtail. It is recommended for developers getting started with open source generally or with only a small amount of experience writing code for shared teams.
This is a long guide - do not worry about following all the steps in one go or doing them perfectly. There are lots of people around in the community to help, but if you can take the time to read and understand yourself you will be a much stronger developer.
Each section has an introduction with an overview and a checklist that can be copied and pasted for you to check through one by one. Get ready to read, there is a lot of reading ahead.
#### NOTE
Avoid ‘claiming’ any issues before completing Steps 0-6. This helps you not over-promise what you can contribute and helps the community support you when you are actually ready to contribute.
Do not worry about issues ‘running out’ - software development is an endless fractal, there is always more to help with.
## Guide
### 0. Understand your motivations
Before you start contributing to Wagtail, take a moment to think about why you want to do it. If your only goal is to add a “first contribution” to your resume (or if you’re just looking for a quick win) you might be better off doing a boot-camp or an online tutorial.
Contributing to open source projects takes time and effort, but it can also help you become a better developer and learn new skills. However, it’s important to know that it might be harder and slower than following a training course. That said, contributing to open source is worth it if you’re willing to take the time to do things well.
One thing to keep in mind is that “scratching your own itch” can be a great motivator for contributing to open source. If you’re interested in the CMS space or the programming languages used in this project, you’ll be more likely to stick with it over the long term.
### 1. Understanding what Wagtail is
Before you start contributing to Wagtail, it’s important to understand what it is and how it works. Wagtail is a content management system (CMS) used for building websites. Unlike other CMSs, it requires some development time to build up the models and supporting code to use as a CMS. Additionally, Wagtail is built on top of another framework called Django, which is a Python web framework. This might be confusing at first, but it provides a powerful way to create custom systems for developers to build with.
To get started, we recommend reading [the Zen of Wagtail](../getting_started/the_zen_of_wagtail.md), which provides a good introduction to the project. You might also want to read the [Django overview](https://docs.djangoproject.com/en/stable/intro/overview/) to understand what Django provides. To get a sense of how Wagtail fits into the CMS landscape, you can search online for articles that compare WordPress to Wagtail or list the top open source CMSs. Finally, reading some of the [Wagtail Guide](https://guide.wagtail.org/) will give you a better understanding of how the CMS works for everyday users.
#### NOTE
Below is a checklist. There are many like these you can copy for yourself as you progress through this guide.
```markdown
- [ ] Read the Zen of Wagtail.
- [ ] Read the Django Overview.
- [ ] Search online for one or two articles that 'compare Wordpress to Wagtail' or 'top ten open source CMS' and read about the CMS landscape.
- [ ] Read some of the Wagtail Guide.
```
### 2. Joining the community
Make an account on [Wagtail Slack](https://github.com/wagtail/wagtail/wiki/Slack) server, this is the way many of the community interact day to day. Introduce yourself on `#new-contributors` and join some of the other channels, remember to keep your intro short and be nice to others. After this, join [GitHub](https://github.com/) and set up your profile. It’s really helpful to the community if your name can be added to your profiles in both communities and an image. It does not have to be your public name or a real image if you want to keep that private but please avoid it staying as the ‘default avatar’.
You may also want to join StackOverflow and [follow the Wagtail tag](https://stackoverflow.com/questions/tagged/wagtail), this way you can upvote great answers to questions you have or maybe consider contributing answers yourself. Before you dive in, take a moment to review the [community guidelines](https://github.com/wagtail/wagtail/blob/main/CODE_OF_CONDUCT.md) to get a grasp on the expectations for participation.
#### Checklist
```markdown
- [ ] Read the community guidelines.
- [ ] Join GitHub.
- [ ] Add your preferred name and image to your GitHub profile.
- [ ] Join Slack.
- [ ] Add your preferred name, timezone and image to your Slack profile.
- [ ] Introduce yourself in `#new-contributors` in Slack.
- [ ] Join the `#support` channel in Slack.
- [ ] _Optional_ Join StackOverflow.
```
### 3. Before contributing code
Firstly, it is important to be able to understand how to **build with** Wagtail before you can understand how to contribute **to** Wagtail. Take the time to do the full [Wagtail getting started tutorial](../getting_started/index.md) without focusing yet on how to contribute code but instead on how to use Wagtail to build your own basic demo website. This will require you to have Python and other dependencies installed on your machine and may not be easy the first time, but keep at it and ask questions if you get stuck.
Remember that there are many other ways to contribute, such as answering questions in StackOverflow or `#support`, contributing to one of the [other packages](https://github.com/wagtail/) or even the [Wagtail user guide](https://guide.wagtail.org/en-latest/contributing/). Sometimes, it’s best to get started with a non-code contribution to get a feel for Wagtail’s code or the CMS interface.
Issue tracking, reading and triage is a critical part of contributing code and it is recommended that you read the [issue tracking guide](issue_tracking.md) in full. This will help you understand how to find issues to work on and how to support the team with triaging issues.
#### NOTE
Take the time to **read** the issue and links before adding new comments or questions. Remember, it’s not time to ‘claim’ any issues yet either.
#### Checklist
```default
- [ ] Do the Wagtail tutorial.
- [ ] Look at the Wagtail organization on GitHub, take note of any interesting projects.
- [ ] Read through the Issue Tracking section in the docs.
- [ ] Give a go at a non-code contribution.
```
### 4. Setting up your development environment
Many contribution sections gloss over the mammoth task that can be a single line in the documentation similar to “fork the code and get it running locally”. This, on its own, can be a daunting task if you are just getting started. This is why it’s best to have done the Wagtail tutorial before this step so you have run into and hopefully solved many of the normal developer environment issues.
First, create a fork of Wagtail on your GitHub account (see below for more details).
#### NOTE
Do not try to move past this step until you have a working `bakerydemo` code locally and a clone of the Wagtail repo that you can edit. When editing the Wagtail core code (both HTML and JavaScript) you should be able to refresh the site running locally and see the changes.
Read (in full) the [Development guide](developing.md#developing-for-wagtail). This will walk you through how to get your code running locally so you can contribute. It’s strongly recommended to use the Vagrant or Docker setups, especially if you are working on Windows.
#### NOTE
When developing, it’s recommended that you always read the `latest` version of the docs. Not the `stable` version. This is because it will better reflect what’s on the `main` code branch.
#### Checklist
```default
- [ ] Install `git` (if not on your machine).
- [ ] Install a code editor/IDE (we recommend VSCode).
- [ ] Install the dependencies set out in the development guide.
- [ ] Follow the development guide.
- [ ] Make a change to the `wagtail/admin/templates/wagtailadmin/home.html` template file and confirm you can see the changes on the Wagtail dashboard (home) page.
- [ ] Add a `console.log` statement to `client/src/entrypoints/admin/wagtailadmin.js` and confirm you can see the logging in the browser.
```
#### Aside: Understanding Git and GitHub
`git` is the version control tool, it is something you install on your device and runs usually in the command line (terminal) or via some GUI application.
GitHub & GitLab are two prominent websites that provide a web user interface for repositories using `git`, Wagtail uses GitHub.
Mozilla has a great guide that helps to explain [Git and GitHub](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/GitHub).
How to clone a remote repository and what that actually even means:
- On GitHub, you will not be allowed to directly create branches or changes in a repository (project) that you do not have access to.
- However, you can make a copy (clone) of this repository using your own account, this clone will have all the branches and history that the original repository had.
- This is also called ‘fork’ in some cases, as your repository will be a branch of its own that forks the original repository.
- See the [GitHub docs explain forking](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
- See [Atlassian’s docs on git clone](https://www.atlassian.com/git/tutorials/setting-up-a-repository/git-clone) for more details.
### 5. Finding an issue
Hopefully, at this point, you have a good sense of the purpose of the project and are still keen to contribute.
Once you have the code forked and running locally, you will probably want to start looking for what to contribute.
Finding something to contribute is not always easy, especially if you are new to the project. Once you have a few candidate issues to investigate, be sure to read the entire issue description, all comments and all linked issues or pull requests. You may often find that someone else has started or finished the issue. Sometimes there are clarifications in the comments about how to approach the problem or whether the problem is even something worth solving.
If an issue has a pull request linked and not yet merged read that pull request and the discussion on it. Maybe the previous contributor got stuck or lost momentum, in which case you could pick up where they left off (assuming it’s been enough time). If you have an idea about how to solve a problem, just add a comment with a suggestion, we should all aim to help each other out.
If the issue is labelled [`good-first-issue`](https://github.com/wagtail/wagtail/labels/good%20first%20issue), that usually means it is smaller and good for first time contributors. There are no problems with finding other issues to contribute to, have a search around and see what you can help with.
Finally, before ‘claiming’ check you can do the following;
#### Checklist (for a candidate issue)
```markdown
- [ ] Confirm that there is not someone actively working on it (no recent PR or comments in the last ~2 months).
- [ ] Ensure you can reproduce the problem/scenario in your local version of Wagtail.
- [ ] Ensure that you feel confident to write a unit test (if it's a code change) to validate that the solution **is** implemented.
```
### 6. Contributing a solution
**Important:** If an issue is not assigned to anyone and doesn’t already have a pull request, feel free to work on it, **no need to ask “please assign me this issue”**. We only use GitHub’s issue assignment feature to assign certain issues to members of the Wagtail core team.
If you feel ready to contribute a solution, now is a good time to add a comment to the issue describing your intention to do so, to prevent duplicating efforts. Instead of asking “please assign me this issue”, write something similar to the following:
#### NOTE
I have been able to reproduce this problem/scenario. I am planning to work on this, my rough solution is (…explain).
If it’s just a documentation request, you may refine this comment to explain where you plan to add the section in the documentation.
#### Create a fresh branch for your contributions
Before writing any code, take a moment to get your `git` hat on. When you clone the project locally, you will be checked out at the `main` branch. This branch is not suitable for you to make your changes on. It is meant to be the branch that tracks the core development of the project.
Instead, take a moment to create a [new branch](https://www.atlassian.com/git/tutorials/using-branches). You can use the command line or install one of the many great git GUI tools. Don’t listen to anyone that says you’re not doing it right unless you use the command line. Reduce the things you need to learn today and focus on the `git` command line interface later. If you have a Mac, I recommend [Fork](https://git-fork.com/), otherwise, the [GitHub GUI](https://desktop.github.com/) is good enough.
This new branch name should have some context as to what you are fixing and if possible the issue number being fixed. For example `git checkout -b 'feature/1234-add-unit-tests-for-inline-panel'`. This branch name uses `/` to represent a folder and also has the issue number `1234`, finally, it uses `lower-kebab-case` with a short description of the issue.
#### NOTE
You may find that your editor has some handy Git tooling and will often be able to tell you what branch you are on or whether you have any changes staged. For example, see [VS Code’s support for Git](https://code.visualstudio.com/docs/sourcecontrol/overview).
#### Keep the changes focused
As a developer, it is easy to get distracted, maybe a typo here or white space that does not feel ‘right’ there. Sometimes, even our editor gets distracted and starts adding line breaks at the end of files as we save or it formats code without our consent due to configuration from a different project.
These added changes that are not the primary goal or not strictly required by the project’s set-up are noise. This noise makes it harder to review the pull request and also can create confusion for future developers that see these commits and wonder how it relates to the bug that was fixed.
When you go to stage changes, only stage the parts you need or at least review the changes and ‘undo’ them before you save the commits.
If you do find a different problem (maybe a typo in the docs for example) this is what branches are for. Save your commits, create a new branch off master `fix/fix-documentation-typo` and then save that change to that branch. Now you have a small change, one that is easy to merge, which you can prepare a pull request for.
Keep your changes focused on the goal, do not add overhead to the reviewer or to yourself by changing things that do not need it (yet).
#### NOTE
It’s OK to make changes in a ‘messy’ way locally, with lots of commits that maybe include things that are not needed. However, be sure to take some time to review your commits and clean up anything that is not required before you do your pull request.
#### Write unit tests
We are getting close to having a pull request, but the next critical step is unit tests. It’s common to find that adding tests for code you wrote will take 5-10x longer than the actual bug fix. Often, if the use case is right, it is better to write the tests first and get them running (but failing) before you fix the problem.
Finding how and where to write the unit tests can be hard in a new project, but hopefully, the project’s development docs contain the clues you need to get started. Read through the [dedicated testing section](developing.md#testing) in the development documentation.
If you fix a bug or introduce a new feature, you want to ensure that fix is long-lived and does not break again. You also want to help yourself by thinking through edge cases or potential problems. Testing helps with this. While regressions do happen, they are less likely to happen when code is tested.
Many projects will not even review a pull request without unit tests. Often, fixing a bug is not hard, ensuring the fix is the ‘real’ fix and that it does not break again is the hard part. Take the time to do the harder thing. It will help you grow as a developer and help your contributions make a longer lasting difference.
#### NOTE
A pull request that just adds unit tests to some core functionality that does not yet have tests is a great way to contribute, it helps you learn about the code and makes the project more reliable.
#### Checklist
```default
- [ ] After feeling confident about a solution, add a comment to the issue.
- [ ] Create a new branch off `main` to track your work separate from the main branch.
- [ ] Keep the changes focused towards your goal, asking questions on the issue if direction is needed.
- [ ] Write unit tests.
```
### 7. Submitting a pull request
A pull request that has the title ‘fixes issue’ is unhelpful at best, and spammy at worst. Take a few moments to think about how to give your change a title. Communicate (in a few words) the problem solved or feature added or bug fixed. Instead of ‘Fixes 10423’, use words and write a title ‘Fixes documentation dark mode refresh issue’. No one in a project knows that issue `10423` is that one about the documentation dark mode refresh issue.
Please try to add a proper title when you create the pull request. This will ensure that any notifications that go out to the team have a suitable title from the start.
#### NOTE
Remember you can make a **draft** pull request in both GitHub and GitLab. This is a way to run the CI steps but in a way that indicates you are not ready for a review yet.
Referencing the issue being fixed within the pull request description is just as important as a good title. A pull request without a description is very difficult to review. Adding a note similar to `fixes #1234` in your description message will let GitHub know that the change is for that issue. Add some context and some steps to reproduce the issue or scenario.
If the change is visual it’s strongly recommended to add before and after screenshots. This helps you confirm the change has worked and also helps reviewers understand the change made.
It is often good to write yourself a checklist for any pull request and fill in the gaps. **Remember that the pull request template is there for a reason so please use that checklist also.**
#### Checklist (for a pull request)
```markdown
- [ ] Small description of the solution, one sentence.
- [ ] Link to issue/s that should be resolved if this pull request gets merged.
- [ ] Questions or assumptions, maybe you made an assumption we no longer support IE11 with your CSS change, if it's not in the docs - write the assumption down.
- [ ] Details - additional details, context or links that help the reviewer understand the pull request.
- [ ] Screenshots - added before and after the change has been applied.
- [ ] Browser and accessibility checks done, or not done. Added to the description.
```
#### 7a. Review & fix the CI failures
Once you have created your pull request, there will often be a series of [build/check/CI](https://about.gitlab.com/topics/ci-cd/) steps that run.
These steps are normally all required to pass before the pull request can be merged. CI is a broad term but usually, the testing and linting will run on the code you have proposed to change. Linting is a tricky one because sometimes the things that are flagged seem trivial, but they are important for code consistency. Re-read the development instructions and see how you can run the linting locally to avoid frustrating back & forth with small linting fixes.
Testing is a bit more complex. Maybe all the tests can be run locally or maybe the CI will run tests on multiple versions of a project or language. Do your best to run all the tests locally, but there may still be issues on the CI when you do. That is OK, and normally you can solve these issues one by one.
The most important thing is to not just ignore CI failures. Read through each error report and try to work out the problem and provide a fix. Ignoring these will likely lead to pull requests that do not get reviewed because they do not get the basics right.
#### NOTE
GitHub will not run the CI automatically for new contributors in some projects. This is an intentional security feature and a core contributor will need to approve your initial CI run.
#### 7b. Push to the same branch with fixes and do not open a new pull request
Finally, after you have fixed the failing linting and tests locally, you will want to push those changes to your remote branch. You do not need to open a new pull request. This creates more noise and confusion. Instead, push your changes up to your branch, and the CI will run automatically on those changes.
You can add a comment if you want to the pull request that you have updated, but often this is not really needed.
**Avoid opening multiple pull requests for the same fix.** Doing that means all the comments and discussion from the previous pull request will get lost and reviewers will have trouble finding them.
### 8. Next steps
When you take time to contribute out of your own personal time, or even that from your paid employer, it can be very frustrating when a pull request does not get reviewed. It is best to temper your expectations with this process and remember that many people on the other side of this are also volunteers or have limited time to prioritize.
It is best to celebrate your accomplishment at this point even if your pull request never gets merged. It’s good to balance that with an eagerness about getting your amazing fix in place to help people who use the project. Balancing this tension is hard, but the unhelpful thing to do is give up and never contribute or decide that you won’t respond to feedback because it came too late.
Remember that it is OK to move on and try something else. Try a different issue or project or area of the code. Don’t just sit waiting for a response on the one thing you did before looking at other challenges.
#### Responding to a review
Almost every pull request (PR) (except for the smallest changes) will have some form of feedback. This will usually come in the form of a review and a request for changes. At this point your PR will be flagged as ‘needs work’, ‘needs tests’ or in some cases ‘needs design decision’. Take the time to read all the feedback and try to resolve or respond to comments if you have questions.
#### WARNING
Avoid closing the PR only to create a new one, instead keep it open and push your changes/fixes to the same branch. Unless directed to make the PR smaller, keep the same one open and work through items one by one.
Once you feel that you have answered all the concerns, just add a comment (it does not need to be directed at the reviewer) that this is ready for another review.
#### Once merged in
Well done! It’s time to party! Thank you for taking the time to contribute to Wagtail and making the project better for thousands of users.
## Common questions
### How can I start contributing?
- Ideally, read this guide in full, otherwise see some quick start tips.
- Start simple - pick something small first. The [good first issue](https://github.com/wagtail/wagtail/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) label is a good place to look.
- Read the entire issue, all comments (links) and related issues.
- Someone may have started work (that work may have stalled).
- Check if assigned, we do not usually use that unless assigned to someone within the core team.
If you have done all of that and think you can give it a go, add a comment with something like ‘I will give this a go’. No need to ask for permission.
### Do I need to ask for permission to work on an issue?
**No.** However, check if there is an existing pull request (PR). If there is nothing, you can optionally add a comment mentioning that you’re starting work on it.
### What should I include in my pull request (PR)
1. The fix or feature you are working on
2. Tests
3. Linted code (we make use of [pre-commit](https://pre-commit.com/). You can run all formatting with `make format`)
4. Updated documentation where relevant (such as when adding a new feature)
### What if I fix multiple issues in the same pull request (PR)
It is best to avoid fixing more than one issue in a single pull request, unless you are a core contributor or there is a clear plan that involves fixing multiple things at once. Even then, it is usually a bad idea as it makes it harder for your pull request to be reviewed and it may never get merged as it’s too complex. This is especially true for completely unrelated issues such as a documentation fix for translators and a bug fix for StreamField. It is always best to create two branches and then two separate pull requests.
### When do I need to write unit tests for a pull request (PR)?
Unless you are updating the documentation or only making visual style changes, your pull request should contain tests.
If you are new to writing tests in Django, start by reading the [Django documentation on testing](https://docs.djangoproject.com/en/stable/topics/testing/overview/). Re-read the [Wagtail documentation notes on testing](developing.md#testing) and have a look at [existing tests](https://cs.github.com/?scopeName=All+repos&scope=&q=repo%3Awagtail%2Fwagtail+path%3A**%2Ftests%2F**).
Note that the JavaScript testing is not as robust as the Python testing, if possible at least attempt to add some basic JS tests to new behavior.
### Where can I get help with my pull request (PR)?
The `#new-contributors` channel in [Slack](https://github.com/wagtail/wagtail/wiki/Slack) is the best place to get started with support for contributing code, especially for help with the process of setting up a dev environment and creating a PR.
There is also the more recently created `#development` channel for advice on understanding and getting around the Wagtail code-base specifically. Finally, if you have a general problem with understanding how to do something in Wagtail itself or with a specific feature, then `#support` can be used.
### What if there is already an open pull request (PR)?
Be sure to always read the issue in full and review all links, sometimes there may already be an open pull request for the same issue. To avoid duplicating efforts it would be best to see if that pull request is close to ready and then move on to something else. Alternatively, if it has been a long enough amount of time, you may want to pick up the code and build on it to get it finished or ask if they need help.
### Can I just use Gitpod to develop?
While Gitpod is great for some small scale pull requests, it will not be a suitable tool for complex contributions and it’s best to take the time to set up a fully functional development environment so you can manage branches and ongoing commits to one branch.
Here are some links for using Gitpod with the Wagtail packages:
- [Bakerydemo Gitpod instructions](https://github.com/wagtail/bakerydemo#setup-with-gitpod)
- [Wagtail Gitpod – Wagtail development setup in one click](https://wagtail.org/blog/gitpod/)
### Can I use Windows to develop?
While a lot of our documentation works best on Linux or MacOS, we do have some guidance for [development on Windows](developing.md#development-on-windows).
You can also go through this [Windows step by step guide to getting bakerydemo running with local Wagtail](https://juliet.hashnode.dev/a-step-by-step-guide-for-manually-setting-up-bakery-demo-with-wagtail).
### How can I be assigned an issue to contribute to?
We only use GitHub’s issue assignment feature for members of the Wagtail core team when tasks are being planned as part of core roadmap features or when being used for a specific internship program. If an issue is not assigned to anyone, feel free to work on it, there is no need to ask to be assigned the issue.
Instead, review the issue, understand it and if you feel you can contribute you can just raise a pull request, or add a comment that you are taking a look at this. There are no strict claiming or reserving rules in place, anyone is free to work on any issue, but try to avoid double effort if someone has already got a pull request underway.
## Helpful links
- [Django’s contributor guide](https://docs.djangoproject.com/en/stable/internals/contributing/) is a helpful resource for contributors, even those not contributing to Wagtail.
- [MDN’s open source etiquette](https://developer.mozilla.org/en-US/docs/MDN/Community/Open_source_etiquette) is a great guideline for how to be a great contributor.
- [Learning Git Branching](https://learngitbranching.js.org/) a solid interactive guide to understand how git branching works.
- [Hacktoberfest](https://hacktoberfest.com/) every October, join in the fun and submit pull requests.
- [21 Pull Requests](https://24pullrequests.com/) a December community effort to contribute to open source.
### Inspiration for this content
Some great further reading also
- [5 simple ways anyone can contribute to Wagtail](https://wagtail.org/blog/5-simple-ways-anyone-can-contribute-to-wagtail/)
- [Ten tasty ingredients for a delicious pull request](https://wagtail.org/blog/ten-tasty-ingredients-for-a-delicious-pull-request/)
- [Preparing a Gourmet Pull Request](https://johnfraney.ca/blog/preparing-a-gourmet-pull-request/)
- [Zulip’s contributor guide](https://zulip.readthedocs.io/en/latest/contributing/contributing.html)
- [Documentation for absolute beginners to software development (discussion)](https://github.com/wagtail/wagtail/discussions/9557)
- [New contributor FAQ](https://github.com/wagtail/wagtail/wiki/New-contributor-FAQ)
# flyio.html.md
# Deploying Wagtail with Fly.io + Backblaze
This tutorial will use two platforms to deploy your site. You’ll host your site on [fly.io](https://fly.io) and serve your site’s images on [Backblaze](https://www.backblaze.com).
You can use fly.io to host your site and serve your images. However, storing your images on a platform other than the one hosting your site provides better performance, security, and reliability.
#### NOTE
In this tutorial, you’ll see “yourname” several times. Replace it with a name of your choice.
## Setup Backblaze B2 Cloud Storage
To serve your images, set up a Backblaze B2 storage following these steps:
1. Visit the Backblaze [website](https://www.backblaze.com) in your browser.
2. Click **Products** from the top navigation and then select **B2 Cloud Storage** from the dropdown.
3. Sign up to Backblaze B2 Cloud Storage by following these steps:
a. Enter your email address and password.
b. Select the appropriate region.
c. Click **Sign Up Now**.
4. Verify your email by following these steps:
a. Go to **Account > My Settings** in your side navigation.
b. Click **Verify Email** in the **Security section**.
c. Enter your sign-up email address and then click send **Send Code**.
d. Check your email inbox or spam folder for the verification email.
e. Click the verification link or use the verification code.
5. Create a Bucket by going to **B2 Cloud Storage > Bucket** and clicking **Create a Bucket**.
6. Go to **B2 Cloud Storage > Bucket** and then click **Create a Bucket**.
7. Add your Bucket information as follows:
| Bucket information | Instruction |
|----------------------|--------------------------------------------------------------------|
| Bucket Unique Name | Use a unique Bucket name. For example,*yourname-wagtail-portfolio* |
| Files in Bucket are | Select **Public** |
| Default Encryption | Select **Disable** |
| Object Lock | Select **Disable** |
1. Click **Create a Bucket**.
## Link your site to Backblaze B2 Cloud Storage
After setting up your Backblaze B2 Cloud Storage, you must link it to your portfolio site.
Start by creating a `.env.production` file at the root of your project directory. At this stage, your project directory should look like this:
```text
mysite/
├── base
├── blog
├── home
├── media
├── mysite
├── portfolio
├── search
├── .dockerignore
├── .gitignore
├── .env.production
├── Dockerfile
├── manage.py
├── mysite/
└── requirements.txt
```
Now add the following environment variables to your `.env.production` file:
```text
AWS_STORAGE_BUCKET_NAME=
AWS_S3_ENDPOINT_URL=https://
AWS_S3_REGION_NAME=
AWS_S3_ACCESS_KEY_ID=
AWS_S3_SECRET_ACCESS_KEY=
DJANGO_ALLOWED_HOSTS=
DJANGO_CSRF_TRUSTED_ORIGINS=https://
DJANGO_SETTINGS_MODULE=mysite.settings.production
```
### Fill in your Backblaze B2 bucket information
The next step is to provide values for your environment variables. In your `.env.production` file, use your Backblaze B2 bucket information as values for your environment variables as follows:
| Environment variable | Instruction |
|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AWS_STORAGE_BUCKET_NAME | Use your Backblaze B2 bucket name |
| AWS_S3_ENDPOINT_URL | Use the Backblaze B2 endpoint URL. For example, *https://s3.us-east-005.backblazeb2.com* |
| AWS_S3_REGION_NAME | Determine your bucket’s region from the endpoint URL. For example, if your endpoint URL is *s3.us-east-005.backblazeb2.com*, then your bucket’s region is *us-east-005* |
| AWS_S3_ACCESS_KEY_ID | Leave this empty for now |
| AWS_S3_SECRET_ACCESS_KEY | Leave this empty for now |
| DJANGO_ALLOWED_HOSTS | Leave this empty for now |
| DJANGO_CSRF_TRUSTED_ORIGINS | Use *https://* |
| DJANGO_SETTINGS_MODULE | Use *mysite.settings.production* |
In the preceding table, you didn’t provide values for your `AWS_S3_ACCESS_KEY_ID`, `AWS_S3_SECRET_ACCESS_KEY`, and `DJANGO_ALLOWED_HOSTS`.
To get values for your `AWS_S3_ACCESS_KEY_ID` and `AWS_S3_SECRET_ACCESS_KEY`, follow these steps:
1. Log in to your Backblaze B2 account.
2. Navigate to **Account > Application Keys**.
3. Click **Add a New Application Key**.
4. Configure the application key settings as follows:
| Setting | Instruction |
|-----------------------------|----------------------------------------------------|
| Name of Key | Provide a unique name |
| Allow access to Buckets | Choose the Backblaze B2 bucket you created earlier |
| Type of Access | Select **Read and Write** |
| Allow List All Bucket Names | Leave this unticked |
| File name prefix | Leave field empty |
| Duration (seconds) | Leave field empty |
1. Click **Create New Key**.
Now, use your `keyID` as the value of `AWS_S3_ACCESS_KEY_ID` and `applicationKey` for `AWS_S3_SECRET_ACCESS_KEY` in your `.env.production` file:
| Environment variable | Instruction |
|--------------------------|-----------------------------|
| AWS_S3_ACCESS_KEY_ID | Use your **keyID** |
| AWS_S3_SECRET_ACCESS_KEY | Use your **applicationKey** |
At this stage, the content of your `.env.production` file looks like this:
```text
AWS_STORAGE_BUCKET_NAME=yourname-wagtail-portfolio
AWS_S3_ENDPOINT_URL=https://s3.us-east-005.backblazeb2.com
AWS_S3_REGION_NAME=us-east-005
AWS_S3_ACCESS_KEY_ID=your Backblaze keyID
AWS_S3_SECRET_ACCESS_KEY=your Backblaze applicationKey
DJANGO_ALLOWED_HOSTS=
DJANGO_CSRF_TRUSTED_ORIGINS=https://
DJANGO_SETTINGS_MODULE=mysite.settings.production
```
#### NOTE
The Backblaze B2 storage uses *AWS* and *S3* because it works like Amazon Web Services’ S3.
Do not commit or share your `.env.production `file. Anyone with the variables can access your site.
If you lost your secret application key, create a new key following the preceding instructions.
For more information on how to set up your Backblaze B2 Cloud Storage, read the [Backblaze B2 Cloud Storage Documentation](https://www.backblaze.com/docs/cloud-storage/).
## Set up Fly.io
Now that you’ve linked your site to your Backblaze storage, it’s time to set up Fly.io to host your site.
To set up your Fly.io account, follow these steps:
1. Visit [Fly.io](https://fly.io/) in your browser.
2. Click **Sign Up**.
3. Sign up using your GitHub account, Google account, or the email option.
4. Check your email inbox for the verification link to verify your email.
#### NOTE
If your email verification fails, go to your Fly.io [Dashboard](https://fly.io/dashboard) and try again.
1. Go to **Dashboard > Billing** and click **Add credit card** to add your credit card.
#### NOTE
Adding your credit card allows you to create a project in Fly.io. Fly.io won’t charge you after adding your credit card.
1. [Install flyctl](https://fly.io/docs/hands-on/install-flyctl/) by navigating to your project directory and then running the following command in your terminal:
On macOS:
```sh
# If you have the Homebrew package manager installed, run the following command:
brew install flyctl
# If you don't have the Homebrew package manager installed, run the following command:
curl -L https://fly.io/install.sh | sh
```
On Linux:
```sh
curl -L https://fly.io/install.sh | sh
```
On Windows, navigate to your project directory on **PowerShell**, activate your environment and run the following command:
```doscon
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"
```
#### NOTE
If you get an error on Windows saying the term `pwsh` is not recognized, install [PowerShell MSI](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3#installing-the-msi-package) and then rerun the preceding Windows command.
1. [Sign in](https://fly.io/docs/hands-on/sign-in/) to your Fly.io by running the following command:
```sh
fly auth login
```
If you use Microsoft WSL, then run:
```doscon
ln -s /usr/bin/wslview /usr/local/bin/xdg-open
```
#### NOTE
If you successfully install flyctl but get an error saying “`fly` is not recognized” or “flyctl: command not found error”, then you must add flyctl to your PATH. For more information, read [Getting flyctl: command not found error post install](https://community.fly.io/t/getting-flyctl-command-not-found-error-post-install/4954/1).
1. Create your Fly.io project by running `fly launch`. Then press `y` to configure the settings.
2. You will be taken to an admin screen on fly.io. Fill out the fields as follows:
| Field | Instruction |
|--------------------------------|--------------------------------------------------------------------------------------|
| Choose a region for deployment | Select the region closest to the *AWS_S3_REGION_NAME* in your *env.production* file. |
| CPU & Memory | VM Size - shared-cpu-1x VM Memory - 512 MB |
| Database | Fly Postgres - choose smallest option |
click confirm **Confirm settings**
#### NOTE
Not creating the database directly with the application leads to the app and the database not connected.
If the app is going to be launched again using fly launch,
it’s recommended to create a new database with the launch of the app through the web UI.
1. Back in your terminal, answer the resulting prompt questions as follows:
| Question | Instruction |
|------------------------------|---------------|
| Overwrite “…/.dockerignore”? | Enter *y* |
| Overwrite “…/Dockerfile”? | Enter *y* |
The `fly launch` command creates two new files, `Dockerfile` and `fly.toml`, in your project directory.
If you use a third-party app terminal like the Visual Studio Code terminal, you may get an error creating your Postgres database. To rectify this error, follow these steps:
1. Delete `fly.toml` file from your project directory.
2. Go to your Fly.io account in your browser and click **Dashboard**.
3. Click the created app in your **Apps** list.
4. Click **Settings** in your side navigation.
5. Click **Delete app**.
6. Enter the name your app.
7. Click **Yes delete it**.
8. Repeat steps 3, 4, 5, 6, and 7 for all apps in your **Apps** list.
9. Run the `fly launch` command in your built-in terminal or PowerShell MSI on Windows.
## Customize your site to use Fly.io
Now, you must configure your portfolio site for the final deployment.
The `fly launch` command creates two new files, `Dockerfile` and `fly.toml`, in your project directory.
Add the following to your `.gitignore` file to make Git ignore your environment files:
```default
.env*
```
Also, add the following to your `.dockerignore` file to make Docker ignore your environment and media files:
```default
.env*
media
```
Configure your Fly.io to use `1` worker. This allows your site to work better with Fly.io’s low memory allowance. To do this, modify the last line of your `Dockerfile` as follows:
```default
CMD ["gunicorn", "--bind", ":8000", "--workers", "1", "mysite.wsgi"]
```
Also, check if your `fly.toml` file has the following:
```toml
[deploy]
release_command = "python manage.py migrate --noinput"
```
Your `fly.toml` file should look as follows:
```toml
app = "yourname-wagtail-portfolio"
primary_region = "lhr"
console_command = "/code/manage.py shell"
[build]
# add the deploy command:
[deploy]
release_command = "python manage.py migrate --noinput"
[env]
PORT = "8000"
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
[[statics]]
guest_path = "/code/static"
url_prefix = "/static/"
```
Now add your production dependencies by replacing the content of your `requirements.txt` file with the following:
```text
Django>=4.2,<4.3
wagtail==5.1.1
gunicorn>=21.2.0,<22.0.0
psycopg[binary]>=3.1.10,<3.2.0
dj-database-url>=2.1.0,<3.0.0
whitenoise>=5.0,<5.1
django-storages[s3]>=1.14.0,<2.0.0
```
The preceding dependencies ensure that the necessary tools and libraries are in place to run your site successfully on the production server. The following are the explanations for the dependencies you may be unaware of:
1. `gunicorn` is a web server that runs your site in Docker.
2. `psycopg` is a PostgreSQL adapter that connects your site to a PostgreSQL database.
3. `dj-database-url` is a package that simplifies your database configurations and connects to your site to a PostgreSQL database.
4. `whitenoise` is a Django package that serves static files.
5. `django-storages` is a Django library that handles your file storage and connects to your Backblaze B2 storage.
Replace the content of your `mysite/settings/production.py` file with the following:
```python
import os
import random
import string
import dj_database_url
from .base import *
DEBUG = False
DATABASES = {
"default": dj_database_url.config(
conn_max_age=600,
conn_health_checks=True
)
}
SECRET_KEY = os.environ["SECRET_KEY"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "*").split(",")
CSRF_TRUSTED_ORIGINS = os.getenv("DJANGO_CSRF_TRUSTED_ORIGINS", "").split(",")
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
MIDDLEWARE.append("whitenoise.middleware.WhiteNoiseMiddleware")
STORAGES["staticfiles"]["BACKEND"] = "whitenoise.storage.CompressedManifestStaticFilesStorage"
if "AWS_STORAGE_BUCKET_NAME" in os.environ:
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
AWS_S3_REGION_NAME = os.getenv("AWS_S3_REGION_NAME")
AWS_S3_ENDPOINT_URL = os.getenv("AWS_S3_ENDPOINT_URL")
AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID")
AWS_S3_SECRET_ACCESS_KEY = os.getenv("AWS_S3_SECRET_ACCESS_KEY")
INSTALLED_APPS.append("storages")
STORAGES["default"]["BACKEND"] = "storages.backends.s3boto3.S3Boto3Storage"
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
},
},
}
WAGTAIL_REDIRECTS_FILE_STORAGE = "cache"
try:
from .local import *
except ImportError:
pass
```
The explanation of some of the code in your `mysite/settings/production.py` file is as follows:
1. `DEBUG = False` turns off debugging for the production environment. It’s important for security and performance.
2. `SECRET_KEY = os.environ["SECRET_KEY"]` retrieves the project’s secret key from your environment variable.
3. `SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")` ensures that Django can detect a secure HTTPS connection if you deploy your site behind a reverse proxy like Heroku.
4. `SECURE_SSL_REDIRECT = True` enforces HTTPS redirect. This ensures that all connections to the site are secure.
5. `ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "*").split(",")` defines the hostnames that can access your site. It retrieves its values from the `DJANGO_ALLOWED_HOSTS` environment variable. If no specific hosts are defined, it defaults to allowing all hosts.
6. `EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"` configures your site to use the console email backend. You can configure this to use a proper email backend for sending emails.
7. `WAGTAIL_REDIRECTS_FILE_STORAGE = "cache"` configures the file storage for Wagtail’s redirects. Here, you set it to use cache.
Now, complete the configuration of your environment variables by modifying your `.env.production` file as follows:
| Environment variable | Instruction |
|-----------------------------|-------------------------------------------------------------------------------------------------------|
| DJANGO_ALLOWED_HOSTS | This must match your fly.io project name. For example, *yourname-wagtail-portfolio.fly.dev* |
| DJANGO_CSRF_TRUSTED_ORIGINS | This must match your project’s domain name. For example, *https://yourname-wagtail-portfolio.fly.dev* |
The content of your `.env.production` file should now look like this:
```text
AWS_STORAGE_BUCKET_NAME=yourname-wagtail-portfolio
AWS_S3_ENDPOINT_URL=https://s3.us-east-005.backblazeb2.com
AWS_S3_REGION_NAME=us-east-005
AWS_S3_ACCESS_KEY_ID=your Backblaze keyID
AWS_S3_SECRET_ACCESS_KEY=your Backblaze applicationKey
DJANGO_ALLOWED_HOSTS=yourname-wagtail-portfolio.fly.dev
DJANGO_CSRF_TRUSTED_ORIGINS=https://yourname-wagtail-portfolio.fly.dev
DJANGO_SETTINGS_MODULE=mysite.settings.production
```
Set the secrets for Fly.io to use by running:
```sh
flyctl secrets import < .env.production
```
On Windows, run the following command in your PowerShell MSI:
```doscon
Get-Content .env.production | flyctl secrets import
```
Finally, deploy your site to Fly.io by running the following command:
```sh
fly deploy --ha=false
```
#### NOTE
Running “fly deploy” creates two machines for your app. Using the “–ha=false” flag creates one machine for your app.
Congratulations! Your site is now live. However, you must add content to it. Start by creating an admin user for your live site. Run the following command:
```sh
flyctl ssh console
```
Then run:
```sh
DJANGO_SUPERUSER_USERNAME=username DJANGO_SUPERUSER_EMAIL=mail@example.com DJANGO_SUPERUSER_PASSWORD=password python manage.py createsuperuser --noinput
```
#### NOTE
Ensure you replace *username*, *mail@example.com*, and *password* with a username, email address, and password of your choice.
For more information on how to set up your Django project on Fly.io, read [Django on Fly.io](https://fly.io/docs/django/).
## Add content to your live site
All this while, you’ve been adding content to your site in the local environment. Now that your site is live on a server, you must add content to the live site. To add content to your live site, go to ` https://yourname-wagtail-portfolio.fly.dev/admin/` in your browser and follow the steps in the following sub-sections of the tutorial:
- [Add content to your homepage](../tutorial/customize_homepage.md#add-content-to-your-homepage)
- [Add your social media links](../tutorial/create_footer_for_all_pages.md#add-your-social-media-links)
- [Add footer text](../tutorial/create_footer_for_all_pages.md#add-footer-text)
- [Add pages to your site menu](../tutorial/set_up_site_menu.md#add-pages-to-your-site-menu)
- [Add your contact information](../tutorial/create_contact_page.md#add-your-contact-information)
- [Add your resume](../tutorial/create_portfolio_page.md#add-your-resume)
#### NOTE
If you encounter errors while trying to access your live site in your browser, check your application logs in your Fly.io Dashboard. To check your application logs, click **Dashboard > Apps > yourname-wagtail-portfolio > Monitoring**
# focal_points.html.md
# Focal points
Focal points are used to indicate to Wagtail the area of an image that contains the subject.
This is used by the `fill` filter to focus the cropping on the subject, and avoid cropping into it.
Focal points can be defined manually by a Wagtail user, or automatically by using face or feature detection.
## Setting the `background-position` inline style based on the focal point
When using a Wagtail image as the background of an element, you can use the `.background_position_style`
attribute on the rendition to position the rendition based on the focal point in the image:
```html+django
{% image page.image width-1024 as image %}
```
For sites enforcing a Content Security Policy, you can apply those styles via a `