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.
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](wagtailsnippets_saving_revisions_of_snippets). To configure how often notifications are updated, use [`WAGTAIL_EDITING_SESSION_PING_INTERVAL`](wagtail_editing_session_ping_interval).
This feature was implemented by Matt Westcott and Sage Abdullah.
The [built-in accessibility checker](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.
* Support a [`HOSTNAMES` parameter on `WAGTAILFRONTENDCACHE`](frontendcache_multiple_backends) to define which hostnames a backend should respond to (Jake Howard, sponsored by Oxfam America)
* Ensure that system checks for `WAGTAIL_DATE_FORMAT`, `WAGTAIL_DATETIME_FORMAT` and `WAGTAIL_TIME_FORMAT` take `FORMAT_MODULE_PATH` into account (Sébastien Corbin)
### 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](frontendcache_multiple_backends), with a `HOSTNAMES` parameter to define the hostnames associated with each one. For example, a configuration such as:
Models registered with a `ModelViewSet` will now automatically have their {class}`~django.contrib.auth.models.Permission` objects registered in the Groups administration area. Previously, you need to use the [`register_permissions`](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 {attr}`~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 {meth}`~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
### 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](user_form_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"))
If you already have a custom `GroupViewSet` as described in [](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:
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 {attr}`~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`.
### 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), you will need to make the following changes.
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.
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`.
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.
### 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](custom_tasks_behavior), such as by overriding `Task.get_actions()`.