A Django content management system with a user-friendly interface and powerful features for building websites and applications.
Comprehensive media management with image processing, renditions, document handling, and collection-based organization. Wagtail provides powerful tools for managing images, documents, and other media assets with automated processing and optimization.
Core image model and processing capabilities for handling images with automatic optimization and multiple renditions.
class Image(AbstractImage):
"""
Core image model with processing and metadata capabilities.
Properties:
title (str): Image title/alt text
file (ImageField): The actual image file
width (int): Original image width in pixels
height (int): Original image height in pixels
created_at (datetime): When image was uploaded
uploaded_by_user (User): User who uploaded the image
focal_point_x (int): X coordinate of focal point
focal_point_y (int): Y coordinate of focal point
focal_point_width (int): Width of focal point area
focal_point_height (int): Height of focal point area
file_size (int): File size in bytes
file_hash (str): SHA1 hash of file content
collection (Collection): Collection this image belongs to
"""
title: str
file: ImageField
width: int
height: int
created_at: datetime
uploaded_by_user: User
focal_point_x: int
focal_point_y: int
focal_point_width: int
focal_point_height: int
file_size: int
file_hash: str
collection: Collection
def get_rendition(self, filter):
"""
Get or create a rendition of this image with specified operations.
Parameters:
filter (str or Filter): Image operations to apply
Returns:
Rendition: Processed image rendition
"""
def get_upload_to(self, filename):
"""Get the upload path for this image file."""
def get_usage(self):
"""Get all places where this image is used."""
def is_portrait(self):
"""Check if image is in portrait orientation."""
def is_landscape(self):
"""Check if image is in landscape orientation."""
class AbstractImage(models.Model):
"""
Base class for custom image models.
Inherit from this to create custom image models with additional fields.
"""
def save(self, *args, **kwargs):
"""Save image with automatic metadata extraction."""
def get_willow_image(self):
"""Get Willow image object for processing."""
class Rendition(AbstractRendition):
"""
Processed version of an image with specific operations applied.
Properties:
image (Image): Source image this rendition was created from
filter_spec (str): Operations applied to create this rendition
file (ImageField): The processed image file
width (int): Rendition width in pixels
height (int): Rendition height in pixels
focal_point_key (str): Focal point used for this rendition
"""
image: Image
filter_spec: str
file: ImageField
width: int
height: int
focal_point_key: str
def img_tag(self, extra_attrs=None):
"""Generate HTML img tag for this rendition."""
def attrs(self, extra_attrs=None):
"""Get HTML attributes dict for this rendition."""
class AbstractRendition(models.Model):
"""
Base class for custom rendition models.
"""
def save(self, *args, **kwargs):
"""Save rendition with automatic file generation."""Image processing operations for creating renditions with different sizes and effects.
class Filter:
"""
Represents a set of image operations to apply.
Parameters:
spec (str): Filter specification string (e.g., 'fill-300x200|jpegquality-80')
"""
def __init__(self, spec):
"""Initialize filter with operation specification."""
def run(self, willow_image, image):
"""Apply filter operations to image."""
# Image operation classes
class Fill:
"""
Fill operation that crops and resizes to exact dimensions.
Parameters:
width (int): Target width in pixels
height (int): Target height in pixels
"""
def __init__(self, width, height):
"""Initialize fill operation."""
class FillMax:
"""
Fill operation with maximum dimensions constraint.
Parameters:
width (int): Maximum width in pixels
height (int): Maximum height in pixels
"""
def __init__(self, width, height):
"""Initialize fill max operation."""
class Width:
"""
Resize operation that sets width and maintains aspect ratio.
Parameters:
width (int): Target width in pixels
"""
def __init__(self, width):
"""Initialize width resize operation."""
class Height:
"""
Resize operation that sets height and maintains aspect ratio.
Parameters:
height (int): Target height in pixels
"""
def __init__(self, height):
"""Initialize height resize operation."""
class Min:
"""
Resize operation based on minimum dimension.
Parameters:
dimension (int): Minimum dimension in pixels
"""
def __init__(self, dimension):
"""Initialize min resize operation."""
class Max:
"""
Resize operation based on maximum dimension.
Parameters:
dimension (int): Maximum dimension in pixels
"""
def __init__(self, dimension):
"""Initialize max resize operation."""
class Scale:
"""
Scale operation that resizes by percentage.
Parameters:
percent (int): Scale percentage (100 = original size)
"""
def __init__(self, percent):
"""Initialize scale operation."""
class CropToPoint:
"""
Crop operation that centers on a specific point.
Parameters:
width (int): Crop width in pixels
height (int): Crop height in pixels
x (int): X coordinate of center point
y (int): Y coordinate of center point
"""
def __init__(self, width, height, x, y):
"""Initialize crop to point operation."""Document handling for files like PDFs, Word documents, spreadsheets, and other non-image media.
class Document(AbstractDocument):
"""
Core document model for file management.
Properties:
title (str): Document title
file (FileField): The actual document file
created_at (datetime): When document was uploaded
uploaded_by_user (User): User who uploaded the document
collection (Collection): Collection this document belongs to
file_size (int): File size in bytes
file_hash (str): SHA1 hash of file content
"""
title: str
file: FileField
created_at: datetime
uploaded_by_user: User
collection: Collection
file_size: int
file_hash: str
def get_usage(self):
"""Get all places where this document is used."""
def get_file_size(self):
"""Get human-readable file size."""
def get_file_extension(self):
"""Get file extension."""
@property
def url(self):
"""Get URL for downloading this document."""
class AbstractDocument(models.Model):
"""
Base class for custom document models.
Inherit from this to create custom document models with additional fields.
"""
def save(self, *args, **kwargs):
"""Save document with automatic metadata extraction."""
def get_upload_to(self, filename):
"""Get the upload path for this document file."""Hierarchical organization system for media assets with permission control.
class Collection(MP_Node):
"""
Hierarchical collection for organizing media assets.
Properties:
name (str): Collection name
path (str): Full path in collection hierarchy
"""
name: str
path: str
def get_descendants(self, inclusive=False):
"""
Get all descendant collections.
Parameters:
inclusive (bool): Whether to include self in results
Returns:
QuerySet: Descendant collections
"""
def get_ancestors(self, inclusive=False):
"""
Get all ancestor collections.
Parameters:
inclusive (bool): Whether to include self in results
Returns:
QuerySet: Ancestor collections
"""
def get_view_restrictions(self):
"""Get view restrictions applied to this collection."""
def get_edit_restrictions(self):
"""Get edit restrictions applied to this collection."""
@classmethod
def get_first_root_node(cls):
"""Get the root collection."""
class CollectionViewRestriction:
"""
Restricts collection viewing to specific users or groups.
Properties:
collection (Collection): Collection to restrict
restriction_type (str): Type of restriction ('password', 'groups', 'login')
password (str): Password for access (if password restriction)
groups (QuerySet): Groups with access (if groups restriction)
"""
collection: Collection
restriction_type: str
password: str
groups: QuerySetUtility functions and classes for media processing and management.
def get_image_model():
"""
Get the configured Image model class.
Returns:
Model: The Image model class being used
"""
def get_document_model():
"""
Get the configured Document model class.
Returns:
Model: The Document model class being used
"""
class SourceImageIOError(Exception):
"""Exception raised when source image cannot be processed."""
class InvalidFilterSpecError(Exception):
"""Exception raised when filter specification is invalid."""
def generate_signature(image_id, filter_spec, key=None):
"""
Generate security signature for image renditions.
Parameters:
image_id (int): ID of source image
filter_spec (str): Filter specification
key (bytes): Secret key for signing
Returns:
str: Security signature
"""
def verify_signature(signature, image_id, filter_spec, key=None):
"""
Verify security signature for image renditions.
Parameters:
signature (str): Signature to verify
image_id (int): ID of source image
filter_spec (str): Filter specification
key (bytes): Secret key for verification
Returns:
bool: Whether signature is valid
"""from wagtail.images.models import Image
from wagtail.images import get_image_model
# Get image model (useful for custom image models)
ImageModel = get_image_model()
# Create/upload image
with open('photo.jpg', 'rb') as f:
image = Image(
title="My Photo",
file=File(f, name='photo.jpg')
)
image.save()
# Get different sized versions
thumbnail = image.get_rendition('fill-150x150|jpegquality-60')
medium = image.get_rendition('width-500')
large = image.get_rendition('fill-1200x800')
# Use in templates
print(f'<img src="{thumbnail.url}" alt="{image.title}">')
# Complex operations
hero_image = image.get_rendition('fill-1920x1080|format-webp|jpegquality-85')
responsive_thumb = image.get_rendition('max-400x400|format-webp')
# Focal point cropping
centered_crop = image.get_rendition('fill-300x200') # Uses focal point if setfrom wagtail.images.models import Image
# Load an image
image = Image.objects.get(title="Hero Image")
# Basic operations
thumbnail = image.get_rendition('fill-200x200') # Crop to exact size
scaled = image.get_rendition('width-800') # Resize maintaining ratio
square = image.get_rendition('min-300') # Minimum dimension
compressed = image.get_rendition('original|jpegquality-70') # Compress
# Advanced operations
hero = image.get_rendition('fill-1920x1080|format-webp|jpegquality-90')
mobile = image.get_rendition('fill-800x600|format-webp|jpegquality-75')
# Multiple operations in sequence
processed = image.get_rendition('width-1000|height-600|jpegquality-80')
# Format conversion
webp_version = image.get_rendition('original|format-webp')
png_version = image.get_rendition('original|format-png')
# Responsive images with multiple renditions
renditions = {
'mobile': image.get_rendition('fill-400x300'),
'tablet': image.get_rendition('fill-800x600'),
'desktop': image.get_rendition('fill-1200x900'),
}from wagtail.documents.models import Document
from django.core.files import File
# Upload document
with open('report.pdf', 'rb') as f:
document = Document(
title="Annual Report 2023",
file=File(f, name='annual-report-2023.pdf')
)
document.save()
# Access document properties
print(f"File size: {document.get_file_size()}")
print(f"Extension: {document.get_file_extension()}")
print(f"Download URL: {document.url}")
# Find document usage
usage = document.get_usage()
for page in usage:
print(f"Used on: {page.title}")
# Organize in collections
from wagtail.models import Collection
reports_collection = Collection.objects.get(name="Reports")
document.collection = reports_collection
document.save()from wagtail.models import Collection
# Create collection hierarchy
root = Collection.get_first_root_node()
# Add main collections
marketing = root.add_child(name="Marketing")
products = root.add_child(name="Products")
# Add subcollections
brochures = marketing.add_child(name="Brochures")
social_media = marketing.add_child(name="Social Media")
product_photos = products.add_child(name="Product Photos")
documentation = products.add_child(name="Documentation")
# Organize media by collection
from wagtail.images.models import Image
# Move images to appropriate collections
hero_images = Image.objects.filter(title__icontains="hero")
for image in hero_images:
image.collection = marketing
image.save()
# Filter media by collection
marketing_images = Image.objects.filter(collection=marketing)
product_docs = Document.objects.filter(collection__path__startswith=products.path)
# Collection permissions
from wagtail.models import CollectionViewRestriction, Group
# Restrict collection to specific groups
marketing_group = Group.objects.get(name="Marketing Team")
restriction = CollectionViewRestriction.objects.create(
collection=marketing,
restriction_type='groups'
)
restriction.groups.add(marketing_group)from wagtail.images.models import AbstractImage, AbstractRendition
from django.db import models
class CustomImage(AbstractImage):
"""Custom image model with additional metadata."""
photographer = models.CharField(max_length=255, blank=True)
caption = models.TextField(blank=True)
copyright_info = models.CharField(max_length=255, blank=True)
keywords = models.CharField(max_length=500, blank=True)
admin_form_fields = (
'title',
'file',
'collection',
'tags',
'photographer',
'caption',
'copyright_info',
'keywords',
'focal_point_x',
'focal_point_y',
'focal_point_width',
'focal_point_height',
)
class CustomRendition(AbstractRendition):
"""Custom rendition model for custom images."""
image = models.ForeignKey(
CustomImage,
on_delete=models.CASCADE,
related_name='renditions'
)
class Meta:
unique_together = (
('image', 'filter_spec', 'focal_point_key'),
)
# Configure custom models in settings.py
# WAGTAILIMAGES_IMAGE_MODEL = 'myapp.CustomImage'# In templates, use the image templatetag
{% load wagtailimages_tags %}
<!-- Basic image -->
{% image page.hero_image fill-800x400 as hero %}
<img src="{{ hero.url }}" alt="{{ hero.alt }}" width="{{ hero.width }}" height="{{ hero.height }}">
<!-- Responsive images -->
{% image page.hero_image fill-400x300 as mobile %}
{% image page.hero_image fill-800x600 as tablet %}
{% image page.hero_image fill-1200x900 as desktop %}
<picture>
<source media="(min-width: 1024px)" srcset="{{ desktop.url }}">
<source media="(min-width: 768px)" srcset="{{ tablet.url }}">
<img src="{{ mobile.url }}" alt="{{ page.hero_image.title }}">
</picture>
<!-- Multiple formats -->
{% image page.hero_image fill-800x600|format-webp as webp_version %}
{% image page.hero_image fill-800x600|format-jpeg as jpeg_version %}
<picture>
<source srcset="{{ webp_version.url }}" type="image/webp">
<img src="{{ jpeg_version.url }}" alt="{{ page.hero_image.title }}">
</picture>from wagtail.images.models import Image
from wagtail.documents.models import Document
from django.core.files.storage import default_storage
# Bulk image processing
def generate_thumbnails():
"""Generate thumbnail renditions for all images."""
for image in Image.objects.all():
try:
# Pre-generate common sizes
image.get_rendition('fill-150x150')
image.get_rendition('fill-300x200')
image.get_rendition('width-800')
print(f"Generated thumbnails for: {image.title}")
except Exception as e:
print(f"Error processing {image.title}: {e}")
# Clean up unused renditions
def cleanup_renditions():
"""Remove unused image renditions to free storage."""
from wagtail.images.models import Rendition
# Delete renditions older than 30 days that haven't been accessed
old_renditions = Rendition.objects.filter(
created_at__lt=timezone.now() - timedelta(days=30)
)
for rendition in old_renditions:
if default_storage.exists(rendition.file.name):
default_storage.delete(rendition.file.name)
rendition.delete()
# Media analytics
def get_media_stats():
"""Get statistics about media usage."""
total_images = Image.objects.count()
total_documents = Document.objects.count()
# Calculate storage usage
image_storage = sum(img.file_size for img in Image.objects.all() if img.file_size)
doc_storage = sum(doc.file_size for doc in Document.objects.all() if doc.file_size)
return {
'total_images': total_images,
'total_documents': total_documents,
'image_storage_mb': image_storage / (1024 * 1024),
'document_storage_mb': doc_storage / (1024 * 1024),
}Install with Tessl CLI
npx tessl i tessl/pypi-wagtail