A Wagtail CMS extension providing reusable components for multilingual content management.
- Translation Tab: Adds a "Translations" tab to all Page models in Wagtail admin
- Base Article Page: Abstract page model with SEO fields, featured images, and excerpt
- Base Article Index Page: Article listing with pagination and category routing
- Category System: Translatable category snippets with automatic routing and URL handling
- Category Routing: Built-in support for category-based article URLs with pagination
- Analytics Settings: Flexible analytics integration (Google Analytics, Matomo, etc.) with page-level control
- Python >= 3.10
- Wagtail >= 6.4.1
pip install wagtail-starlingAdd wagtail_starling to your INSTALLED_APPS in your Django settings:
INSTALLED_APPS = [
# ... other apps
'wagtail.contrib.settings', # Required for Analytics Settings
'wagtail_starling',
# ... wagtail apps
]Add the settings context processor (required for Analytics Settings):
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'context_processors': [
# ... other context processors
'wagtail.contrib.settings.context_processors.settings',
],
},
},
]BaseArticlePage provides a reusable foundation for blog/article pages with common fields:
from wagtail_starling.models import BaseArticlePage, CategoryMixin
from wagtail.fields import StreamField
# Simple article without categories
class BlogPost(BaseArticlePage):
body = StreamField([...])
# Article with category support
class ArticlePage(CategoryMixin, BaseArticlePage):
body = StreamField([...])
author = ForeignKey('people.Author', ...)Included Fields:
excerpt- Brief description for listings and SEOmeta_description- SEO meta descriptionfeatured_image- Featured image for hero and social sharingog_image- Open Graph image (optional, falls back to featured_image)canonical_url- Canonical URL if content published elsewhere
Included Methods:
get_meta_description()- Returns meta_description with fallback to excerptget_og_image()- Returns og_image with fallback to featured_imagesave()- Preserves slug when translating to prevent non-ASCII slugs
BaseArticleIndexPage provides article listing with category routing and pagination:
from wagtail_starling.models import BaseArticleIndexPage
from wagtail.contrib.routable_page.models import RoutablePageMixin
class ArticleIndexPage(BaseArticleIndexPage, RoutablePageMixin):
intro = models.CharField(max_length=250)
content_panels = Page.content_panels + [
FieldPanel("intro"),
]
def get_article_model(self):
from myapp.models import ArticlePage
return ArticlePageIncluded Features:
- Article listing with pagination (uses
settings.ARTICLES_PER_PAGE) - Category filter display in context
- Category-based routing (via CategoryRoutingMixin)
- Slug preservation for translations
get_context()automatically providesarticlesandcategories
Context Variables:
articles- Paginated article querysetcategories- All categories for filter pills
Create category-enabled pages with automatic routing:
from wagtail_starling.models import Category, CategoryMixin, CategoryRoutingMixin
from wagtail.contrib.routable_page.models import RoutablePageMixin
from wagtail.models import Page
# Add category field to your article model
# IMPORTANT: CategoryMixin must come before Page for URL generation to work
class ArticlePage(CategoryMixin, Page):
# Inherits a category ForeignKey field
# URLs automatically include category slug when assigned
body = RichTextField()
# Enable category-based routing
class ArticleIndexPage(CategoryRoutingMixin, RoutablePageMixin, Page):
intro = CharField(max_length=250)
def get_article_model(self):
return ArticlePageURL Patterns:
/articles/- All articles/articles/<category-slug>/- Articles in category/articles/<category-slug>/<article-slug>/- Article with category
Default Template: Includes wagtail_starling/category_index_page.html which extends base.html. Override by creating <app_label>/category_index_page.html in your project.
Configure site-wide analytics (Google Analytics, Matomo, or custom tracking) with flexible page-level control.
- Ensure
wagtail.contrib.settingsis inINSTALLED_APPS - Add settings context processor (see Configuration above)
- Run migrations:
python manage.py migrate wagtail_starling - Add template tags to your base template
Add the analytics template tags to your base template:
{% load analytics_tags %}
<!DOCTYPE html>
<html>
<head>
<!-- Your existing head content -->
{% analytics_head %}
</head>
<body>
<!-- Your page content -->
{% analytics_body %}
</body>
</html>Access Analytics Settings in Wagtail Admin under Settings → Analytics Settings:
Global Settings:
- Enabled: Enable/disable analytics tracking site-wide
- Head Tracking Code: Code to insert in
<head>(e.g., Google Analytics) - Body Tracking Code: Code to insert at end of
<body>(e.g., Google Tag Manager noscript)
Page Inclusion Rules:
- Include on all pages (default): Analytics on every page
- Include only on specific pages: Choose which pages get analytics
- Include on all pages except specific pages: Exclude certain pages (e.g., admin, private pages)
<!-- Head Tracking Code -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script><!-- Head Tracking Code -->
<script>
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//your-matomo-url/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '1']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- Body Tracking Code -->
<noscript><p><img src="//your-matomo-url/matomo.php?idsite=1&rec=1" style="border:0;" alt="" /></p></noscript>See LICENSE file for details.