or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

admin-interface.mdapi.mdcontent-fields.mdcontrib.mdindex.mdmedia.mdpage-models.mdsearch.mdsystem-integration.mdtemplates.mdworkflows.md

api.mddocs/

0

# API Framework

1

2

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.

3

4

## Capabilities

5

6

### Core API Components

7

8

Base classes and viewsets for creating REST API endpoints.

9

10

```python { .api }

11

class BaseAPIViewSet(GenericViewSet):

12

"""

13

Base viewset for Wagtail API endpoints with pagination, filtering, and authentication.

14

15

Provides common functionality for all API endpoints including:

16

- Pagination support

17

- Field filtering and expansion

18

- Search capabilities

19

- Content negotiation

20

"""

21

base_serializer_class: BaseSerializer

22

filter_backends: list

23

known_query_parameters: set

24

25

def get_queryset(self):

26

"""Get the base queryset for this endpoint."""

27

28

def get_serializer_class(self):

29

"""Get the serializer class for the current request."""

30

31

def filter_queryset(self, queryset):

32

"""Apply filters to the queryset."""

33

34

def paginate_queryset(self, queryset):

35

"""Apply pagination to the queryset."""

36

37

class PagesAPIViewSet(BaseAPIViewSet):

38

"""

39

REST API endpoint for pages with hierarchical navigation and content access.

40

41

Supports:

42

- Page listing with filtering

43

- Individual page retrieval

44

- Hierarchical navigation (children, ancestors, descendants)

45

- Content serialization with StreamField support

46

"""

47

base_serializer_class: PageSerializer

48

model: Page

49

50

def get_queryset(self):

51

"""Get live pages accessible to the current user."""

52

53

def find_view(self, request):

54

"""Find pages by slug or path."""

55

56

def listing_view(self, request):

57

"""List pages with filtering and pagination."""

58

59

def detail_view(self, request, pk):

60

"""Get individual page details."""

61

62

class ImagesAPIViewSet(BaseAPIViewSet):

63

"""

64

REST API endpoint for images with rendition generation and metadata access.

65

66

Supports:

67

- Image listing and search

68

- Image metadata access

69

- Rendition URLs for different sizes

70

- Collection-based filtering

71

"""

72

base_serializer_class: ImageSerializer

73

model: Image

74

75

def get_queryset(self):

76

"""Get images accessible to the current user."""

77

78

class DocumentsAPIViewSet(BaseAPIViewSet):

79

"""

80

REST API endpoint for documents with download URLs and metadata.

81

82

Supports:

83

- Document listing and search

84

- Document metadata access

85

- Download URLs

86

- Collection-based filtering

87

"""

88

base_serializer_class: DocumentSerializer

89

model: Document

90

91

def get_queryset(self):

92

"""Get documents accessible to the current user."""

93

```

94

95

### Serializers

96

97

Serializer classes for converting models to JSON representations.

98

99

```python { .api }

100

class BaseSerializer(serializers.ModelSerializer):

101

"""

102

Base serializer for API responses with field selection and expansion.

103

104

Features:

105

- Dynamic field selection via 'fields' parameter

106

- Field expansion for related objects

107

- Consistent error handling

108

"""

109

def __init__(self, *args, **kwargs):

110

"""Initialize serializer with dynamic field configuration."""

111

112

def to_representation(self, instance):

113

"""Convert model instance to dictionary representation."""

114

115

class PageSerializer(BaseSerializer):

116

"""

117

Serializer for Page models with content and metadata serialization.

118

119

Handles:

120

- Page hierarchy information

121

- StreamField content serialization

122

- Rich text field processing

123

- Image and document references

124

"""

125

id: int

126

meta: dict

127

title: str

128

html_url: str

129

slug: str

130

url_path: str

131

seo_title: str

132

search_description: str

133

show_in_menus: bool

134

first_published_at: datetime

135

last_published_at: datetime

136

137

def get_meta(self, instance):

138

"""Get metadata about the page type and properties."""

139

140

def get_html_url(self, instance):

141

"""Get the full HTML URL for this page."""

142

143

class ImageSerializer(BaseSerializer):

144

"""

145

Serializer for Image models with rendition support.

146

147

Provides:

148

- Image metadata (dimensions, file size, etc.)

149

- Rendition generation for different sizes

150

- Collection and tag information

151

"""

152

id: int

153

meta: dict

154

title: str

155

original: dict

156

thumbnail: dict

157

width: int

158

height: int

159

created_at: datetime

160

161

def get_original(self, instance):

162

"""Get original image URL and metadata."""

163

164

def get_thumbnail(self, instance):

165

"""Get thumbnail rendition URL."""

166

167

class DocumentSerializer(BaseSerializer):

168

"""

169

Serializer for Document models with download information.

170

171

Provides:

172

- Document metadata (file size, type, etc.)

173

- Download URLs

174

- Collection information

175

"""

176

id: int

177

meta: dict

178

title: str

179

download_url: str

180

created_at: datetime

181

182

def get_download_url(self, instance):

183

"""Get secure download URL for this document."""

184

```

185

186

### API Configuration

187

188

Classes for configuring API field exposure and customization.

189

190

```python { .api }

191

class APIField:

192

"""

193

Configuration for exposing model fields through the API.

194

195

Controls how model fields are serialized and what data is included.

196

"""

197

def __init__(self, name, serializer=None):

198

"""

199

Initialize API field configuration.

200

201

Parameters:

202

name (str): Name of the field to expose

203

serializer (Serializer): Custom serializer for this field

204

"""

205

206

class WagtailAPIRouter:

207

"""

208

URL router for configuring API endpoints and routing.

209

210

Manages URL patterns and endpoint registration for the API.

211

"""

212

def __init__(self, name='wagtailapi'):

213

"""

214

Initialize API router.

215

216

Parameters:

217

name (str): Name for the API URL namespace

218

"""

219

220

def register_endpoint(self, prefix, viewset):

221

"""

222

Register an API endpoint.

223

224

Parameters:

225

prefix (str): URL prefix for the endpoint

226

viewset (ViewSet): ViewSet class handling the endpoint

227

"""

228

229

@property

230

def urls(self):

231

"""Get URL patterns for all registered endpoints."""

232

```

233

234

### API Filters

235

236

Filter classes for querying and searching API endpoints.

237

238

```python { .api }

239

class FieldsFilter:

240

"""

241

Filter for selecting specific fields in API responses.

242

243

Allows clients to request only the fields they need.

244

"""

245

def filter_queryset(self, request, queryset, view):

246

"""Apply field selection to the response."""

247

248

class ChildOfFilter:

249

"""

250

Filter for finding pages that are children of a specific page.

251

"""

252

def filter_queryset(self, request, queryset, view):

253

"""Filter to pages that are children of specified parent."""

254

255

class DescendantOfFilter:

256

"""

257

Filter for finding pages that are descendants of a specific page.

258

"""

259

def filter_queryset(self, request, queryset, view):

260

"""Filter to pages that are descendants of specified ancestor."""

261

262

class OrderingFilter:

263

"""

264

Filter for ordering API results by specified fields.

265

"""

266

def filter_queryset(self, request, queryset, view):

267

"""Apply ordering to the queryset."""

268

269

class SearchFilter:

270

"""

271

Filter for full-text search across API endpoints.

272

"""

273

def filter_queryset(self, request, queryset, view):

274

"""Apply search query to the queryset."""

275

```

276

277

### API Utilities

278

279

Utility functions and classes for API functionality.

280

281

```python { .api }

282

def get_base_url(request=None):

283

"""

284

Get the base URL for API endpoints.

285

286

Parameters:

287

request (HttpRequest): Current request object

288

289

Returns:

290

str: Base URL for the API

291

"""

292

293

def get_object_detail_url(context, model, pk, url_name=None):

294

"""

295

Get detail URL for an API object.

296

297

Parameters:

298

context (dict): Serializer context

299

model (Model): Model class

300

pk (int): Primary key of the object

301

url_name (str): URL name for the endpoint

302

303

Returns:

304

str: Detail URL for the object

305

"""

306

307

class BadRequestError(Exception):

308

"""Exception for API bad request errors."""

309

310

class NotFoundError(Exception):

311

"""Exception for API not found errors."""

312

```

313

314

## Usage Examples

315

316

### Setting Up API Endpoints

317

318

```python

319

# urls.py

320

from wagtail.api.v2.router import WagtailAPIRouter

321

from wagtail.api.v2.views import PagesAPIViewSet

322

from wagtail.images.api.v2.views import ImagesAPIViewSet

323

from wagtail.documents.api.v2.views import DocumentsAPIViewSet

324

325

# Create API router

326

api_router = WagtailAPIRouter('wagtailapi')

327

328

# Register default endpoints

329

api_router.register_endpoint('pages', PagesAPIViewSet)

330

api_router.register_endpoint('images', ImagesAPIViewSet)

331

api_router.register_endpoint('documents', DocumentsAPIViewSet)

332

333

# Add to URL patterns

334

urlpatterns = [

335

path('admin/', admin.site.urls),

336

path('api/v2/', api_router.urls),

337

path('', include(wagtail_urls)),

338

]

339

```

340

341

### Custom API Endpoints

342

343

```python

344

from wagtail.api.v2.views import BaseAPIViewSet

345

from wagtail.api.v2.serializers import BaseSerializer

346

from rest_framework.fields import CharField, DateTimeField

347

from myapp.models import BlogPage, Author

348

349

class AuthorSerializer(BaseSerializer):

350

"""Custom serializer for Author model."""

351

name = CharField(read_only=True)

352

bio = CharField(read_only=True)

353

posts_count = serializers.SerializerMethodField()

354

355

def get_posts_count(self, instance):

356

"""Get number of blog posts by this author."""

357

return BlogPage.objects.filter(author=instance).count()

358

359

class AuthorAPIViewSet(BaseAPIViewSet):

360

"""Custom API endpoint for authors."""

361

base_serializer_class = AuthorSerializer

362

filter_backends = [SearchFilter, OrderingFilter]

363

model = Author

364

known_query_parameters = BaseAPIViewSet.known_query_parameters.union([

365

'name', 'bio'

366

])

367

368

def get_queryset(self):

369

return Author.objects.all()

370

371

# Register custom endpoint

372

api_router.register_endpoint('authors', AuthorAPIViewSet)

373

```

374

375

### Configuring Page API Fields

376

377

```python

378

from wagtail.api.v2.serializers import BaseSerializer

379

from wagtail.api import APIField

380

from wagtail.models import Page

381

from wagtail.fields import StreamField

382

from rest_framework.fields import CharField

383

384

class BlogPage(Page):

385

"""Blog page with API field configuration."""

386

date = models.DateField("Post date")

387

intro = models.CharField(max_length=250)

388

body = StreamField([

389

('heading', CharBlock()),

390

('paragraph', RichTextBlock()),

391

('image', ImageChooserBlock()),

392

])

393

author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)

394

395

# Configure API field exposure

396

api_fields = [

397

APIField('date'),

398

APIField('intro'),

399

APIField('body'), # StreamField automatically serialized

400

APIField('author', serializer=AuthorSerializer()),

401

]

402

403

content_panels = Page.content_panels + [

404

FieldPanel('date'),

405

FieldPanel('intro'),

406

FieldPanel('body'),

407

FieldPanel('author'),

408

]

409

410

# Custom serializer for complex fields

411

class BlogPageSerializer(BaseSerializer):

412

"""Custom serializer for blog pages."""

413

reading_time = serializers.SerializerMethodField()

414

related_posts = serializers.SerializerMethodField()

415

416

def get_reading_time(self, instance):

417

"""Calculate estimated reading time."""

418

word_count = len(str(instance.body).split())

419

return max(1, word_count // 200) # Assume 200 words per minute

420

421

def get_related_posts(self, instance):

422

"""Get related blog posts."""

423

related = BlogPage.objects.live().exclude(pk=instance.pk)[:3]

424

return [{'title': post.title, 'url': post.url} for post in related]

425

```

426

427

### Making API Requests

428

429

```python

430

import requests

431

432

# Base API URL

433

base_url = 'https://example.com/api/v2/'

434

435

# Get all pages

436

response = requests.get(f'{base_url}pages/')

437

pages = response.json()

438

439

# Get specific page

440

page_id = 123

441

response = requests.get(f'{base_url}pages/{page_id}/')

442

page = response.json()

443

444

# Search pages

445

response = requests.get(f'{base_url}pages/', params={

446

'search': 'django tutorial',

447

'type': 'blog.BlogPage',

448

'fields': 'title,slug,date,author'

449

})

450

search_results = response.json()

451

452

# Get page children

453

parent_id = 10

454

response = requests.get(f'{base_url}pages/', params={

455

'child_of': parent_id,

456

'limit': 20

457

})

458

children = response.json()

459

460

# Get images with renditions

461

response = requests.get(f'{base_url}images/')

462

images = response.json()

463

464

# Get specific image

465

image_id = 456

466

response = requests.get(f'{base_url}images/{image_id}/')

467

image = response.json()

468

print(f"Original: {image['meta']['download_url']}")

469

print(f"Thumbnail: {image['meta']['thumbnail']['url']}")

470

471

# Filter by collection

472

collection_id = 5

473

response = requests.get(f'{base_url}images/', params={

474

'collection_id': collection_id

475

})

476

collection_images = response.json()

477

```

478

479

### JavaScript Frontend Integration

480

481

```javascript

482

// API client class

483

class WagtailAPI {

484

constructor(baseURL) {

485

this.baseURL = baseURL;

486

}

487

488

async getPages(params = {}) {

489

const url = new URL(`${this.baseURL}pages/`);

490

Object.keys(params).forEach(key => {

491

url.searchParams.append(key, params[key]);

492

});

493

494

const response = await fetch(url);

495

return response.json();

496

}

497

498

async getPage(id, fields = []) {

499

const url = new URL(`${this.baseURL}pages/${id}/`);

500

if (fields.length > 0) {

501

url.searchParams.append('fields', fields.join(','));

502

}

503

504

const response = await fetch(url);

505

return response.json();

506

}

507

508

async searchPages(query, type = null) {

509

return this.getPages({

510

search: query,

511

...(type && { type: type })

512

});

513

}

514

515

async getImages(params = {}) {

516

const url = new URL(`${this.baseURL}images/`);

517

Object.keys(params).forEach(key => {

518

url.searchParams.append(key, params[key]);

519

});

520

521

const response = await fetch(url);

522

return response.json();

523

}

524

}

525

526

// Usage example

527

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

528

529

// Load blog posts

530

async function loadBlogPosts() {

531

try {

532

const data = await api.getPages({

533

type: 'blog.BlogPage',

534

fields: 'title,slug,date,intro,author',

535

limit: 10,

536

order: '-date'

537

});

538

539

const posts = data.items.map(post => ({

540

title: post.title,

541

slug: post.slug,

542

date: post.date,

543

intro: post.intro,

544

author: post.author?.name || 'Unknown',

545

url: post.meta.html_url

546

}));

547

548

renderBlogPosts(posts);

549

} catch (error) {

550

console.error('Failed to load blog posts:', error);

551

}

552

}

553

554

// Search functionality

555

async function searchContent(query) {

556

try {

557

const [pages, images] = await Promise.all([

558

api.searchPages(query),

559

api.getImages({ search: query })

560

]);

561

562

return {

563

pages: pages.items,

564

images: images.items

565

};

566

} catch (error) {

567

console.error('Search failed:', error);

568

}

569

}

570

```

571

572

### Advanced API Customization

573

574

```python

575

from wagtail.api.v2.views import BaseAPIViewSet

576

from wagtail.api.v2.filters import BaseFilterSet

577

from django_filters import rest_framework as filters

578

579

class BlogPageFilter(BaseFilterSet):

580

"""Custom filter for blog pages."""

581

date_from = filters.DateFilter(field_name='date', lookup_expr='gte')

582

date_to = filters.DateFilter(field_name='date', lookup_expr='lte')

583

author_name = filters.CharFilter(field_name='author__name', lookup_expr='icontains')

584

585

class Meta:

586

model = BlogPage

587

fields = ['date_from', 'date_to', 'author_name']

588

589

class BlogPageAPIViewSet(PagesAPIViewSet):

590

"""Enhanced blog page API with custom filtering."""

591

filter_backends = PagesAPIViewSet.filter_backends + [filters.DjangoFilterBackend]

592

filterset_class = BlogPageFilter

593

594

known_query_parameters = PagesAPIViewSet.known_query_parameters.union([

595

'date_from', 'date_to', 'author_name'

596

])

597

598

def get_queryset(self):

599

return BlogPage.objects.live().select_related('author')

600

601

# Custom endpoint with authentication

602

from rest_framework.authentication import TokenAuthentication

603

from rest_framework.permissions import IsAuthenticated

604

605

class PrivateContentAPIViewSet(BaseAPIViewSet):

606

"""API endpoint requiring authentication."""

607

authentication_classes = [TokenAuthentication]

608

permission_classes = [IsAuthenticated]

609

610

def get_queryset(self):

611

# Return content based on user permissions

612

user = self.request.user

613

return Page.objects.live().filter(

614

owner=user

615

)

616

```

617

618

### API Response Caching

619

620

```python

621

from django.views.decorators.cache import cache_page

622

from django.utils.decorators import method_decorator

623

624

@method_decorator(cache_page(60 * 15), name='listing_view') # 15 minutes

625

@method_decorator(cache_page(60 * 60), name='detail_view') # 1 hour

626

class CachedPagesAPIViewSet(PagesAPIViewSet):

627

"""API viewset with response caching."""

628

629

def get_queryset(self):

630

# Add cache invalidation logic

631

return super().get_queryset().select_related('locale')

632

633

# Custom cache invalidation

634

from django.core.cache import cache

635

from wagtail.signals import page_published, page_unpublished

636

637

def invalidate_api_cache(sender, **kwargs):

638

"""Invalidate API cache when pages are published/unpublished."""

639

cache.delete_pattern('views.decorators.cache.cache_page.*')

640

641

page_published.connect(invalidate_api_cache)

642

page_unpublished.connect(invalidate_api_cache)

643

```