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

templates.mddocs/

0

# Template System

1

2

Template tags for page URLs, content rendering, and template utilities for building Wagtail-powered front-ends. Wagtail provides comprehensive template integration with Django's template system.

3

4

## Capabilities

5

6

### Core Template Tags

7

8

Essential template tags for page navigation, URL generation, and content rendering.

9

10

```python { .api }

11

def pageurl(page, request=None):

12

"""

13

Get the URL for a page, with optional request context for relative URLs.

14

15

Usage in templates: {% pageurl page %}

16

17

Parameters:

18

page (Page): Page object to get URL for

19

request (HttpRequest): Optional request for relative URL generation

20

21

Returns:

22

str: URL path for the page (relative by default)

23

"""

24

25

def fullpageurl(page, request=None):

26

"""

27

Get the absolute URL for a page including domain.

28

29

Usage in templates: {% fullpageurl page %}

30

31

Parameters:

32

page (Page): Page object to get URL for

33

request (HttpRequest): Optional request for domain detection

34

35

Returns:

36

str: Absolute URL including protocol and domain

37

"""

38

39

def slugurl(slug, request=None):

40

"""

41

Get page URL by slug lookup within the current site.

42

43

Usage in templates: {% slugurl 'my-page-slug' %}

44

45

Parameters:

46

slug (str): Page slug to find and link to

47

request (HttpRequest): Optional request for site context

48

49

Returns:

50

str: URL for the page with matching slug, or empty string if not found

51

"""

52

53

def wagtail_site(request=None):

54

"""

55

Get the current Site object for use in templates.

56

57

Usage in templates: {% wagtail_site as current_site %}

58

59

Parameters:

60

request (HttpRequest): Current request for site detection

61

62

Returns:

63

Site: Current Site object with hostname, root_page, etc.

64

"""

65

66

def include_block(block_value, context=None):

67

"""

68

Render a StreamField block with its template.

69

70

Usage in templates: {% include_block page.body %}

71

72

Parameters:

73

block_value (StreamValue): StreamField content to render

74

context (dict): Additional template context

75

76

Returns:

77

str: Rendered HTML for all blocks in the StreamField

78

"""

79

80

def wagtail_version():

81

"""

82

Get the current Wagtail version string.

83

84

Usage in templates: {% wagtail_version %}

85

86

Returns:

87

str: Wagtail version (e.g., "4.1.0")

88

"""

89

```

90

91

### Rich Text Filters

92

93

Template filters for processing and rendering rich text content safely.

94

95

```python { .api }

96

def richtext(value):

97

"""

98

Process rich text content for safe HTML rendering.

99

100

Usage in templates: {{ page.body|richtext }}

101

102

Processes rich text to:

103

- Convert internal page links to proper URLs

104

- Process document download links

105

- Handle embedded content

106

- Apply security filtering

107

108

Parameters:

109

value (str): Raw rich text content from RichTextField

110

111

Returns:

112

SafeString: Processed HTML safe for rendering

113

"""

114

115

def linebreaks_richtext(value):

116

"""

117

Convert line breaks in rich text to proper HTML paragraphs.

118

119

Usage in templates: {{ content|linebreaks_richtext }}

120

121

Parameters:

122

value (str): Rich text content with line breaks

123

124

Returns:

125

SafeString: Content with proper paragraph tags

126

"""

127

```

128

129

### Image Template Tags

130

131

Template tags for rendering images with automatic rendition generation.

132

133

```python { .api }

134

def image(image, filter_spec, attrs=None):

135

"""

136

Generate an image rendition and return HTML img tag or rendition object.

137

138

Usage in templates:

139

{% image page.photo fill-400x300 %}

140

{% image page.photo width-800 as hero %}

141

142

Parameters:

143

image (Image): Image object to render

144

filter_spec (str): Image operations to apply (e.g., 'fill-400x300')

145

attrs (dict): Additional HTML attributes for img tag

146

147

Returns:

148

str or Rendition: HTML img tag or rendition object (if using 'as' syntax)

149

"""

150

151

def responsive_image(image, sizes=None, attrs=None):

152

"""

153

Generate responsive image with multiple renditions and srcset.

154

155

Usage in templates: {% responsive_image page.hero_image %}

156

157

Parameters:

158

image (Image): Image object to make responsive

159

sizes (list): List of size specifications for different breakpoints

160

attrs (dict): Additional HTML attributes

161

162

Returns:

163

str: HTML picture element with responsive images

164

"""

165

```

166

167

### Page Navigation Tags

168

169

Template tags for building navigation menus and page hierarchies.

170

171

```python { .api }

172

def get_site_root(request=None):

173

"""

174

Get the root page for the current site.

175

176

Usage in templates: {% get_site_root as site_root %}

177

178

Parameters:

179

request (HttpRequest): Current request for site detection

180

181

Returns:

182

Page: Root page of the current site

183

"""

184

185

def top_menu(parent, calling_page=None):

186

"""

187

Get top-level menu items from a parent page.

188

189

Usage in templates: {% top_menu parent calling_page %}

190

191

Parameters:

192

parent (Page): Parent page to get children from

193

calling_page (Page): Current page for active state detection

194

195

Returns:

196

QuerySet: Child pages suitable for top-level navigation

197

"""

198

199

def breadcrumbs(calling_page):

200

"""

201

Generate breadcrumb navigation for a page.

202

203

Usage in templates: {% breadcrumbs self %}

204

205

Parameters:

206

calling_page (Page): Current page to generate breadcrumbs for

207

208

Returns:

209

list: List of ancestor pages from root to current page

210

"""

211

```

212

213

### Search Template Tags

214

215

Template tags for search functionality and query handling.

216

217

```python { .api }

218

def search(query_string, model_or_queryset=None, fields=None):

219

"""

220

Perform search from templates.

221

222

Usage in templates: {% search "query" as search_results %}

223

224

Parameters:

225

query_string (str): Search query

226

model_or_queryset: Model or QuerySet to search

227

fields (list): Fields to search in

228

229

Returns:

230

SearchResults: Search results with pagination support

231

"""

232

233

def paginate(page_list, per_page, page_number):

234

"""

235

Paginate a list of items for template display.

236

237

Usage in templates: {% paginate items 10 request.GET.page %}

238

239

Parameters:

240

page_list (list): Items to paginate

241

per_page (int): Number of items per page

242

page_number (int): Current page number

243

244

Returns:

245

Page: Paginated page object with items and navigation

246

"""

247

```

248

249

### Utility Template Tags

250

251

General utility template tags for common Wagtail operations.

252

253

```python { .api }

254

def get_page_by_slug(slug, parent=None):

255

"""

256

Get a page by its slug within an optional parent.

257

258

Usage in templates: {% get_page_by_slug 'about' as about_page %}

259

260

Parameters:

261

slug (str): Page slug to find

262

parent (Page): Optional parent page to search within

263

264

Returns:

265

Page: Found page or None if not found

266

"""

267

268

def get_pages_by_type(page_type, parent=None):

269

"""

270

Get pages of a specific type.

271

272

Usage in templates: {% get_pages_by_type 'blog.BlogPage' as blog_pages %}

273

274

Parameters:

275

page_type (str): Page type in 'app.ModelName' format

276

parent (Page): Optional parent to search within

277

278

Returns:

279

QuerySet: Pages of the specified type

280

"""

281

282

def get_site_setting(setting_name, site=None):

283

"""

284

Get a site-specific setting value.

285

286

Usage in templates: {% get_site_setting 'contact_email' as contact %}

287

288

Parameters:

289

setting_name (str): Name of the setting to retrieve

290

site (Site): Optional site (defaults to current site)

291

292

Returns:

293

Any: Setting value or None if not found

294

"""

295

```

296

297

## Usage Examples

298

299

### Basic Page Templates

300

301

```html

302

<!-- Base template: base.html -->

303

<!DOCTYPE html>

304

<html lang="en">

305

<head>

306

<meta charset="UTF-8">

307

<meta name="viewport" content="width=device-width, initial-scale=1.0">

308

<title>{% block title %}{{ page.seo_title|default:page.title }} - {{ site.site_name }}{% endblock %}</title>

309

<meta name="description" content="{{ page.search_description }}">

310

{% load wagtailcore_tags %}

311

</head>

312

<body class="{% block body_class %}{% endblock %}">

313

<header>

314

{% get_site_root as site_root %}

315

<nav>

316

<a href="{% pageurl site_root %}">{{ site.site_name }}</a>

317

{% top_menu site_root self as menu_items %}

318

<ul>

319

{% for item in menu_items %}

320

<li><a href="{% pageurl item %}" {% if item == self %}class="active"{% endif %}>{{ item.title }}</a></li>

321

{% endfor %}

322

</ul>

323

</nav>

324

</header>

325

326

<main>

327

{% block breadcrumbs %}

328

{% breadcrumbs self as breadcrumb_items %}

329

<ol class="breadcrumbs">

330

{% for item in breadcrumb_items %}

331

<li><a href="{% pageurl item %}">{{ item.title }}</a></li>

332

{% endfor %}

333

</ol>

334

{% endblock %}

335

336

{% block content %}{% endblock %}

337

</main>

338

339

<footer>

340

<p>&copy; 2023 {{ site.site_name }}. Powered by Wagtail {% wagtail_version %}.</p>

341

</footer>

342

</body>

343

</html>

344

345

<!-- Page template: blog/blog_page.html -->

346

{% extends "base.html" %}

347

{% load wagtailcore_tags wagtailimages_tags %}

348

349

{% block title %}{{ page.title }} - Blog{% endblock %}

350

351

{% block content %}

352

<article>

353

<header>

354

<h1>{{ page.title }}</h1>

355

<p class="meta">

356

Published {{ page.date|date:"F j, Y" }}

357

{% if page.author %}by {{ page.author.name }}{% endif %}

358

</p>

359

{% if page.featured_image %}

360

{% image page.featured_image fill-800x400 as hero %}

361

<img src="{{ hero.url }}" alt="{{ hero.alt }}" class="hero-image">

362

{% endif %}

363

</header>

364

365

<div class="content">

366

<p class="intro">{{ page.intro }}</p>

367

{{ page.body|richtext }}

368

</div>

369

370

<footer>

371

<p>

372

<a href="{% slugurl 'blog' %}">← Back to Blog</a>

373

</p>

374

</footer>

375

</article>

376

{% endblock %}

377

```

378

379

### StreamField Templates

380

381

```html

382

<!-- StreamField block templates -->

383

384

<!-- blocks/heading_block.html -->

385

<h{{ self.level|default:2 }} class="heading-block">{{ self.text }}</h{{ self.level|default:2 }}>

386

387

<!-- blocks/paragraph_block.html -->

388

<div class="paragraph-block">

389

{{ self|richtext }}

390

</div>

391

392

<!-- blocks/image_block.html -->

393

{% load wagtailimages_tags %}

394

<figure class="image-block">

395

{% image self.image fill-800x600 as img %}

396

<img src="{{ img.url }}" alt="{{ img.alt }}" width="{{ img.width }}" height="{{ img.height }}">

397

{% if self.caption %}

398

<figcaption>{{ self.caption|richtext }}</figcaption>

399

{% endif %}

400

</figure>

401

402

<!-- blocks/quote_block.html -->

403

<blockquote class="quote-block">

404

{{ self.quote|richtext }}

405

{% if self.attribution %}

406

<cite>{{ self.attribution }}</cite>

407

{% endif %}

408

</blockquote>

409

410

<!-- Main page template using StreamField -->

411

{% extends "base.html" %}

412

{% load wagtailcore_tags %}

413

414

{% block content %}

415

<div class="page-content">

416

<h1>{{ page.title }}</h1>

417

{% if page.intro %}

418

<div class="intro">{{ page.intro|richtext }}</div>

419

{% endif %}

420

421

<div class="stream-content">

422

{% include_block page.body %}

423

</div>

424

</div>

425

{% endblock %}

426

```

427

428

### Navigation and Menus

429

430

```html

431

<!-- Custom navigation template -->

432

{% load wagtailcore_tags %}

433

434

<!-- Main navigation -->

435

{% get_site_root as site_root %}

436

<nav class="main-nav">

437

<ul class="nav-list">

438

{% top_menu site_root self as main_items %}

439

{% for item in main_items %}

440

<li class="nav-item {% if item == self or item in self.get_ancestors %}active{% endif %}">

441

<a href="{% pageurl item %}" class="nav-link">{{ item.title }}</a>

442

443

<!-- Sub-navigation -->

444

{% if item.get_children.live %}

445

<ul class="sub-nav">

446

{% for child in item.get_children.live %}

447

<li class="sub-nav-item {% if child == self %}active{% endif %}">

448

<a href="{% pageurl child %}" class="sub-nav-link">{{ child.title }}</a>

449

</li>

450

{% endfor %}

451

</ul>

452

{% endif %}

453

</li>

454

{% endfor %}

455

</ul>

456

</nav>

457

458

<!-- Sidebar navigation -->

459

<aside class="sidebar">

460

<h3>In This Section</h3>

461

{% get_pages_by_type 'blog.BlogPage' as blog_pages %}

462

<ul class="sidebar-nav">

463

{% for page in blog_pages.live|slice:":5" %}

464

<li><a href="{% pageurl page %}">{{ page.title }}</a></li>

465

{% endfor %}

466

</ul>

467

</aside>

468

469

<!-- Footer navigation -->

470

<footer class="site-footer">

471

<div class="footer-nav">

472

{% get_page_by_slug 'contact' as contact_page %}

473

{% get_page_by_slug 'privacy' as privacy_page %}

474

{% get_page_by_slug 'terms' as terms_page %}

475

476

<ul class="footer-links">

477

{% if contact_page %}<li><a href="{% pageurl contact_page %}">{{ contact_page.title }}</a></li>{% endif %}

478

{% if privacy_page %}<li><a href="{% pageurl privacy_page %}">{{ privacy_page.title }}</a></li>{% endif %}

479

{% if terms_page %}<li><a href="{% pageurl terms_page %}">{{ terms_page.title }}</a></li>{% endif %}

480

</ul>

481

</div>

482

</footer>

483

```

484

485

### Image Handling

486

487

```html

488

{% load wagtailimages_tags %}

489

490

<!-- Basic image rendering -->

491

{% if page.hero_image %}

492

{% image page.hero_image fill-1200x600 as hero %}

493

<div class="hero" style="background-image: url({{ hero.url }});">

494

<h1>{{ page.title }}</h1>

495

</div>

496

{% endif %}

497

498

<!-- Responsive images -->

499

{% if page.featured_image %}

500

{% image page.featured_image fill-400x300 as mobile %}

501

{% image page.featured_image fill-800x600 as tablet %}

502

{% image page.featured_image fill-1200x800 as desktop %}

503

504

<picture class="responsive-image">

505

<source media="(min-width: 1024px)" srcset="{{ desktop.url }}">

506

<source media="(min-width: 768px)" srcset="{{ tablet.url }}">

507

<img src="{{ mobile.url }}" alt="{{ page.featured_image.title }}"

508

width="{{ mobile.width }}" height="{{ mobile.height }}">

509

</picture>

510

{% endif %}

511

512

<!-- Image gallery -->

513

<div class="image-gallery">

514

{% for gallery_image in page.gallery_images.all %}

515

{% image gallery_image.image fill-300x200 as thumb %}

516

{% image gallery_image.image width-1000 as full %}

517

518

<div class="gallery-item">

519

<a href="{{ full.url }}" data-lightbox="gallery">

520

<img src="{{ thumb.url }}" alt="{{ gallery_image.image.title }}">

521

</a>

522

{% if gallery_image.caption %}

523

<p class="caption">{{ gallery_image.caption }}</p>

524

{% endif %}

525

</div>

526

{% endfor %}

527

</div>

528

529

<!-- Optimized images with WebP -->

530

{% image page.hero_image fill-1200x600|format-webp as webp_hero %}

531

{% image page.hero_image fill-1200x600|format-jpeg as jpeg_hero %}

532

533

<picture>

534

<source srcset="{{ webp_hero.url }}" type="image/webp">

535

<img src="{{ jpeg_hero.url }}" alt="{{ page.hero_image.title }}" class="hero-image">

536

</picture>

537

```

538

539

### Search Templates

540

541

```html

542

<!-- Search form -->

543

<form action="{% url 'search' %}" method="get" class="search-form">

544

<input type="text" name="q" value="{{ query_string }}" placeholder="Search..." required>

545

<button type="submit">Search</button>

546

</form>

547

548

<!-- Search results template -->

549

{% load wagtailcore_tags %}

550

551

{% block content %}

552

<div class="search-results">

553

<h1>Search Results</h1>

554

555

{% if query_string %}

556

<p>You searched for: <strong>{{ query_string }}</strong></p>

557

558

{% if search_results %}

559

<p>{{ search_results.paginator.count }} result{{ search_results.paginator.count|pluralize }} found</p>

560

561

<div class="results-list">

562

{% for result in search_results %}

563

<article class="search-result">

564

<h3><a href="{% pageurl result %}">{{ result.title }}</a></h3>

565

<p class="meta">{{ result.content_type.model_class.get_verbose_name }}</p>

566

{% if result.search_description %}

567

<p>{{ result.search_description }}</p>

568

{% endif %}

569

</article>

570

{% endfor %}

571

</div>

572

573

<!-- Pagination -->

574

{% if search_results.has_other_pages %}

575

<nav class="pagination">

576

{% if search_results.has_previous %}

577

<a href="?q={{ query_string }}&page={{ search_results.previous_page_number }}">← Previous</a>

578

{% endif %}

579

580

<span class="current">

581

Page {{ search_results.number }} of {{ search_results.paginator.num_pages }}

582

</span>

583

584

{% if search_results.has_next %}

585

<a href="?q={{ query_string }}&page={{ search_results.next_page_number }}">Next →</a>

586

{% endif %}

587

</nav>

588

{% endif %}

589

590

{% else %}

591

<p>No results found.</p>

592

{% endif %}

593

594

{% endif %}

595

</div>

596

{% endblock %}

597

```

598

599

### Custom Template Tags

600

601

```python

602

# templatetags/blog_tags.py

603

from django import template

604

from django.utils import timezone

605

from blog.models import BlogPage

606

607

register = template.Library()

608

609

@register.inclusion_tag('blog/recent_posts.html')

610

def recent_blog_posts(count=5):

611

"""Get recent blog posts for sidebar."""

612

posts = BlogPage.objects.live().order_by('-date')[:count]

613

return {'posts': posts}

614

615

@register.simple_tag

616

def get_popular_posts(count=5):

617

"""Get most popular blog posts."""

618

return BlogPage.objects.live().order_by('-page_views')[:count]

619

620

@register.filter

621

def reading_time(content):

622

"""Calculate reading time for content."""

623

word_count = len(str(content).split())

624

minutes = max(1, word_count // 200) # 200 words per minute

625

return f"{minutes} min read"

626

627

@register.inclusion_tag('blog/tag_cloud.html')

628

def tag_cloud():

629

"""Generate tag cloud for blog."""

630

from django.db.models import Count

631

tags = BlogTag.objects.annotate(

632

post_count=Count('blogpage')

633

).filter(post_count__gt=0).order_by('-post_count')[:20]

634

return {'tags': tags}

635

636

# Usage in templates

637

{% load blog_tags %}

638

639

<!-- Recent posts sidebar -->

640

{% recent_blog_posts 3 %}

641

642

<!-- Popular posts -->

643

{% get_popular_posts 5 as popular %}

644

<ul>

645

{% for post in popular %}

646

<li><a href="{% pageurl post %}">{{ post.title }}</a></li>

647

{% endfor %}

648

</ul>

649

650

<!-- Reading time -->

651

<p class="meta">{{ page.body|reading_time }}</p>

652

653

<!-- Tag cloud -->

654

{% tag_cloud %}

655

```

656

657

### Form Templates

658

659

```html

660

<!-- Contact form template -->

661

{% extends "base.html" %}

662

{% load wagtailcore_tags %}

663

664

{% block content %}

665

<div class="contact-page">

666

<h1>{{ page.title }}</h1>

667

668

{% if page.intro %}

669

<div class="intro">

670

{{ page.intro|richtext }}

671

</div>

672

{% endif %}

673

674

<form action="{% pageurl page %}" method="post" class="contact-form">

675

{% csrf_token %}

676

677

{% for field in form %}

678

<div class="form-field {% if field.errors %}error{% endif %}">

679

{{ field.label_tag }}

680

{{ field }}

681

{% if field.help_text %}

682

<p class="help-text">{{ field.help_text }}</p>

683

{% endif %}

684

{% for error in field.errors %}

685

<p class="error-message">{{ error }}</p>

686

{% endfor %}

687

</div>

688

{% endfor %}

689

690

<button type="submit" class="submit-button">Send Message</button>

691

</form>

692

693

{% if page.thank_you_text %}

694

<div class="thank-you" style="display: none;">

695

{{ page.thank_you_text|richtext }}

696

</div>

697

{% endif %}

698

</div>

699

{% endblock %}

700

```