May 16, 2022
---
local:
depth: 1
---
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:
Other changes that are specific to the Page Editor include:
Further updates to the page editor are expected in the next release. Development on this feature was sponsored by Google.
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.
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.
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.
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:
class LandingPage(Page):
page_description = "Use this page for converting users"
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.
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.
content_json
TextField
with content
JSONField
in PageRevision
(Sage Abdullah)replace_text
management command (Sage Abdullah)data_json
TextField
with data
JSONField
in BaseLogEntry
(Sage Abdullah):focus-visible
for cross-browser consistency (Paarth Agarwal)ModelAdmin
(Serafeim Papastefanos)README.md
logo to work for GitHub dark mode (Paarth Agarwal)If-Modified-Since
header in sendfile_streaming_backend
which was only used by IE (Mariusz Felisiak)StreamField
to use JSONField
to store data, rather than TextField
(Sage Abdullah)trimmed
attribute to all blocktrans tags, so spacing is more reliable in translated strings (Harris Lapiroff)ModelAdmin
to manage Tag
s (Abdulmajeed Isa)BASE_URL
(undocumented) to WAGTAILADMIN_BASE_URL
and add to documentation, BASE_URL
will be removed in a future release (Sandil Ranasinghe)AbstractEmailForm
(Jake Howard)WAGTAILIMAGES_RENDITION_STORAGE
setting to allow an alternative image rendition storage (Heather White)wagtail_update_image_renditions
management command to regenerate image renditions or purge all existing renditions (Hitansh Shah, Onno Timmerman, Damian Moore)PageQuerySet.specific()
to reduce memory consumption (Andy Babic)TAG_SPACES_ALLOWED
is True
or False
(Abdulmajeed Isa)AbstractFormSubmission
's form_data
to use JSONField to store form submissions (Jake Howard)simple_translations
ensure that the user is redirected to the page edit view when submitting for a single locale (Mitchel Cabuloy)Form
pages, ensure that all added fields are correctly shown in the preview (Joshua Munn)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)aria-haspopup="menu"
for all sidebar menu items that have sub-menus (LB (Ben Johnston))aria-expanded
is always explicitly set as a string in sidebar (LB (Ben Johnston))role="main"
attributes on <main>
elements causing HTML validation issues (Luis Espinoza)lang
attributes to <html>
elements (James Ray)thumb_col_header_text
is correctly used by ThumbnailMixin
within ModelAdmin
as the column header label (Kyle J. Roux)exclude_fields_in_copy
(John-Scott Atlakson)Orderable
s when page is copied without being published (Kalob Taulien, Dan Braghis)GenericRelation
when copying pages (John-Scott Atlakson)wagtail updatemodulepaths
works when system locale is not UTF-8 (Matt Westcott)STATIC_URL
is not "/static/"
(Jacob Topp-Mugglestone)Various modules of Wagtail have been reorganised, and imports should be updated as follows:
wagtail.core.utils
module is renamed to wagtail.coreutils
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
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:
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
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
. 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.
JSON1
extension enabledDue to JSONField
requirements, SQLite will only be supported with the JSON1 extension enabled.
See Enabling JSON1 extension on SQLite and JSON1 extension for details.
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 to keep access to IE11-only sites, while other sites and apps like Wagtail can leverage modern browser capabilities.
Hallo was deprecated in Wagtail v2.0 (February 2018) 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 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.
clean_name
on AbstractFormField
If you are upgrading a pre-2.10 project that uses the Wagtail 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 package) has been dropped in Wagtail 3.0. Any new form fields created under Wagtail 2.10 or above use the AnyAscii library instead.
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.
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:
Widget
class that produces your desired HTML rendering.widget_overrides
method, your code should instead call register_form_field_override
so that the desired widget is always selected for the relevant model field type.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 favour of a new get_form_options
method that returns a dict of configuration options to be passed on to the generated form class:
required_fields
should instead return this value as a fields
item in the dict returned from get_form_options
required_formsets
should instead return this value as a formsets
item in the dict returned from get_form_options
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 customisations to the form object, use a custom form class 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 initialisation 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
:
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.
Some changes of behaviour have been made to ModelAdmin as a result of the panel API changes:
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.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
.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.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.
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:
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:
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",
),
]
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 customisations 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 serialisable Python object. When interacting with the form_data
you will now receive a Python object and not a string.
Example change
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
)
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 and existed only as a an unofficial implementation in IE browsers.