0
# Page Models and Content Structure
1
2
Core page functionality providing the foundation for all content in Wagtail. The Page model hierarchy enables tree-based content organization with powerful inheritance and customization capabilities.
3
4
## Capabilities
5
6
### Base Page Model
7
8
The core Page model provides the foundation for all pages in Wagtail, including hierarchical structure, URL routing, publishing workflow, and content management.
9
10
```python { .api }
11
class Page(
12
WorkflowMixin,
13
PreviewableMixin,
14
DraftStateMixin,
15
LockableMixin,
16
RevisionMixin,
17
TranslatableMixin,
18
SpecificMixin,
19
MP_Node,
20
index.Indexed,
21
ClusterableModel
22
):
23
"""
24
Base page model with full CMS functionality.
25
26
Core Fields:
27
title (str): Page title
28
draft_title (str): Draft version of title (auto-managed)
29
slug (str): URL slug for the page
30
content_type (ForeignKey): Content type for polymorphic behavior
31
url_path (str): Full URL path from site root (auto-managed)
32
seo_title (str): SEO-optimized title
33
search_description (str): Meta description for search engines
34
show_in_menus (bool): Whether page appears in navigation
35
owner (User): Page owner for permissions
36
37
Publishing Fields (from DraftStateMixin):
38
live (bool): Whether page is published and visible
39
has_unpublished_changes (bool): Whether there are unpublished changes
40
first_published_at (datetime): When page was first published
41
last_published_at (datetime): When page was last published
42
latest_revision_created_at (datetime): When latest revision was created
43
go_live_at (datetime): Scheduled publish time
44
expire_at (datetime): Scheduled expiration time
45
expired (bool): Whether page has expired
46
live_revision (ForeignKey): Currently live revision
47
48
Locking Fields (from LockableMixin):
49
locked (bool): Whether page is locked for editing
50
locked_by (User): User who locked the page
51
locked_at (datetime): When page was locked
52
53
Revision Fields (from RevisionMixin):
54
latest_revision (ForeignKey): Most recent revision
55
56
Translation Fields (from TranslatableMixin):
57
locale (ForeignKey): Language/locale for this page
58
translation_key (UUID): Links translations together
59
60
Tree Fields (from MP_Node):
61
path (str): Tree path for hierarchical queries
62
depth (int): Tree depth level
63
numchild (int): Number of direct children
64
65
Other Fields:
66
alias_of (ForeignKey): Original page if this is an alias
67
"""
68
# Core fields
69
title: str
70
draft_title: str
71
slug: str
72
content_type: ContentType
73
url_path: str
74
seo_title: str
75
search_description: str
76
show_in_menus: bool
77
owner: User
78
79
# Publishing state (DraftStateMixin)
80
live: bool
81
has_unpublished_changes: bool
82
first_published_at: datetime
83
last_published_at: datetime
84
latest_revision_created_at: datetime
85
go_live_at: datetime
86
expire_at: datetime
87
expired: bool
88
live_revision: Revision
89
90
# Locking (LockableMixin)
91
locked: bool
92
locked_by: User
93
locked_at: datetime
94
95
# Revisions (RevisionMixin)
96
latest_revision: Revision
97
98
# Translation (TranslatableMixin)
99
locale: Locale
100
translation_key: UUID
101
102
# Tree structure (MP_Node)
103
path: str
104
depth: int
105
numchild: int
106
107
# Aliases
108
alias_of: Page
109
110
# URL and routing methods
111
def get_url(self, request=None, current_site=None):
112
"""Get the URL for this page."""
113
114
def get_full_url(self, request=None):
115
"""Get the absolute URL including domain for this page."""
116
117
def route(self, request, path_components):
118
"""Route a request to this page or its descendants."""
119
120
def set_url_path(self, parent):
121
"""Update the url_path field based on this page's slug and parent."""
122
123
@classmethod
124
def route_for_request(cls, request, path):
125
"""Find the page to serve for the given request path."""
126
127
@classmethod
128
def find_for_request(cls, request, path):
129
"""Find page matching the request, returns (page, args, kwargs)."""
130
131
# Content serving methods
132
def serve(self, request):
133
"""Serve this page for the given request."""
134
135
def get_context(self, request):
136
"""Get template context for rendering this page."""
137
138
def get_template(self, request, *args, **kwargs):
139
"""Get template for rendering this page."""
140
141
# Admin interface methods
142
def get_admin_display_title(self):
143
"""Get title for display in admin interface."""
144
145
def get_admin_base_path(self):
146
"""Get the base path for admin URLs for this page."""
147
148
# Lifecycle methods
149
def save(self, *args, **kwargs):
150
"""Save the page, updating URL paths and search index."""
151
152
def delete(self):
153
"""Delete the page and all its descendants."""
154
155
def clean(self):
156
"""Validate the page before saving."""
157
158
# Content management methods
159
def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True):
160
"""Create a copy of this page."""
161
162
def move(self, target, pos=None):
163
"""Move this page to a new location in the tree."""
164
165
def update_aliases(self, *, revision=None, _content_json=None, user=None):
166
"""Update any aliases of this page with new content."""
167
168
# Tree traversal methods
169
def get_children(self):
170
"""Get direct children of this page."""
171
172
def get_descendants(self, inclusive=False):
173
"""Get all descendants of this page."""
174
175
def get_ancestors(self, inclusive=False):
176
"""Get all ancestors of this page."""
177
178
def get_siblings(self, inclusive=False):
179
"""Get sibling pages at the same level."""
180
181
def is_site_root(self):
182
"""Check if this page is the root of a site."""
183
184
# Validation methods
185
@classmethod
186
def can_exist_under(cls, parent):
187
"""Check if this page type can be created under the parent."""
188
189
@classmethod
190
def can_create_at(cls, parent):
191
"""Check if this page type can be created under the parent."""
192
193
def can_move_to(self, parent):
194
"""Check if this page can be moved under the parent."""
195
196
@classmethod
197
def allowed_parent_page_models(cls):
198
"""Get allowed parent page models for this page type."""
199
200
@classmethod
201
def allowed_subpage_models(cls):
202
"""Get allowed subpage models under this page type."""
203
204
# Permission methods
205
def permissions_for_user(self, user):
206
"""Get permission tester for the given user."""
207
208
def get_view_restrictions(self):
209
"""Get view restrictions applying to this page."""
210
211
# Workflow methods
212
def get_workflow(self):
213
"""Get the workflow assigned to this page."""
214
215
# SEO and sitemap methods
216
def get_sitemap_urls(self, request=None):
217
"""Get URLs for XML sitemap generation."""
218
219
# Caching methods
220
def get_cache_key_components(self):
221
"""Get components for generating cache keys."""
222
223
# HTTP method validation
224
@classmethod
225
def allowed_http_method_names(cls):
226
"""Get allowed HTTP methods for this page type."""
227
```
228
229
### Model Mixins
230
231
Mixins that provide specific functionality for custom models and pages.
232
233
```python { .api }
234
class RevisionMixin:
235
"""
236
Adds revision capabilities to models.
237
238
Methods provide version control and content history.
239
"""
240
def save_revision(self, user=None, approved_go_live_at=None, changed=True, log_action=False, previous_revision=None, clean=True):
241
"""
242
Save a new revision of this object.
243
244
Parameters:
245
user: User who made the changes
246
approved_go_live_at: When changes should go live
247
changed: Whether content has changed
248
log_action: Whether to log this action
249
previous_revision: Previous revision for comparison
250
clean: Whether to clean the object before saving
251
"""
252
253
def get_latest_revision(self):
254
"""Get the most recent revision."""
255
256
def get_latest_revision_as_object(self):
257
"""Get the content of the latest revision as an object."""
258
259
class DraftStateMixin:
260
"""
261
Adds draft/live state management to models.
262
263
Provides publishing workflow capabilities.
264
"""
265
live: bool
266
has_unpublished_changes: bool
267
first_published_at: datetime
268
last_published_at: datetime
269
go_live_at: datetime
270
expire_at: datetime
271
expired: bool
272
live_revision: Revision
273
274
def publish(self, revision=None, user=None):
275
"""Publish this content, making it live."""
276
277
def unpublish(self, set_expired=False, commit=True, user=None):
278
"""Remove this content from live site."""
279
280
def get_latest_revision_as_object(self):
281
"""Get latest revision content as object (overrides RevisionMixin)."""
282
283
def get_scheduled_revision_as_object(self):
284
"""Get scheduled revision if one exists."""
285
286
@property
287
def status_string(self):
288
"""Get human-readable status for admin display."""
289
290
class LockableMixin:
291
"""
292
Adds content locking functionality to prevent concurrent editing.
293
"""
294
locked: bool
295
locked_by: User
296
locked_at: datetime
297
298
def get_lock(self):
299
"""Get lock object if this content is locked."""
300
301
def with_content_json(self, content):
302
"""Return a copy with the given content restored."""
303
304
class WorkflowMixin:
305
"""
306
Adds workflow support for approval processes.
307
"""
308
@property
309
def current_workflow_task_state(self):
310
"""Get current workflow task state."""
311
312
@property
313
def current_workflow_task(self):
314
"""Get current workflow task."""
315
316
@property
317
def workflow_states(self):
318
"""Get all workflow states for this content."""
319
320
@classmethod
321
def get_default_workflow(cls):
322
"""Get default workflow for this content type."""
323
324
def get_workflow(self):
325
"""Get the workflow assigned to this content."""
326
327
class PreviewableMixin:
328
"""
329
Adds preview functionality for draft content.
330
"""
331
@property
332
def preview_modes(self):
333
"""Get available preview modes."""
334
335
@property
336
def preview_sizes(self):
337
"""Get available preview sizes."""
338
339
def serve_preview(self, request, mode_name):
340
"""Serve a preview of this content."""
341
342
def make_preview_request(self, request=None, mode_name=''):
343
"""Create a fake request for previewing content."""
344
345
def get_preview_context(self, request, mode_name):
346
"""Get context for preview rendering."""
347
348
def get_preview_template(self, request, mode_name):
349
"""Get template for preview rendering."""
350
351
def is_previewable(self):
352
"""Check if this content can be previewed."""
353
354
class TranslatableMixin:
355
"""
356
Adds translation support for multi-language content.
357
"""
358
locale: Locale
359
translation_key: UUID
360
361
def get_translations(self, inclusive=False):
362
"""Get all translations of this content."""
363
364
def copy_for_translation(self, locale):
365
"""Create a copy for translation to another locale."""
366
```
367
368
### Content Management Models
369
370
Models for managing content lifecycle, organization, and access control.
371
372
```python { .api }
373
class Revision:
374
"""
375
Represents a saved version of page or model content.
376
"""
377
content_object: Model
378
base_content_type: ContentType
379
object_str: str
380
created_at: datetime
381
user: User
382
approved_go_live_at: datetime
383
384
def as_object(self):
385
"""Return the content of this revision as a model instance."""
386
387
def publish(self):
388
"""Publish this revision, making it live."""
389
390
def is_latest_revision(self):
391
"""Check if this is the latest revision."""
392
393
def get_previous(self):
394
"""Get the previous revision."""
395
396
def get_next(self):
397
"""Get the next revision."""
398
399
class Collection:
400
"""
401
Organizes media and content with hierarchical permissions.
402
"""
403
name: str
404
path: str
405
406
def get_descendants(self, inclusive=False):
407
"""Get all descendant collections."""
408
409
def get_ancestors(self, inclusive=False):
410
"""Get all ancestor collections."""
411
412
def get_view_restrictions(self):
413
"""Get view restrictions for this collection."""
414
415
class Site:
416
"""
417
Represents a website with its own domain and root page.
418
"""
419
hostname: str
420
port: int
421
site_name: str
422
root_page: Page
423
root_url: str
424
425
@classmethod
426
def find_for_request(cls, request):
427
"""Find the Site object for the given HTTP request."""
428
429
@classmethod
430
def get_site_root_paths(cls):
431
"""Get URL paths for all site root pages."""
432
433
class Locale:
434
"""
435
Represents a language/region combination for internationalization.
436
"""
437
language_code: str
438
region_code: str
439
440
@classmethod
441
def get_default(cls):
442
"""Get the default locale for the site."""
443
444
@classmethod
445
def get_active(cls):
446
"""Get all active locales."""
447
```
448
449
### Permission Models
450
451
Models for managing user permissions and access control.
452
453
```python { .api }
454
class GroupPagePermission:
455
"""
456
Assigns page permissions to user groups.
457
"""
458
group: Group
459
page: Page
460
permission: Permission
461
462
objects: GroupPagePermissionManager
463
464
class PageViewRestriction:
465
"""
466
Restricts page viewing to specific users or groups.
467
"""
468
page: Page
469
restriction_type: str # 'password', 'groups', 'login'
470
password: str
471
groups: ManyToManyField[Group]
472
473
def save(self, **kwargs):
474
"""Save with audit logging."""
475
476
def delete(self, **kwargs):
477
"""Delete with audit logging."""
478
479
class PagePermissionTester:
480
"""
481
Utility class for testing user permissions on pages.
482
"""
483
def __init__(self, user, page):
484
"""Initialize with user and page to test."""
485
486
def can_edit(self):
487
"""Check if user can edit the page."""
488
489
def can_delete(self):
490
"""Check if user can delete the page."""
491
492
def can_publish(self):
493
"""Check if user can publish the page."""
494
495
def can_unpublish(self):
496
"""Check if user can unpublish the page."""
497
```
498
499
## Usage Examples
500
501
### Creating Custom Page Models
502
503
```python
504
from wagtail.models import Page
505
from wagtail.fields import RichTextField
506
from wagtail.admin.panels import FieldPanel
507
from django.db import models
508
509
class BlogPage(Page):
510
"""Custom page model for blog posts."""
511
date = models.DateField("Post date")
512
intro = models.CharField(max_length=250)
513
body = RichTextField(blank=True)
514
515
content_panels = Page.content_panels + [
516
FieldPanel('date'),
517
FieldPanel('intro'),
518
FieldPanel('body'),
519
]
520
521
def get_context(self, request):
522
context = super().get_context(request)
523
context['recent_posts'] = BlogPage.objects.live().order_by('-date')[:5]
524
return context
525
```
526
527
### Working with Page Hierarchy
528
529
```python
530
# Get all pages under a section
531
section_page = Page.objects.get(slug='news')
532
news_articles = section_page.get_children().live().order_by('-first_published_at')
533
534
# Move a page to a new parent
535
page_to_move = Page.objects.get(slug='old-location')
536
new_parent = Page.objects.get(slug='new-section')
537
page_to_move.move(new_parent, pos='last-child')
538
539
# Copy a page with all its content
540
original_page = Page.objects.get(slug='template-page')
541
copied_page = original_page.copy(
542
recursive=True, # Copy child pages too
543
update_attrs={'title': 'New Page Title', 'slug': 'new-page-slug'}
544
)
545
```
546
547
### Using Mixins for Custom Models
548
549
```python
550
from wagtail.models import RevisionMixin, DraftStateMixin
551
from django.db import models
552
553
class Article(RevisionMixin, DraftStateMixin, models.Model):
554
"""Custom model with revision and publishing capabilities."""
555
title = models.CharField(max_length=255)
556
content = models.TextField()
557
558
def save(self, *args, **kwargs):
559
super().save(*args, **kwargs)
560
# Save revision after saving the model
561
self.save_revision()
562
563
def go_live(self):
564
"""Publish this article."""
565
self.live = True
566
self.save()
567
self.publish()
568
```