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.
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:
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 customisations. For more details, see [the project template reference](/reference/project_template). 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.
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.
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).
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](image_file_formats), [output image format](output_image_format) and [image quality](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).
* Refactor GroupPagePermission to use Django's Permission model (Sage Abdullah)
### Snippet enhancements
we have made a number of 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 customisations 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)
* 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 customised](customising_group_views_permissions_order) (Daniel Kirkham)
* Add support for adding [HTML `attrs`](panels_attrs) on `FieldPanel`, `FieldRowPanel`, `MultiFieldPanel`, and others (Aman Pandey, Antoni Martyniuk, LB (Ben) Johnston)
* Prevent choosers from failing when initial value is an unrecognised ID such as when moving a page from a location where `parent_page_types` would disallow it (Dan Braghis)
* Improve accessibility for header search, remove autofocus on page load, advise screen readers that content has changed when results update (LB (Ben) Johnston)
## Upgrade considerations - changes affecting all projects
### `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:
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:
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 ordering which never intentionally implemented.
The default ordering is now `["content_type__app_label", "content_type__model"]`, which can now be customised [](customising_group_views_permissions_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](custom_image_renditions_cache) to use a different cache backend or to provide
additional configuration parameters.
## Upgrade considerations - deprecation of old functionality
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`.
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`](snippets) instead. See [](../reference/contrib/modeladmin/migrating_to_snippets) for more details.
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.
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
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.
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.
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.
### 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.
Note: The `data-w-tag-options-value` is a JSON object serialised 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 behaviour. 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](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 }}
<script>
window.headerSearch = {
url: "{% url 'myapp:search_results' %}",
termInput: '#id_q',
targetOutput: '#my-results',
};
</script>
{% 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 customisations that manually declare or override `window.headerSearch`, here's how to adopt the new approach.
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.