0
# Media Management
1
2
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.
3
4
## Capabilities
5
6
### Image Management
7
8
Core image model and processing capabilities for handling images with automatic optimization and multiple renditions.
9
10
```python { .api }
11
class Image(AbstractImage):
12
"""
13
Core image model with processing and metadata capabilities.
14
15
Properties:
16
title (str): Image title/alt text
17
file (ImageField): The actual image file
18
width (int): Original image width in pixels
19
height (int): Original image height in pixels
20
created_at (datetime): When image was uploaded
21
uploaded_by_user (User): User who uploaded the image
22
focal_point_x (int): X coordinate of focal point
23
focal_point_y (int): Y coordinate of focal point
24
focal_point_width (int): Width of focal point area
25
focal_point_height (int): Height of focal point area
26
file_size (int): File size in bytes
27
file_hash (str): SHA1 hash of file content
28
collection (Collection): Collection this image belongs to
29
"""
30
title: str
31
file: ImageField
32
width: int
33
height: int
34
created_at: datetime
35
uploaded_by_user: User
36
focal_point_x: int
37
focal_point_y: int
38
focal_point_width: int
39
focal_point_height: int
40
file_size: int
41
file_hash: str
42
collection: Collection
43
44
def get_rendition(self, filter):
45
"""
46
Get or create a rendition of this image with specified operations.
47
48
Parameters:
49
filter (str or Filter): Image operations to apply
50
51
Returns:
52
Rendition: Processed image rendition
53
"""
54
55
def get_upload_to(self, filename):
56
"""Get the upload path for this image file."""
57
58
def get_usage(self):
59
"""Get all places where this image is used."""
60
61
def is_portrait(self):
62
"""Check if image is in portrait orientation."""
63
64
def is_landscape(self):
65
"""Check if image is in landscape orientation."""
66
67
class AbstractImage(models.Model):
68
"""
69
Base class for custom image models.
70
71
Inherit from this to create custom image models with additional fields.
72
"""
73
def save(self, *args, **kwargs):
74
"""Save image with automatic metadata extraction."""
75
76
def get_willow_image(self):
77
"""Get Willow image object for processing."""
78
79
class Rendition(AbstractRendition):
80
"""
81
Processed version of an image with specific operations applied.
82
83
Properties:
84
image (Image): Source image this rendition was created from
85
filter_spec (str): Operations applied to create this rendition
86
file (ImageField): The processed image file
87
width (int): Rendition width in pixels
88
height (int): Rendition height in pixels
89
focal_point_key (str): Focal point used for this rendition
90
"""
91
image: Image
92
filter_spec: str
93
file: ImageField
94
width: int
95
height: int
96
focal_point_key: str
97
98
def img_tag(self, extra_attrs=None):
99
"""Generate HTML img tag for this rendition."""
100
101
def attrs(self, extra_attrs=None):
102
"""Get HTML attributes dict for this rendition."""
103
104
class AbstractRendition(models.Model):
105
"""
106
Base class for custom rendition models.
107
"""
108
def save(self, *args, **kwargs):
109
"""Save rendition with automatic file generation."""
110
```
111
112
### Image Operations
113
114
Image processing operations for creating renditions with different sizes and effects.
115
116
```python { .api }
117
class Filter:
118
"""
119
Represents a set of image operations to apply.
120
121
Parameters:
122
spec (str): Filter specification string (e.g., 'fill-300x200|jpegquality-80')
123
"""
124
def __init__(self, spec):
125
"""Initialize filter with operation specification."""
126
127
def run(self, willow_image, image):
128
"""Apply filter operations to image."""
129
130
# Image operation classes
131
class Fill:
132
"""
133
Fill operation that crops and resizes to exact dimensions.
134
135
Parameters:
136
width (int): Target width in pixels
137
height (int): Target height in pixels
138
"""
139
def __init__(self, width, height):
140
"""Initialize fill operation."""
141
142
class FillMax:
143
"""
144
Fill operation with maximum dimensions constraint.
145
146
Parameters:
147
width (int): Maximum width in pixels
148
height (int): Maximum height in pixels
149
"""
150
def __init__(self, width, height):
151
"""Initialize fill max operation."""
152
153
class Width:
154
"""
155
Resize operation that sets width and maintains aspect ratio.
156
157
Parameters:
158
width (int): Target width in pixels
159
"""
160
def __init__(self, width):
161
"""Initialize width resize operation."""
162
163
class Height:
164
"""
165
Resize operation that sets height and maintains aspect ratio.
166
167
Parameters:
168
height (int): Target height in pixels
169
"""
170
def __init__(self, height):
171
"""Initialize height resize operation."""
172
173
class Min:
174
"""
175
Resize operation based on minimum dimension.
176
177
Parameters:
178
dimension (int): Minimum dimension in pixels
179
"""
180
def __init__(self, dimension):
181
"""Initialize min resize operation."""
182
183
class Max:
184
"""
185
Resize operation based on maximum dimension.
186
187
Parameters:
188
dimension (int): Maximum dimension in pixels
189
"""
190
def __init__(self, dimension):
191
"""Initialize max resize operation."""
192
193
class Scale:
194
"""
195
Scale operation that resizes by percentage.
196
197
Parameters:
198
percent (int): Scale percentage (100 = original size)
199
"""
200
def __init__(self, percent):
201
"""Initialize scale operation."""
202
203
class CropToPoint:
204
"""
205
Crop operation that centers on a specific point.
206
207
Parameters:
208
width (int): Crop width in pixels
209
height (int): Crop height in pixels
210
x (int): X coordinate of center point
211
y (int): Y coordinate of center point
212
"""
213
def __init__(self, width, height, x, y):
214
"""Initialize crop to point operation."""
215
```
216
217
### Document Management
218
219
Document handling for files like PDFs, Word documents, spreadsheets, and other non-image media.
220
221
```python { .api }
222
class Document(AbstractDocument):
223
"""
224
Core document model for file management.
225
226
Properties:
227
title (str): Document title
228
file (FileField): The actual document file
229
created_at (datetime): When document was uploaded
230
uploaded_by_user (User): User who uploaded the document
231
collection (Collection): Collection this document belongs to
232
file_size (int): File size in bytes
233
file_hash (str): SHA1 hash of file content
234
"""
235
title: str
236
file: FileField
237
created_at: datetime
238
uploaded_by_user: User
239
collection: Collection
240
file_size: int
241
file_hash: str
242
243
def get_usage(self):
244
"""Get all places where this document is used."""
245
246
def get_file_size(self):
247
"""Get human-readable file size."""
248
249
def get_file_extension(self):
250
"""Get file extension."""
251
252
@property
253
def url(self):
254
"""Get URL for downloading this document."""
255
256
class AbstractDocument(models.Model):
257
"""
258
Base class for custom document models.
259
260
Inherit from this to create custom document models with additional fields.
261
"""
262
def save(self, *args, **kwargs):
263
"""Save document with automatic metadata extraction."""
264
265
def get_upload_to(self, filename):
266
"""Get the upload path for this document file."""
267
```
268
269
### Collection Management
270
271
Hierarchical organization system for media assets with permission control.
272
273
```python { .api }
274
class Collection(MP_Node):
275
"""
276
Hierarchical collection for organizing media assets.
277
278
Properties:
279
name (str): Collection name
280
path (str): Full path in collection hierarchy
281
"""
282
name: str
283
path: str
284
285
def get_descendants(self, inclusive=False):
286
"""
287
Get all descendant collections.
288
289
Parameters:
290
inclusive (bool): Whether to include self in results
291
292
Returns:
293
QuerySet: Descendant collections
294
"""
295
296
def get_ancestors(self, inclusive=False):
297
"""
298
Get all ancestor collections.
299
300
Parameters:
301
inclusive (bool): Whether to include self in results
302
303
Returns:
304
QuerySet: Ancestor collections
305
"""
306
307
def get_view_restrictions(self):
308
"""Get view restrictions applied to this collection."""
309
310
def get_edit_restrictions(self):
311
"""Get edit restrictions applied to this collection."""
312
313
@classmethod
314
def get_first_root_node(cls):
315
"""Get the root collection."""
316
317
class CollectionViewRestriction:
318
"""
319
Restricts collection viewing to specific users or groups.
320
321
Properties:
322
collection (Collection): Collection to restrict
323
restriction_type (str): Type of restriction ('password', 'groups', 'login')
324
password (str): Password for access (if password restriction)
325
groups (QuerySet): Groups with access (if groups restriction)
326
"""
327
collection: Collection
328
restriction_type: str
329
password: str
330
groups: QuerySet
331
```
332
333
### Media Utilities
334
335
Utility functions and classes for media processing and management.
336
337
```python { .api }
338
def get_image_model():
339
"""
340
Get the configured Image model class.
341
342
Returns:
343
Model: The Image model class being used
344
"""
345
346
def get_document_model():
347
"""
348
Get the configured Document model class.
349
350
Returns:
351
Model: The Document model class being used
352
"""
353
354
class SourceImageIOError(Exception):
355
"""Exception raised when source image cannot be processed."""
356
357
class InvalidFilterSpecError(Exception):
358
"""Exception raised when filter specification is invalid."""
359
360
def generate_signature(image_id, filter_spec, key=None):
361
"""
362
Generate security signature for image renditions.
363
364
Parameters:
365
image_id (int): ID of source image
366
filter_spec (str): Filter specification
367
key (bytes): Secret key for signing
368
369
Returns:
370
str: Security signature
371
"""
372
373
def verify_signature(signature, image_id, filter_spec, key=None):
374
"""
375
Verify security signature for image renditions.
376
377
Parameters:
378
signature (str): Signature to verify
379
image_id (int): ID of source image
380
filter_spec (str): Filter specification
381
key (bytes): Secret key for verification
382
383
Returns:
384
bool: Whether signature is valid
385
"""
386
```
387
388
## Usage Examples
389
390
### Working with Images
391
392
```python
393
from wagtail.images.models import Image
394
from wagtail.images import get_image_model
395
396
# Get image model (useful for custom image models)
397
ImageModel = get_image_model()
398
399
# Create/upload image
400
with open('photo.jpg', 'rb') as f:
401
image = Image(
402
title="My Photo",
403
file=File(f, name='photo.jpg')
404
)
405
image.save()
406
407
# Get different sized versions
408
thumbnail = image.get_rendition('fill-150x150|jpegquality-60')
409
medium = image.get_rendition('width-500')
410
large = image.get_rendition('fill-1200x800')
411
412
# Use in templates
413
print(f'<img src="{thumbnail.url}" alt="{image.title}">')
414
415
# Complex operations
416
hero_image = image.get_rendition('fill-1920x1080|format-webp|jpegquality-85')
417
responsive_thumb = image.get_rendition('max-400x400|format-webp')
418
419
# Focal point cropping
420
centered_crop = image.get_rendition('fill-300x200') # Uses focal point if set
421
```
422
423
### Image Operations and Filters
424
425
```python
426
from wagtail.images.models import Image
427
428
# Load an image
429
image = Image.objects.get(title="Hero Image")
430
431
# Basic operations
432
thumbnail = image.get_rendition('fill-200x200') # Crop to exact size
433
scaled = image.get_rendition('width-800') # Resize maintaining ratio
434
square = image.get_rendition('min-300') # Minimum dimension
435
compressed = image.get_rendition('original|jpegquality-70') # Compress
436
437
# Advanced operations
438
hero = image.get_rendition('fill-1920x1080|format-webp|jpegquality-90')
439
mobile = image.get_rendition('fill-800x600|format-webp|jpegquality-75')
440
441
# Multiple operations in sequence
442
processed = image.get_rendition('width-1000|height-600|jpegquality-80')
443
444
# Format conversion
445
webp_version = image.get_rendition('original|format-webp')
446
png_version = image.get_rendition('original|format-png')
447
448
# Responsive images with multiple renditions
449
renditions = {
450
'mobile': image.get_rendition('fill-400x300'),
451
'tablet': image.get_rendition('fill-800x600'),
452
'desktop': image.get_rendition('fill-1200x900'),
453
}
454
```
455
456
### Document Management
457
458
```python
459
from wagtail.documents.models import Document
460
from django.core.files import File
461
462
# Upload document
463
with open('report.pdf', 'rb') as f:
464
document = Document(
465
title="Annual Report 2023",
466
file=File(f, name='annual-report-2023.pdf')
467
)
468
document.save()
469
470
# Access document properties
471
print(f"File size: {document.get_file_size()}")
472
print(f"Extension: {document.get_file_extension()}")
473
print(f"Download URL: {document.url}")
474
475
# Find document usage
476
usage = document.get_usage()
477
for page in usage:
478
print(f"Used on: {page.title}")
479
480
# Organize in collections
481
from wagtail.models import Collection
482
483
reports_collection = Collection.objects.get(name="Reports")
484
document.collection = reports_collection
485
document.save()
486
```
487
488
### Collection Organization
489
490
```python
491
from wagtail.models import Collection
492
493
# Create collection hierarchy
494
root = Collection.get_first_root_node()
495
496
# Add main collections
497
marketing = root.add_child(name="Marketing")
498
products = root.add_child(name="Products")
499
500
# Add subcollections
501
brochures = marketing.add_child(name="Brochures")
502
social_media = marketing.add_child(name="Social Media")
503
504
product_photos = products.add_child(name="Product Photos")
505
documentation = products.add_child(name="Documentation")
506
507
# Organize media by collection
508
from wagtail.images.models import Image
509
510
# Move images to appropriate collections
511
hero_images = Image.objects.filter(title__icontains="hero")
512
for image in hero_images:
513
image.collection = marketing
514
image.save()
515
516
# Filter media by collection
517
marketing_images = Image.objects.filter(collection=marketing)
518
product_docs = Document.objects.filter(collection__path__startswith=products.path)
519
520
# Collection permissions
521
from wagtail.models import CollectionViewRestriction, Group
522
523
# Restrict collection to specific groups
524
marketing_group = Group.objects.get(name="Marketing Team")
525
restriction = CollectionViewRestriction.objects.create(
526
collection=marketing,
527
restriction_type='groups'
528
)
529
restriction.groups.add(marketing_group)
530
```
531
532
### Custom Image Model
533
534
```python
535
from wagtail.images.models import AbstractImage, AbstractRendition
536
from django.db import models
537
538
class CustomImage(AbstractImage):
539
"""Custom image model with additional metadata."""
540
photographer = models.CharField(max_length=255, blank=True)
541
caption = models.TextField(blank=True)
542
copyright_info = models.CharField(max_length=255, blank=True)
543
keywords = models.CharField(max_length=500, blank=True)
544
545
admin_form_fields = (
546
'title',
547
'file',
548
'collection',
549
'tags',
550
'photographer',
551
'caption',
552
'copyright_info',
553
'keywords',
554
'focal_point_x',
555
'focal_point_y',
556
'focal_point_width',
557
'focal_point_height',
558
)
559
560
class CustomRendition(AbstractRendition):
561
"""Custom rendition model for custom images."""
562
image = models.ForeignKey(
563
CustomImage,
564
on_delete=models.CASCADE,
565
related_name='renditions'
566
)
567
568
class Meta:
569
unique_together = (
570
('image', 'filter_spec', 'focal_point_key'),
571
)
572
573
# Configure custom models in settings.py
574
# WAGTAILIMAGES_IMAGE_MODEL = 'myapp.CustomImage'
575
```
576
577
### Template Usage with Images
578
579
```python
580
# In templates, use the image templatetag
581
{% load wagtailimages_tags %}
582
583
<!-- Basic image -->
584
{% image page.hero_image fill-800x400 as hero %}
585
<img src="{{ hero.url }}" alt="{{ hero.alt }}" width="{{ hero.width }}" height="{{ hero.height }}">
586
587
<!-- Responsive images -->
588
{% image page.hero_image fill-400x300 as mobile %}
589
{% image page.hero_image fill-800x600 as tablet %}
590
{% image page.hero_image fill-1200x900 as desktop %}
591
592
<picture>
593
<source media="(min-width: 1024px)" srcset="{{ desktop.url }}">
594
<source media="(min-width: 768px)" srcset="{{ tablet.url }}">
595
<img src="{{ mobile.url }}" alt="{{ page.hero_image.title }}">
596
</picture>
597
598
<!-- Multiple formats -->
599
{% image page.hero_image fill-800x600|format-webp as webp_version %}
600
{% image page.hero_image fill-800x600|format-jpeg as jpeg_version %}
601
602
<picture>
603
<source srcset="{{ webp_version.url }}" type="image/webp">
604
<img src="{{ jpeg_version.url }}" alt="{{ page.hero_image.title }}">
605
</picture>
606
```
607
608
### Programmatic Media Management
609
610
```python
611
from wagtail.images.models import Image
612
from wagtail.documents.models import Document
613
from django.core.files.storage import default_storage
614
615
# Bulk image processing
616
def generate_thumbnails():
617
"""Generate thumbnail renditions for all images."""
618
for image in Image.objects.all():
619
try:
620
# Pre-generate common sizes
621
image.get_rendition('fill-150x150')
622
image.get_rendition('fill-300x200')
623
image.get_rendition('width-800')
624
print(f"Generated thumbnails for: {image.title}")
625
except Exception as e:
626
print(f"Error processing {image.title}: {e}")
627
628
# Clean up unused renditions
629
def cleanup_renditions():
630
"""Remove unused image renditions to free storage."""
631
from wagtail.images.models import Rendition
632
633
# Delete renditions older than 30 days that haven't been accessed
634
old_renditions = Rendition.objects.filter(
635
created_at__lt=timezone.now() - timedelta(days=30)
636
)
637
638
for rendition in old_renditions:
639
if default_storage.exists(rendition.file.name):
640
default_storage.delete(rendition.file.name)
641
rendition.delete()
642
643
# Media analytics
644
def get_media_stats():
645
"""Get statistics about media usage."""
646
total_images = Image.objects.count()
647
total_documents = Document.objects.count()
648
649
# Calculate storage usage
650
image_storage = sum(img.file_size for img in Image.objects.all() if img.file_size)
651
doc_storage = sum(doc.file_size for doc in Document.objects.all() if doc.file_size)
652
653
return {
654
'total_images': total_images,
655
'total_documents': total_documents,
656
'image_storage_mb': image_storage / (1024 * 1024),
657
'document_storage_mb': doc_storage / (1024 * 1024),
658
}
659
```