A rapid template development system for Wagtail that allows you to build and iterate on templates using YAML fixture files before creating backend models.
This prototyping system enables frontend-first development by:
- Creating templates with YAML fixtures instead of database content
- Providing mock objects that mimic Wagtail's Page and StreamField APIs
- Offering a dashboard to preview all prototype templates
- Working only in DEBUG mode (production-safe)
Once your template is complete, create the actual Wagtail page model and the template continues to work without changes.
Copy the entire app/prototypes/ folder from this repository into your Wagtail project:
# If your project has an app directory structure (e.g., myproject/app/)
cp -r app/prototypes /path/to/your/project/app/
# Or if your apps are at the project root (e.g., myproject/home/, myproject/core/)
cp -r app/prototypes /path/to/your/project/This includes:
views.py- Prototype view logic and mock objectsfixtures/- Directory for YAML fixture files (examples included - you'll create your own)templates/prototypes/dashboard.html- Prototype dashboard template- Other app files
Note: The fixtures/ folder contains example fixtures for this demo project's pages (home_page.yaml, standard_page.yaml). These are just examples to show you the structure. You'll delete these and create your own fixture files for your project's templates.
Add PyYAML to your project:
pip install PyYAML
# or if using uv
uv add PyYAMLAdd the prototypes app to your settings.py:
INSTALLED_APPS = [
# ... your other apps
'prototypes', # or 'app.prototypes' if in app/ subdirectory
]Add prototype URLs to your main urls.py (only enabled in DEBUG mode):
from django.conf import settings
from django.urls import include, path
# ... your other imports and url patterns
if settings.DEBUG:
from prototypes import views as prototype_views
# or: from app.prototypes import views as prototype_views
urlpatterns += [
path("prototype/", prototype_views.dashboard, name="prototype_dashboard"),
path("prototype/<path:template_path>/", prototype_views.prototype_view, name="prototype"),
]If your project structure differs from this repository, you may need to adjust file paths in prototypes/views.py:
In prototype_view() function (around line 114):
# Current (assumes app/prototypes structure):
fixture_path = Path(settings.BASE_DIR) / 'app' / 'prototypes' / 'fixtures' / fixture_filename
# Adjust if prototypes is at project root:
fixture_path = Path(settings.BASE_DIR) / 'prototypes' / 'fixtures' / fixture_filenameIn dashboard() function (around line 154-160):
# Current:
fixtures_dir = Path(settings.BASE_DIR) / 'app' / 'prototypes' / 'fixtures'
app_dir = Path(settings.BASE_DIR) / 'app'
# Adjust based on your project structure
# For example, if apps are at root:
fixtures_dir = Path(settings.BASE_DIR) / 'prototypes' / 'fixtures'
app_dir = Path(settings.BASE_DIR)Also update the apps to scan (around line 161):
# Skip prototypes app and any system directories
if not app_path.is_dir() or app_path.name.startswith('.') or app_path.name == 'prototypes':
continueThe prototypes/fixtures/ folder contains example fixtures from this demo project. You can delete them and create your own:
# Remove example fixtures (optional)
rm -rf prototypes/fixtures/home/
rm -rf prototypes/fixtures/core/
# Or keep them as reference while you create your ownRun your development server and visit /prototype/ to see the dashboard:
python manage.py runserver
# Visit: http://localhost:8000/prototype/- Create your template in any app's templates directory:
# Example: create a landing page template
mkdir -p home/templates/home
touch home/templates/home/landing_page.html- Write your template using standard Django/Wagtail template syntax:
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block content %}
<h1>{{ page.title }}</h1>
{% for block in page.body %}
{% if block.block_type == 'richtext' %}
<div class="richtext-block">
{{ block.value|safe }}
</div>
{% elif block.block_type == 'image' %}
<figure class="image-block">
<img src="{{ block.value.image.url }}" alt="{{ block.value.image.alt }}">
{% if block.value.caption %}
<figcaption>{{ block.value.caption }}</figcaption>
{% endif %}
</figure>
{% endif %}
{% endfor %}
{% endblock %}- Create a YAML fixture with mock data at
prototypes/fixtures/{app}/{template}.yaml:
# Create fixture matching template path
mkdir -p prototypes/fixtures/home
touch prototypes/fixtures/home/landing_page.yaml# prototypes/fixtures/home/landing_page.yaml
page:
title: "Welcome to Our Site"
seo_title: "Home - Our Site"
search_description: "Welcome to our amazing website"
# StreamField data
body:
- type: richtext
value: |
<h2>About Us</h2>
<p>We build amazing things.</p>
- type: image
value:
url: "https://picsum.photos/1200/600"
alt: "Hero image"
caption: "Our amazing product"
- type: richtext
value: |
<p>More content here...</p>
# Custom fields (simulating ForeignKeys, snippets, etc.)
cta:
title: "Get Started"
description: "Join us today"
button_text: "Sign Up"
button_link: "/signup/"- View your prototype:
- Visit
http://localhost:8000/prototype/to see the dashboard - Your template will be listed with fixture status
- Click "View Prototype" to preview it with the fixture data
- Visit
Important: The fixtures included in this repository (app/prototypes/fixtures/home/home_page.yaml and app/prototypes/fixtures/core/standard_page.yaml) are examples from this demo project. They won't work with your project's templates. Use them as reference for structure, then create your own fixtures for your own templates.
Reference the example fixtures for structure:
app/prototypes/fixtures/home/home_page.yaml- Shows homepage with StreamField and CTA structureapp/prototypes/fixtures/core/standard_page.yaml- Shows content page with multiple block types
StreamField blocks:
page:
body:
# Rich text content
- type: richtext
value: "<p>HTML content</p>"
# Headings
- type: heading
value: "Section Title"
# Images
- type: image
value:
url: "https://example.com/image.jpg"
alt: "Image description"
caption: "Optional caption"
# Quotes
- type: quote
value:
quote: "The quote text"
attribution: "Author Name"
# Embeds (YouTube, Vimeo, etc.)
- type: embed
value:
url: "https://www.youtube.com/embed/VIDEO_ID"
caption: "Video caption"Simulating ForeignKeys and Snippets:
page:
# Simple field
subtitle: "A subtitle"
# Object/snippet (e.g., Author snippet)
author:
name: "John Doe"
bio: "Author biography"
avatar: "https://example.com/avatar.jpg"
# Lists
featured_items:
- title: "Item 1"
description: "Description"
image: "https://example.com/item1.jpg"
- title: "Item 2"
description: "Description"
image: "https://example.com/item2.jpg"Once your template is finalized, create the actual Wagtail page model.
If you're using Claude Code, there's a built-in skill that can automatically generate the page model from your fixture file:
# In Claude Code, run:
/wagtail-model-creator
# Or mention it in your prompt:
"Create a Wagtail page model from app/prototypes/fixtures/home/landing_page.yaml"The skill will analyze your fixture structure and generate the complete page model with:
- Correct field types based on your fixture data
- StreamField blocks matching your block types
- Proper imports and content panels
- Ready-to-use model code
Write the page model yourself based on your fixture structure:
# home/models.py
from wagtail.models import Page
from wagtail.fields import StreamField
from wagtail import blocks
from wagtail.admin.panels import FieldPanel
from wagtail.images.blocks import ImageChooserBlock
class LandingPage(Page):
body = StreamField([
('richtext', blocks.RichTextBlock()),
('heading', blocks.CharBlock()),
('image', blocks.StructBlock([
('image', ImageChooserBlock()),
('caption', blocks.CharBlock(required=False)),
])),
('quote', blocks.StructBlock([
('quote', blocks.TextBlock()),
('attribution', blocks.CharBlock()),
])),
], use_json_field=True)
content_panels = Page.content_panels + [
FieldPanel('body'),
]Regardless of which option you choose:
python manage.py makemigrations
python manage.py migrateYour template will now work with real database content without any changes!
The prototype system includes several mock classes in prototypes/views.py:
MockPage- Mimics Wagtail's Page model, converts YAML data to page attributesMockStreamValue- Mimics StreamField, allows iteration over blocksMockStreamBlock- Mimics individual content blocks withblock_typeandvalueMockImage- Mimics Wagtail's Image model for the{% image %}tag
These mock objects provide the same API as real Wagtail objects, so templates work identically with both fixture data and real database content.
The dashboard automatically:
- Scans all app template directories
- Discovers
.htmlfiles (excluding base templates and partials) - Shows which templates have fixtures
- Displays fixture titles
- Provides direct links to preview each template
This repository includes a Claude Code skill (/wagtail-model-creator) that automates converting your YAML fixtures into production-ready Wagtail page models. The skill:
- Analyzes your fixture keys and values
- Determines appropriate field types (StreamField, CharField, ForeignKey, etc.)
- Generates complete model code with imports and panels
- Handles complex structures like StreamFields, StructBlocks, and nested data
To use it:
- Create and test your template with a fixture file
- Run
/wagtail-model-creatorin Claude Code - Review and save the generated model code
- Run migrations to apply changes
When viewing a prototype, templates receive an is_prototype context variable. The base template (app/templates/base.html) uses this to display a prototype mode badge:
{% if is_prototype %}
<div class="prototype-badge">
Prototype Mode | <a href="/prototype/">Dashboard</a>
</div>
{% endif %}All prototype URLs only exist when DEBUG=True. In production:
/prototype/routes don't exist- No prototype code is accessible
- No security risk from development features
- Match your planned models - Structure fixtures to match the page models you'll create
- Use realistic data - Catch layout issues early with real-world content lengths
- Test edge cases - Long titles, missing images, empty content
- Organize by app - Keep fixtures in
prototypes/fixtures/{app}/matching template paths - Frontend-first workflow - Design templates before writing models
- Iterate quickly - Change fixtures and refresh to see updates instantly
- Use placeholder images - Services like
https://picsum.photos/for quick mockups - Use the model generator skill - If using Claude Code, leverage
/wagtail-model-creatorto automatically generate models from fixtures
To support custom block types, extend MockStreamBlock in prototypes/views.py:
class MockStreamBlock:
def __init__(self, block_type, value):
self.block_type = block_type
# Add your custom block type handling
if block_type == 'gallery':
# Convert list of image URLs to MockImage objects
self.value = [MockImage(img['url'], img.get('alt', '')) for img in value]
elif block_type == 'accordion':
# Handle accordion data structure
self.value = value # or transform as needed
# ... existing code for image, embed, etc.
else:
self.value = valueEdit prototypes/templates/prototypes/dashboard.html to:
- Change styling and branding
- Add filtering or search functionality
- Show more fixture metadata
- Add links to your documentation
- Group templates differently
Update the dashboard() view to skip certain templates:
# Skip base templates, partials, and specific patterns
if (template_path.startswith('base') or
'/_' in template_path or
template_path.startswith('includes/') or
'email' in template_path):
continueTemplates not appearing in dashboard:
- Verify templates are in
{app}/templates/directories - Check path configuration in
dashboard()view - Ensure templates don't match skip patterns (base*, /_, etc.)
- Check that app directories are being scanned
Fixture not loading:
- Verify YAML syntax with a validator
- Check fixture path matches template path exactly
- Example:
home/landing_page.html→prototypes/fixtures/home/landing_page.yaml - Ensure PyYAML is installed:
pip show PyYAML
Template rendering errors:
- Check template syntax for Django/Wagtail tags
- Verify fixture data structure matches template expectations
- Look for typos in variable names
- Check browser console and Django debug page for details
Images not displaying:
- Use full URLs starting with
http://orhttps:// - Verify URLs are publicly accessible
- Check CORS and SSL for external images
- Try placeholder service:
https://picsum.photos/WIDTH/HEIGHT
Dashboard shows wrong path:
- Adjust
BASE_DIRpaths inviews.py - Check that
settings.BASE_DIRpoints to your project root - Update both
prototype_view()anddashboard()functions
This repository structure:
wagtail-prototyper/
├── app/
│ ├── prototypes/ # ← Copy this entire folder
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ ├── views.py # Mock objects and view logic
│ │ ├── fixtures/ # YAML fixture files
│ │ │ ├── home/
│ │ │ │ └── home_page.yaml
│ │ │ └── core/
│ │ │ └── standard_page.yaml
│ │ └── templates/
│ │ └── prototypes/
│ │ └── dashboard.html
│ ├── home/
│ ├── core/
│ └── templates/
│ └── base.html # Has prototype mode indicator
├── README.md # This file
└── manage.py
- Example Templates: See
app/home/templates/andapp/core/templates/in this repo for working examples (these are examples from this demo project) - Example Fixtures: Check
app/prototypes/fixtures/in this repo for YAML structure examples (create your own for your project) - Base Template: Reference
app/templates/base.htmlfor prototype mode integration - Claude Code Skill: Use
/wagtail-model-creatorto automatically generate models from fixtures - Wagtail Docs: wagtail.org/documentation
Remember: The templates and fixtures in this repository are examples from the demo project. They show you how the system works, but you'll need to create your own templates and fixtures for your own project's pages.
This prototyping system can be freely used and modified for your projects.