CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-wagtail

A Django content management system with a user-friendly interface and powerful features for building websites and applications.

Overview
Eval results
Files

api.mddocs/

API Framework

REST API endpoints for headless CMS functionality with comprehensive serializers for pages, images, documents, and custom content. Wagtail's API framework enables decoupled front-end applications and third-party integrations.

Capabilities

Core API Components

Base classes and viewsets for creating REST API endpoints.

class BaseAPIViewSet(GenericViewSet):
    """
    Base viewset for Wagtail API endpoints with pagination, filtering, and authentication.
    
    Provides common functionality for all API endpoints including:
    - Pagination support
    - Field filtering and expansion
    - Search capabilities
    - Content negotiation
    """
    base_serializer_class: BaseSerializer
    filter_backends: list
    known_query_parameters: set
    
    def get_queryset(self):
        """Get the base queryset for this endpoint."""
    
    def get_serializer_class(self):
        """Get the serializer class for the current request."""
    
    def filter_queryset(self, queryset):
        """Apply filters to the queryset."""
    
    def paginate_queryset(self, queryset):
        """Apply pagination to the queryset."""

class PagesAPIViewSet(BaseAPIViewSet):
    """
    REST API endpoint for pages with hierarchical navigation and content access.
    
    Supports:
    - Page listing with filtering
    - Individual page retrieval
    - Hierarchical navigation (children, ancestors, descendants)
    - Content serialization with StreamField support
    """
    base_serializer_class: PageSerializer
    model: Page
    
    def get_queryset(self):
        """Get live pages accessible to the current user."""
    
    def find_view(self, request):
        """Find pages by slug or path."""
    
    def listing_view(self, request):
        """List pages with filtering and pagination."""
    
    def detail_view(self, request, pk):
        """Get individual page details."""

class ImagesAPIViewSet(BaseAPIViewSet):
    """
    REST API endpoint for images with rendition generation and metadata access.
    
    Supports:
    - Image listing and search
    - Image metadata access
    - Rendition URLs for different sizes
    - Collection-based filtering
    """
    base_serializer_class: ImageSerializer
    model: Image
    
    def get_queryset(self):
        """Get images accessible to the current user."""

class DocumentsAPIViewSet(BaseAPIViewSet):
    """
    REST API endpoint for documents with download URLs and metadata.
    
    Supports:
    - Document listing and search  
    - Document metadata access
    - Download URLs
    - Collection-based filtering
    """
    base_serializer_class: DocumentSerializer
    model: Document
    
    def get_queryset(self):
        """Get documents accessible to the current user."""

Serializers

Serializer classes for converting models to JSON representations.

class BaseSerializer(serializers.ModelSerializer):
    """
    Base serializer for API responses with field selection and expansion.
    
    Features:
    - Dynamic field selection via 'fields' parameter
    - Field expansion for related objects
    - Consistent error handling
    """
    def __init__(self, *args, **kwargs):
        """Initialize serializer with dynamic field configuration."""
    
    def to_representation(self, instance):
        """Convert model instance to dictionary representation."""

class PageSerializer(BaseSerializer):
    """
    Serializer for Page models with content and metadata serialization.
    
    Handles:
    - Page hierarchy information
    - StreamField content serialization
    - Rich text field processing
    - Image and document references
    """
    id: int
    meta: dict
    title: str
    html_url: str
    slug: str
    url_path: str
    seo_title: str
    search_description: str
    show_in_menus: bool
    first_published_at: datetime
    last_published_at: datetime
    
    def get_meta(self, instance):
        """Get metadata about the page type and properties."""
    
    def get_html_url(self, instance):
        """Get the full HTML URL for this page."""

class ImageSerializer(BaseSerializer):
    """
    Serializer for Image models with rendition support.
    
    Provides:
    - Image metadata (dimensions, file size, etc.)
    - Rendition generation for different sizes
    - Collection and tag information
    """
    id: int
    meta: dict
    title: str
    original: dict
    thumbnail: dict
    width: int
    height: int
    created_at: datetime
    
    def get_original(self, instance):
        """Get original image URL and metadata."""
    
    def get_thumbnail(self, instance):
        """Get thumbnail rendition URL."""

class DocumentSerializer(BaseSerializer):
    """
    Serializer for Document models with download information.
    
    Provides:
    - Document metadata (file size, type, etc.)
    - Download URLs
    - Collection information
    """
    id: int
    meta: dict
    title: str
    download_url: str
    created_at: datetime
    
    def get_download_url(self, instance):
        """Get secure download URL for this document."""

API Configuration

Classes for configuring API field exposure and customization.

class APIField:
    """
    Configuration for exposing model fields through the API.
    
    Controls how model fields are serialized and what data is included.
    """
    def __init__(self, name, serializer=None):
        """
        Initialize API field configuration.
        
        Parameters:
            name (str): Name of the field to expose
            serializer (Serializer): Custom serializer for this field
        """

class WagtailAPIRouter:
    """
    URL router for configuring API endpoints and routing.
    
    Manages URL patterns and endpoint registration for the API.
    """
    def __init__(self, name='wagtailapi'):
        """
        Initialize API router.
        
        Parameters:
            name (str): Name for the API URL namespace
        """
    
    def register_endpoint(self, prefix, viewset):
        """
        Register an API endpoint.
        
        Parameters:
            prefix (str): URL prefix for the endpoint
            viewset (ViewSet): ViewSet class handling the endpoint
        """
    
    @property
    def urls(self):
        """Get URL patterns for all registered endpoints."""

API Filters

Filter classes for querying and searching API endpoints.

class FieldsFilter:
    """
    Filter for selecting specific fields in API responses.
    
    Allows clients to request only the fields they need.
    """
    def filter_queryset(self, request, queryset, view):
        """Apply field selection to the response."""

class ChildOfFilter:
    """
    Filter for finding pages that are children of a specific page.
    """
    def filter_queryset(self, request, queryset, view):
        """Filter to pages that are children of specified parent."""

class DescendantOfFilter:
    """
    Filter for finding pages that are descendants of a specific page.
    """
    def filter_queryset(self, request, queryset, view):
        """Filter to pages that are descendants of specified ancestor."""

class OrderingFilter:
    """
    Filter for ordering API results by specified fields.
    """
    def filter_queryset(self, request, queryset, view):
        """Apply ordering to the queryset."""

class SearchFilter:
    """
    Filter for full-text search across API endpoints.
    """
    def filter_queryset(self, request, queryset, view):
        """Apply search query to the queryset."""

API Utilities

Utility functions and classes for API functionality.

def get_base_url(request=None):
    """
    Get the base URL for API endpoints.
    
    Parameters:
        request (HttpRequest): Current request object
        
    Returns:
        str: Base URL for the API
    """

def get_object_detail_url(context, model, pk, url_name=None):
    """
    Get detail URL for an API object.
    
    Parameters:
        context (dict): Serializer context
        model (Model): Model class
        pk (int): Primary key of the object
        url_name (str): URL name for the endpoint
        
    Returns:
        str: Detail URL for the object
    """

class BadRequestError(Exception):
    """Exception for API bad request errors."""

class NotFoundError(Exception):
    """Exception for API not found errors."""

Usage Examples

Setting Up API Endpoints

# urls.py
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet

# Create API router
api_router = WagtailAPIRouter('wagtailapi')

# Register default endpoints
api_router.register_endpoint('pages', PagesAPIViewSet)
api_router.register_endpoint('images', ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet)

# Add to URL patterns
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v2/', api_router.urls),
    path('', include(wagtail_urls)),
]

Custom API Endpoints

from wagtail.api.v2.views import BaseAPIViewSet
from wagtail.api.v2.serializers import BaseSerializer
from rest_framework.fields import CharField, DateTimeField
from myapp.models import BlogPage, Author

class AuthorSerializer(BaseSerializer):
    """Custom serializer for Author model."""
    name = CharField(read_only=True)
    bio = CharField(read_only=True)
    posts_count = serializers.SerializerMethodField()
    
    def get_posts_count(self, instance):
        """Get number of blog posts by this author."""
        return BlogPage.objects.filter(author=instance).count()

class AuthorAPIViewSet(BaseAPIViewSet):
    """Custom API endpoint for authors."""
    base_serializer_class = AuthorSerializer
    filter_backends = [SearchFilter, OrderingFilter]
    model = Author
    known_query_parameters = BaseAPIViewSet.known_query_parameters.union([
        'name', 'bio'
    ])
    
    def get_queryset(self):
        return Author.objects.all()

# Register custom endpoint
api_router.register_endpoint('authors', AuthorAPIViewSet)

Configuring Page API Fields

from wagtail.api.v2.serializers import BaseSerializer
from wagtail.api import APIField
from wagtail.models import Page
from wagtail.fields import StreamField
from rest_framework.fields import CharField

class BlogPage(Page):
    """Blog page with API field configuration."""
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = StreamField([
        ('heading', CharBlock()),
        ('paragraph', RichTextBlock()),
        ('image', ImageChooserBlock()),
    ])
    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
    
    # Configure API field exposure
    api_fields = [
        APIField('date'),
        APIField('intro'),
        APIField('body'),  # StreamField automatically serialized
        APIField('author', serializer=AuthorSerializer()),
    ]
    
    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('intro'),
        FieldPanel('body'),
        FieldPanel('author'),
    ]

# Custom serializer for complex fields
class BlogPageSerializer(BaseSerializer):
    """Custom serializer for blog pages."""
    reading_time = serializers.SerializerMethodField()
    related_posts = serializers.SerializerMethodField()
    
    def get_reading_time(self, instance):
        """Calculate estimated reading time."""
        word_count = len(str(instance.body).split())
        return max(1, word_count // 200)  # Assume 200 words per minute
    
    def get_related_posts(self, instance):
        """Get related blog posts."""
        related = BlogPage.objects.live().exclude(pk=instance.pk)[:3]
        return [{'title': post.title, 'url': post.url} for post in related]

Making API Requests

import requests

# Base API URL
base_url = 'https://example.com/api/v2/'

# Get all pages
response = requests.get(f'{base_url}pages/')
pages = response.json()

# Get specific page
page_id = 123
response = requests.get(f'{base_url}pages/{page_id}/')
page = response.json()

# Search pages
response = requests.get(f'{base_url}pages/', params={
    'search': 'django tutorial',
    'type': 'blog.BlogPage',
    'fields': 'title,slug,date,author'
})
search_results = response.json()

# Get page children
parent_id = 10
response = requests.get(f'{base_url}pages/', params={
    'child_of': parent_id,
    'limit': 20
})
children = response.json()

# Get images with renditions
response = requests.get(f'{base_url}images/')
images = response.json()

# Get specific image
image_id = 456
response = requests.get(f'{base_url}images/{image_id}/')
image = response.json()
print(f"Original: {image['meta']['download_url']}")
print(f"Thumbnail: {image['meta']['thumbnail']['url']}")

# Filter by collection
collection_id = 5
response = requests.get(f'{base_url}images/', params={
    'collection_id': collection_id
})
collection_images = response.json()

JavaScript Frontend Integration

// API client class
class WagtailAPI {
    constructor(baseURL) {
        this.baseURL = baseURL;
    }
    
    async getPages(params = {}) {
        const url = new URL(`${this.baseURL}pages/`);
        Object.keys(params).forEach(key => {
            url.searchParams.append(key, params[key]);
        });
        
        const response = await fetch(url);
        return response.json();
    }
    
    async getPage(id, fields = []) {
        const url = new URL(`${this.baseURL}pages/${id}/`);
        if (fields.length > 0) {
            url.searchParams.append('fields', fields.join(','));
        }
        
        const response = await fetch(url);
        return response.json();
    }
    
    async searchPages(query, type = null) {
        return this.getPages({
            search: query,
            ...(type && { type: type })
        });
    }
    
    async getImages(params = {}) {
        const url = new URL(`${this.baseURL}images/`);
        Object.keys(params).forEach(key => {
            url.searchParams.append(key, params[key]);
        });
        
        const response = await fetch(url);
        return response.json();
    }
}

// Usage example
const api = new WagtailAPI('https://example.com/api/v2/');

// Load blog posts
async function loadBlogPosts() {
    try {
        const data = await api.getPages({
            type: 'blog.BlogPage',
            fields: 'title,slug,date,intro,author',
            limit: 10,
            order: '-date'
        });
        
        const posts = data.items.map(post => ({
            title: post.title,
            slug: post.slug,
            date: post.date,
            intro: post.intro,
            author: post.author?.name || 'Unknown',
            url: post.meta.html_url
        }));
        
        renderBlogPosts(posts);
    } catch (error) {
        console.error('Failed to load blog posts:', error);
    }
}

// Search functionality
async function searchContent(query) {
    try {
        const [pages, images] = await Promise.all([
            api.searchPages(query),
            api.getImages({ search: query })
        ]);
        
        return {
            pages: pages.items,
            images: images.items
        };
    } catch (error) {
        console.error('Search failed:', error);
    }
}

Advanced API Customization

from wagtail.api.v2.views import BaseAPIViewSet
from wagtail.api.v2.filters import BaseFilterSet
from django_filters import rest_framework as filters

class BlogPageFilter(BaseFilterSet):
    """Custom filter for blog pages."""
    date_from = filters.DateFilter(field_name='date', lookup_expr='gte')
    date_to = filters.DateFilter(field_name='date', lookup_expr='lte')
    author_name = filters.CharFilter(field_name='author__name', lookup_expr='icontains')
    
    class Meta:
        model = BlogPage
        fields = ['date_from', 'date_to', 'author_name']

class BlogPageAPIViewSet(PagesAPIViewSet):
    """Enhanced blog page API with custom filtering."""
    filter_backends = PagesAPIViewSet.filter_backends + [filters.DjangoFilterBackend]
    filterset_class = BlogPageFilter
    
    known_query_parameters = PagesAPIViewSet.known_query_parameters.union([
        'date_from', 'date_to', 'author_name'
    ])
    
    def get_queryset(self):
        return BlogPage.objects.live().select_related('author')

# Custom endpoint with authentication
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

class PrivateContentAPIViewSet(BaseAPIViewSet):
    """API endpoint requiring authentication."""
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
    
    def get_queryset(self):
        # Return content based on user permissions
        user = self.request.user
        return Page.objects.live().filter(
            owner=user
        )

API Response Caching

from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator

@method_decorator(cache_page(60 * 15), name='listing_view')  # 15 minutes
@method_decorator(cache_page(60 * 60), name='detail_view')   # 1 hour
class CachedPagesAPIViewSet(PagesAPIViewSet):
    """API viewset with response caching."""
    
    def get_queryset(self):
        # Add cache invalidation logic
        return super().get_queryset().select_related('locale')

# Custom cache invalidation
from django.core.cache import cache
from wagtail.signals import page_published, page_unpublished

def invalidate_api_cache(sender, **kwargs):
    """Invalidate API cache when pages are published/unpublished."""
    cache.delete_pattern('views.decorators.cache.cache_page.*')

page_published.connect(invalidate_api_cache)
page_unpublished.connect(invalidate_api_cache)

Install with Tessl CLI

npx tessl i tessl/pypi-wagtail

docs

admin-interface.md

api.md

content-fields.md

contrib.md

index.md

media.md

page-models.md

search.md

system-integration.md

templates.md

workflows.md

tile.json