A Django content management system with a user-friendly interface and powerful features for building websites and applications.
Additional functionality modules that extend Wagtail's core capabilities with forms, redirects, settings management, and specialized content types. These modules provide common functionality needed in most Wagtail sites.
Form handling functionality for creating contact forms, surveys, and data collection pages.
class AbstractForm(Page):
"""
Base class for form pages with submission handling.
Properties:
thank_you_text (RichTextField): Message shown after successful submission
from_address (str): Default sender email address
to_address (str): Email address to send submissions to
subject (str): Email subject line
"""
thank_you_text: RichTextField
from_address: str
to_address: str
subject: str
def get_form_class(self):
"""Get the form class for this form page."""
def get_form(self, *args, **kwargs):
"""Get form instance with data."""
def process_form_submission(self, form):
"""Process valid form submission and return form submission object."""
def render_landing_page(self, request, form_submission=None, *args, **kwargs):
"""Render thank you page after form submission."""
class AbstractFormField(Orderable):
"""
Individual field definition for forms.
Properties:
label (str): Field label text
field_type (str): Type of form field ('singleline', 'multiline', 'email', etc.)
required (bool): Whether field is required
choices (str): Choices for dropdown/radio fields (newline separated)
default_value (str): Default field value
help_text (str): Help text for the field
"""
label: str
field_type: str
required: bool
choices: str
default_value: str
help_text: str
@property
def clean_name(self):
"""Get cleaned field name for use in forms."""
class AbstractEmailForm(AbstractForm):
"""
Form that emails submissions to specified addresses.
"""
def process_form_submission(self, form):
"""Process submission and send email notification."""
class AbstractFormSubmission(models.Model):
"""
Stores form submission data.
Properties:
form_data (JSONField): Submitted form data
submit_time (datetime): When form was submitted
"""
form_data: JSONField
submit_time: datetime
def get_data(self):
"""Get form data as dictionary."""URL redirect management for handling moved content and SEO.
class Redirect(models.Model):
"""
URL redirect configuration.
Properties:
old_path (str): Original URL path to redirect from
site (Site): Site this redirect applies to
is_permanent (bool): Whether redirect is permanent (301) or temporary (302)
redirect_page (Page): Page to redirect to (optional)
redirect_link (str): External URL to redirect to (optional)
"""
old_path: str
site: Site
is_permanent: bool
redirect_page: Page
redirect_link: str
def get_is_permanent(self):
"""Check if this is a permanent redirect."""
@property
def redirect_to(self):
"""Get the target URL for this redirect."""
@classmethod
def get_for_site(cls, site=None):
"""Get all redirects for a specific site."""Site-specific settings management with admin interface integration.
class BaseSiteSetting(models.Model):
"""
Base class for site-specific settings.
Properties:
site (Site): Site these settings apply to
"""
site: Site
class Meta:
abstract = True
@classmethod
def for_site(cls, site):
"""Get settings instance for a specific site."""
def register_setting(model=None, **kwargs):
"""
Decorator to register a model as a site setting.
Usage:
@register_setting
class SocialMediaSettings(BaseSiteSetting):
facebook_url = models.URLField(blank=True)
Parameters:
model (Model): Model class to register
icon (str): Icon name for admin interface
"""
class SettingsMenuItem:
"""Menu item for settings in admin interface."""
def __init__(self, model, icon='cog', **kwargs):
"""Initialize settings menu item."""XML sitemap generation for search engine optimization.
def sitemap(request):
"""
Generate XML sitemap for all live pages.
Returns:
HttpResponse: XML sitemap response
"""
class Sitemap:
"""
Sitemap configuration class.
Customize sitemap generation by subclassing.
"""
def items(self):
"""Get items to include in sitemap."""
def location(self, item):
"""Get URL for sitemap item."""
def lastmod(self, item):
"""Get last modification date for item."""
def changefreq(self, item):
"""Get change frequency for item."""
def priority(self, item):
"""Get priority for item."""Custom URL routing within page hierarchies for dynamic content.
class RoutablePageMixin:
"""
Mixin that adds custom URL routing to pages.
Allows pages to handle multiple URL patterns and views.
"""
def serve(self, request, *args, **kwargs):
"""Enhanced serve method with route handling."""
def reverse_subpage(self, name, args=None, kwargs=None):
"""Get URL for a named sub-route."""
def route(pattern, name=None):
"""
Decorator to define custom routes within a page.
Parameters:
pattern (str): URL regex pattern
name (str): Optional name for the route
Usage:
class BlogPage(RoutablePageMixin, Page):
@route(r'^archive/(\d{4})/$', name='archive_year')
def archive_year(self, request, year):
return render(request, 'blog/archive.html', {'year': year})
"""Editable table content blocks for structured data presentation.
class TableBlock(StructBlock):
"""
Block for creating editable tables in StreamField.
Properties:
table_options (dict): Configuration options for table editing
"""
def __init__(self, required=True, help_text=None, table_options=None, **kwargs):
"""
Initialize table block.
Parameters:
table_options (dict): Options like row/column headers, cell types
"""
def render(self, value, context=None):
"""Render table as HTML."""
class TypedTableBlock(TableBlock):
"""
Table block with column type definitions for structured data.
Parameters:
columns (list): List of column definitions with types
"""
def __init__(self, columns, **kwargs):
"""
Initialize typed table block.
Parameters:
columns (list): List of (name, block_type) tuples
"""Registration and management of reusable content snippets.
def register_snippet(model):
"""
Decorator to register a model as a snippet for admin interface.
Usage:
@register_snippet
class Category(models.Model):
name = models.CharField(max_length=100)
Parameters:
model (Model): Django model to register as snippet
"""
class SnippetViewSet(ModelViewSet):
"""
ViewSet for managing snippets in admin interface.
Provides CRUD operations for snippet models.
"""
def get_queryset(self):
"""Get queryset for snippet listing."""
class SnippetChooserBlock(ChooserBlock):
"""
Block for choosing snippets in StreamField.
Parameters:
target_model (Model): Snippet model to choose from
"""
def __init__(self, target_model, **kwargs):
"""Initialize snippet chooser block."""Promoted search results for editorial control over search rankings.
class SearchPromotion(models.Model):
"""
Promoted search result for specific queries.
Properties:
query (str): Search query to promote results for
page (Page): Page to promote in results
sort_order (int): Order of promotion in results
description (str): Custom description for promoted result
"""
query: str
page: Page
sort_order: int
description: str
@classmethod
def get_for_query(cls, query_string):
"""Get promotions matching a search query."""Cache invalidation for CDN and reverse proxy integration.
def purge_page_from_cache(page, backend_settings=None, backends=None):
"""
Purge a specific page from frontend caches.
Parameters:
page (Page): Page to purge from cache
backend_settings (dict): Cache backend configuration
backends (list): List of cache backends to purge from
"""
def purge_pages_from_cache(pages, backend_settings=None, backends=None):
"""
Purge multiple pages from frontend caches.
Parameters:
pages (QuerySet): Pages to purge from cache
backend_settings (dict): Cache backend configuration
backends (list): List of cache backends to purge from
"""
class CloudflareBackend:
"""Cache backend for Cloudflare CDN integration."""
def __init__(self, config):
"""Initialize Cloudflare backend with API credentials."""
def purge(self, urls):
"""Purge specific URLs from Cloudflare cache."""
class CloudfrontBackend:
"""Cache backend for AWS CloudFront CDN integration."""
def __init__(self, config):
"""Initialize CloudFront backend with AWS credentials."""
def purge(self, urls):
"""Create CloudFront invalidation for URLs."""from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
from wagtail.contrib.forms.panels import FormSubmissionsPanel
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from modelcluster.fields import ParentalKey
class FormField(AbstractFormField):
"""Custom form field with page relationship."""
page = ParentalKey('ContactPage', on_delete=models.CASCADE, related_name='form_fields')
class ContactPage(AbstractEmailForm):
"""Contact form page with email notifications."""
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel('intro'),
InlinePanel('form_fields', label="Form fields"),
FieldPanel('thank_you_text'),
MultiFieldPanel([
FieldPanel('to_address', classname="col6"),
FieldPanel('from_address', classname="col6"),
FieldPanel('subject'),
], "Email Settings"),
]
def get_form_fields(self):
return self.form_fields.all()
# In templates
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block content %}
<div class="contact-page">
<h1>{{ page.title }}</h1>
{{ page.intro|richtext }}
<form action="{% pageurl page %}" method="post">
{% csrf_token %}
{% for field in form %}
<div class="field">
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
</div>
{% endfor %}
<button type="submit">Send Message</button>
</form>
</div>
{% endblock %}from wagtail.contrib.settings.models import BaseSiteSetting, register_setting
from wagtail.admin.panels import FieldPanel
@register_setting
class SocialMediaSettings(BaseSiteSetting):
"""Social media links and API keys."""
facebook_url = models.URLField(blank=True, help_text='Facebook page URL')
twitter_handle = models.CharField(max_length=100, blank=True)
instagram_url = models.URLField(blank=True)
google_analytics_id = models.CharField(max_length=100, blank=True)
panels = [
MultiFieldPanel([
FieldPanel('facebook_url'),
FieldPanel('twitter_handle'),
FieldPanel('instagram_url'),
], heading='Social Media Links'),
FieldPanel('google_analytics_id'),
]
# Usage in templates
{% load wagtailsettings_tags %}
{% get_settings "myapp.SocialMediaSettings" as social_settings %}
<footer>
{% if social_settings.facebook_url %}
<a href="{{ social_settings.facebook_url }}">Facebook</a>
{% endif %}
{% if social_settings.twitter_handle %}
<a href="https://twitter.com/{{ social_settings.twitter_handle }}">Twitter</a>
{% endif %}
</footer>from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from django.shortcuts import render
class BlogIndexPage(RoutablePageMixin, Page):
"""Blog index with custom routing for archives and tags."""
def get_context(self, request):
context = super().get_context(request)
context['blog_posts'] = BlogPage.objects.child_of(self).live()
return context
@route(r'^$')
def blog_index(self, request):
"""Default blog listing."""
return self.serve(request)
@route(r'^archive/(\d{4})/$', name='archive_year')
def archive_year(self, request, year):
"""Blog posts for specific year."""
posts = BlogPage.objects.child_of(self).live().filter(date__year=year)
return render(request, 'blog/archive.html', {
'page': self,
'posts': posts,
'year': year,
})
@route(r'^tag/([\w-]+)/$', name='tag')
def tag_view(self, request, tag_slug):
"""Blog posts with specific tag."""
posts = BlogPage.objects.child_of(self).live().filter(tags__slug=tag_slug)
return render(request, 'blog/tag.html', {
'page': self,
'posts': posts,
'tag': tag_slug,
})
def get_sitemap_urls(self, request=None):
"""Add custom routes to sitemap."""
sitemap = super().get_sitemap_urls(request)
# Add yearly archives
years = BlogPage.objects.dates('date', 'year')
for year in years:
sitemap.append({
'location': self.reverse_subpage('archive_year', args=[year.year]),
'lastmod': BlogPage.objects.filter(date__year=year.year).latest('last_published_at').last_published_at,
})
return sitemap
# Template usage
<nav class="blog-nav">
<a href="{% pageurl page %}">All Posts</a>
<a href="{% pageurl page %}archive/2023/">2023 Archive</a>
<a href="{% pageurl page %}tag/django/">Django Posts</a>
</nav>from wagtail.snippets.models import register_snippet
from wagtail.admin.panels import FieldPanel
from wagtail.search import index
@register_snippet
class Category(models.Model):
"""Blog category snippet."""
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
icon = models.CharField(max_length=50, blank=True)
panels = [
FieldPanel('name'),
FieldPanel('slug'),
FieldPanel('description'),
FieldPanel('icon'),
]
search_fields = [
index.SearchField('name'),
index.SearchField('description'),
]
def __str__(self):
return self.name
class Meta:
ordering = ['name']
@register_snippet
class Author(models.Model):
"""Author snippet with contact information."""
name = models.CharField(max_length=100)
bio = models.TextField()
photo = models.ForeignKey('wagtailimages.Image', on_delete=models.SET_NULL, null=True, blank=True)
email = models.EmailField(blank=True)
website = models.URLField(blank=True)
panels = [
FieldPanel('name'),
FieldPanel('bio'),
FieldPanel('photo'),
FieldPanel('email'),
FieldPanel('website'),
]
def __str__(self):
return self.name
# Using snippets in pages
class BlogPage(Page):
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True, blank=True)
content_panels = Page.content_panels + [
FieldPanel('category'),
FieldPanel('author'),
]
# In StreamField blocks
from wagtail.snippets.blocks import SnippetChooserBlock
class ContentBlock(StructBlock):
heading = CharBlock()
text = RichTextBlock()
category = SnippetChooserBlock(Category, required=False)from wagtail.contrib.table_block.blocks import TableBlock
class ContentPage(Page):
"""Page with table support."""
body = StreamField([
('paragraph', RichTextBlock()),
('table', TableBlock(table_options={
'minSpareRows': 0,
'startRows': 4,
'startCols': 4,
'colHeaders': False,
'rowHeaders': False,
'contextMenu': True,
'editor': 'text',
'stretchH': 'all',
'height': 216,
'language': 'en',
'renderer': 'text',
'autoColumnSize': False,
})),
])
content_panels = Page.content_panels + [
FieldPanel('body'),
]
# Custom table template (templates/table_block.html)
<table class="data-table">
{% for row in self.data %}
<tr>
{% for cell in row %}
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>Install with Tessl CLI
npx tessl i tessl/pypi-wagtail