A Django content management system with a user-friendly interface and powerful features for building websites and applications.
Template tags for page URLs, content rendering, and template utilities for building Wagtail-powered front-ends. Wagtail provides comprehensive template integration with Django's template system.
Essential template tags for page navigation, URL generation, and content rendering.
def pageurl(page, request=None):
"""
Get the URL for a page, with optional request context for relative URLs.
Usage in templates: {% pageurl page %}
Parameters:
page (Page): Page object to get URL for
request (HttpRequest): Optional request for relative URL generation
Returns:
str: URL path for the page (relative by default)
"""
def fullpageurl(page, request=None):
"""
Get the absolute URL for a page including domain.
Usage in templates: {% fullpageurl page %}
Parameters:
page (Page): Page object to get URL for
request (HttpRequest): Optional request for domain detection
Returns:
str: Absolute URL including protocol and domain
"""
def slugurl(slug, request=None):
"""
Get page URL by slug lookup within the current site.
Usage in templates: {% slugurl 'my-page-slug' %}
Parameters:
slug (str): Page slug to find and link to
request (HttpRequest): Optional request for site context
Returns:
str: URL for the page with matching slug, or empty string if not found
"""
def wagtail_site(request=None):
"""
Get the current Site object for use in templates.
Usage in templates: {% wagtail_site as current_site %}
Parameters:
request (HttpRequest): Current request for site detection
Returns:
Site: Current Site object with hostname, root_page, etc.
"""
def include_block(block_value, context=None):
"""
Render a StreamField block with its template.
Usage in templates: {% include_block page.body %}
Parameters:
block_value (StreamValue): StreamField content to render
context (dict): Additional template context
Returns:
str: Rendered HTML for all blocks in the StreamField
"""
def wagtail_version():
"""
Get the current Wagtail version string.
Usage in templates: {% wagtail_version %}
Returns:
str: Wagtail version (e.g., "4.1.0")
"""Template filters for processing and rendering rich text content safely.
def richtext(value):
"""
Process rich text content for safe HTML rendering.
Usage in templates: {{ page.body|richtext }}
Processes rich text to:
- Convert internal page links to proper URLs
- Process document download links
- Handle embedded content
- Apply security filtering
Parameters:
value (str): Raw rich text content from RichTextField
Returns:
SafeString: Processed HTML safe for rendering
"""
def linebreaks_richtext(value):
"""
Convert line breaks in rich text to proper HTML paragraphs.
Usage in templates: {{ content|linebreaks_richtext }}
Parameters:
value (str): Rich text content with line breaks
Returns:
SafeString: Content with proper paragraph tags
"""Template tags for rendering images with automatic rendition generation.
def image(image, filter_spec, attrs=None):
"""
Generate an image rendition and return HTML img tag or rendition object.
Usage in templates:
{% image page.photo fill-400x300 %}
{% image page.photo width-800 as hero %}
Parameters:
image (Image): Image object to render
filter_spec (str): Image operations to apply (e.g., 'fill-400x300')
attrs (dict): Additional HTML attributes for img tag
Returns:
str or Rendition: HTML img tag or rendition object (if using 'as' syntax)
"""
def responsive_image(image, sizes=None, attrs=None):
"""
Generate responsive image with multiple renditions and srcset.
Usage in templates: {% responsive_image page.hero_image %}
Parameters:
image (Image): Image object to make responsive
sizes (list): List of size specifications for different breakpoints
attrs (dict): Additional HTML attributes
Returns:
str: HTML picture element with responsive images
"""Template tags for building navigation menus and page hierarchies.
def get_site_root(request=None):
"""
Get the root page for the current site.
Usage in templates: {% get_site_root as site_root %}
Parameters:
request (HttpRequest): Current request for site detection
Returns:
Page: Root page of the current site
"""
def top_menu(parent, calling_page=None):
"""
Get top-level menu items from a parent page.
Usage in templates: {% top_menu parent calling_page %}
Parameters:
parent (Page): Parent page to get children from
calling_page (Page): Current page for active state detection
Returns:
QuerySet: Child pages suitable for top-level navigation
"""
def breadcrumbs(calling_page):
"""
Generate breadcrumb navigation for a page.
Usage in templates: {% breadcrumbs self %}
Parameters:
calling_page (Page): Current page to generate breadcrumbs for
Returns:
list: List of ancestor pages from root to current page
"""Template tags for search functionality and query handling.
def search(query_string, model_or_queryset=None, fields=None):
"""
Perform search from templates.
Usage in templates: {% search "query" as search_results %}
Parameters:
query_string (str): Search query
model_or_queryset: Model or QuerySet to search
fields (list): Fields to search in
Returns:
SearchResults: Search results with pagination support
"""
def paginate(page_list, per_page, page_number):
"""
Paginate a list of items for template display.
Usage in templates: {% paginate items 10 request.GET.page %}
Parameters:
page_list (list): Items to paginate
per_page (int): Number of items per page
page_number (int): Current page number
Returns:
Page: Paginated page object with items and navigation
"""General utility template tags for common Wagtail operations.
def get_page_by_slug(slug, parent=None):
"""
Get a page by its slug within an optional parent.
Usage in templates: {% get_page_by_slug 'about' as about_page %}
Parameters:
slug (str): Page slug to find
parent (Page): Optional parent page to search within
Returns:
Page: Found page or None if not found
"""
def get_pages_by_type(page_type, parent=None):
"""
Get pages of a specific type.
Usage in templates: {% get_pages_by_type 'blog.BlogPage' as blog_pages %}
Parameters:
page_type (str): Page type in 'app.ModelName' format
parent (Page): Optional parent to search within
Returns:
QuerySet: Pages of the specified type
"""
def get_site_setting(setting_name, site=None):
"""
Get a site-specific setting value.
Usage in templates: {% get_site_setting 'contact_email' as contact %}
Parameters:
setting_name (str): Name of the setting to retrieve
site (Site): Optional site (defaults to current site)
Returns:
Any: Setting value or None if not found
"""<!-- Base template: base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ page.seo_title|default:page.title }} - {{ site.site_name }}{% endblock %}</title>
<meta name="description" content="{{ page.search_description }}">
{% load wagtailcore_tags %}
</head>
<body class="{% block body_class %}{% endblock %}">
<header>
{% get_site_root as site_root %}
<nav>
<a href="{% pageurl site_root %}">{{ site.site_name }}</a>
{% top_menu site_root self as menu_items %}
<ul>
{% for item in menu_items %}
<li><a href="{% pageurl item %}" {% if item == self %}class="active"{% endif %}>{{ item.title }}</a></li>
{% endfor %}
</ul>
</nav>
</header>
<main>
{% block breadcrumbs %}
{% breadcrumbs self as breadcrumb_items %}
<ol class="breadcrumbs">
{% for item in breadcrumb_items %}
<li><a href="{% pageurl item %}">{{ item.title }}</a></li>
{% endfor %}
</ol>
{% endblock %}
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2023 {{ site.site_name }}. Powered by Wagtail {% wagtail_version %}.</p>
</footer>
</body>
</html>
<!-- Page template: blog/blog_page.html -->
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block title %}{{ page.title }} - Blog{% endblock %}
{% block content %}
<article>
<header>
<h1>{{ page.title }}</h1>
<p class="meta">
Published {{ page.date|date:"F j, Y" }}
{% if page.author %}by {{ page.author.name }}{% endif %}
</p>
{% if page.featured_image %}
{% image page.featured_image fill-800x400 as hero %}
<img src="{{ hero.url }}" alt="{{ hero.alt }}" class="hero-image">
{% endif %}
</header>
<div class="content">
<p class="intro">{{ page.intro }}</p>
{{ page.body|richtext }}
</div>
<footer>
<p>
<a href="{% slugurl 'blog' %}">← Back to Blog</a>
</p>
</footer>
</article>
{% endblock %}<!-- StreamField block templates -->
<!-- blocks/heading_block.html -->
<h{{ self.level|default:2 }} class="heading-block">{{ self.text }}</h{{ self.level|default:2 }}>
<!-- blocks/paragraph_block.html -->
<div class="paragraph-block">
{{ self|richtext }}
</div>
<!-- blocks/image_block.html -->
{% load wagtailimages_tags %}
<figure class="image-block">
{% image self.image fill-800x600 as img %}
<img src="{{ img.url }}" alt="{{ img.alt }}" width="{{ img.width }}" height="{{ img.height }}">
{% if self.caption %}
<figcaption>{{ self.caption|richtext }}</figcaption>
{% endif %}
</figure>
<!-- blocks/quote_block.html -->
<blockquote class="quote-block">
{{ self.quote|richtext }}
{% if self.attribution %}
<cite>{{ self.attribution }}</cite>
{% endif %}
</blockquote>
<!-- Main page template using StreamField -->
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block content %}
<div class="page-content">
<h1>{{ page.title }}</h1>
{% if page.intro %}
<div class="intro">{{ page.intro|richtext }}</div>
{% endif %}
<div class="stream-content">
{% include_block page.body %}
</div>
</div>
{% endblock %}<!-- Custom navigation template -->
{% load wagtailcore_tags %}
<!-- Main navigation -->
{% get_site_root as site_root %}
<nav class="main-nav">
<ul class="nav-list">
{% top_menu site_root self as main_items %}
{% for item in main_items %}
<li class="nav-item {% if item == self or item in self.get_ancestors %}active{% endif %}">
<a href="{% pageurl item %}" class="nav-link">{{ item.title }}</a>
<!-- Sub-navigation -->
{% if item.get_children.live %}
<ul class="sub-nav">
{% for child in item.get_children.live %}
<li class="sub-nav-item {% if child == self %}active{% endif %}">
<a href="{% pageurl child %}" class="sub-nav-link">{{ child.title }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
<!-- Sidebar navigation -->
<aside class="sidebar">
<h3>In This Section</h3>
{% get_pages_by_type 'blog.BlogPage' as blog_pages %}
<ul class="sidebar-nav">
{% for page in blog_pages.live|slice:":5" %}
<li><a href="{% pageurl page %}">{{ page.title }}</a></li>
{% endfor %}
</ul>
</aside>
<!-- Footer navigation -->
<footer class="site-footer">
<div class="footer-nav">
{% get_page_by_slug 'contact' as contact_page %}
{% get_page_by_slug 'privacy' as privacy_page %}
{% get_page_by_slug 'terms' as terms_page %}
<ul class="footer-links">
{% if contact_page %}<li><a href="{% pageurl contact_page %}">{{ contact_page.title }}</a></li>{% endif %}
{% if privacy_page %}<li><a href="{% pageurl privacy_page %}">{{ privacy_page.title }}</a></li>{% endif %}
{% if terms_page %}<li><a href="{% pageurl terms_page %}">{{ terms_page.title }}</a></li>{% endif %}
</ul>
</div>
</footer>{% load wagtailimages_tags %}
<!-- Basic image rendering -->
{% if page.hero_image %}
{% image page.hero_image fill-1200x600 as hero %}
<div class="hero" style="background-image: url({{ hero.url }});">
<h1>{{ page.title }}</h1>
</div>
{% endif %}
<!-- Responsive images -->
{% if page.featured_image %}
{% image page.featured_image fill-400x300 as mobile %}
{% image page.featured_image fill-800x600 as tablet %}
{% image page.featured_image fill-1200x800 as desktop %}
<picture class="responsive-image">
<source media="(min-width: 1024px)" srcset="{{ desktop.url }}">
<source media="(min-width: 768px)" srcset="{{ tablet.url }}">
<img src="{{ mobile.url }}" alt="{{ page.featured_image.title }}"
width="{{ mobile.width }}" height="{{ mobile.height }}">
</picture>
{% endif %}
<!-- Image gallery -->
<div class="image-gallery">
{% for gallery_image in page.gallery_images.all %}
{% image gallery_image.image fill-300x200 as thumb %}
{% image gallery_image.image width-1000 as full %}
<div class="gallery-item">
<a href="{{ full.url }}" data-lightbox="gallery">
<img src="{{ thumb.url }}" alt="{{ gallery_image.image.title }}">
</a>
{% if gallery_image.caption %}
<p class="caption">{{ gallery_image.caption }}</p>
{% endif %}
</div>
{% endfor %}
</div>
<!-- Optimized images with WebP -->
{% image page.hero_image fill-1200x600|format-webp as webp_hero %}
{% image page.hero_image fill-1200x600|format-jpeg as jpeg_hero %}
<picture>
<source srcset="{{ webp_hero.url }}" type="image/webp">
<img src="{{ jpeg_hero.url }}" alt="{{ page.hero_image.title }}" class="hero-image">
</picture><!-- Search form -->
<form action="{% url 'search' %}" method="get" class="search-form">
<input type="text" name="q" value="{{ query_string }}" placeholder="Search..." required>
<button type="submit">Search</button>
</form>
<!-- Search results template -->
{% load wagtailcore_tags %}
{% block content %}
<div class="search-results">
<h1>Search Results</h1>
{% if query_string %}
<p>You searched for: <strong>{{ query_string }}</strong></p>
{% if search_results %}
<p>{{ search_results.paginator.count }} result{{ search_results.paginator.count|pluralize }} found</p>
<div class="results-list">
{% for result in search_results %}
<article class="search-result">
<h3><a href="{% pageurl result %}">{{ result.title }}</a></h3>
<p class="meta">{{ result.content_type.model_class.get_verbose_name }}</p>
{% if result.search_description %}
<p>{{ result.search_description }}</p>
{% endif %}
</article>
{% endfor %}
</div>
<!-- Pagination -->
{% if search_results.has_other_pages %}
<nav class="pagination">
{% if search_results.has_previous %}
<a href="?q={{ query_string }}&page={{ search_results.previous_page_number }}">← Previous</a>
{% endif %}
<span class="current">
Page {{ search_results.number }} of {{ search_results.paginator.num_pages }}
</span>
{% if search_results.has_next %}
<a href="?q={{ query_string }}&page={{ search_results.next_page_number }}">Next →</a>
{% endif %}
</nav>
{% endif %}
{% else %}
<p>No results found.</p>
{% endif %}
{% endif %}
</div>
{% endblock %}# templatetags/blog_tags.py
from django import template
from django.utils import timezone
from blog.models import BlogPage
register = template.Library()
@register.inclusion_tag('blog/recent_posts.html')
def recent_blog_posts(count=5):
"""Get recent blog posts for sidebar."""
posts = BlogPage.objects.live().order_by('-date')[:count]
return {'posts': posts}
@register.simple_tag
def get_popular_posts(count=5):
"""Get most popular blog posts."""
return BlogPage.objects.live().order_by('-page_views')[:count]
@register.filter
def reading_time(content):
"""Calculate reading time for content."""
word_count = len(str(content).split())
minutes = max(1, word_count // 200) # 200 words per minute
return f"{minutes} min read"
@register.inclusion_tag('blog/tag_cloud.html')
def tag_cloud():
"""Generate tag cloud for blog."""
from django.db.models import Count
tags = BlogTag.objects.annotate(
post_count=Count('blogpage')
).filter(post_count__gt=0).order_by('-post_count')[:20]
return {'tags': tags}
# Usage in templates
{% load blog_tags %}
<!-- Recent posts sidebar -->
{% recent_blog_posts 3 %}
<!-- Popular posts -->
{% get_popular_posts 5 as popular %}
<ul>
{% for post in popular %}
<li><a href="{% pageurl post %}">{{ post.title }}</a></li>
{% endfor %}
</ul>
<!-- Reading time -->
<p class="meta">{{ page.body|reading_time }}</p>
<!-- Tag cloud -->
{% tag_cloud %}<!-- Contact form template -->
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block content %}
<div class="contact-page">
<h1>{{ page.title }}</h1>
{% if page.intro %}
<div class="intro">
{{ page.intro|richtext }}
</div>
{% endif %}
<form action="{% pageurl page %}" method="post" class="contact-form">
{% csrf_token %}
{% for field in form %}
<div class="form-field {% if field.errors %}error{% endif %}">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
<p class="help-text">{{ field.help_text }}</p>
{% endif %}
{% for error in field.errors %}
<p class="error-message">{{ error }}</p>
{% endfor %}
</div>
{% endfor %}
<button type="submit" class="submit-button">Send Message</button>
</form>
{% if page.thank_you_text %}
<div class="thank-you" style="display: none;">
{{ page.thank_you_text|richtext }}
</div>
{% endif %}
</div>
{% endblock %}Install with Tessl CLI
npx tessl i tessl/pypi-wagtail