Pages
Wagtail requires a little careful setup to define the types of content that you want to present through your website. The basic unit of content in Wagtail is the Page
, and all of your page-level content will inherit basic webpage-related properties from it. But for the most part, you will be defining content yourself, through the construction of Django models using Wagtail’s Page
as a base.
Wagtail organizes content created from your models in a tree, which can have any structure and combination of model objects in it. Wagtail doesn’t prescribe ways to organize and interrelate your content, but here we’ve sketched out some strategies for organizing your models.
The presentation of your content, the actual webpages, includes the normal use of the Django template system. We’ll cover additional functionality that Wagtail provides at the template level later on.
Theory
Introduction to Trees
If you’re unfamiliar with trees as an abstract data type, you might want to review the concepts involved.
As a web developer, though, you probably already have a good understanding of trees as filesystem directories or paths. Wagtail pages can create the same structure, as each page in the tree has its own URL path, like so:
/
people/
nien-nunb/
laura-roslin/
events/
captain-picard-day/
winter-wrap-up/
The Wagtail admin interface uses the tree to organize content for editing, letting you navigate up and down levels in the tree through its Explorer menu. This method of organization is a good place to start in thinking about your own Wagtail models.
Nodes and Leaves
It might be handy to think of the Page
-derived models you want to create as being one of two node types: parents and leaves. Wagtail isn’t prescriptive in this approach, but it’s a good place to start if you’re not experienced in structuring your own content types.
Nodes
Parent nodes on the Wagtail tree probably want to organize and display a browse-able index of their descendants. A blog, for instance, needs a way to show a list of individual posts.
A Parent node could provide its own function returning its descendant objects.
class EventPageIndex(Page):
# ...
def events(self):
# Get list of live event pages that are descendants of this page
events = EventPage.objects.live().descendant_of(self)
# Filter events list to get ones that are either
# running now or start in the future
events = events.filter(date_from__gte=date.today())
# Order by date
events = events.order_by('date_from')
return events
This example makes sure to limit the returned objects to pieces of content which make sense, specifically ones which have been published through Wagtail’s admin interface (live()
) and are children of this node (descendant_of(self)
). By setting a subpage_types
class property in your model, you can specify which models are allowed to be set as children, and by setting a parent_page_types
class property, you can specify which models are allowed to be parents of this page model. Wagtail will allow any Page
-derived model by default. Regardless, it’s smart for a parent model to provide an index filtered to make sense.
Leaves
Leaves are the pieces of content itself, a page which is consumable, and might just consist of a bunch of properties. A blog page leaf might have some body text and an image. A person page leaf might have a photo, a name, and an address.
It might be helpful for a leaf to provide a way to back up along the tree to a parent, such as in the case of breadcrumbs navigation. The tree might also be deep enough that a leaf’s parent won’t be included in general site navigation.
The model for the leaf could provide a function that traverses the tree in the opposite direction and returns an appropriate ancestor:
class EventPage(Page):
# ...
def event_index(self):
# Find closest ancestor which is an event index
return self.get_ancestors().type(EventIndexPage).last()
If defined, subpage_types
and parent_page_types
will also limit the parent models allowed to contain a leaf. If not, Wagtail will allow any combination of parents and leafs to be associated in the Wagtail tree. Like with index pages, it’s a good idea to make sure that the index is actually of the expected model to contain the leaf.
Other Relationships
Your Page
-derived models might have other interrelationships which extend the basic Wagtail tree or depart from it entirely. You could provide functions to navigate between siblings, such as a “Next Post” link on a blog page (post->post->post
). It might make sense for subtrees to interrelate, such as in a discussion forum (forum->post->replies
) Skipping across the hierarchy might make sense, too, as all objects of a certain model class might interrelate regardless of their ancestors (events = EventPage.objects.all
). It’s largely up to the models to define their interrelations, the possibilities are really endless.
Anatomy of a Wagtail Request
For going beyond the basics of model definition and interrelation, it might help to know how Wagtail handles requests and constructs responses. In short, it goes something like:
- Django gets a request and routes through Wagtail’s URL dispatcher definitions
- Wagtail checks the hostname of the request to determine which
Site
record will handle this request.
- Starting from the root page of that site, Wagtail traverses the page tree, calling the
route()
method and letting each page model decide whether it will handle the request itself or pass it on to a child page.
- The page responsible for handling the request returns a
RouteResult
object from route()
, which identifies the page along with any additional args
/kwargs
to be passed to serve()
.
- Wagtail calls
serve()
, which constructs a context using get_context()
serve()
finds a template to pass it to using get_template()
- A response object is returned by
serve()
and Django responds to the requester.
You can apply custom behaviour to this process by overriding Page
class methods such as route()
and serve()
in your own models. For examples, see Recipes.
Scheduled Publishing
Page publishing can be scheduled through the Go live date/time feature in the Settings tab of the Edit page. This allows you to set set up initial page publishing or a page update in advance.
In order for pages to be published at the scheduled time you should set up the publish_scheduled_pages management command.
The basic workflow is as follows:
- Scheduling a revision for a page that is not currently live means that page will go live when the scheduled time comes.
- Scheduling a revision for a page that is already live means that revision will be published when the time comes.
- If page has a scheduled revision and you set another revision to publish immediately, the scheduled revision will be unscheduled.
The Revisions view for a given page will show which revision is scheduled and when it is scheduled for. A scheduled revision in the list will also provide an Unschedule button to cancel it.
Recipes
Overriding the serve()
Method
Wagtail defaults to serving Page
-derived models by passing a reference to the page object to a Django HTML template matching the model’s name, but suppose you wanted to serve something other than HTML? You can override the serve()
method provided by the Page
class and handle the Django request and response more directly.
Consider this example from the Wagtail demo site’s models.py
, which serves an EventPage
object as an iCal file if the format
variable is set in the request:
class EventPage(Page):
...
def serve(self, request):
if "format" in request.GET:
if request.GET['format'] == 'ical':
# Export to ical format
response = HttpResponse(
export_event(self, 'ical'),
content_type='text/calendar',
)
response['Content-Disposition'] = 'attachment; filename=' + self.slug + '.ics'
return response
else:
# Unrecognised format error
message = 'Could not export event\n\nUnrecognised format: ' + request.GET['format']
return HttpResponse(message, content_type='text/plain')
else:
# Display event page as usual
return super().serve(request)
serve()
takes a Django request object and returns a Django response object. Wagtail returns a TemplateResponse
object with the template and context which it generates, which allows middleware to function as intended, so keep in mind that a simpler response object like a HttpResponse
will not receive these benefits.
With this strategy, you could use Django or Python utilities to render your model in JSON or XML or any other format you’d like.
Adding Endpoints with Custom route()
Methods
Note
A much simpler way of adding more endpoints to pages is provided by the routable_page
module.
Wagtail routes requests by iterating over the path components (separated with a forward slash /
), finding matching objects based on their slug, and delegating further routing to that object’s model class. The Wagtail source is very instructive in figuring out what’s happening. This is the default route()
method of the Page
class:
class Page(...):
...
def route(self, request, path_components):
if path_components:
# request is for a child of this page
child_slug = path_components[0]
remaining_components = path_components[1:]
# find a matching child or 404
try:
subpage = self.get_children().get(slug=child_slug)
except Page.DoesNotExist:
raise Http404
# delegate further routing
return subpage.specific.route(request, remaining_components)
else:
# request is for this very page
if self.live:
# Return a RouteResult that will tell Wagtail to call
# this page's serve() method
return RouteResult(self)
else:
# the page matches the request, but isn't published, so 404
raise Http404
route()
takes the current object (self
), the request
object, and a list of the remaining path_components
from the request URL. It either continues delegating routing by calling route()
again on one of its children in the Wagtail tree, or ends the routing process by returning a RouteResult
object or raising a 404 error.
The RouteResult
object (defined in wagtail.core.url_routing) encapsulates all the information Wagtail needs to call a page’s serve()
method and return a final response: this information consists of the page object, and any additional args
/kwargs
to be passed to serve()
.
By overriding the route()
method, we could create custom endpoints for each object in the Wagtail tree. One use case might be using an alternate template when encountering the print/
endpoint in the path. Another might be a REST API which interacts with the current object. Just to see what’s involved, lets make a simple model which prints out all of its child path components.
First, models.py
:
from django.shortcuts import render
from wagtail.core.url_routing import RouteResult
from django.http.response import Http404
from wagtail.core.models import Page
...
class Echoer(Page):
def route(self, request, path_components):
if path_components:
# tell Wagtail to call self.serve() with an additional 'path_components' kwarg
return RouteResult(self, kwargs={'path_components': path_components})
else:
if self.live:
# tell Wagtail to call self.serve() with no further args
return RouteResult(self)
else:
raise Http404
def serve(self, path_components=[]):
return render(request, self.template, {
'page': self,
'echo': ' '.join(path_components),
})
This model, Echoer
, doesn’t define any properties, but does subclass Page
so objects will be able to have a custom title and slug. The template just has to display our {{ echo }}
property.
Now, once creating a new Echoer
page in the Wagtail admin titled “Echo Base,” requests such as:
http://127.0.0.1:8000/echo-base/tauntaun/kennel/bed/and/breakfast/
Will return:
tauntaun kennel bed and breakfast
Be careful if you’re introducing new required arguments to the serve()
method - Wagtail still needs to be able to display a default view of the page for previewing and moderation, and by default will attempt to do this by calling serve()
with a request object and no further arguments. If your serve()
method does not accept that as a method signature, you will need to override the page’s serve_preview()
method to call serve()
with suitable arguments:
def serve_preview(self, request, mode_name):
return self.serve(request, color='purple')
Tagging
Wagtail provides tagging capabilities through the combination of two Django modules, django-taggit (which provides a general-purpose tagging implementation) and django-modelcluster (which extends django-taggit’s TaggableManager
to allow tag relations to be managed in memory without writing to the database - necessary for handling previews and revisions). To add tagging to a page model, you’ll need to define a ‘through’ model inheriting from TaggedItemBase
to set up the many-to-many relationship between django-taggit’s Tag
model and your page model, and add a ClusterTaggableManager
accessor to your page model to present this relation as a single tag field.
In this example, we set up tagging on BlogPage
through a BlogPageTag
model:
# models.py
from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey('demo.BlogPage', on_delete=models.CASCADE, related_name='tagged_items')
class BlogPage(Page):
...
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
promote_panels = Page.promote_panels + [
...
FieldPanel('tags'),
]
Wagtail’s admin provides a nice interface for inputting tags into your content, with typeahead tag completion and friendly tag icons.
We can now make use of the many-to-many tag relationship in our views and templates. For example, we can set up the blog’s index page to accept a ?tag=...
query parameter to filter the BlogPage
listing by tag:
from django.shortcuts import render
class BlogIndexPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
# Get blog entries
blog_entries = BlogPage.objects.child_of(self).live()
# Filter by tag
tag = request.GET.get('tag')
if tag:
blog_entries = blog_entries.filter(tags__name=tag)
context['blog_entries'] = blog_entries
return context
Here, blog_entries.filter(tags__name=tag)
follows the tags
relation on BlogPage
, to filter the listing to only those pages with a matching tag name before passing this to the template for rendering. We can now update the blog_page.html
template to show a list of tags associated with the page, with links back to the filtered index page:
{% for tag in page.tags.all %}
<a href="{% pageurl page.blog_index %}?tag={{ tag }}">{{ tag }}</a>
{% endfor %}
Iterating through page.tags.all
will display each tag associated with page
, while the links back to the index make use of the filter option added to the BlogIndexPage
model. A Django query could also use the tagged_items
related name field to get BlogPage
objects associated with a tag.
The same approach can be used to add tagging to non-page models managed through Snippets and ModelAdmin. In this case, the model must inherit from modelcluster.models.ClusterableModel
to be compatible with ClusterTaggableManager
.
Custom tag models
In the above example, any newly-created tags will be added to django-taggit’s default Tag
model, which will be shared by all other models using the same recipe as well as Wagtail’s image and document models. In particular, this means that the autocompletion suggestions on tag fields will include tags previously added to other models. To avoid this, you can set up a custom tag model inheriting from TagBase
, along with a ‘through’ model inheriting from ItemBase
, which will provide an independent pool of tags for that page model.
from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TagBase, ItemBase
class BlogTag(TagBase):
class Meta:
verbose_name = "blog tag"
verbose_name_plural = "blog tags"
class TaggedBlog(ItemBase):
tag = models.ForeignKey(
BlogTag, related_name="tagged_blogs", on_delete=models.CASCADE
)
content_object = ParentalKey(
to='demo.BlogPage',
on_delete=models.CASCADE,
related_name='tagged_items'
)
class BlogPage(Page):
...
tags = ClusterTaggableManager(through='demo.TaggedBlog', blank=True)
Within the admin, the tag field will automatically recognise the custom tag model being used, and will offer autocomplete suggestions taken from that tag model.
Disabling free tagging
By default, tag fields work on a “free tagging” basis: editors can enter anything into the field, and upon saving, any tag text not recognised as an existing tag will be created automatically. To disable this behaviour, and only allow editors to enter tags that already exist in the database, custom tag models accept a free_tagging = False
option:
from taggit.models import TagBase
from wagtail.snippets.models import register_snippet
@register_snippet
class BlogTag(TagBase):
free_tagging = False
class Meta:
verbose_name = "blog tag"
verbose_name_plural = "blog tags"
Here we have registered BlogTag
as a snippet, to provide an interface for administrators (and other users with the appropriate permissions) to manage the allowed set of tags. With the free_tagging = False
option set, editors can no longer enter arbitrary text into the tag field, and must instead select existing tags from the autocomplete dropdown.
Have redirects created automatically when changing page slug
You may want redirects created automatically when a url gets changed in the admin so as to avoid broken links. You can add something like the following block to a wagtail_hooks.py
file within one of your project’s apps.
from wagtail.core import hooks
from wagtail.contrib.redirects.models import Redirect
# Create redirect when editing slugs
@hooks.register('before_edit_page')
def create_redirect_on_slug_change(request, page):
if request.method == 'POST':
if page.slug != request.POST['slug']:
Redirect.objects.create(
old_path=page.url[:-1],
site=page.get_site(),
redirect_page=page
)
Note: This does not work in some cases e.g. when you redirect a page, create a new page in that url and then move the new one. It should be helpful in most cases however.
Panel types
Built-in Fields and Choosers
Django’s field types are automatically recognised and provided with an appropriate widget for input. Just define that field the normal Django way and pass the field name into FieldPanel
when defining your panels. Wagtail will take care of the rest.
Here are some Wagtail-specific types that you might include as fields in your models.
FieldPanel
-
class
wagtail.admin.edit_handlers.
FieldPanel
(field_name, classname=None, widget=None, heading='', disable_comments=False)
This is the panel used for basic Django field types.
-
field_name
This is the name of the class property used in your model definition.
-
classname
This is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields.
The CSS class full
can be used to format the panel so it covers the full width of the Wagtail page editor.
The CSS class title
can be used to give the field a larger text size, suitable for representing page titles and section headings.
-
widget
(optional)
This parameter allows you to specify a Django form widget to use instead of the default widget for this field type.
-
heading
(optional)
This allows you to override the heading for the panel, which will otherwise be set automatically using the form field’s label (taken in turn from a model field’s verbose_name
).
-
disable_comments
(optional)
This allows you to prevent a field level comment button showing for this panel if set to True
(see Commenting).
StreamFieldPanel
-
class
wagtail.admin.edit_handlers.
StreamFieldPanel
(field_name, classname=None, widget=None)
This is the panel used for Wagtail’s StreamField type (see How to use StreamField for mixed content).
-
FieldPanel.
field_name
This is the name of the class property used in your model definition.
-
FieldPanel.
classname
(optional)
This is a string of optional CSS classes given to the panel which are used in formatting and scripted interactivity. By default, panels are formatted as inset fields.
The CSS class full
can be used to format the panel so it covers the full width of the Wagtail page editor.
MultiFieldPanel
-
class
wagtail.admin.edit_handlers.
MultiFieldPanel
(children, heading="", classname=None)
This panel condenses several FieldPanel
s or choosers, from a list
or tuple
, under a single heading
string.
-
children
A list
or tuple
of child panels
-
heading
A heading for the fields
Collapsing MultiFieldPanels to save space
By default, MultiFieldPanel
s are expanded and not collapsible. Adding collapsible
to classname
will enable the collapse control. Adding both collapsible
and collapsed
to the classname
parameter will load the editor page with the MultiFieldPanel
collapsed under its heading.
content_panels = [
MultiFieldPanel(
[
ImageChooserPanel('cover'),
DocumentChooserPanel('book_file'),
PageChooserPanel('publisher'),
],
heading="Collection of Book Fields",
classname="collapsible collapsed"
),
]
InlinePanel
-
class
wagtail.admin.edit_handlers.
InlinePanel
(relation_name, panels=None, classname='', heading='', label='', help_text='', min_num=None, max_num=None)
This panel allows for the creation of a “cluster” of related objects over a join to a separate model, such as a list of related links or slides to an image carousel.
This is a powerful but complex feature which will take some space to cover, so we’ll skip over it for now. For a full explanation on the usage of InlinePanel
, see Inline Panels and Model Clusters.
FieldRowPanel
-
class
wagtail.admin.edit_handlers.
FieldRowPanel
(children, classname=None)
This panel creates a columnar layout in the editing interface, where each of the child Panels appears alongside each other rather than below.
Use of FieldRowPanel particularly helps reduce the “snow-blindness” effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example if you created a model representing an “Event” which had a starting date and ending date, it may be intuitive to find the start and end date on the same “row”.
By default, the panel is divided into equal-width columns, but this can be overridden by adding col*
class names to each of the child Panels of the FieldRowPanel. The Wagtail editing interface is laid out using a grid system, in which the maximum width of the editor is 12 columns. Classes col1
-col12
can be applied to each child of a FieldRowPanel. The class col3
will ensure that field appears 3 columns wide or a quarter the width. col4
would cause the field to be 4 columns wide, or a third the width.
-
children
A list
or tuple
of child panels to display on the row
-
classname
A class to apply to the FieldRowPanel as a whole
HelpPanel
-
class
wagtail.admin.edit_handlers.
HelpPanel
(content='', template='wagtailadmin/edit_handlers/help_panel.html', heading='', classname='')
-
content
HTML string that gets displayed in the panel.
-
template
Path to a template rendering the full panel HTML.
-
heading
A heading for the help content.
-
classname
String of CSS classes given to the panel which are used in formatting and scripted interactivity.
PageChooserPanel
-
class
wagtail.admin.edit_handlers.
PageChooserPanel
(field_name, page_type=None, can_choose_root=False)
You can explicitly link Page
-derived models together using the Page
model and PageChooserPanel
.
from wagtail.core.models import Page
from wagtail.admin.edit_handlers import PageChooserPanel
class BookPage(Page):
related_page = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
)
content_panels = Page.content_panels + [
PageChooserPanel('related_page', 'demo.PublisherPage'),
]
PageChooserPanel
takes one required argument, the field name. Optionally, specifying a page type (in the form of an "appname.modelname"
string) will filter the chooser to display only pages of that type. A list or tuple of page types can also be passed in, to allow choosing a page that matches any of those page types:
PageChooserPanel('related_page', ['demo.PublisherPage', 'demo.AuthorPage'])
Passing can_choose_root=True
will allow the editor to choose the tree root as a page. Normally this would be undesirable, since the tree root is never a usable page, but in some specialised cases it may be appropriate; for example, a page with an automatic “related articles” feed could use a PageChooserPanel to select which subsection articles will be taken from, with the root corresponding to ‘everywhere’.
ImageChooserPanel
-
class
wagtail.images.edit_handlers.
ImageChooserPanel
(field_name)
Wagtail includes a unified image library, which you can access in your models through the Image
model and the ImageChooserPanel
chooser. Here’s how:
from wagtail.images.models import Image
from wagtail.images.edit_handlers import ImageChooserPanel
class BookPage(Page):
cover = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
content_panels = Page.content_panels + [
ImageChooserPanel('cover'),
]
Django’s default behaviour is to “cascade” deletions through a ForeignKey relationship, which may not be what you want. This is why the null
, blank
, and on_delete
parameters should be set to allow for an empty field. ImageChooserPanel
takes only one argument: the name of the field.
Displaying Image
objects in a template requires the use of a template tag. See How to use images in templates.
DocumentChooserPanel
-
class
wagtail.documents.edit_handlers.
DocumentChooserPanel
(field_name)
For files in other formats, Wagtail provides a generic file store through the Document
model:
from wagtail.documents.models import Document
from wagtail.documents.edit_handlers import DocumentChooserPanel
class BookPage(Page):
book_file = models.ForeignKey(
'wagtaildocs.Document',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
content_panels = Page.content_panels + [
DocumentChooserPanel('book_file'),
]
As with images, Wagtail documents should also have the appropriate extra parameters to prevent cascade deletions across a ForeignKey relationship. DocumentChooserPanel
takes only one argument: the name of the field.
SnippetChooserPanel
-
class
wagtail.snippets.edit_handlers.
SnippetChooserPanel
(field_name, snippet_type=None)
Snippets are vanilla Django models you create yourself without a Wagtail-provided base class. A chooser, SnippetChooserPanel
, is provided which takes the field name as an argument.
from wagtail.snippets.edit_handlers import SnippetChooserPanel
class BookPage(Page):
advert = models.ForeignKey(
'demo.Advert',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
content_panels = Page.content_panels + [
SnippetChooserPanel('advert'),
]
See Snippets for more information.
Field Customisation
By adding CSS classes to your panel definitions or adding extra parameters to your field definitions, you can control much of how your fields will display in the Wagtail page editing interface. Wagtail’s page editing interface takes much of its behaviour from Django’s admin, so you may find many options for customisation covered there. (See Django model field reference).
Titles
Use classname="title"
to make Page’s built-in title field stand out with more vertical padding.
Placeholder Text
By default, Wagtail uses the field’s label as placeholder text. To change it, pass to the FieldPanel a widget with a placeholder attribute set to your desired text. You can select widgets from Django’s form widgets, or any of the Wagtail’s widgets found in wagtail.admin.widgets
.
For example, to customize placeholders for a Book model exposed via ModelAdmin:
# models.py
from django import forms # the default Django widgets live here
from wagtail.admin import widgets # to use Wagtail's special datetime widget
class Book(models.Model):
title = models.CharField(max_length=256)
release_date = models.DateField()
price = models.DecimalField(max_digits=5, decimal_places=2)
# you can create them separately
title_widget = forms.TextInput(
attrs = {
'placeholder': 'Enter Full Title'
}
)
# using the correct widget for your field type and desired effect
date_widget = widgets.AdminDateInput(
attrs = {
'placeholder': 'dd-mm-yyyy'
}
)
panels = [
FieldPanel('title', widget=title_widget), # then add them as a variable
FieldPanel('release_date', widget=date_widget),
FieldPanel('price', widget=forms.NumberInput(attrs={'placeholder': 'Retail price on release'})) # or directly inline
]
Required Fields
To make input or chooser selection mandatory for a field, add blank=False
to its model definition.
Hiding Fields
Without a panel definition, a default form field (without label) will be used to represent your fields. If you intend to hide a field on the Wagtail page editor, define the field with editable=False
.
Inline Panels and Model Clusters
The django-modelcluster
module allows for streamlined relation of extra models to a Wagtail page via a ForeignKey-like relationship called ParentalKey
. Normally, your related objects “cluster” would need to be created beforehand (or asynchronously) before being linked to a Page; however, objects related to a Wagtail page via ParentalKey
can be created on-the-fly and saved to a draft revision of a Page
object.
Let’s look at the example of adding related links to a Page
-derived model. We want to be able to add as many as we like, assign an order, and do all of this without leaving the page editing screen.
from wagtail.core.models import Orderable, Page
from modelcluster.fields import ParentalKey
# The abstract model for related links, complete with panels
class RelatedLink(models.Model):
title = models.CharField(max_length=255)
link_external = models.URLField("External link", blank=True)
panels = [
FieldPanel('title'),
FieldPanel('link_external'),
]
class Meta:
abstract = True
# The real model which combines the abstract model, an
# Orderable helper class, and what amounts to a ForeignKey link
# to the model we want to add related links to (BookPage)
class BookPageRelatedLinks(Orderable, RelatedLink):
page = ParentalKey('demo.BookPage', on_delete=models.CASCADE, related_name='related_links')
class BookPage(Page):
# ...
content_panels = Page.content_panels + [
InlinePanel('related_links', label="Related Links"),
]
The RelatedLink
class is a vanilla Django abstract model. The BookPageRelatedLinks
model extends it with capability for being ordered in the Wagtail interface via the Orderable
class as well as adding a page
property which links the model to the BookPage
model we’re adding the related links objects to. Finally, in the panel definitions for BookPage
, we’ll add an InlinePanel
to provide an interface for it all. Let’s look again at the parameters that InlinePanel
accepts:
InlinePanel( relation_name, panels=None, heading='', label='', help_text='', min_num=None, max_num=None )
The relation_name
is the related_name
label given to the cluster’s ParentalKey
relation. You can add the panels
manually or make them part of the cluster model. heading
and help_text
provide a heading and caption, respectively, for the Wagtail editor. label
sets the text on the add button, and is used as the heading when heading
is not present. Finally, min_num
and max_num
allow you to set the minimum/maximum number of forms that the user must submit.
For another example of using model clusters, see Tagging
For more on django-modelcluster
, visit the django-modelcluster github project page.
Model Reference
This document contains reference information for the model classes inside the wagtailcore
module.
Page
Database fields
-
class
wagtail.core.models.
Page
-
title
(text)
Human-readable title of the page.
-
draft_title
(text)
Human-readable title of the page, incorporating any changes that have been made in a draft edit (in contrast to the title
field, which for published pages will be the title as it exists in the current published version).
-
slug
(text)
This is used for constructing the page’s URL.
For example: http://domain.com/blog/[my-slug]/
-
content_type
(foreign key to django.contrib.contenttypes.models.ContentType
)
A foreign key to the ContentType
object that represents the specific model of this page.
-
live
(boolean)
A boolean that is set to True
if the page is published.
Note: this field defaults to True
meaning that any pages that are created programmatically will be published by default.
-
has_unpublished_changes
(boolean)
A boolean that is set to True
when the page is either in draft or published with draft changes.
-
owner
(foreign key to user model)
A foreign key to the user that created the page.
-
first_published_at
(date/time)
The date/time when the page was first published.
-
last_published_at
(date/time)
The date/time when the page was last published.
-
seo_title
(text)
Alternate SEO-crafted title, for use in the page’s <title>
HTML tag.
-
search_description
(text)
SEO-crafted description of the content, used for search indexing. This is also suitable for the page’s <meta name="description">
HTML tag.
(boolean)
Toggles whether the page should be included in site-wide menus.
This is used by the in_menu()
QuerySet filter.
Defaults to False
and can be overridden on the model with show_in_menus_default = True
.
Note
To set the global default for all pages, set Page.show_in_menus_default = True
once where you first import the Page
model.
-
locked
(boolean)
When set to True
, the Wagtail editor will not allow any users to edit
the content of the page.
If locked_by
is also set, only that user can edit the page.
-
locked_by
(foreign key to user model)
The user who has currently locked the page. Only this user can edit the page.
If this is None
when locked
is True
, nobody can edit the page.
-
locked_at
(date/time)
The date/time when the page was locked.
-
alias_of
(foreign key to another page)
If set, this page is an alias of the page referenced in this field.
-
locale
(foreign key to Locale)
This foreign key links to the Locale
object that represents the page language.
-
translation_key
(uuid)
A UUID that is shared between translations of a page. These are randomly generated
when a new page is created and copied when a translation of a page is made.
A translation_key value can only be used on one page in each locale.
Methods and properties
In addition to the model fields provided, Page
has many properties and methods that you may wish to reference, use, or override in creating your own models.
-
class
wagtail.core.models.
Page
-
get_specific
(deferred=False, copy_attrs=None, copy_attrs_exclude=None)
-
Return this page in its most specific subclassed form.
Changed in version 2.13: * When copy_attrs
is not supplied, all known non-field attribute
values are copied to the returned object. Previously, no non-field
values would be copied.
* The copy_attrs_exclude
option was added.
By default, a database query is made to fetch all field values for the
specific object. If you only require access to custom methods or other
non-field attributes on the specific object, you can use
deferred=True
to avoid this query. However, any attempts to access
specific field values from the returned object will trigger additional
database queries.
By default, references to all non-field attribute values are copied
from current object to the returned one. This includes:
- Values set by a queryset, for example: annotations, or values set as
a result of using
select_related()
or prefetch_related()
.
- Any
cached_property
values that have been evaluated.
- Attributes set elsewhere in Python code.
For fine-grained control over which non-field values are copied to the
returned object, you can use copy_attrs
to specify a complete list
of attribute names to include. Alternatively, you can use
copy_attrs_exclude
to specify a list of attribute names to exclude.
If called on a page object that is already an instance of the most
specific class (e.g. an EventPage
), the object will be returned
as is, and no database queries or other operations will be triggered.
If the page was originally created using a page type that has since
been removed from the codebase, a generic Page
object will be
returned (without any custom field values or other functionality
present on the original class). Usually, deleting these pages is the
best course of action, but there is currently no safe way for Wagtail
to do that at migration time.
-
specific
Returns this page in its most specific subclassed form with all field
values fetched from the database. The result is cached in memory.
-
specific_deferred
-
Returns this page in its most specific subclassed form without any
additional field values being fetched from the database. The result
is cached in memory.
-
specific_class
Return the class that this page would be if instantiated in its
most specific form.
If the model class can no longer be found in the codebase, and the
relevant ContentType
has been removed by a database migration,
the return value will be None
.
If the model class can no longer be found in the codebase, but the
relevant ContentType
is still present in the database (usually a
result of switching between git branches without running or reverting
database migrations beforehand), the return value will be None
.
-
cached_content_type
-
Return this page’s content_type
value from the ContentType
model’s cached manager, which will avoid a database query if the
object is already in memory.
-
get_url
(request=None, current_site=None)
Return the ‘most appropriate’ URL for referring to this page from the pages we serve,
within the Wagtail backend and actual website templates;
this is the local URL (starting with ‘/’) if we’re only running a single site
(i.e. we know that whatever the current page is being served from, this link will be on the
same domain), and the full URL (with domain) if not.
Return None if the page is not routable.
Accepts an optional but recommended request
keyword argument that, if provided, will
be used to cache site-level URL information (thereby avoiding repeated database / cache
lookups) and, via the Site.find_for_request()
function, determine whether a relative
or full URL is most appropriate.
-
full_url
Return the full URL (including protocol / domain) to this page, or None if it is not routable
-
relative_url
(current_site, request=None)
Return the ‘most appropriate’ URL for this page taking into account the site we’re currently on;
a local URL if the site matches, or a fully qualified one otherwise.
Return None if the page is not routable.
Accepts an optional but recommended request
keyword argument that, if provided, will
be used to cache site-level URL information (thereby avoiding repeated database / cache
lookups).
-
get_site
()
Return the Site object that this page belongs to.
-
get_url_parts
(request=None)
Determine the URL for this page and return it as a tuple of
(site_id, site_root_url, page_url_relative_to_site_root)
.
Return None if the page is not routable.
This is used internally by the full_url
, url
, relative_url
and get_site
properties and methods; pages with custom URL routing
should override this method in order to have those operations return
the custom URLs.
Accepts an optional keyword argument request
, which may be used
to avoid repeated database / cache lookups. Typically, a page model
that overrides get_url_parts
should not need to deal with
request
directly, and should just pass it to the original method
when calling super
.
-
route
(request, path_components)
-
serve
(request, *args, **kwargs)
-
context_object_name
= None
Custom name for page instance in page’s Context
.
-
get_context
(request, *args, **kwargs)
-
get_template
(request, *args, **kwargs)
-
get_admin_display_title
()
Return the title for this page as it should appear in the admin backend;
override this if you wish to display extra contextual information about the page,
such as language. By default, returns draft_title
.
-
preview_modes
A list of (internal_name, display_name) tuples for the modes in which
this page can be displayed for preview/moderation purposes. Ordinarily a page
will only have one display mode, but subclasses of Page can override this -
for example, a page containing a form might have a default view of the form,
and a post-submission ‘thank you’ page
-
serve_preview
(request, mode_name)
Return an HTTP response for use in page previews. Normally this would be equivalent
to self.serve(request), since we obviously want the preview to be indicative of how
it looks on the live site. However, there are a couple of cases where this is not
appropriate, and custom behaviour is required:
1) The page has custom routing logic that derives some additional required
args/kwargs to be passed to serve(). The routing mechanism is bypassed when
previewing, so there’s no way to know what args we should pass. In such a case,
the page model needs to implement its own version of serve_preview.
2) The page has several different renderings that we would like to be able to see
when previewing - for example, a form page might have one rendering that displays
the form, and another rendering to display a landing page when the form is posted.
This can be done by setting a custom preview_modes list on the page model -
Wagtail will allow the user to specify one of those modes when previewing, and
pass the chosen mode_name to serve_preview so that the page model can decide how
to render it appropriately. (Page models that do not specify their own preview_modes
list will always receive an empty string as mode_name.)
Any templates rendered during this process should use the ‘request’ object passed
here - this ensures that request.user and other properties are set appropriately for
the wagtail user bar to be displayed. This request will always be a GET.
-
get_parent
(update=False)
Returns: | the parent node of the current node object.
Caches the result in the object itself to help in loops. |
-
get_ancestors
(inclusive=False)
Returns a queryset of the current page’s ancestors, starting at the root page
and descending to the parent, or to the current page itself if inclusive
is true.
-
get_descendants
(inclusive=False)
Returns a queryset of all pages underneath the current page, any number of levels deep.
If inclusive
is true, the current page itself is included in the queryset.
-
get_siblings
(inclusive=True)
Returns a queryset of all other pages with the same parent as the current page.
If inclusive
is true, the current page itself is included in the queryset.
-
get_translations
(inclusive=False)
Returns a queryset containing the translations of this instance.
-
get_translation
(locale)
Finds the translation in the specified locale.
If there is no translation in that locale, this raises a model.DoesNotExist
exception.
-
get_translation_or_none
(locale)
Finds the translation in the specified locale.
If there is no translation in that locale, this returns None.
-
has_translation
(locale)
Returns True if a translation exists in the specified locale.
-
copy_for_translation
(locale, copy_parents=False, alias=False, exclude_fields=None)
Creates a copy of this page in the specified locale.
The new page will be created in draft as a child of this page’s translated
parent.
For example, if you are translating a blog post from English into French,
this method will look for the French version of the blog index and create
the French translation of the blog post under that.
If this page’s parent is not translated into the locale, then a ParentNotTranslatedError
is raised. You can circumvent this error by passing copy_parents=True
which
copies any parents that are not translated yet.
The exclude_fields
parameter can be used to set any fields to a blank value
in the copy.
Note that this method calls the .copy()
method internally so any fields that
are excluded in .exclude_fields_in_copy
will be excluded from the translation.
-
localized
Finds the translation in the current active language.
If there is no translation in the active language, self is returned.
Note: This will not return the translation if it is in draft.
If you want to include drafts, use the .localized_draft
attribute instead.
-
localized_draft
Finds the translation in the current active language.
If there is no translation in the active language, self is returned.
Note: This will return translations that are in draft. If you want to exclude
these, use the .localized
attribute.
-
search_fields
A list of fields to be indexed by the search engine. See Search docs Indexing extra fields
-
subpage_types
A list of page models which can be created as children of this page type. For example, a BlogIndex
page might allow a BlogPage
as a child, but not a JobPage
:
class BlogIndex(Page):
subpage_types = ['mysite.BlogPage', 'mysite.BlogArchivePage']
The creation of child pages can be blocked altogether for a given page by setting its subpage_types attribute to an empty array:
class BlogPage(Page):
subpage_types = []
-
parent_page_types
A list of page models which are allowed as parent page types. For example, a BlogPage
may only allow itself to be created below the BlogIndex
page:
class BlogPage(Page):
parent_page_types = ['mysite.BlogIndexPage']
Pages can block themselves from being created at all by setting parent_page_types to an empty array (this is useful for creating unique pages that should only be created once):
class HiddenPage(Page):
parent_page_types = []
-
classmethod
can_exist_under
(parent)
Checks if this page type can exist as a subpage under a parent page
instance.
See also: Page.can_create_at()
and Page.can_move_to()
-
classmethod
can_create_at
(parent)
Checks if this page type can be created as a subpage under a parent
page instance.
-
can_move_to
(parent)
Checks if this page instance can be moved to be a subpage of a parent
page instance.
-
password_required_template
Defines which template file should be used to render the login form for Protected pages using this model. This overrides the default, defined using PASSWORD_REQUIRED_TEMPLATE
in your settings. See Private pages
-
is_creatable
Controls if this page can be created through the Wagtail administration. Defaults to True
, and is not inherited by subclasses. This is useful when using multi-table inheritance, to stop the base model from being created as an actual page.
-
max_count
Controls the maximum number of pages of this type that can be created through the Wagtail administration interface. This is useful when needing “allow at most 3 of these pages to exist”, or for singleton pages.
-
max_count_per_parent
Controls the maximum number of pages of this type that can be created under any one parent page.
-
exclude_fields_in_copy
An array of field names that will not be included when a Page is copied.
Useful when you have relations that do not use ClusterableModel or should not be copied.
class BlogPage(Page):
exclude_fields_in_copy = ['special_relation', 'custom_uuid']
The following fields will always be excluded in a copy - [‘id’, ‘path’, ‘depth’, ‘numchild’, ‘url_path’, ‘path’].
-
base_form_class
The form class used as a base for editing Pages of this type in the Wagtail page editor.
This attribute can be set on a model to customise the Page editor form.
Forms must be a subclass of WagtailAdminPageForm
.
See Customising generated forms for more information.
-
with_content_json
(content_json)
Returns a new version of the page with field values updated to reflect changes
in the provided content_json
(which usually comes from a previously-saved
page revision).
Certain field values are preserved in order to prevent errors if the returned
page is saved, such as id
, content_type
and some tree-related values.
The following field values are also preserved, as they are considered to be
meaningful to the page as a whole, rather than to a specific revision:
draft_title
live
has_unpublished_changes
owner
locked
locked_by
locked_at
latest_revision_created_at
first_published_at
alias_of
comments
-
save
(clean=True, user=None, log_action=False, **kwargs)
Overrides default method behaviour to make additional updates unique to pages,
such as updating the url_path
value of descendant page to reflect changes
to this page’s slug.
New pages should generally be saved via the add_child()
or add_sibling()
method of an existing page, which will correctly set the path
and depth
fields on the new page before saving it.
By default, pages are validated using full_clean()
before attempting to
save changes to the database, which helps to preserve validity when restoring
pages from historic revisions (which might not necessarily reflect the current
model state). This validation step can be bypassed by calling the method with
clean=False
.
-
create_alias
(*, recursive=False, parent=None, update_slug=None, update_locale=None, user=None, log_action='wagtail.create_alias', reset_translation_key=True, _mpnode_attrs=None)
Creates an alias of the given page.
An alias is like a copy, but an alias remains in sync with the original page. They
are not directly editable and do not have revisions.
You can convert an alias into a regular page by setting the .alias_of attibute to None
and creating an initial revision.
Parameters: |
- recursive (boolean, optional) – create aliases of the page’s subtree, defaults to False
- parent (Page, optional) – The page to create the new alias under
- update_slug (string, optional) – The slug of the new alias page, defaults to the slug of the original page
- update_locale (Locale, optional) – The locale of the new alias page, defaults to the locale of the original page
- user (User, optional) – The user who is performing this action. This user would be assigned as the owner of the new page and appear in the audit log
- log_action (string or None, optional) – Override the log action with a custom one. or pass None to skip logging, defaults to ‘wagtail.create_alias’
- reset_translation_key (boolean, optional) – Generate new translation_keys for the page and any translatable child objects, defaults to False
|
-
update_aliases
(*, revision=None, user=None, _content_json=None, _updated_ids=None)
Publishes all aliases that follow this page with the latest content from this page.
This is called by Wagtail whenever a page with aliases is published.
Parameters: |
- revision (PageRevision, optional) – The revision of the original page that we are updating to (used for logging purposes)
- user (User, optional) – The user who is publishing (used for logging purposes)
|
-
has_workflow
Returns True if the page or an ancestor has an active workflow assigned, otherwise False
-
get_workflow
()
Returns the active workflow assigned to the page or its nearest ancestor
-
workflow_in_progress
Returns True if a workflow is in progress on the current page, otherwise False
-
current_workflow_state
Returns the in progress or needs changes workflow state on this page, if it exists
-
current_workflow_task_state
Returns (specific class of) the current task state of the workflow on this page, if it exists
-
current_workflow_task
Returns (specific class of) the current task in progress on this page, if it exists
Site
The Site
model is useful for multi-site installations as it allows an administrator to configure which part of the tree to use for each hostname that the server responds on.
The find_for_request()
function returns the Site object that will handle the given HTTP request.
Database fields
-
class
wagtail.core.models.
Site
-
hostname
(text)
This is the hostname of the site, excluding the scheme, port and path.
For example: www.mysite.com
Note
If you’re looking for how to get the root url of a site, use the root_url
attribute.
-
port
(number)
This is the port number that the site responds on.
-
site_name
(text - optional)
A human-readable name for the site. This is not used by Wagtail itself, but is suitable for use on the site front-end, such as in <title>
elements.
For example: Rod's World of Birds
-
root_page
(foreign key to Page
)
This is a link to the root page of the site. This page will be what appears at the /
URL on the site and would usually be a homepage.
-
is_default_site
(boolean)
This is set to True
if the site is the default. Only one site can be the default.
The default site is used as a fallback in situations where a site with the required hostname/port couldn’t be found.
Methods and properties
-
class
wagtail.core.models.
Site
-
static
find_for_request
(request)
Find the site object responsible for responding to this HTTP
request object. Try:
- unique hostname first
- then hostname and port
- if there is no matching hostname at all, or no matching
hostname:port combination, fall back to the unique default site,
or raise an exception
NB this means that high-numbered ports on an extant hostname may
still be routed to a different hostname which is set as the default
The site will be cached via request._wagtail_site
-
root_url
This returns the URL of the site. It is calculated from the hostname
and the port
fields.
The scheme part of the URL is calculated based on value of the port
field:
- 80 =
http://
- 443 =
https://
- Everything else will use the
http://
scheme and the port will be appended to the end of the hostname (eg. http://mysite.com:8000/
)
-
static
get_site_root_paths
()
Return a list of SiteRootPath instances, most specific path
first - used to translate url_paths into actual URLs with hostnames
Each root path is an instance of the SiteRootPath named tuple,
and have the following attributes:
- site_id - The ID of the Site record
- root_path - The internal URL path of the site’s home page (for example ‘/home/’)
- root_url - The scheme/domain name of the site (for example ‘https://www.example.com/’)
- language_code - The language code of the site (for example ‘en’)
Locale
The Locale
model defines the set of languages and/or locales that can be used on a site.
Each Locale
record corresponds to a “language code” defined in the WAGTAIL_CONTENT_LANGUAGES setting.
Wagtail will initially set up one Locale
to act as the default language for all existing content.
This first locale will automatically pick the value from WAGTAIL_CONTENT_LANGUAGES
that most closely matches the site primary language code defined in LANGUAGE_CODE
.
If the primary language code is changed later, Wagtail will not automatically create a new Locale
record or update an existing one.
Before internationalisation is enabled, all pages use this primary Locale
record.
This is to satisfy the database constraints, and makes it easier to switch internationalisation on at a later date.
Changing WAGTAIL_CONTENT_LANGUAGES
Languages can be added or removed from WAGTAIL_CONTENT_LANGUAGES
over time.
Before removing an option from WAGTAIL_CONTENT_LANGUAGES
, it’s important that the Locale
record is updated to a use a different content language or is deleted.
Any Locale
instances that have invalid content languages are automatically filtered out from all
database queries making them unable to be edited or viewed.
Methods and properties
-
class
wagtail.core.models.
Locale
-
language_code
The language code that represents this locale
The language code can either be a language code on its own (such as en
, fr
),
or it can include a region code (such as en-gb
, fr-fr
).
-
classmethod
get_default
()
Returns the default Locale based on the site’s LANGUAGE_CODE setting
-
classmethod
get_active
()
Returns the Locale that corresponds to the currently activated language in Django.
-
get_display_name
()
Translatable Mixin
TranslatableMixin
is an abstract model that can be added to any non-page Django model to make it translatable.
Pages already include this mixin, so there is no need to add it.
Methods and properties
The locale
and translation_key
fields have a unique key constraint to prevent the object being translated into a language more than once.
-
class
wagtail.core.models.
TranslatableMixin
-
locale
(Foreign Key to Locale
)
For pages, this defaults to the locale of the parent page.
-
translation_key
(uuid)
A UUID that is randomly generated whenever a new model instance is created.
This is shared with all translations of that instance so can be used for querying translations.
-
get_translations
(inclusive=False)
Returns a queryset containing the translations of this instance.
-
get_translation
(locale)
Finds the translation in the specified locale.
If there is no translation in that locale, this raises a model.DoesNotExist
exception.
-
get_translation_or_none
(locale)
Finds the translation in the specified locale.
If there is no translation in that locale, this returns None.
-
has_translation
(locale)
Returns True if a translation exists in the specified locale.
-
copy_for_translation
(locale)
Creates a copy of this instance with the specified locale.
Note that the copy is initially unsaved.
-
classmethod
get_translation_model
()
Returns this model’s “Translation model”.
The “Translation model” is the model that has the locale
and
translation_key
fields.
Typically this would be the current model, but it may be a
super-class if multi-table inheritance is in use (as is the case
for wagtailcore.Page
).
-
localized
Finds the translation in the current active language.
If there is no translation in the active language, self is returned.
GroupPagePermission
Database fields
-
class
wagtail.core.models.
GroupPagePermission
-
group
(foreign key to django.contrib.auth.models.Group
)
-
page
(foreign key to Page
)
-
permission_type
(choice list)
PageViewRestriction
Database fields
-
class
wagtail.core.models.
PageViewRestriction
-
page
(foreign key to Page
)
-
password
(text)
Orderable
(abstract)
Database fields
-
class
wagtail.core.models.
Orderable
-
sort_order
(number)
Workflow
Workflows represent sequences of tasks which much be approved for an action to be performed on a page - typically publication.
Database fields
-
class
wagtail.core.models.
Workflow
-
name
(text)
Human-readable name of the workflow.
-
active
(boolean)
Whether or not the workflow is active: active workflows can be added to pages, and started. Inactive workflows cannot.
Methods and properties
-
class
wagtail.core.models.
Workflow
-
start
(page, user)
Initiates a workflow by creating an instance of WorkflowState
-
tasks
Returns all Task
instances linked to this workflow
-
deactivate
(user=None)
Sets the workflow as inactive, and cancels all in progress instances of WorkflowState
linked to this workflow
-
all_pages
()
Returns a queryset of all the pages that this Workflow applies to.
WorkflowState
Workflow states represent the status of a started workflow on a page.
Database fields
-
class
wagtail.core.models.
WorkflowState
-
page
(foreign key to Page
)
The page on which the workflow has been started
-
workflow
(foreign key to Workflow
)
The workflow whose state the WorkflowState
represents
-
status
(text)
The current status of the workflow (options are WorkflowState.STATUS_CHOICES
)
-
created_at
(date/time)
When this instance of WorkflowState
was created - when the workflow was started
-
requested_by
(foreign key to user model)
The user who started this workflow
-
current_task_state
(foreign key to TaskState
)
The TaskState
model for the task the workflow is currently at: either completing (if in progress) or the final task state (if finished)
Methods and properties
-
class
wagtail.core.models.
WorkflowState
-
STATUS_CHOICES
A tuple of the possible options for the status
field, and their verbose names. Options are STATUS_IN_PROGRESS
, STATUS_APPROVED
,
STATUS_CANCELLED
and STATUS_NEEDS_CHANGES
.
-
update
(user=None, next_task=None)
Checks the status of the current task, and progresses (or ends) the workflow if appropriate. If the workflow progresses,
next_task will be used to start a specific task next if provided.
-
get_next_task
()
Returns the next active task, which has not been either approved or skipped
-
cancel
(user=None)
Cancels the workflow state
-
finish
(user=None)
Finishes a successful in progress workflow, marking it as approved and performing the on_finish
action
-
resume
(user=None)
Put a STATUS_NEEDS_CHANGES workflow state back into STATUS_IN_PROGRESS, and restart the current task
-
copy_approved_task_states_to_revision
(revision)
This creates copies of previously approved task states with page_revision set to a different revision.
-
all_tasks_with_status
()
Returns a list of Task objects that are linked with this workflow state’s
workflow. The status of that task in this workflow state is annotated in the
.status field. And a displayable version of that status is annotated in the
.status_display field.
This is different to querying TaskState as it also returns tasks that haven’t
been started yet (so won’t have a TaskState).
-
revisions
()
Returns all page revisions associated with task states linked to the current workflow state
Task
Tasks represent stages in a workflow which must be approved for the workflow to complete successfully.
Database fields
-
class
wagtail.core.models.
Task
-
name
(text)
Human-readable name of the task.
-
active
(boolean)
Whether or not the task is active: active workflows can be added to workflows, and started. Inactive workflows cannot, and are skipped when in
an existing workflow.
-
content_type
(foreign key to django.contrib.contenttypes.models.ContentType
)
A foreign key to the ContentType
object that represents the specific model of this task.
Methods and properties
-
class
wagtail.core.models.
Task
-
workflows
Returns all Workflow
instances that use this task
-
active_workflows
Return a QuerySet`
of active workflows that this task is part of
-
task_state_class
The specific task state class to generate to store state information for this task. If not specified, this will be TaskState
.
-
classmethod
get_verbose_name
()
Returns the human-readable “verbose name” of this task model e.g “Group approval task”.
-
specific
Return this Task in its most specific subclassed form.
-
start
(workflow_state, user=None)
Start this task on the provided workflow state by creating an instance of TaskState
-
on_action
(task_state, user, action_name, **kwargs)
Performs an action on a task state determined by the action_name
string passed
-
user_can_access_editor
(page, user)
Returns True if a user who would not normally be able to access the editor for the page should be able to if the page is currently on this task.
Note that returning False does not remove permissions from users who would otherwise have them.
-
user_can_lock
(page, user)
Returns True if a user who would not normally be able to lock the page should be able to if the page is currently on this task.
Note that returning False does not remove permissions from users who would otherwise have them.
-
user_can_unlock
(page, user)
Returns True if a user who would not normally be able to unlock the page should be able to if the page is currently on this task.
Note that returning False does not remove permissions from users who would otherwise have them.
-
page_locked_for_user
(page, user)
Returns True if the page should be locked to a given user’s edits. This can be used to prevent editing by non-reviewers.
-
get_actions
(page, user)
Get the list of action strings (name, verbose_name, whether the action requires additional data - see
get_form_for_action
) for actions the current user can perform for this task on the given page.
These strings should be the same as those able to be passed to on_action
-
get_task_states_user_can_moderate
(user, **kwargs)
Returns a QuerySet
of the task states the current user can moderate
-
deactivate
(user=None)
Set active
to False and cancel all in progress task states linked to this task
-
get_form_for_action
(action)
-
get_template_for_action
(action)
-
classmethod
get_description
()
Returns the task description.
TaskState
Task states store state information about the progress of a task on a particular page revision.
Database fields
-
class
wagtail.core.models.
TaskState
-
workflow_state
(foreign key to WorkflowState
)
The workflow state which started this task state.
-
page revision
(foreign key to PageRevision
)
The page revision this task state was created on.
-
task
(foreign key to Task
)
The task that this task state is storing state information for.
-
status
(text)
The completion status of the task on this revision. Options are available in TaskState.STATUS_CHOICES
)
-
content_type
(foreign key to django.contrib.contenttypes.models.ContentType
)
A foreign key to the ContentType
object that represents the specific model of this task.
-
started_at
(date/time)
When this task state was created.
-
finished_at
(date/time)
When this task state was cancelled, rejected, or approved.
-
finished_by
(foreign key to user model)
The user who completed (cancelled, rejected, approved) the task.
(text)
A text comment, typically added by a user when the task is completed.
Methods and properties
-
class
wagtail.core.models.
TaskState
-
STATUS_CHOICES
A tuple of the possible options for the status
field, and their verbose names. Options are STATUS_IN_PROGRESS
, STATUS_APPROVED
,
STATUS_CANCELLED
, STATUS_REJECTED
and STATUS_SKIPPED
.
-
exclude_fields_in_copy
A list of fields not to copy when the TaskState.copy()
method is called.
-
specific
Return this TaskState in its most specific subclassed form.
-
approve
(user=None, update=True, comment='')
Approve the task state and update the workflow state
-
reject
(user=None, update=True, comment='')
Reject the task state and update the workflow state
-
task_type_started_at
Finds the first chronological started_at for successive TaskStates - ie started_at if the task had not been restarted
-
cancel
(user=None, resume=False, comment='')
Cancel the task state and update the workflow state. If resume
is set to True, then upon update the workflow state
is passed the current task as next_task
, causing it to start a new task state on the current task if possible
-
copy
(update_attrs=None, exclude_fields=None)
Copy this task state, excluding the attributes in the exclude_fields
list and updating any attributes to values
specified in the update_attrs
dictionary of attribute
: new value
pairs
Returns a string that is displayed in workflow history.
This could be a comment by the reviewer, or generated.
Use mark_safe to return HTML.
WorkflowTask
Represents the ordering of a task in a specific workflow.
Database fields
-
class
wagtail.core.models.
WorkflowTask
-
workflow
(foreign key to Workflow
)
-
task
(foreign key to Task
)
-
sort_order
(number)
The ordering of the task in the workflow.
WorkflowPage
Represents the assignment of a workflow to a page and its descendants.
Database fields
-
class
wagtail.core.models.
WorkflowPage
-
workflow
(foreign key to Workflow
)
-
page
(foreign key to Page
)
BaseLogEntry
An abstract base class that represents a record of an action performed on an object.
Database fields
-
class
wagtail.core.models.
BaseLogEntry
-
content_type
(foreign key to django.contrib.contenttypes.models.ContentType
)
A foreign key to the ContentType
object that represents the specific model of this model.
-
label
(text)
The object title at the time of the entry creation
Note: Wagtail will attempt to use get_admin_display_title
or the string representation of the object passed to log_action()
-
user
(foreign key to user model)
A foreign key to the user that triggered the action.
-
data_json
(text)
The JSON representation of any additional details for each action.
e.g. source page id and title when copying from a page. Or workflow id/name and next step id/name on a workflow transition
-
timestamp
(date/time)
The date/time when the entry was created.
-
content_changed
(boolean)
A boolean that can set to True
when the content has changed.
-
deleted
(boolean)
A boolean that can set to True
when the object is deleted. Used to filter entries in the Site History report.
Methods and properties
-
class
wagtail.core.models.
BaseLogEntry
-
user_display_name
Returns the display name of the associated user;
get_full_name if available and non-empty, otherwise get_username.
Defaults to ‘system’ when none is provided
-
data
Provides deserialized data
-
object_verbose_name
-
object_id
()
PageLogEntry
Represents a record of an action performed on an Page
, subclasses BaseLogEntry
.
Database fields
-
class
wagtail.core.models.
PageLogEntry
-
page
(foreign key to Page
)
A foreign key to the page the action is performed on.
-
revision
(foreign key to PageRevision
)
A foreign key to the current page revision.
PageSubscription
Represents a user’s subscription to email notifications about page events.
Currently only used for comment notifications.
Database fields
-
class
wagtail.core.models.
PageSubscription
-
page
(parental key to Page
)
-
user
(foreign key to user model)
(boolean)
Whether the user should receive comment notifications for all comments,
or just comments in threads they participate in.
Page QuerySet reference
All models that inherit from Page
are given some extra QuerySet methods accessible from their .objects
attribute.
Examples
Selecting only live pages
live_pages = Page.objects.live()
Selecting published EventPages that are descendants of events_index
events = EventPage.objects.live().descendant_of(events_index)
Getting a list of menu items
# This gets a QuerySet of live children of the homepage with ``show_in_menus`` set
menu_items = homepage.get_children().live().in_menu()
Reference
-
class
wagtail.core.query.
PageQuerySet
(*args, **kwargs)
-
live
()
This filters the QuerySet to only contain published pages.
Example:
published_pages = Page.objects.live()
-
not_live
()
This filters the QuerySet to only contain unpublished pages.
Example:
unpublished_pages = Page.objects.not_live()
This filters the QuerySet to only contain pages that are in the menus.
Example:
# Build a menu from live pages that are children of the homepage
menu_items = homepage.get_children().live().in_menu()
Note
To put your page in menus, set the show_in_menus flag to true:
# Add 'my_page' to the menu
my_page.show_in_menus = True
This filters the QuerySet to only contain pages that are not in the menus.
-
in_site
(site)
This filters the QuerySet to only contain pages within the specified site.
Example:
# Get all the EventPages in the current site
site = Site.find_for_request(request)
site_events = EventPage.objects.in_site(site)
-
page
(other)
This filters the QuerySet so it only contains the specified page.
Example:
# Append an extra page to a QuerySet
new_queryset = old_queryset | Page.objects.page(page_to_add)
-
not_page
(other)
This filters the QuerySet so it doesn’t contain the specified page.
Example:
# Remove a page from a QuerySet
new_queryset = old_queryset & Page.objects.not_page(page_to_remove)
-
descendant_of
(other, inclusive=False)
This filters the QuerySet to only contain pages that descend from the specified page.
If inclusive is set to True, it will also contain the page itself (instead of just its descendants).
Example:
# Get EventPages that are under the special_events Page
special_events = EventPage.objects.descendant_of(special_events_index)
# Alternative way
special_events = special_events_index.get_descendants()
-
not_descendant_of
(other, inclusive=False)
This filters the QuerySet to not contain any pages that descend from the specified page.
If inclusive is set to True, it will also exclude the specified page.
Example:
# Get EventPages that are not under the archived_events Page
non_archived_events = EventPage.objects.not_descendant_of(archived_events_index)
-
child_of
(other)
This filters the QuerySet to only contain pages that are direct children of the specified page.
Example:
# Get a list of sections
sections = Page.objects.child_of(homepage)
# Alternative way
sections = homepage.get_children()
-
not_child_of
(other)
This filters the QuerySet to not contain any pages that are direct children of the specified page.
-
ancestor_of
(other, inclusive=False)
This filters the QuerySet to only contain pages that are ancestors of the specified page.
If inclusive is set to True, it will also include the specified page.
Example:
# Get the current section
current_section = Page.objects.ancestor_of(current_page).child_of(homepage).first()
# Alternative way
current_section = current_page.get_ancestors().child_of(homepage).first()
-
not_ancestor_of
(other, inclusive=False)
This filters the QuerySet to not contain any pages that are ancestors of the specified page.
If inclusive is set to True, it will also exclude the specified page.
Example:
# Get the other sections
other_sections = Page.objects.not_ancestor_of(current_page).child_of(homepage)
-
parent_of
(other)
This filters the QuerySet to only contain the parent of the specified page.
-
not_parent_of
(other)
This filters the QuerySet to exclude the parent of the specified page.
-
sibling_of
(other, inclusive=True)
This filters the QuerySet to only contain pages that are siblings of the specified page.
By default, inclusive is set to True so it will include the specified page in the results.
If inclusive is set to False, the page will be excluded from the results.
Example:
# Get list of siblings
siblings = Page.objects.sibling_of(current_page)
# Alternative way
siblings = current_page.get_siblings()
-
not_sibling_of
(other, inclusive=True)
This filters the QuerySet to not contain any pages that are siblings of the specified page.
By default, inclusive is set to True so it will exclude the specified page from the results.
If inclusive is set to False, the page will be included in the results.
-
public
()
This filters the QuerySet to only contain pages that are not in a private section
See: Private pages
Note
This doesn’t filter out unpublished pages. If you want to only have published public pages, use .live().public()
Example:
# Find all the pages that are viewable by the public
all_pages = Page.objects.live().public()
-
not_public
()
This filters the QuerySet to only contain pages that are in a private section
-
search
(query, fields=None, operator=None, order_by_relevance=True, partial_match=True, backend='default')
This runs a search query on all the items in the QuerySet
See: Searching QuerySets
Example:
# Search future events
results = EventPage.objects.live().filter(date__gt=timezone.now()).search("Hello")
-
type
(*types)
This filters the QuerySet to only contain pages that are an instance
of the specified model(s) (including subclasses).
Example:
# Find all pages that are of type AbstractEmailForm, or one of it's subclasses
form_pages = Page.objects.type(AbstractEmailForm)
# Find all pages that are of type AbstractEmailForm or AbstractEventPage, or one of their subclasses
form_and_event_pages = Page.objects.type(AbstractEmailForm, AbstractEventPage)
-
not_type
(*types)
This filters the QuerySet to exclude any pages which are an instance of the specified model(s).
-
exact_type
(*types)
This filters the QuerySet to only contain pages that are an instance of the specified model(s)
(matching the model exactly, not subclasses).
Example:
# Find all pages that are of the exact type EventPage
event_pages = Page.objects.exact_type(EventPage)
# Find all page of the exact type EventPage or NewsPage
news_and_events_pages = Page.objects.exact_type(EventPage, NewsPage)
Note
If you are only interested in pages of a single type, it is clearer (and often more efficient) to use
the specific model’s manager to get a queryset. For example:
event_pages = EventPage.objects.all()
-
not_exact_type
(*types)
This filters the QuerySet to exclude any pages which are an instance of the specified model(s)
(matching the model exactly, not subclasses).
Example:
# First, find all news and event pages
news_and_events = Page.objects.type(NewsPage, EventPage)
# Now exclude pages with an exact type of EventPage or NewsPage,
# leaving only instance of more 'specialist' types
specialised_news_and_events = news_and_events.not_exact_type(NewsPage, EventPage)
-
unpublish
()
This unpublishes all live pages in the QuerySet.
Example:
# Unpublish current_page and all of its children
Page.objects.descendant_of(current_page, inclusive=True).unpublish()
-
specific
(defer=False)
This efficiently gets all the specific pages for the queryset, using
the minimum number of queries.
When the “defer” keyword argument is set to True, only generic page
field values will be loaded and all specific fields will be deferred.
Example:
# Get the specific instance of all children of the hompage,
# in a minimum number of database queries.
homepage.get_children().specific()
See also: Page.specific
-
defer_streamfields
()
Apply to a queryset to prevent fetching/decoding of StreamField values on
evaluation. Useful when working with potentially large numbers of results,
where StreamField values are unlikely to be needed. For example, when
generating a sitemap or a long list of page links.
Example:
# Apply to a queryset to avoid fetching StreamField values
# for a specific model
EventPage.objects.all().defer_streamfields()
# Or combine with specific() to avoid fetching StreamField
# values for all models
homepage.get_children().defer_streamfields().specific()
-
first_common_ancestor
(include_self=False, strict=False)
Find the first ancestor that all pages in this queryset have in common.
For example, consider a page hierarchy like:
- Home/
- Foo Event Index/
- Foo Event Page 1/
- Foo Event Page 2/
- Bar Event Index/
- Bar Event Page 1/
- Bar Event Page 2/
The common ancestors for some queries would be:
>>> Page.objects\
... .type(EventPage)\
... .first_common_ancestor()
<Page: Home>
>>> Page.objects\
... .type(EventPage)\
... .filter(title__contains='Foo')\
... .first_common_ancestor()
<Page: Foo Event Index>
This method tries to be efficient, but if you have millions of pages
scattered across your page tree, it will be slow.
If include_self is True, the ancestor can be one of the pages in the
queryset:
>>> Page.objects\
... .filter(title__contains='Foo')\
... .first_common_ancestor()
<Page: Foo Event Index>
>>> Page.objects\
... .filter(title__exact='Bar Event Index')\
... .first_common_ancestor()
<Page: Bar Event Index>
A few invalid cases exist: when the queryset is empty, when the root
Page is in the queryset and include_self
is False, and when there
are multiple page trees with no common root (a case Wagtail does not
support). If strict
is False (the default), then the first root
node is returned in these cases. If strict
is True, then a
ObjectDoesNotExist
is raised.
Hooks
On loading, Wagtail will search for any app with the file wagtail_hooks.py
and execute the contents. This provides a way to register your own functions to execute at certain points in Wagtail’s execution, such as when a page is saved or when the main menu is constructed.
Note
Hooks are typically used to customise the view-level behaviour of the Wagtail admin and front-end. For customisations that only deal with model-level behaviour - such as calling an external service when a page or document is added - it is often better to use Django’s signal mechanism (see also: Wagtail signals), as these are not dependent on a user taking a particular path through the admin interface.
Registering functions with a Wagtail hook is done through the @hooks.register
decorator:
from wagtail.core import hooks
@hooks.register('name_of_hook')
def my_hook_function(arg1, arg2...)
# your code here
Alternatively, hooks.register
can be called as an ordinary function, passing in the name of the hook and a handler function defined elsewhere:
hooks.register('name_of_hook', my_hook_function)
If you need your hooks to run in a particular order, you can pass the order
parameter. If order is not specified then the hooks proceed in the order given by INSTALLED_APPS. Wagtail uses hooks internally, too, so you need to be aware of order when overriding built-in Wagtail functionality (i.e. removing default summary items):
@hooks.register('name_of_hook', order=1) # This will run after every hook in the wagtail core
def my_hook_function(arg1, arg2...)
# your code here
@hooks.register('name_of_hook', order=-1) # This will run before every hook in the wagtail core
def my_other_hook_function(arg1, arg2...)
# your code here
@hooks.register('name_of_hook', order=2) # This will run after `my_hook_function`
def yet_another_hook_function(arg1, arg2...)
# your code here
Unit testing hooks
Hooks are usually registered on startup and can’t be changed at runtime. But when writing unit tests, you might want to register a hook
function just for a single test or block of code and unregister it so that it doesn’t run when other tests are run.
You can register hooks temporarily using the hooks.register_temporarily
function, this can be used as both a decorator and a context
manager. Here’s an example of how to register a hook function for just a single test:
def my_hook_function():
...
class MyHookTest(TestCase):
@hooks.register_temporarily('name_of_hook', my_hook_function)
def test_my_hook_function(self):
# Test with the hook registered here
...
And here’s an example of registering a hook function for a single block of code:
def my_hook_function():
...
with hooks.register_temporarily('name_of_hook', my_hook_function):
# Hook is registered here
..
# Hook is unregistered here
The available hooks are listed below.
Admin modules
Hooks for building new areas of the admin interface (alongside pages, images, documents and so on).
construct_homepage_panels
Add or remove panels from the Wagtail admin homepage. The callable passed into this hook should take a request
object and a list of panels
, objects which have a render()
method returning a string. The objects also have an order
property, an integer used for ordering the panels. The default panels use integers between 100
and 300
. Hook functions should modify the panels
list in-place as required.
from django.utils.safestring import mark_safe
from wagtail.core import hooks
class WelcomePanel:
order = 50
def render(self):
return mark_safe("""
<section class="panel summary nice-padding">
<h3>No, but seriously -- welcome to the admin homepage.</h3>
</section>
""")
@hooks.register('construct_homepage_panels')
def add_another_welcome_panel(request, panels):
panels.append(WelcomePanel())
construct_homepage_summary_items
Add or remove items from the ‘site summary’ bar on the admin homepage (which shows the number of pages and other object that exist on the site). The callable passed into this hook should take a request
object and a list of SummaryItem
objects to be modified as required. These objects have a render()
method, which returns an HTML string, and an order
property, which is an integer that specifies the order in which the items will appear.
construct_main_menu
Called just before the Wagtail admin menu is output, to allow the list of menu items to be modified. The callable passed to this hook will receive a request
object and a list of menu_items
, and should modify menu_items
in-place as required. Adding menu items should generally be done through the register_admin_menu_item
hook instead - items added through construct_main_menu
will be missing any associated JavaScript includes, and their is_shown
check will not be applied.
from wagtail.core import hooks
@hooks.register('construct_main_menu')
def hide_explorer_menu_item_from_frank(request, menu_items):
if request.user.username == 'frank':
menu_items[:] = [item for item in menu_items if item.name != 'explorer']
describe_collection_contents
Called when Wagtail needs to find out what objects exist in a collection, if any. Currently this happens on the confirmation before deleting a collection, to ensure that non-empty collections cannot be deleted. The callable passed to this hook will receive a collection
object, and should return either None
(to indicate no objects in this collection), or a dict containing the following keys:
count
- A numeric count of items in this collection
count_text
- A human-readable string describing the number of items in this collection, such as “3 documents”. (Sites with multi-language support should return a translatable string here, most likely using the
django.utils.translation.ngettext
function.)
url
(optional)
- A URL to an index page that lists the objects being described.
register_account_settings_panel
Registers a new settings panel class to add to the “Account” view in the admin.
This hook can be added to a sub-class of BaseSettingsPanel
. For example:
from wagtail.admin.views.account import BaseSettingsPanel
from wagtail.core import hooks
@hooks.register('register_account_settings_panel')
class CustomSettingsPanel(BaseSettingsPanel):
name = 'custom'
title = "My custom settings"
order = 500
form_class = CustomSettingsForm
Alternatively, it can also be added to a function. For example, this function is equivalent to the above:
from wagtail.admin.views.account import BaseSettingsPanel
from wagtail.core import hooks
class CustomSettingsPanel(BaseSettingsPanel):
name = 'custom'
title = "My custom settings"
order = 500
form_class = CustomSettingsForm
@hooks.register('register_account_settings_panel')
def register_custom_settings_panel(request, user, profile):
return CustomSettingsPanel(request, user, profile)
More details about the options that are available can be found at Customising the user account settings form.
register_admin_urls
Register additional admin page URLs. The callable fed into this hook should return a list of Django URL patterns which define the structure of the pages and endpoints of your extension to the Wagtail admin. For more about vanilla Django URLconfs and views, see url dispatcher.
from django.http import HttpResponse
from django.urls import path
from wagtail.core import hooks
def admin_view(request):
return HttpResponse(
"I have approximate knowledge of many things!",
content_type="text/plain")
@hooks.register('register_admin_urls')
def urlconf_time():
return [
path('how_did_you_almost_know_my_name/', admin_view, name='frank'),
]
register_group_permission_panel
Add a new panel to the Groups form in the ‘settings’ area. The callable passed to this hook must return a ModelForm / ModelFormSet-like class, with a constructor that accepts a group object as its instance
keyword argument, and which implements the methods save
, is_valid
, and as_admin_panel
(which returns the HTML to be included on the group edit page).
register_admin_search_area
Add an item to the Wagtail admin search “Other Searches”. Behaviour of this hook is similar to register_admin_menu_item
. The callable passed to this hook must return an instance of wagtail.admin.search.SearchArea
. New items can be constructed from the SearchArea
class by passing the following parameters:
label: | text displayed in the “Other Searches” option box. |
name: | an internal name used to identify the search option; defaults to the slugified form of the label. |
url: | the URL of the target search page. |
classnames: | arbitrary CSS classnames applied to the link |
icon_name: | icon to display next to the label. |
attrs: | additional HTML attributes to apply to the link. |
order: | an integer which determines the item’s position in the list of options. |
Setting the URL can be achieved using reverse() on the target search page. The GET parameter ‘q’ will be appended to the given URL.
A template tag, search_other
is provided by the wagtailadmin_tags
template module. This tag takes a single, optional parameter, current
, which allows you to specify the name
of the search option currently active. If the parameter is not given, the hook defaults to a reverse lookup of the page’s URL for comparison against the url
parameter.
SearchArea
can be subclassed to customise the HTML output, specify JavaScript files required by the option, or conditionally show or hide the item for specific requests (for example, to apply permission checks); see the source code (wagtail/admin/search.py
) for details.
from django.urls import reverse
from wagtail.core import hooks
from wagtail.admin.search import SearchArea
@hooks.register('register_admin_search_area')
def register_frank_search_area():
return SearchArea('Frank', reverse('frank'), icon_name='folder-inverse', order=10000)
register_permissions
Return a QuerySet of Permission
objects to be shown in the Groups administration area.
Editor interface
Hooks for customising the editing interface for pages and snippets.
register_rich_text_features
Rich text fields in Wagtail work with a list of ‘feature’ identifiers that determine which editing controls are available in the editor, and which elements are allowed in the output; for example, a rich text field defined as
RichTextField(features=['h2', 'h3', 'bold', 'italic', 'link'])
would allow headings, bold / italic formatting and links, but not (for example) bullet lists or images. The
register_rich_text_features
hook allows new feature identifiers to be defined - see
Limiting features in a rich text field for details.
insert_editor_css
Add additional CSS files or snippets to the page editor.
from django.templatetags.static import static
from django.utils.html import format_html
from wagtail.core import hooks
@hooks.register('insert_editor_css')
def editor_css():
return format_html(
'<link rel="stylesheet" href="{}">',
static('demo/css/vendor/font-awesome/css/font-awesome.min.css')
)
insert_global_admin_css
Add additional CSS files or snippets to all admin pages.
from django.utils.html import format_html
from django.templatetags.static import static
from wagtail.core import hooks
@hooks.register('insert_global_admin_css')
def global_admin_css():
return format_html('<link rel="stylesheet" href="{}">', static('my/wagtail/theme.css'))
insert_editor_js
Add additional JavaScript files or code snippets to the page editor.
from django.utils.html import format_html, format_html_join
from django.templatetags.static import static
from wagtail.core import hooks
@hooks.register('insert_editor_js')
def editor_js():
js_files = [
'demo/js/jquery.raptorize.1.0.js',
]
js_includes = format_html_join('\n', '<script src="{0}"></script>',
((static(filename),) for filename in js_files)
)
# remember to use double '{{' so they are not parsed as template placeholders
return js_includes + format_html(
"""
<script>
$(function() {{
$('button').raptorize();
}});
</script>
"""
)
insert_global_admin_js
Add additional JavaScript files or code snippets to all admin pages.
from django.utils.html import format_html
from wagtail.core import hooks
@hooks.register('insert_global_admin_js')
def global_admin_js():
return format_html(
'<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"></script>',
)
Editor workflow
Hooks for customising the way users are directed through the process of creating page content.
after_create_page
Do something with a Page
object after it has been saved to the database (as a published page or a revision). The callable passed to this hook should take a request
object and a page
object. The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object. By default, Wagtail will instead redirect to the Explorer page for the new page’s parent.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('after_create_page')
def do_after_page_create(request, page):
return HttpResponse("Congrats on making content!", content_type="text/plain")
If you set attributes on a Page
object, you should also call save_revision()
, since the edit and index view pick up their data from the revisions table rather than the actual saved page record.
@hooks.register('after_create_page')
def set_attribute_after_page_create(request, page):
page.title = 'Persistent Title'
new_revision = page.save_revision()
if page.live:
# page has been created and published at the same time,
# so ensure that the updated title is on the published version too
new_revision.publish()
before_create_page
Called at the beginning of the “create page” view passing in the request, the parent page and page model class.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
Unlike, after_create_page
, this is run both for both GET
and POST
requests.
This can be used to completely override the editor on a per-view basis:
from wagtail.core import hooks
from .models import AwesomePage
from .admin_views import edit_awesome_page
@hooks.register('before_create_page')
def before_create_page(request, parent_page, page_class):
# Use a custom create view for the AwesomePage model
if page_class == AwesomePage:
return create_awesome_page(request, parent_page)
after_delete_page
Do something after a Page
object is deleted. Uses the same behaviour as after_create_page
.
before_delete_page
Called at the beginning of the “delete page” view passing in the request and the page object.
Uses the same behaviour as before_create_page
.
after_edit_page
Do something with a Page
object after it has been updated. Uses the same behaviour as after_create_page
.
before_edit_page
Called at the beginning of the “edit page” view passing in the request and the page object.
Uses the same behaviour as before_create_page
.
after_publish_page
Do something with a Page
object after it has been published via page create view or page edit view.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
before_publish_page
Do something with a Page
object before it has been published via page create view or page edit view.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
after_unpublish_page
Called after unpublish action in “unpublish” view passing in the request and the page object.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
before_unpublish_page
Called before unpublish action in “unpublish” view passing in the request and the page object.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
after_copy_page
Do something with a Page
object after it has been copied passing in the request, page object and the new copied page. Uses the same behaviour as after_create_page
.
before_copy_page
Called at the beginning of the “copy page” view passing in the request and the page object.
Uses the same behaviour as before_create_page
.
after_move_page
Do something with a Page
object after it has been moved passing in the request and page object. Uses the same behaviour as after_create_page
.
before_move_page
Called at the beginning of the “move page” view passing in the request, the page object and the destination page object.
Uses the same behaviour as before_create_page
.
before_convert_alias_page
Called at the beginning of the convert_alias
view, which is responsible for converting alias pages into normal Wagtail pages.
The request and the page being converted are passed in as arguments to the hook.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
after_convert_alias_page
Do something with a Page
object after it has been converted from an alias.
The request and the page that was just converted are passed in as arguments to the hook.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
construct_page_listing_buttons
Modify the final list of page listing buttons in the page explorer. The
callable passed to this hook receives a list of PageListingButton
objects, a page,
a page perms object, and a context dictionary as per register_page_listing_buttons
,
and should modify the list of listing items in-place.
@hooks.register('construct_page_listing_buttons')
def remove_page_listing_button_item(buttons, page, page_perms, is_parent=False, context=None):
if is_parent:
buttons.pop() # removes the last 'more' dropdown button on the parent page listing buttons
construct_wagtail_userbar
Add or remove items from the wagtail userbar. Add, edit, and moderation tools are provided by default. The callable passed into the hook must take the request
object and a list of menu objects, items
. The menu item objects must have a render
method which can take a request
object and return the HTML string representing the menu item. See the userbar templates and menu item classes for more information.
from wagtail.core import hooks
class UserbarPuppyLinkItem:
def render(self, request):
return '<li><a href="http://cuteoverload.com/tag/puppehs/" ' \
+ 'target="_parent" class="action icon icon-wagtail">Puppies!</a></li>'
@hooks.register('construct_wagtail_userbar')
def add_puppy_link_item(request, items):
return items.append( UserbarPuppyLinkItem() )
Admin workflow
Hooks for customising the way admins are directed through the process of editing users.
after_create_user
Do something with a User
object after it has been saved to the database. The callable passed to this hook should take a request
object and a user
object. The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object. By default, Wagtail will instead redirect to the User index page.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('after_create_user')
def do_after_page_create(request, user):
return HttpResponse("Congrats on creating a new user!", content_type="text/plain")
before_create_user
Called at the beginning of the “create user” view passing in the request.
The function does not have to return anything, but if an object with a status_code
property is returned, Wagtail will use it as a response object and skip the rest of the view.
Unlike, after_create_user
, this is run both for both GET
and POST
requests.
This can be used to completely override the user editor on a per-view basis:
from django.http import HttpResponse
from wagtail.core import hooks
from .models import AwesomePage
from .admin_views import edit_awesome_page
@hooks.register('before_create_user')
def before_create_page(request):
return HttpResponse("A user creation form", content_type="text/plain")
after_delete_user
Do something after a User
object is deleted. Uses the same behaviour as after_create_user
.
before_delete_user
Called at the beginning of the “delete user” view passing in the request and the user object.
Uses the same behaviour as before_create_user
.
after_edit_user
Do something with a User
object after it has been updated. Uses the same behaviour as after_create_user
.
before_edit_user
Called at the beginning of the “edit user” view passing in the request and the user object.
Uses the same behaviour as before_create_user
.
Choosers
construct_page_chooser_queryset
Called when rendering the page chooser view, to allow the page listing QuerySet to be customised. The callable passed into the hook will receive the current page QuerySet and the request object, and must return a Page QuerySet (either the original one, or a new one).
from wagtail.core import hooks
@hooks.register('construct_page_chooser_queryset')
def show_my_pages_only(pages, request):
# Only show own pages
pages = pages.filter(owner=request.user)
return pages
construct_document_chooser_queryset
Called when rendering the document chooser view, to allow the document listing QuerySet to be customised. The callable passed into the hook will receive the current document QuerySet and the request object, and must return a Document QuerySet (either the original one, or a new one).
from wagtail.core import hooks
@hooks.register('construct_document_chooser_queryset')
def show_my_uploaded_documents_only(documents, request):
# Only show uploaded documents
documents = documents.filter(uploaded_by_user=request.user)
return documents
construct_image_chooser_queryset
Called when rendering the image chooser view, to allow the image listing QuerySet to be customised. The callable passed into the hook will receive the current image QuerySet and the request object, and must return an Image QuerySet (either the original one, or a new one).
from wagtail.core import hooks
@hooks.register('construct_image_chooser_queryset')
def show_my_uploaded_images_only(images, request):
# Only show uploaded images
images = images.filter(uploaded_by_user=request.user)
return images
Page explorer
construct_explorer_page_queryset
Called when rendering the page explorer view, to allow the page listing QuerySet to be customised. The callable passed into the hook will receive the parent page object, the current page QuerySet, and the request object, and must return a Page QuerySet (either the original one, or a new one).
from wagtail.core import hooks
@hooks.register('construct_explorer_page_queryset')
def show_my_profile_only(parent_page, pages, request):
# If we're in the 'user-profiles' section, only show the user's own profile
if parent_page.slug == 'user-profiles':
pages = pages.filter(owner=request.user)
return pages
register_page_listing_buttons
Add buttons to the actions list for a page in the page explorer. This is useful when adding custom actions to the listing, such as translations or a complex workflow.
This example will add a simple button to the listing:
from wagtail.admin import widgets as wagtailadmin_widgets
@hooks.register('register_page_listing_buttons')
def page_listing_buttons(page, page_perms, is_parent=False, next_url=None):
yield wagtailadmin_widgets.PageListingButton(
'A page listing button',
'/goes/to/a/url/',
priority=10
)
The arguments passed to the hook are as follows:
page
- the page object to generate the button for
page_perms
- a PagePermissionTester
object that can be queried to determine the current user’s permissions on the given page
is_parent
- if true, this button is being rendered for the parent page being displayed at the top of the listing
next_url
- the URL that the linked action should redirect back to on completion of the action, if the view supports it
The priority
argument controls the order the buttons are displayed in. Buttons are ordered from low to high priority, so a button with priority=10
will be displayed before a button with priority=20
.
register_page_listing_more_buttons
Add buttons to the “More” dropdown menu for a page in the page explorer. This works similarly to the register_page_listing_buttons
hook but is useful for lesser-used custom actions that are better suited for the dropdown.
This example will add a simple button to the dropdown menu:
from wagtail.admin import widgets as wagtailadmin_widgets
@hooks.register('register_page_listing_more_buttons')
def page_listing_more_buttons(page, page_perms, is_parent=False, next_url=None):
yield wagtailadmin_widgets.Button(
'A dropdown button',
'/goes/to/a/url/',
priority=60
)
The arguments passed to the hook are as follows:
page
- the page object to generate the button for
page_perms
- a PagePermissionTester
object that can be queried to determine the current user’s permissions on the given page
is_parent
- if true, this button is being rendered for the parent page being displayed at the top of the listing
next_url
- the URL that the linked action should redirect back to on completion of the action, if the view supports it
The priority
argument controls the order the buttons are displayed in the dropdown. Buttons are ordered from low to high priority, so a button with priority=10
will be displayed before a button with priority=60
.
Page serving
before_serve_page
Called when Wagtail is about to serve a page. The callable passed into the hook will receive the page object, the request object, and the args
and kwargs
that will be passed to the page’s serve()
method. If the callable returns an HttpResponse
, that response will be returned immediately to the user, and Wagtail will not proceed to call serve()
on the page.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('before_serve_page')
def block_googlebot(page, request, serve_args, serve_kwargs):
if request.META.get('HTTP_USER_AGENT') == 'GoogleBot':
return HttpResponse("<h1>bad googlebot no cookie</h1>")
Document serving
before_serve_document
Called when Wagtail is about to serve a document. The callable passed into the hook will receive the document object and the request object. If the callable returns an
HttpResponse
, that response will be returned immediately to the user, instead of serving the document. Note that this hook will be skipped if the
WAGTAILDOCS_SERVE_METHOD setting is set to
direct
.
Snippets
Hooks for working with registered Snippets.
after_edit_snippet
Called when a Snippet is edited. The callable passed into the hook will receive the model instance, the request object. If the callable returns an HttpResponse
, that response will be returned immediately to the user, and Wagtail will not proceed to call redirect()
to the listing view.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('after_edit_snippet')
def after_snippet_update(request, instance):
return HttpResponse(f"Congrats on editing a snippet with id {instance.pk}", content_type="text/plain")
before_edit_snippet
Called at the beginning of the edit snippet view. The callable passed into the hook will receive the model instance, the request object. If the callable returns an HttpResponse
, that response will be returned immediately to the user, and Wagtail will not proceed to call redirect()
to the listing view.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('before_edit_snippet')
def block_snippet_edit(request, instance):
if isinstance(instance, RestrictedSnippet) and instance.prevent_edit:
return HttpResponse("Sorry, you can't edit this snippet", content_type="text/plain")
after_create_snippet
Called when a Snippet is created. after_create_snippet
and
after_edit_snippet
work in identical ways. The only difference is where
the hook is called.
before_create_snippet
Called at the beginning of the create snippet view. Works in a similar way to before_edit_snippet except the model is passed as an argument instead of an instance.
after_delete_snippet
Called when a Snippet is deleted. The callable passed into the hook will receive the model instance(s) as a queryset along with the request object. If the callable returns an HttpResponse
, that response will be returned immediately to the user, and Wagtail will not proceed to call redirect()
to the listing view.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('after_delete_snippet')
def after_snippet_delete(request, instances):
# "instances" is a QuerySet
total = len(instances)
return HttpResponse(f"{total} snippets have been deleted", content_type="text/plain")
before_delete_snippet
Called at the beginning of the delete snippet view. The callable passed into the hook will receive the model instance(s) as a queryset along with the request object. If the callable returns an HttpResponse
, that response will be returned immediately to the user, and Wagtail will not proceed to call redirect()
to the listing view.
from django.http import HttpResponse
from wagtail.core import hooks
@hooks.register('before_delete_snippet')
def before_snippet_delete(request, instances):
# "instances" is a QuerySet
total = len(instances)
if request.method == 'POST':
# Override the deletion behaviour
instances.delete()
return HttpResponse(f"{total} snippets have been deleted", content_type="text/plain")
Audit log
register_log_actions
See Audit log
To add new actions to the registry, call the register_action
method with the action type, its label and the message to be displayed in administrative listings.
from django.utils.translation import gettext_lazy as _
from wagtail.core import hooks
@hooks.register('register_log_actions')
def additional_log_actions(actions):
actions.register_action('wagtail_package.echo', _('Echo'), _('Sent an echo'))
def callback_message(data):
return _('Hello %(audience)s') % {
'audience': data['audience'],
}
actions.register_action('wagtail_package.with_callback', _('Callback'), callback_message)
Settings
Wagtail makes use of the following settings, in addition to Django’s core settings:
Site Name
WAGTAIL_SITE_NAME = 'Stark Industries Skunkworks'
This is the human-readable name of your Wagtail install which welcomes users upon login to the Wagtail admin.
Append Slash
# Don't add a trailing slash to Wagtail-served URLs
WAGTAIL_APPEND_SLASH = False
Similar to Django’s APPEND_SLASH
, this setting controls how Wagtail will handle requests that don’t end in a trailing slash.
When WAGTAIL_APPEND_SLASH
is True
(default), requests to Wagtail pages which omit a trailing slash will be redirected by Django’s CommonMiddleware
to a URL with a trailing slash.
When WAGTAIL_APPEND_SLASH
is False
, requests to Wagtail pages will be served both with and without trailing slashes. Page links generated by Wagtail, however, will not include trailing slashes.
Note
If you use the False
setting, keep in mind that serving your pages both with and without slashes may affect search engines’ ability to index your site. See this Google Search Central Blog post for more details.
Search
WAGTAILSEARCH_BACKENDS = {
'default': {
'BACKEND': 'wagtail.search.backends.elasticsearch5',
'INDEX': 'myapp'
}
}
Define a search backend. For a full explanation, see Backends.
WAGTAILSEARCH_RESULTS_TEMPLATE = 'myapp/search_results.html'
WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX = 'myapp/includes/search_listing.html'
Override the templates used by the search front-end views.
WAGTAILSEARCH_HITS_MAX_AGE = 14
Set the number of days (default 7) that search query logs are kept for; these are used to identify popular search terms for promoted search results. Queries older than this will be removed by the search_garbage_collect command.
Internationalisation
Wagtail supports internationalisation of content by maintaining separate trees
of pages for each language.
For a guide on how to enable internationalisation on your site, see the configuration guide.
WAGTAIL_I18N_ENABLED
(boolean, default False
)
When set to True
, Wagtail’s internationalisation features will be enabled:
WAGTAIL_I18N_ENABLED = True
WAGTAIL_CONTENT_LANGUAGES
(list, default []
)
A list of languages and/or locales that Wagtail content can be authored in.
For example:
WAGTAIL_CONTENT_LANGUAGES = [
('en', _("English")),
('fr', _("French")),
]
Each item in the list is a 2-tuple containing a language code and a display name.
The language code can either be a language code on its own (such as en
, fr
),
or it can include a region code (such as en-gb
, fr-fr
).
You can mix the two formats if you only need to localize in some regions but not others.
This setting follows the same structure of Django’s LANGUAGES
setting,
so they can both be set to the same value:
LANGUAGES = WAGTAIL_CONTENT_LANGUAGES = [
('en-gb', _("English (United Kingdom)")),
('en-us', _("English (United States)")),
('es-es', _("Spanish (Spain)")),
('es-mx', _("Spanish (Mexico)")),
]
However having them separate allows you to configure many different regions on your site
yet have them share Wagtail content (but defer on things like date formatting, currency, etc):
LANGUAGES = [
('en', _("English (United Kingdom)")),
('en-us', _("English (United States)")),
('es', _("Spanish (Spain)")),
('es-mx', _("Spanish (Mexico)")),
]
WAGTAIL_CONTENT_LANGUAGES = [
('en', _("English")),
('es', _("Spanish")),
]
This would mean that your site will respond on the
https://www.mysite.com/es/
and https://www.mysite.com/es-MX/
URLs, but both
of them will serve content from the same “Spanish” tree in Wagtail.
Note
WAGTAIL_CONTENT_LANGUAGES
must be a subset of LANGUAGES
Note that all languages that exist in WAGTAIL_CONTENT_LANGUAGES
must also exist in your LANGUAGES
setting. This is so that Wagtail can
generate a live URL to these pages from an untranslated context (e.g. the admin
interface).
Embeds
Wagtail supports generating embed code from URLs to content on an external
providers such as Youtube or Twitter. By default, Wagtail will fetch the embed
code directly from the relevant provider’s site using the oEmbed protocol.
Wagtail has a builtin list of the most common providers.
The embeds fetching can be fully configured using the WAGTAILEMBEDS_FINDERS
setting. This is fully documented in Configuring embed “finders”.
WAGTAILEMBEDS_RESPONSIVE_HTML = True
Adds class="responsive-object"
and an inline padding-bottom
style to embeds,
to assist in making them responsive. See Responsive Embeds for details.
Dashboard
WAGTAILADMIN_RECENT_EDITS_LIMIT = 5
This setting lets you change the number of items shown at ‘Your most recent edits’ on the dashboard.
WAGTAILADMIN_USER_LOGIN_FORM = 'users.forms.LoginForm'
Allows the default LoginForm
to be extended with extra fields.
WAGTAIL_GRAVATAR_PROVIDER_URL = '//www.gravatar.com/avatar'
If a user has not uploaded a profile picture, Wagtail will look for an avatar linked to their email address on gravatar.com. This setting allows you to specify an alternative provider such as like robohash.org, or can be set to None
to disable the use of remote avatars completely.
WAGTAIL_MODERATION_ENABLED = True
Changes whether the Submit for Moderation button is displayed in the action menu.
Images
WAGTAILIMAGES_IMAGE_MODEL = 'myapp.MyImage'
This setting lets you provide your own image model for use in Wagtail, which should extend the built-in AbstractImage
class.
WAGTAILIMAGES_IMAGE_FORM_BASE = 'myapp.forms.MyImageBaseForm'
This setting lets you provide your own image base form for use in Wagtail, which might extend the built-in BaseImageForm
class or replace it entirely.
You can use it to specify or override the widgets to use in the admin form.
WAGTAILIMAGES_MAX_UPLOAD_SIZE = 20 * 1024 * 1024 # i.e. 20MB
This setting lets you override the maximum upload size for images (in bytes). If omitted, Wagtail will fall back to using its 10MB default value.
WAGTAILIMAGES_MAX_IMAGE_PIXELS = 128000000 # i.e. 128 megapixels
This setting lets you override the maximum number of pixels an image can have. If omitted, Wagtail will fall back to using its 128 megapixels default value. The pixel count takes animation frames into account - for example, a 25-frame animation of size 100x100 is considered to have 100 * 100 * 25 = 250000 pixels.
WAGTAILIMAGES_FEATURE_DETECTION_ENABLED = True
This setting enables feature detection once OpenCV is installed, see all details on the Feature Detection documentation.
WAGTAILIMAGES_INDEX_PAGE_SIZE = 20
Specifies the number of images per page shown on the main Images listing in the Wagtail admin.
WAGTAILIMAGES_USAGE_PAGE_SIZE = 20
Specifies the number of items per page shown when viewing an image’s usage (see WAGTAIL_USAGE_COUNT_ENABLED).
WAGTAILIMAGES_CHOOSER_PAGE_SIZE = 12
Specifies the number of images shown per page in the image chooser modal.
Documents
WAGTAILDOCS_DOCUMENT_MODEL = 'myapp.MyDocument'
This setting lets you provide your own document model for use in Wagtail, which should extend the built-in AbstractDocument
class.
WAGTAILDOCS_DOCUMENT_FORM_BASE = 'myapp.forms.MyDocumentBaseForm'
This setting lets you provide your own Document base form for use in Wagtail, which might extend the built-in BaseDocumentForm
class or replace it entirely.
You can use it to specify or override the widgets to use in the admin form.
WAGTAILDOCS_SERVE_METHOD = 'redirect'
Determines how document downloads will be linked to and served. Normally, requests for documents are sent through a Django view, to perform permission checks (see Image / document permissions) and potentially other housekeeping tasks such as hit counting. To fully protect against users bypassing this check, it needs to happen in the same request where the document is served; however, this incurs a performance hit as the document then needs to be served by the Django server. In particular, this cancels out much of the benefit of hosting documents on external storage, such as S3 or a CDN.
For this reason, Wagtail provides a number of serving methods which trade some of the strictness of the permission check for performance:
'direct'
- links to documents point directly to the URL provided by the underlying storage, bypassing the Django view that provides the permission check. This is most useful when deploying sites as fully static HTML (e.g. using wagtail-bakery or Gatsby).
'redirect'
- links to documents point to a Django view which will check the user’s permission; if successful, it will redirect to the URL provided by the underlying storage to allow the document to be downloaded. This is most suitable for remote storage backends such as S3, as it allows the document to be served independently of the Django server. Note that if a user is able to guess the latter URL, they will be able to bypass the permission check; some storage backends may provide configuration options to generate a random or short-lived URL to mitigate this.
'serve_view'
- links to documents point to a Django view which both checks the user’s permission, and serves the document. Serving will be handled by django-sendfile, if this is installed and supported by your server configuration, or as a streaming response from Django if not. When using this method, it is recommended that you configure your webserver to disallow serving documents directly from their location under MEDIA_ROOT
, as this would provide a way to bypass the permission check.
If WAGTAILDOCS_SERVE_METHOD
is unspecified or set to None
, the default method is 'redirect'
when a remote storage backend is in use (i.e. one that exposes a URL but not a local filesystem path), and 'serve_view'
otherwise. Finally, some storage backends may not expose a URL at all; in this case, serving will proceed as for 'serve_view'
.
WAGTAILDOCS_CONTENT_TYPES = {
'pdf': 'application/pdf',
'txt': 'text/plain',
}
Specifies the MIME content type that will be returned for the given file extension, when using the serve_view
method. Content types not listed here will be guessed using the Python mimetypes.guess_type
function, or application/octet-stream
if unsuccessful.
WAGTAILDOCS_INLINE_CONTENT_TYPES = ['application/pdf', 'text/plain']
A list of MIME content types that will be shown inline in the browser (by serving the HTTP header Content-Disposition: inline
) rather than served as a download, when using the serve_view
method. Defaults to application/pdf
.
WAGTAILDOCS_EXTENSIONS = ['pdf', 'docx']
A list of allowed document extensions that will be validated during document uploading.
If this isn’t supplied all document extensions are allowed.
Warning: this doesn’t always ensure that the uploaded file is valid as files can
be renamed to have an extension no matter what data they contain.
Password Management
WAGTAIL_PASSWORD_MANAGEMENT_ENABLED = True
This specifies whether users are allowed to change their passwords (enabled by default).
WAGTAIL_PASSWORD_RESET_ENABLED = True
This specifies whether users are allowed to reset their passwords. Defaults to the same as WAGTAIL_PASSWORD_MANAGEMENT_ENABLED
. Password reset emails will be sent from the address specified in Django’s DEFAULT_FROM_EMAIL
setting.
WAGTAILUSERS_PASSWORD_ENABLED = True
This specifies whether password fields are shown when creating or editing users through Settings -> Users (enabled by default). Set this to False (along with WAGTAIL_PASSWORD_MANAGEMENT_ENABLED
and WAGTAIL_PASSWORD_RESET_ENABLED
) if your users are authenticated through an external system such as LDAP.
WAGTAILUSERS_PASSWORD_REQUIRED = True
This specifies whether password is a required field when creating a new user. True by default; ignored if WAGTAILUSERS_PASSWORD_ENABLED
is false. If this is set to False, and the password field is left blank when creating a user, then that user will have no usable password; in order to log in, they will have to reset their password (if WAGTAIL_PASSWORD_RESET_ENABLED
is True) or use an alternative authentication system such as LDAP (if one is set up).
WAGTAIL_EMAIL_MANAGEMENT_ENABLED = True
This specifies whether users are allowed to change their email (enabled by default).
Email Notifications
WAGTAILADMIN_NOTIFICATION_FROM_EMAIL = 'wagtail@myhost.io'
Wagtail sends email notifications when content is submitted for moderation, and when the content is accepted or rejected. This setting lets you pick which email address these automatic notifications will come from. If omitted, Wagtail will fall back to using Django’s DEFAULT_FROM_EMAIL
setting if set, or webmaster@localhost
if not.
WAGTAILADMIN_NOTIFICATION_USE_HTML = True
Notification emails are sent in text/plain by default, change this to use HTML formatting.
WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS = False
Notification emails are sent to moderators and superusers by default. You can change this to exclude superusers and only notify moderators.
Wagtail update notifications
WAGTAIL_ENABLE_UPDATE_CHECK = True
For admins only, Wagtail performs a check on the dashboard to see if newer releases are available. This also provides the Wagtail team with the hostname of your Wagtail site. If you’d rather not receive update notifications, or if you’d like your site to remain unknown, you can disable it with this setting.
Private pages / documents
PASSWORD_REQUIRED_TEMPLATE = 'myapp/password_required.html'
This is the path to the Django template which will be used to display the “password required” form when a user accesses a private page. For more details, see the Private pages documentation.
DOCUMENT_PASSWORD_REQUIRED_TEMPLATE = 'myapp/document_password_required.html'
As above, but for password restrictions on documents. For more details, see the Private pages documentation.
Login page
The basic login page can be customised with a custom template.
WAGTAIL_FRONTEND_LOGIN_TEMPLATE = 'myapp/login.html'
Or the login page can be a redirect to an external or internal URL.
WAGTAIL_FRONTEND_LOGIN_URL = '/accounts/login/'
For more details, see the Setting up a login page documentation.
Tag limit
Limit the number of tags that can be added to (django-taggit) Tag model. Default setting is None
, meaning no limit on tags.
Unicode Page Slugs
WAGTAIL_ALLOW_UNICODE_SLUGS = True
By default, page slugs can contain any alphanumeric characters, including non-Latin alphabets. Set this to False to limit slugs to ASCII characters.
Auto update preview
WAGTAIL_AUTO_UPDATE_PREVIEW = False
When enabled, data from an edited page is automatically sent to the server
on each change, even without saving. That way, users don’t have to click on
“Preview” to update the content of the preview page. However, the preview page
tab is not refreshed automatically, users have to do it manually.
This behaviour is disabled by default.
Usage for images, documents and snippets
WAGTAIL_USAGE_COUNT_ENABLED = True
When enabled Wagtail shows where a particular image, document or snippet is being used on your site.
This is disabled by default because it generates a query which may run slowly on sites with large numbers of pages.
A link will appear on the edit page (in the rightmost column) showing you how many times the item is used.
Clicking this link takes you to the “Usage” page, which shows you where the snippet, document or image is used.
The link is also shown on the delete page, above the “Delete” button.
Note
The usage count only applies to direct (database) references. Using documents, images and snippets within StreamFields or rich text fields will not be taken into account.
Time zones
Logged-in users can choose their current time zone for the admin interface in the account settings. If is no time zone selected by the user, then TIME_ZONE
will be used.
(Note that time zones are only applied to datetime fields, not to plain time or date fields. This is a Django design decision.)
The list of time zones is by default the common_timezones list from pytz.
It is possible to override this list via the WAGTAIL_USER_TIME_ZONES
setting.
If there is zero or one time zone permitted, the account settings form will be hidden.
WAGTAIL_USER_TIME_ZONES = ['America/Chicago', 'Australia/Sydney', 'Europe/Rome']
Admin languages
Users can choose between several languages for the admin interface
in the account settings. The list of languages is by default all the available
languages in Wagtail with at least 90% coverage. To change it, set WAGTAILADMIN_PERMITTED_LANGUAGES
:
WAGTAILADMIN_PERMITTED_LANGUAGES = [('en', 'English'),
('pt', 'Portuguese')]
Since the syntax is the same as Django LANGUAGES
, you can do this so users
can only choose between front office languages:
LANGUAGES = WAGTAILADMIN_PERMITTED_LANGUAGES = [('en', 'English'),
('pt', 'Portuguese')]
Static files
WAGTAILADMIN_STATIC_FILE_VERSION_STRINGS = False
Static file URLs within the Wagtail admin are given a version-specific query string of the form ?v=1a2b3c4d
, to prevent outdated cached copies of JavaScript and CSS files from persisting after a Wagtail upgrade. To disable these, set WAGTAILADMIN_STATIC_FILE_VERSION_STRINGS
to False
.
API Settings
For full documentation on API configuration, including these settings, see Wagtail API v2 Configuration Guide documentation.
WAGTAILAPI_BASE_URL = 'http://api.example.com/'
Required when using frontend cache invalidation, used to generate absolute URLs to document files and invalidating the cache.
WAGTAILAPI_LIMIT_MAX = 500
Default is 20, used to change the maximum number of results a user can request at a time, set to None
for no limit.
WAGTAILAPI_SEARCH_ENABLED = False
Default is true, setting this to false will disable full text search on all endpoints.
WAGTAILAPI_USE_FRONTENDCACHE = True
Requires wagtailfrontendcache
app to be installed, indicates the API should use the frontend cache.
Frontend cache
For full documentation on frontend cache invalidation, including these settings, see Frontend cache invalidator.
WAGTAILFRONTENDCACHE = {
'varnish': {
'BACKEND': 'wagtail.contrib.frontend_cache.backends.HTTPBackend',
'LOCATION': 'http://localhost:8000',
},
}
See documentation linked above for full options available.
Note
WAGTAILFRONTENDCACHE_LOCATION
is no longer the preferred way to set the cache location, instead set the LOCATION
within the WAGTAILFRONTENDCACHE
item.
WAGTAILFRONTENDCACHE_LANGUAGES = [l[0] for l in settings.LANGUAGES]
Default is an empty list, must be a list of languages to also purge the urls for each language of a purging url. This setting needs settings.USE_I18N
to be True
to work.
Rich text
WAGTAILADMIN_RICH_TEXT_EDITORS = {
'default': {
'WIDGET': 'wagtail.admin.rich_text.DraftailRichTextArea',
'OPTIONS': {
'features': ['h2', 'bold', 'italic', 'link', 'document-link']
}
},
'legacy': {
'WIDGET': 'wagtail.admin.rich_text.HalloRichTextArea',
}
}
Customise the behaviour of rich text fields. By default, RichTextField
and RichTextBlock
use the configuration given under the 'default'
key, but this can be overridden on a per-field basis through the editor
keyword argument, e.g. body = RichTextField(editor='legacy')
. Within each configuration block, the following fields are recognised:
WIDGET
: The rich text widget implementation to use. Wagtail provides two implementations: wagtail.admin.rich_text.DraftailRichTextArea
(a modern extensible editor which enforces well-structured markup) and wagtail.admin.rich_text.HalloRichTextArea
(deprecated; works directly at the HTML level). Other widgets may be provided by third-party packages.
OPTIONS
: Configuration options to pass to the widget. Recognised options are widget-specific, but both DraftailRichTextArea
and HalloRichTextArea
accept a features
list indicating the active rich text features (see Limiting features in a rich text field).
If a 'default'
editor is not specified, rich text fields that do not specify an editor
argument will use the Draftail editor with the default feature set enabled.
Page locking
WAGTAILADMIN_GLOBAL_PAGE_EDIT_LOCK
can be set to True
to prevent users
from editing pages that they have locked.
Redirects
WAGTAIL_REDIRECTS_FILE_STORAGE = 'tmp_file'
By default the redirect importer keeps track of the uploaded file as a temp file, but on certain environments (load balanced/cloud environments), you cannot keep a shared file between environments. For those cases you can use the built-in cache to store the file instead.
WAGTAIL_REDIRECTS_FILE_STORAGE = 'cache'
Workflow
WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDIT = True
Moderation workflows can be used in two modes. The first is to require that all tasks must approve a specific page revision for the workflow to complete. As a result,
if edits are made to a page while it is in moderation, any approved tasks will need to be re-approved for the new revision before the workflow finishes.
This is the default, WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDIT = True
. The second mode does not require reapproval: if edits are made when
tasks have already been approved, those tasks do not need to be reapproved. This is more suited to a hierarchical workflow system. To use workflows in this mode,
set WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDIT = False
.
WAGTAIL_FINISH_WORKFLOW_ACTION = 'wagtail.core.workflows.publish_workflow_state'
This sets the function to be called when a workflow completes successfully - by default, wagtail.core.workflows.publish_workflow_state
,
which publishes the page. The function must accept a WorkflowState
object as its only positional argument.
WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH = True
This determines whether publishing a page with an ongoing workflow will cancel the workflow (if true) or leave the workflow unaffected (false).
Disabling this could be useful if your site has long, multi-step workflows, and you want to be able to publish urgent page updates while the
workflow continues to provide less urgent feedback.