# Wagtail Feathers
A comprehensive Wagtail CMS extension providing foundational functionality for building sophisticated content management systems.
[](https://pypi.org/project/wagtail-feathers/)
[](https://docs.djangoproject.com/en/5.2/)
[](https://wagtail.org/)
[](LICENSE)
> [!CAUTION]
> This package is a beta version which means it is **almost** stable and definitely lacks some documentation and tests
> The package is aimed at new installations
## Features
Wagtail Feathers provides a comprehensive set of reusable components and patterns:
### 🏗️ **Advanced Page Architecture**
- **FeatherBasePage**: Foundation for all custom pages with comprehensive SEO capabilities
- **Flexible Content Blocks**: Rich StreamField blocks for dynamic content composition
- **Page Relationships**: Built-in related page functionality and cross-references
### 📂 **Taxonomy & Classification**
- **Hierarchical Categories**: Tree-structured content classification
- **Flexible Subjects**: Tagging and subject matter organization
- **Custom Attributes**: Extensible metadata and properties system
- **Admin Integration**: Beautiful select widgets and management interface
### 🧭 **Dynamic Navigation**
- **Multiple Menu Types**: Traditional hierarchical and StreamField-based navigation
- **Flexible Menu Items**: Support for both page links and external URLs
### 🔍 **SEO & Metadata**
- **Built on Wagtail SEO**: Extends Wagtail's built-in `seo_title` and `search_description` fields
- **Social Media Ready**: Open Graph and Twitter Card support with dedicated image field
- **Structured Data**: Automatic JSON-LD generation for articles, organizations, and more
- **Smart Fallbacks**: Intelligent content detection for missing SEO fields
- **Search Engine Control**: No-index/no-follow directives and robots meta tags
- **Reading Time**: Automatic reading time calculation with SEO integration
### 🎨 **Theme System**
- **Multi-Site Theming**: Different themes per site
- **Template Discovery**: Automatic theme-based template resolution
- **Asset Management**: Theme-specific CSS/JS handling
- **Configuration**: Site-wide settings and theme preferences
### 🌍 **Internationalization** (Optional)
- **Locale-Aware Ready**: Models ready for i18n when you need it
- **Simple Translation**: Use Wagtail's built-in translation features
- **Advanced Workflows**: Optional wagtail-localize integration (recommended)
### 👥 **Authorship & People**
- **Author Management**: Flexible author attribution system with AuthorType and PageAuthor models
- **People Directory**: Staff and contributor profiles with PersonGroup organization
- **Social Integration**: Social media links and profiles with SocialMediaSettings
### ❌ **Error Pages**
- **Custom Error Pages**: Configurable 404, and 500 error pages (TBC: 400, 403)
- **ErrorPage Model**: Editable error pages with custom messages and images
### ❓ **FAQ System**
- **FAQ Management**: Structured FAQ system with FAQCategory and FAQ models
- **Categorized Content**: Organize frequently asked questions by category
### 🔗 **Page Relationships**
- **Related Content**: RelatedPage, RelatedDocument, and RelatedExternalLink models
- **Cross-References**: Built-in page relationship functionality
- **Geographic Tagging**: PageCountry model for location-based content
## Installation
### Basic Installation (No i18n)
```bash
pip install wagtail-feathers
```
### With Advanced Translation Workflows
```bash
pip install wagtail-feathers[localize]
```
## Quick Start
1. **Add to Django Settings**:
**Basic Setup:**
```python
INSTALLED_APPS = [
# ... your apps
"wagtail_feathers",
"wagtailmarkdown",
"django_extensions",
# ... other apps
]
```
**With Simple Translation:**
```python
INSTALLED_APPS = [
# ... your apps
"wagtail_feathers",
"wagtail.contrib.simple_translation", # Wagtail built-in
# ... other apps
]
```
**With Advanced Translation:**
```python
INSTALLED_APPS = [
# ... your apps
"wagtail_feathers",
"wagtail_localize",
"wagtail_localize.locales",
# ... other apps
]
```
2. **Run Migrations**:
```bash
python manage.py migrate
```
3. **Create Your First Page**:
```python
from wagtail_feathers.models import FeatherBasePage
class MyPage(FeatherBasePage):
# Your custom fields here
pass
```
## Advanced Usage
### Custom Page Models
```python
from wagtail_feathers.models import FeatherBasePage, ItemPage
from wagtail_feathers.blocks import CommonContentBlock
from wagtail.fields import StreamField
class ArticlePage(ItemPage):
body = StreamField([
("content", CommonContentBlock()),
], use_json_field=True)
content_panels = ItemPage.content_panels + [
FieldPanel("body"),
]
```
### Taxonomy Integration
```python
from wagtail_feathers.models import Category, Classifier, ClassifierGroup
# Create categories
category = Category.objects.create(name="Technology", slug="tech")
# Create classifier groups and classifiers
group = ClassifierGroup.objects.create(name="Topics", max_selections=3)
classifier = Classifier.objects.create(
name="AI & Machine Learning",
slug="ai-ml",
group=group
)
```
### Navigation Menus
```python
from wagtail_feathers.models import Menu, MenuItem, FlatMenu, NestedMenu
# Create a flat menu
flat_menu = FlatMenu.objects.create(name="Main Navigation")
# Create a nested menu
nested_menu = NestedMenu.objects.create(name="Footer Navigation")
# Create menu items
MenuItem.objects.create(
menu=flat_menu,
link_page=home_page,
link_text="Home",
sort_order=1
)
```
### Error Pages
```python
from wagtail_feathers.models import ErrorPage
# Create custom error pages
error_404 = ErrorPage.objects.create(
title="404",
error_code="404",
heading="Page Not Found",
message="<p>The page you are looking for does not exist.</p>"
)
```
### FAQ System
```python
from wagtail_feathers.models import FAQCategory, FAQ
# Create FAQ categories and items
category = FAQCategory.objects.create(
title="General Questions",
slug="general"
)
faq = FAQ.objects.create(
question="How do I get started?",
answer="<p>Follow our quick start guide...</p>",
category=category
)
```
### SEO & Metadata
```python
from wagtail_feathers.models import FeatherBasePage
from wagtail_feathers.models.seo import SeoContentType, TwitterCardType
class ArticlePage(FeatherBasePage):
# SEO fields are automatically available via SeoMixin
pass
# In your templates - add to <head> section:
# {% load seo_tags %}
# {% include "wagtail_feathers/seo/meta.html" %}
# In your templates - add before </body>:
# {% include "wagtail_feathers/seo/structured_data.html" %}
# Programmatic SEO control:
page = ArticlePage.objects.get(pk=1)
page.seo_title = "Custom SEO Title" # Uses Wagtail's built-in field
page.search_description = "Custom meta description for search engines" # Uses Wagtail's built-in field
page.seo_content_type = SeoContentType.ARTICLE
page.twitter_card_type = TwitterCardType.SUMMARY_LARGE_IMAGE
page.save()
```
### Reading Time Features
All `ItemPage` models automatically include reading time calculation:
```python
from wagtail_feathers.models import ItemPage
class BlogPost(ItemPage):
# Reading time is automatically calculated from:
# - title, introduction, body content
# - Configurable words-per-minute (default: 200 WPM)
pass
# In templates:
# {% load reading_time_tags %}
# {% reading_time page %} # "5 min read"
# {% reading_time page "long" %} # "5 minutes read"
# {% reading_time_badge page %} # Badge with clock icon
# Custom reading time logic:
class TechnicalArticle(ItemPage):
summary = RichTextField(blank=True)
def get_additional_word_sources(self):
"""Include summary in word count."""
words = 0
if self.summary:
words += self._count_text_words(str(self.summary))
return words
# Slower reading speed for technical content
words_per_minute = 150
# Reading time extends SEO automatically:
# - Twitter Cards: "Reading time: 5 min read"
# - Structured Data: "timeRequired": "PT5M"
# - Meta tags: article:reading_time
```
### Extending SEO Features
The SEO functionality in wagtail_feathers is designed to be extensible. Here are several ways to enhance it:
#### 1. Adding Custom SEO Fields
```python
from wagtail_feathers.models import FeatherBasePage, SeoMixin
from wagtail.admin.panels import FieldPanel
class ArticlePage(FeatherBasePage):
# Add your own SEO-related fields
meta_keywords = models.CharField(
max_length=255,
blank=True,
help_text="Comma-separated keywords (optional for modern SEO)"
)
facebook_app_id = models.CharField(
max_length=50,
blank=True,
help_text="Facebook App ID for Open Graph"
)
# Extend the SEO panels
seo_panels = SeoMixin.seo_panels + [
FieldPanel("meta_keywords"),
FieldPanel("facebook_app_id"),
]
# Override SEO methods for custom behavior
def get_seo_description(self):
"""Custom SEO description logic."""
# Try your custom field first
if hasattr(self, "excerpt") and self.excerpt:
return self.excerpt[:160]
# Fall back to parent implementation
return super().get_seo_description()
```
#### 2. Custom Structured Data
```python
import json
from wagtail_feathers.models import SeoMixin
class ProductPage(FeatherBasePage):
price = models.DecimalField(max_digits=10, decimal_places=2)
brand = models.CharField(max_length=100)
sku = models.CharField(max_length=50)
def get_structured_data(self):
"""Override to add Product schema."""
if self.seo_content_type == "product":
site = self.get_site()
base_url = site.root_url if site.root_url else f"https://{site.hostname}"
schema = {
"@context": "https://schema.org",
"@type": "Product",
"name": self.get_seo_title(),
"description": self.get_seo_description(),
"sku": self.sku,
"brand": {
"@type": "Brand",
"name": self.brand
},
"offers": {
"@type": "Offer",
"price": str(self.price),
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}
# Add image if available
if self.get_seo_image():
schema["image"] = f"{base_url}{self.get_seo_image().file.url}"
return json.dumps(schema, ensure_ascii=False)
# Fall back to parent implementation
return super().get_structured_data()
```
#### 3. SEO-Focused Page Types
```python
from wagtail_feathers.models import ItemPage
from wagtail_feathers.models.seo import SeoContentType, TwitterCardType
class SEOOptimizedArticle(ItemPage):
"""Article page optimized for SEO best practices."""
# Set SEO defaults
seo_content_type = SeoContentType.ARTICLE
twitter_card_type = TwitterCardType.SUMMARY_LARGE_IMAGE
# Additional SEO fields
focus_keyword = models.CharField(
max_length=100,
blank=True,
help_text="Primary keyword/phrase for this article"
)
reading_time = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Estimated reading time in minutes"
)
content_panels = ItemPage.content_panels + [
FieldPanel("focus_keyword"),
FieldPanel("reading_time"),
]
def get_structured_data(self):
"""Enhanced article schema with reading time."""
schema_str = super().get_structured_data()
if schema_str and self.seo_content_type == SeoContentType.ARTICLE:
schema = json.loads(schema_str)
# Add reading time
if self.reading_time:
schema["timeRequired"] = f"PT{self.reading_time}M"
# Add focus keyword as about
if self.focus_keyword:
schema["about"] = {
"@type": "Thing",
"name": self.focus_keyword
}
return json.dumps(schema, ensure_ascii=False)
return schema_str
```
#### 4. Custom SEO Template Tags
```python
# In your app"s templatetags/custom_seo_tags.py
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def custom_meta_tags(page):
"""Add custom meta tags based on page type."""
tags = []
# Add article-specific tags
if hasattr(page, "focus_keyword") and page.focus_keyword:
tags.append(f"<meta name='keywords' content='{page.focus_keyword}'>")
# Add Facebook App ID if available
if hasattr(page, "facebook_app_id") and page.facebook_app_id:
tags.append(f"<meta property='fb:app_id' content='{page.facebook_app_id}'>")
# Add reading time for articles
if hasattr(page, "reading_time") and page.reading_time:
tags.append(f"<meta name='twitter:label1' content="Reading time">")
tags.append(f"<meta name='twitter:data1' content='{page.reading_time} min read'>")
return mark_safe("\n".join(tags))
# In your templates:
# {% load custom_seo_tags %}
# {% custom_meta_tags page %}
```
#### 5. SEO Analytics Integration
```python
class AnalyticsEnhancedPage(FeatherBasePage):
"""Page with SEO analytics tracking."""
google_analytics_id = models.CharField(
max_length=20,
blank=True,
help_text="Google Analytics tracking ID (e.g., GA-XXXXX-X)"
)
track_scroll_depth = models.BooleanField(
default=False,
help_text="Enable scroll depth tracking for SEO insights"
)
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
# Add SEO tracking context
context.update({
"enable_seo_tracking": self.track_scroll_depth,
"ga_tracking_id": self.google_analytics_id,
})
return context
```
#### 6. Multi-language SEO
```python
# If using wagtail-localize
from wagtail_feathers.models.seo import SeoMixin
from wagtail_localize.fields import TranslatableField
class MultilingualSEOPage(FeatherBasePage):
"""Page with enhanced multilingual SEO support."""
# Additional translatable SEO fields
local_keywords = models.CharField(
max_length=255,
blank=True,
help_text="Keywords in local language"
)
cultural_context = models.TextField(
blank=True,
help_text="Cultural context for this market"
)
# Add to translatable fields
if SeoMixin.WAGTAIL_LOCALIZE_AVAILABLE:
translatable_fields = SeoMixin.translatable_fields + [
TranslatableField("local_keywords"),
TranslatableField("cultural_context"),
]
def get_seo_description(self):
"""Localized SEO description."""
if self.cultural_context:
return f"{super().get_seo_description()} {self.cultural_context}"[:160]
return super().get_seo_description()
```
#### 7. Using Extension Points
The SeoMixin provides several extension points for easy customization:
```python
class BlogArticlePage(ItemPage):
"""Blog article with enhanced SEO using extension points."""
author_twitter = models.CharField(max_length=50, blank=True)
focus_keyword = models.CharField(max_length=100, blank=True)
# Note: reading_time is automatically provided by ItemPage
def get_custom_meta_tags(self):
"""Add custom meta tags."""
tags = {}
if self.focus_keyword:
tags["keywords"] = self.focus_keyword
# Reading time is automatically added via ReadingTimeMixin
# but you can customize it here if needed
return tags
def get_custom_twitter_tags(self):
"""Add custom Twitter Card tags."""
tags = {}
if self.author_twitter:
tags["twitter:creator"] = f"@{self.author_twitter}"
# Reading time Twitter tags are automatically added via ReadingTimeMixin
# Additional Twitter tags can be added here
return tags
def get_custom_og_tags(self):
"""Add custom Open Graph tags."""
tags = {}
if self.author_twitter:
tags["article:author"] = f"https://twitter.com/{self.author_twitter}"
return tags
def get_sitemap_priority(self):
"""Higher priority for recent articles and longer reads."""
from django.utils import timezone
from datetime import timedelta
base_priority = super().get_sitemap_priority()
# Boost recent articles
if hasattr(self, "publication_date"):
recent_threshold = timezone.now().date() - timedelta(days=30)
if self.publication_date >= recent_threshold:
base_priority += 0.1
# Boost longer articles (more comprehensive content)
if self.reading_time_minutes and self.reading_time_minutes >= 10:
base_priority += 0.1
return min(1.0, base_priority) # Cap at 1.0
```
These examples show how to build upon wagtail_feathers SEO foundation to create powerful, customized SEO solutions for specific use cases.
### People & Authors
```python
from wagtail_feathers.models import Person, PersonGroup, AuthorType, PageAuthor
# Create person groups and people
group = PersonGroup.objects.create(name="Editorial Team")
person = Person.objects.create(
first_name="John",
last_name="Doe",
email="john@example.com"
)
person.groups.add(group)
# Create author types and assign to pages
author_type = AuthorType.objects.create(name="Writer")
PageAuthor.objects.create(
page=my_page,
person=person,
author_type=author_type
)
```
## Requirements
- Python 3.11+
- Django 5.0+
- Wagtail 5.2+
See `pyproject.toml` for complete dependency list.
## Development
### Setup Development Environment
```bash
# Clone the repository
git clone https://github.com/softquantum/wagtail-feathers.git
cd wagtail-feathers
# Navigate to demo directory
cd demo
# Install demo dependencies (includes editable package install)
pip install -r requirements.txt
# Set up database and demo data
python manage.py migrate
python manage.py setup_demo_data
# Start the demo server
python manage.py runserver
```
```bash
# Run tests
pytest
# Run tests across multiple environments
tox
```
### Try the Demo Site
**Demo Access:**
- **Frontend:** http://localhost:8000
- **Admin:** http://localhost:8000/admin (admin/admin123)
The demo includes (WORK IN PROGRESS):
- Sample pages using wagtail-feathers features
- SEO optimization examples
- Reading time calculation
- Taxonomy and navigation examples
- Live package development (changes reflect immediately)
### Testing
The package includes some tests covering:
- Model functionality and relationships
- Template rendering and themes
- Navigation generation
- Taxonomy management
- Multi-site behavior
```bash
# Run all tests
pytest
```
### Code Quality
```bash
# Lint code
ruff check src/
# Format code
ruff format src/
# Run via tox
tox -e lint
tox -e format
```
## Architecture
Wagtail Feathers follows a modular architecture:
```
wagtail_feathers/
├── models/ # Core model definitions
│ ├── base.py # FeatherBasePage and foundation classes
│ ├── author.py # AuthorType and PageAuthor models
│ ├── errors.py # ErrorPage model
│ ├── faq.py # FAQ and FAQCategory models
│ ├── geographic.py # PageCountry model
│ ├── inline.py # Related content models
│ ├── navigation.py # Menu and navigation models
│ ├── person.py # Person and PersonGroup models
│ ├── settings.py # SiteSettings model
│ ├── social.py # Social media models
│ ├── specialized_pages.py # Specialized page types
│ ├── taxonomy.py # Category and Classifier models
│ └── utils.py # Model utilities
├── blocks.py # StreamField content blocks
├── templatetags/ # Template tag libraries
├── viewsets/ # Wagtail admin viewsets
├── themes.py # Theme system
└── management/ # Django management commands
```
## Key Models
Based on the migration, wagtail_feathers provides these core models:
- **Page Foundation**: `FeatherBasePage`, `FeatherBasePageTag`
- **Taxonomy**: `Category`, `Classifier`, `ClassifierGroup`, `PageCategory`, `PageClassifier`
- **Navigation**: `Menu`, `MenuItem`, `FlatMenu`, `NestedMenu`, `Footer`, `FooterNavigation`
- **People & Authors**: `Person`, `PersonGroup`, `AuthorType`, `PageAuthor`
- **Content Organization**: `FAQ`, `FAQCategory`, `ErrorPage`
- **Relationships**: `RelatedPage`, `RelatedDocument`, `RelatedExternalLink`
- **Geography**: `PageCountry`
- **Social**: `SocialMediaSettings`, `SocialMediaLink`
- **Settings**: `SiteSettings`
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request
## License
[BSD-3-Clause](LICENSE)
## Credits & Attributions
- ✅ Wagtail Feathers is Developed by [Maxime Decooman](https://github.com/softquantum).
- ✅ Built on the excellent [Wagtail CMS](https://wagtail.org/) (License [BSD-3-Clause](https://github.com/wagtail/wagtail/blob/main/LICENSE))
- ✅ The icons prefixed "heroicons-" are sourced from version 2.2.0 of [Heroicons](https://github.com/tailwindlabs/heroicons), the beautiful hand-crafted SVG icons library, by the makers of Tailwind CSS (License [MIT](https://github.com/tailwindlabs/heroicons/blob/master/LICENSE)).
Raw data
{
"_id": null,
"home_page": null,
"name": "wagtail-feathers",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": "Maxime Decooman <maxime@softquantum.com>",
"keywords": "wagtail, django, cms, content, management, taxonomy, navigation, themes",
"author": null,
"author_email": "Maxime Decooman <maxime@softquantum.com>",
"download_url": "https://files.pythonhosted.org/packages/fb/32/23e795ee48de27def18859fc6965871181c725d6fac473bdd44222498d21/wagtail_feathers-1.0b1.tar.gz",
"platform": null,
"description": "# Wagtail Feathers\n\nA comprehensive Wagtail CMS extension providing foundational functionality for building sophisticated content management systems.\n\n[](https://pypi.org/project/wagtail-feathers/)\n[](https://docs.djangoproject.com/en/5.2/)\n[](https://wagtail.org/)\n[](LICENSE)\n\n> [!CAUTION]\n> This package is a beta version which means it is **almost** stable and definitely lacks some documentation and tests\n> The package is aimed at new installations\n\n## Features\n\nWagtail Feathers provides a comprehensive set of reusable components and patterns:\n\n### \ud83c\udfd7\ufe0f **Advanced Page Architecture**\n- **FeatherBasePage**: Foundation for all custom pages with comprehensive SEO capabilities\n- **Flexible Content Blocks**: Rich StreamField blocks for dynamic content composition\n- **Page Relationships**: Built-in related page functionality and cross-references\n\n### \ud83d\udcc2 **Taxonomy & Classification** \n- **Hierarchical Categories**: Tree-structured content classification\n- **Flexible Subjects**: Tagging and subject matter organization\n- **Custom Attributes**: Extensible metadata and properties system\n- **Admin Integration**: Beautiful select widgets and management interface\n\n### \ud83e\udded **Dynamic Navigation**\n- **Multiple Menu Types**: Traditional hierarchical and StreamField-based navigation\n- **Flexible Menu Items**: Support for both page links and external URLs\n\n### \ud83d\udd0d **SEO & Metadata**\n- **Built on Wagtail SEO**: Extends Wagtail's built-in `seo_title` and `search_description` fields\n- **Social Media Ready**: Open Graph and Twitter Card support with dedicated image field\n- **Structured Data**: Automatic JSON-LD generation for articles, organizations, and more\n- **Smart Fallbacks**: Intelligent content detection for missing SEO fields\n- **Search Engine Control**: No-index/no-follow directives and robots meta tags\n- **Reading Time**: Automatic reading time calculation with SEO integration\n\n### \ud83c\udfa8 **Theme System**\n- **Multi-Site Theming**: Different themes per site\n- **Template Discovery**: Automatic theme-based template resolution\n- **Asset Management**: Theme-specific CSS/JS handling\n- **Configuration**: Site-wide settings and theme preferences\n\n\n### \ud83c\udf0d **Internationalization** (Optional)\n- **Locale-Aware Ready**: Models ready for i18n when you need it\n- **Simple Translation**: Use Wagtail's built-in translation features\n- **Advanced Workflows**: Optional wagtail-localize integration (recommended)\n\n### \ud83d\udc65 **Authorship & People**\n- **Author Management**: Flexible author attribution system with AuthorType and PageAuthor models\n- **People Directory**: Staff and contributor profiles with PersonGroup organization\n- **Social Integration**: Social media links and profiles with SocialMediaSettings\n\n### \u274c **Error Pages**\n- **Custom Error Pages**: Configurable 404, and 500 error pages (TBC: 400, 403)\n- **ErrorPage Model**: Editable error pages with custom messages and images\n\n### \u2753 **FAQ System**\n- **FAQ Management**: Structured FAQ system with FAQCategory and FAQ models\n- **Categorized Content**: Organize frequently asked questions by category\n\n### \ud83d\udd17 **Page Relationships**\n- **Related Content**: RelatedPage, RelatedDocument, and RelatedExternalLink models\n- **Cross-References**: Built-in page relationship functionality\n- **Geographic Tagging**: PageCountry model for location-based content\n\n## Installation\n\n### Basic Installation (No i18n)\n```bash\npip install wagtail-feathers\n```\n\n### With Advanced Translation Workflows\n```bash\npip install wagtail-feathers[localize]\n```\n\n## Quick Start\n\n1. **Add to Django Settings**:\n\n**Basic Setup:**\n```python\nINSTALLED_APPS = [\n # ... your apps\n \"wagtail_feathers\",\n \"wagtailmarkdown\",\n \"django_extensions\",\n # ... other apps\n]\n```\n\n**With Simple Translation:**\n```python\nINSTALLED_APPS = [\n # ... your apps\n \"wagtail_feathers\",\n \"wagtail.contrib.simple_translation\", # Wagtail built-in\n # ... other apps\n]\n```\n\n**With Advanced Translation:**\n```python\nINSTALLED_APPS = [\n # ... your apps\n \"wagtail_feathers\",\n \"wagtail_localize\",\n \"wagtail_localize.locales\",\n # ... other apps\n]\n```\n\n2. **Run Migrations**:\n```bash\npython manage.py migrate\n```\n\n3. **Create Your First Page**:\n```python\nfrom wagtail_feathers.models import FeatherBasePage\n\nclass MyPage(FeatherBasePage):\n # Your custom fields here\n pass\n```\n\n## Advanced Usage\n\n### Custom Page Models\n\n```python\nfrom wagtail_feathers.models import FeatherBasePage, ItemPage\nfrom wagtail_feathers.blocks import CommonContentBlock\nfrom wagtail.fields import StreamField\n\nclass ArticlePage(ItemPage):\n body = StreamField([\n (\"content\", CommonContentBlock()),\n ], use_json_field=True)\n \n content_panels = ItemPage.content_panels + [\n FieldPanel(\"body\"),\n ]\n```\n\n### Taxonomy Integration\n\n```python\nfrom wagtail_feathers.models import Category, Classifier, ClassifierGroup\n\n# Create categories\ncategory = Category.objects.create(name=\"Technology\", slug=\"tech\")\n\n# Create classifier groups and classifiers\ngroup = ClassifierGroup.objects.create(name=\"Topics\", max_selections=3)\nclassifier = Classifier.objects.create(\n name=\"AI & Machine Learning\",\n slug=\"ai-ml\",\n group=group\n)\n```\n\n### Navigation Menus\n\n```python\nfrom wagtail_feathers.models import Menu, MenuItem, FlatMenu, NestedMenu\n\n# Create a flat menu\nflat_menu = FlatMenu.objects.create(name=\"Main Navigation\")\n\n# Create a nested menu\nnested_menu = NestedMenu.objects.create(name=\"Footer Navigation\")\n\n# Create menu items\nMenuItem.objects.create(\n menu=flat_menu,\n link_page=home_page,\n link_text=\"Home\",\n sort_order=1\n)\n```\n\n### Error Pages\n\n```python\nfrom wagtail_feathers.models import ErrorPage\n\n# Create custom error pages\nerror_404 = ErrorPage.objects.create(\n title=\"404\",\n error_code=\"404\",\n heading=\"Page Not Found\",\n message=\"<p>The page you are looking for does not exist.</p>\"\n)\n```\n\n### FAQ System\n\n```python\nfrom wagtail_feathers.models import FAQCategory, FAQ\n\n# Create FAQ categories and items\ncategory = FAQCategory.objects.create(\n title=\"General Questions\",\n slug=\"general\"\n)\n\nfaq = FAQ.objects.create(\n question=\"How do I get started?\",\n answer=\"<p>Follow our quick start guide...</p>\",\n category=category\n)\n```\n\n### SEO & Metadata\n\n```python\nfrom wagtail_feathers.models import FeatherBasePage\nfrom wagtail_feathers.models.seo import SeoContentType, TwitterCardType\n\nclass ArticlePage(FeatherBasePage):\n # SEO fields are automatically available via SeoMixin\n pass\n\n# In your templates - add to <head> section:\n# {% load seo_tags %}\n# {% include \"wagtail_feathers/seo/meta.html\" %}\n\n# In your templates - add before </body>:\n# {% include \"wagtail_feathers/seo/structured_data.html\" %}\n\n# Programmatic SEO control:\npage = ArticlePage.objects.get(pk=1)\npage.seo_title = \"Custom SEO Title\" # Uses Wagtail's built-in field\npage.search_description = \"Custom meta description for search engines\" # Uses Wagtail's built-in field\npage.seo_content_type = SeoContentType.ARTICLE\npage.twitter_card_type = TwitterCardType.SUMMARY_LARGE_IMAGE\npage.save()\n```\n\n### Reading Time Features\n\nAll `ItemPage` models automatically include reading time calculation:\n\n```python\nfrom wagtail_feathers.models import ItemPage\n\nclass BlogPost(ItemPage):\n # Reading time is automatically calculated from:\n # - title, introduction, body content\n # - Configurable words-per-minute (default: 200 WPM)\n pass\n\n# In templates:\n# {% load reading_time_tags %}\n# {% reading_time page %} # \"5 min read\"\n# {% reading_time page \"long\" %} # \"5 minutes read\" \n# {% reading_time_badge page %} # Badge with clock icon\n\n# Custom reading time logic:\nclass TechnicalArticle(ItemPage):\n summary = RichTextField(blank=True)\n \n def get_additional_word_sources(self):\n \"\"\"Include summary in word count.\"\"\"\n words = 0\n if self.summary:\n words += self._count_text_words(str(self.summary))\n return words\n \n # Slower reading speed for technical content\n words_per_minute = 150\n\n# Reading time extends SEO automatically:\n# - Twitter Cards: \"Reading time: 5 min read\"\n# - Structured Data: \"timeRequired\": \"PT5M\"\n# - Meta tags: article:reading_time\n```\n\n### Extending SEO Features\n\nThe SEO functionality in wagtail_feathers is designed to be extensible. Here are several ways to enhance it:\n\n#### 1. Adding Custom SEO Fields\n\n```python\nfrom wagtail_feathers.models import FeatherBasePage, SeoMixin\nfrom wagtail.admin.panels import FieldPanel\n\nclass ArticlePage(FeatherBasePage):\n # Add your own SEO-related fields\n meta_keywords = models.CharField(\n max_length=255, \n blank=True,\n help_text=\"Comma-separated keywords (optional for modern SEO)\"\n )\n \n facebook_app_id = models.CharField(\n max_length=50,\n blank=True,\n help_text=\"Facebook App ID for Open Graph\"\n )\n \n # Extend the SEO panels\n seo_panels = SeoMixin.seo_panels + [\n FieldPanel(\"meta_keywords\"),\n FieldPanel(\"facebook_app_id\"),\n ]\n \n # Override SEO methods for custom behavior\n def get_seo_description(self):\n \"\"\"Custom SEO description logic.\"\"\"\n # Try your custom field first\n if hasattr(self, \"excerpt\") and self.excerpt:\n return self.excerpt[:160]\n \n # Fall back to parent implementation\n return super().get_seo_description()\n```\n\n#### 2. Custom Structured Data\n\n```python\nimport json\nfrom wagtail_feathers.models import SeoMixin\n\nclass ProductPage(FeatherBasePage):\n price = models.DecimalField(max_digits=10, decimal_places=2)\n brand = models.CharField(max_length=100)\n sku = models.CharField(max_length=50)\n \n def get_structured_data(self):\n \"\"\"Override to add Product schema.\"\"\"\n if self.seo_content_type == \"product\":\n site = self.get_site()\n base_url = site.root_url if site.root_url else f\"https://{site.hostname}\"\n \n schema = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Product\",\n \"name\": self.get_seo_title(),\n \"description\": self.get_seo_description(),\n \"sku\": self.sku,\n \"brand\": {\n \"@type\": \"Brand\",\n \"name\": self.brand\n },\n \"offers\": {\n \"@type\": \"Offer\",\n \"price\": str(self.price),\n \"priceCurrency\": \"USD\",\n \"availability\": \"https://schema.org/InStock\"\n }\n }\n \n # Add image if available\n if self.get_seo_image():\n schema[\"image\"] = f\"{base_url}{self.get_seo_image().file.url}\"\n \n return json.dumps(schema, ensure_ascii=False)\n \n # Fall back to parent implementation\n return super().get_structured_data()\n```\n\n#### 3. SEO-Focused Page Types\n\n```python\nfrom wagtail_feathers.models import ItemPage\nfrom wagtail_feathers.models.seo import SeoContentType, TwitterCardType\n\nclass SEOOptimizedArticle(ItemPage):\n \"\"\"Article page optimized for SEO best practices.\"\"\"\n \n # Set SEO defaults\n seo_content_type = SeoContentType.ARTICLE\n twitter_card_type = TwitterCardType.SUMMARY_LARGE_IMAGE\n \n # Additional SEO fields\n focus_keyword = models.CharField(\n max_length=100,\n blank=True,\n help_text=\"Primary keyword/phrase for this article\"\n )\n \n reading_time = models.PositiveIntegerField(\n null=True,\n blank=True,\n help_text=\"Estimated reading time in minutes\"\n )\n \n content_panels = ItemPage.content_panels + [\n FieldPanel(\"focus_keyword\"),\n FieldPanel(\"reading_time\"),\n ]\n \n def get_structured_data(self):\n \"\"\"Enhanced article schema with reading time.\"\"\"\n schema_str = super().get_structured_data()\n if schema_str and self.seo_content_type == SeoContentType.ARTICLE:\n schema = json.loads(schema_str)\n \n # Add reading time\n if self.reading_time:\n schema[\"timeRequired\"] = f\"PT{self.reading_time}M\"\n \n # Add focus keyword as about\n if self.focus_keyword:\n schema[\"about\"] = {\n \"@type\": \"Thing\",\n \"name\": self.focus_keyword\n }\n \n return json.dumps(schema, ensure_ascii=False)\n \n return schema_str\n```\n\n#### 4. Custom SEO Template Tags\n\n```python\n# In your app\"s templatetags/custom_seo_tags.py\nfrom django import template\nfrom django.utils.safestring import mark_safe\n\nregister = template.Library()\n\n@register.simple_tag\ndef custom_meta_tags(page):\n \"\"\"Add custom meta tags based on page type.\"\"\"\n tags = []\n \n # Add article-specific tags\n if hasattr(page, \"focus_keyword\") and page.focus_keyword:\n tags.append(f\"<meta name='keywords' content='{page.focus_keyword}'>\")\n \n # Add Facebook App ID if available\n if hasattr(page, \"facebook_app_id\") and page.facebook_app_id:\n tags.append(f\"<meta property='fb:app_id' content='{page.facebook_app_id}'>\")\n \n # Add reading time for articles\n if hasattr(page, \"reading_time\") and page.reading_time:\n tags.append(f\"<meta name='twitter:label1' content=\"Reading time\">\")\n tags.append(f\"<meta name='twitter:data1' content='{page.reading_time} min read'>\")\n \n return mark_safe(\"\\n\".join(tags))\n\n# In your templates:\n# {% load custom_seo_tags %}\n# {% custom_meta_tags page %}\n```\n\n#### 5. SEO Analytics Integration\n\n```python\nclass AnalyticsEnhancedPage(FeatherBasePage):\n \"\"\"Page with SEO analytics tracking.\"\"\"\n \n google_analytics_id = models.CharField(\n max_length=20,\n blank=True,\n help_text=\"Google Analytics tracking ID (e.g., GA-XXXXX-X)\"\n )\n \n track_scroll_depth = models.BooleanField(\n default=False,\n help_text=\"Enable scroll depth tracking for SEO insights\"\n )\n \n def get_context(self, request, *args, **kwargs):\n context = super().get_context(request, *args, **kwargs)\n \n # Add SEO tracking context\n context.update({\n \"enable_seo_tracking\": self.track_scroll_depth,\n \"ga_tracking_id\": self.google_analytics_id,\n })\n \n return context\n```\n\n#### 6. Multi-language SEO\n\n```python\n# If using wagtail-localize\nfrom wagtail_feathers.models.seo import SeoMixin\nfrom wagtail_localize.fields import TranslatableField\n\nclass MultilingualSEOPage(FeatherBasePage):\n \"\"\"Page with enhanced multilingual SEO support.\"\"\"\n \n # Additional translatable SEO fields\n local_keywords = models.CharField(\n max_length=255,\n blank=True,\n help_text=\"Keywords in local language\"\n )\n \n cultural_context = models.TextField(\n blank=True,\n help_text=\"Cultural context for this market\"\n )\n \n # Add to translatable fields\n if SeoMixin.WAGTAIL_LOCALIZE_AVAILABLE:\n translatable_fields = SeoMixin.translatable_fields + [\n TranslatableField(\"local_keywords\"),\n TranslatableField(\"cultural_context\"),\n ]\n \n def get_seo_description(self):\n \"\"\"Localized SEO description.\"\"\"\n if self.cultural_context:\n return f\"{super().get_seo_description()} {self.cultural_context}\"[:160]\n return super().get_seo_description()\n```\n\n#### 7. Using Extension Points\n\nThe SeoMixin provides several extension points for easy customization:\n\n```python\nclass BlogArticlePage(ItemPage):\n \"\"\"Blog article with enhanced SEO using extension points.\"\"\"\n \n author_twitter = models.CharField(max_length=50, blank=True)\n focus_keyword = models.CharField(max_length=100, blank=True)\n # Note: reading_time is automatically provided by ItemPage\n \n def get_custom_meta_tags(self):\n \"\"\"Add custom meta tags.\"\"\"\n tags = {}\n \n if self.focus_keyword:\n tags[\"keywords\"] = self.focus_keyword\n \n # Reading time is automatically added via ReadingTimeMixin\n # but you can customize it here if needed\n \n return tags\n \n def get_custom_twitter_tags(self):\n \"\"\"Add custom Twitter Card tags.\"\"\"\n tags = {}\n \n if self.author_twitter:\n tags[\"twitter:creator\"] = f\"@{self.author_twitter}\"\n \n # Reading time Twitter tags are automatically added via ReadingTimeMixin\n # Additional Twitter tags can be added here\n \n return tags\n \n def get_custom_og_tags(self):\n \"\"\"Add custom Open Graph tags.\"\"\"\n tags = {}\n \n if self.author_twitter:\n tags[\"article:author\"] = f\"https://twitter.com/{self.author_twitter}\"\n \n return tags\n \n def get_sitemap_priority(self):\n \"\"\"Higher priority for recent articles and longer reads.\"\"\"\n from django.utils import timezone\n from datetime import timedelta\n \n base_priority = super().get_sitemap_priority()\n \n # Boost recent articles\n if hasattr(self, \"publication_date\"):\n recent_threshold = timezone.now().date() - timedelta(days=30)\n if self.publication_date >= recent_threshold:\n base_priority += 0.1\n \n # Boost longer articles (more comprehensive content)\n if self.reading_time_minutes and self.reading_time_minutes >= 10:\n base_priority += 0.1\n \n return min(1.0, base_priority) # Cap at 1.0\n```\n\nThese examples show how to build upon wagtail_feathers SEO foundation to create powerful, customized SEO solutions for specific use cases.\n\n### People & Authors\n\n```python\nfrom wagtail_feathers.models import Person, PersonGroup, AuthorType, PageAuthor\n\n# Create person groups and people\ngroup = PersonGroup.objects.create(name=\"Editorial Team\")\nperson = Person.objects.create(\n first_name=\"John\",\n last_name=\"Doe\",\n email=\"john@example.com\"\n)\nperson.groups.add(group)\n\n# Create author types and assign to pages\nauthor_type = AuthorType.objects.create(name=\"Writer\")\nPageAuthor.objects.create(\n page=my_page,\n person=person,\n author_type=author_type\n)\n```\n\n## Requirements\n\n- Python 3.11+\n- Django 5.0+ \n- Wagtail 5.2+\n\nSee `pyproject.toml` for complete dependency list.\n\n## Development\n\n### Setup Development Environment\n\n```bash\n# Clone the repository\ngit clone https://github.com/softquantum/wagtail-feathers.git\ncd wagtail-feathers\n\n# Navigate to demo directory\ncd demo\n\n# Install demo dependencies (includes editable package install)\npip install -r requirements.txt\n\n# Set up database and demo data\npython manage.py migrate\npython manage.py setup_demo_data\n\n# Start the demo server\npython manage.py runserver\n```\n\n```bash\n# Run tests\npytest\n\n# Run tests across multiple environments\ntox\n```\n\n### Try the Demo Site\n\n**Demo Access:**\n- **Frontend:** http://localhost:8000\n- **Admin:** http://localhost:8000/admin (admin/admin123)\n\nThe demo includes (WORK IN PROGRESS):\n- Sample pages using wagtail-feathers features\n- SEO optimization examples\n- Reading time calculation\n- Taxonomy and navigation examples\n- Live package development (changes reflect immediately)\n\n### Testing\n\nThe package includes some tests covering:\n- Model functionality and relationships\n- Template rendering and themes\n- Navigation generation\n- Taxonomy management\n- Multi-site behavior\n\n```bash\n# Run all tests\npytest\n```\n\n### Code Quality\n\n```bash\n# Lint code\nruff check src/\n\n# Format code \nruff format src/\n\n# Run via tox\ntox -e lint\ntox -e format\n```\n\n## Architecture\n\nWagtail Feathers follows a modular architecture:\n\n```\nwagtail_feathers/\n\u251c\u2500\u2500 models/ # Core model definitions\n\u2502 \u251c\u2500\u2500 base.py # FeatherBasePage and foundation classes\n\u2502 \u251c\u2500\u2500 author.py # AuthorType and PageAuthor models\n\u2502 \u251c\u2500\u2500 errors.py # ErrorPage model\n\u2502 \u251c\u2500\u2500 faq.py # FAQ and FAQCategory models \n\u2502 \u251c\u2500\u2500 geographic.py # PageCountry model\n\u2502 \u251c\u2500\u2500 inline.py # Related content models\n\u2502 \u251c\u2500\u2500 navigation.py # Menu and navigation models\n\u2502 \u251c\u2500\u2500 person.py # Person and PersonGroup models\n\u2502 \u251c\u2500\u2500 settings.py # SiteSettings model\n\u2502 \u251c\u2500\u2500 social.py # Social media models\n\u2502 \u251c\u2500\u2500 specialized_pages.py # Specialized page types\n\u2502 \u251c\u2500\u2500 taxonomy.py # Category and Classifier models\n\u2502 \u2514\u2500\u2500 utils.py # Model utilities\n\u251c\u2500\u2500 blocks.py # StreamField content blocks\n\u251c\u2500\u2500 templatetags/ # Template tag libraries\n\u251c\u2500\u2500 viewsets/ # Wagtail admin viewsets\n\u251c\u2500\u2500 themes.py # Theme system\n\u2514\u2500\u2500 management/ # Django management commands\n```\n\n## Key Models\n\nBased on the migration, wagtail_feathers provides these core models:\n\n- **Page Foundation**: `FeatherBasePage`, `FeatherBasePageTag`\n- **Taxonomy**: `Category`, `Classifier`, `ClassifierGroup`, `PageCategory`, `PageClassifier`\n- **Navigation**: `Menu`, `MenuItem`, `FlatMenu`, `NestedMenu`, `Footer`, `FooterNavigation`\n- **People & Authors**: `Person`, `PersonGroup`, `AuthorType`, `PageAuthor`\n- **Content Organization**: `FAQ`, `FAQCategory`, `ErrorPage`\n- **Relationships**: `RelatedPage`, `RelatedDocument`, `RelatedExternalLink`\n- **Geography**: `PageCountry`\n- **Social**: `SocialMediaSettings`, `SocialMediaLink`\n- **Settings**: `SiteSettings`\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## License\n[BSD-3-Clause](LICENSE)\n\n## Credits & Attributions\n- \u2705 Wagtail Feathers is Developed by [Maxime Decooman](https://github.com/softquantum).\n- \u2705 Built on the excellent [Wagtail CMS](https://wagtail.org/) (License [BSD-3-Clause](https://github.com/wagtail/wagtail/blob/main/LICENSE))\n- \u2705 The icons prefixed \"heroicons-\" are sourced from version 2.2.0 of [Heroicons](https://github.com/tailwindlabs/heroicons), the beautiful hand-crafted SVG icons library, by the makers of Tailwind CSS (License [MIT](https://github.com/tailwindlabs/heroicons/blob/master/LICENSE)).",
"bugtrack_url": null,
"license": null,
"summary": "A comprehensive Wagtail CMS extension providing theming and foundational features.",
"version": "1.0b1",
"project_urls": {
"Home": "https://github.com/softquantum/wagtail-feathers",
"Issues": "https://github.com/softquantum/wagtail-feathers/issues"
},
"split_keywords": [
"wagtail",
" django",
" cms",
" content",
" management",
" taxonomy",
" navigation",
" themes"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "c5001f6dbee32e9764a0007768a77e248e398c68feaebcf3dbe87f092c45c2db",
"md5": "794fc774ef9546578f43a61ef86b53f9",
"sha256": "44457274286297cec0ff0d68c6a2f7039d65e6d6b5ac6305b3f78db419a89a40"
},
"downloads": -1,
"filename": "wagtail_feathers-1.0b1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "794fc774ef9546578f43a61ef86b53f9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 553936,
"upload_time": "2025-07-29T01:47:56",
"upload_time_iso_8601": "2025-07-29T01:47:56.122782Z",
"url": "https://files.pythonhosted.org/packages/c5/00/1f6dbee32e9764a0007768a77e248e398c68feaebcf3dbe87f092c45c2db/wagtail_feathers-1.0b1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "fb3223e795ee48de27def18859fc6965871181c725d6fac473bdd44222498d21",
"md5": "f6c25e7f4ee0beb9815eae0be78d6766",
"sha256": "6a8343f302b336881cd290ca4867ce33731cff611b84dbd5a1979778b2a08624"
},
"downloads": -1,
"filename": "wagtail_feathers-1.0b1.tar.gz",
"has_sig": false,
"md5_digest": "f6c25e7f4ee0beb9815eae0be78d6766",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 658681,
"upload_time": "2025-07-29T01:47:57",
"upload_time_iso_8601": "2025-07-29T01:47:57.960511Z",
"url": "https://files.pythonhosted.org/packages/fb/32/23e795ee48de27def18859fc6965871181c725d6fac473bdd44222498d21/wagtail_feathers-1.0b1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-29 01:47:57",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "softquantum",
"github_project": "wagtail-feathers",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "wagtail-feathers"
}