Menguasai Django Template Language (DTL): sintaks variabel dan tag, template inheritance dengan base.html, custom filters & template tags, serta built-in context processors untuk membangun layout dan partials yang bisa dipakai ulang.

Di episode 4 kita mengembalikan respons, tetapi masih berupa template kosong. Sekarang kita mengisi lapisan Template — bagian "T" di pola MTV yang sempat kita bahas di episode 2. Template adalah satu-satunya tempat di mana presentasi boleh ditulis, dan Django punya bahasa templating sendiri: Django Template Language (DTL).
Mengapa topik ini penting? Karena template adalah apa yang dilihat pengguna. Template yang rapi dan terorganisir — lewat inheritance dan partials — membuat perubahan UI menjadi pekerjaan kecil, bukan pekerjaan besar. Dan karena DTL dirancang untuk memaksa presentasi terpisah dari logika, memahami batasannya akan menyelamatkan kalian dari menulis "logika bisnis di dalam HTML".
DTL hanya punya dua konstruksi utama:
{{ variabel }} — menampilkan nilai, termasuk atribut dan pemanggilan method aman.{% tag %} — menjalankan logika: loop, kondisi, include, block, url, dsb.<h1>{{ post.title }}</h1>
<p>{{ post.body|truncatewords:30 }}</p>
{% if post.published %}
<p>Dipublikasikan pada {{ post.published_date|date:"d M Y" }}</p>
{% else %}
<p>Belum dipublikasikan</p>
{% endif %}
{% for comment in post.comments.all %}
<li>{{ comment.author }}: {{ comment.body }}</li>
{% empty %}
<li>Belum ada komentar.</li>
{% endfor %}{{ post.body|truncatewords:30 }} memakai filter — pipa untuk transformasi nilai. {{ post.comments.all }} mengakses relasi ORM langsung dari template (hati-hati dengan N+1, kita bahas di episode 14). DTL sengaja tidak lengkap dibanding Python: tidak ada assignment variabel, tidak ada pemanggilan fungsi sebarang, dan ekspresi dibatasi — ini disengaja agar logika tidak bocor ke presentasi.
Pola terkuat DTL adalah inheritance: satu file base.html menjadi kerangka semua halaman, dan halaman lain hanya mengisi {% block %}.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>{% block title %}Devblog{% endblock %}</title>
</head>
<body>
<header>
{% include "partials/navbar.html" %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% include "partials/footer.html" %}
</footer>
</body>
</html>{% block title %} dan {% block content %} adalah lubang yang diisi halaman turunan:
{% extends "base.html" %}
{% block title %}Beranda - Devblog{% endblock %}
{% block content %}
<h1>Post Terbaru</h1>
{% for post in posts %}
<article>
<h2><a href="{% url 'blog:post_detail' post.pk %}">{{ post.title }}</a></h2>
<p>{{ post.body|truncatewords:50 }}</p>
</article>
{% empty %}
<p>Belum ada post.</p>
{% endfor %}
{% endblock %}{% extends "base.html" %} harus menjadi tag pertama di template turunan. {% include %} menyisipkan partial (navbar, footer) yang bisa dipakai ulang di banyak halaman. Struktur ini adalah asal frasa layout + partials: satu kerangka, banyak komponen.
Tip
Atur folder template per-app dengan pola blog/post_list.html, bukan post_list.html. Tanpa subfolder app, dua app dengan nama template sama akan saling menimpa dalam urutan yang tidak bisa diprediksi. Subfolder per-app menghilangkan konflik ini sekaligus menjaga konvensi Django.
Context processors adalah fungsi yang menambahkan variabel ke semua template otomatis — tanpa perlu memasukkannya satu per satu di setiap view. Django sudah menyediakan beberapa bawaan:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]django.contrib.auth.context_processors.auth menyuntikkan variabel {{ user }} dan {{ perms }} ke semua template — inilah mengapa {% if user.is_authenticated %} bekerja di mana pun. django.template.context_processors.request menyuntikkan objek request yang dibutuhkan banyak library. APP_DIRS: True membuat Django mencari templates/ di dalam setiap app.
Buat context processor kustom:
from .models import Category
def site_stats(request):
return {
"total_posts": Category.objects.count(),
}Lalu daftarkan di context_processors settings. Setiap variabel yang dikembalikan langsung tersedia di semua template — berguna untuk data global seperti menu navigasi atau info situs.
Warning
Context processor kustom dipanggil untuk setiap request, jadi jangan isi dengan query mahal seperti menghitung semua post tanpa indeks — setiap halaman akan membayarnya. Kalau datanya jarang berubah, cache hasilnya (episode 12) atau pindahkan ke endpoint khusus.
DTL punya ratusan filter bawaan, tapi kadang kalian butuh transformasi sendiri. Custom filter = fungsi Python satu argumen yang didaftarkan via decorator:
from django import template
register = template.Library()
@register.filter
def read_time(text):
words = len(text.split())
return max(1, round(words / 200))
@register.simple_tag
def badge(value, label="info"):
return f'<span class="badge badge-{label}">{value}</span>'File ini harus berada di folder blog/templatetags/ yang berisi __init__.py. Lalu di template:
{% load blog_extras %}
<p>Waktu baca: {{ post.body|read_time }} menit</p>
{% badge "Baru" "success" %}@register.filter membuat filter read_time. @register.simple_tag membuat tag badge yang bisa menerima argumen. Jangan lupa {% load blog_extras %} di atas template — tanpa itu Django tidak tahu keberadaan library ini.
Satu hal yang membedakan DTL dari banyak template engine: autoescaping aktif secara default. Nilai yang disisipkan {{ post.title }} otomatis di-escape HTML, sehingga <script> tetap tampil sebagai teks, bukan dieksekusi. Ini adalah garis pertahanan pertama terhadap XSS — kita bahas mendalam di episode 18.
Inti yang harus dibawa pulang:
{{ variabel }} untuk nilai, {% tag %} untuk logika.extends + block) dan include menghasilkan layout + partials yang dipakai ulang.APP_DIRS: True membuat template otomatis ditemukan dari folder templates/ tiap app.{{ user }} — tetapi jangan isi query mahal.{% load %} wajib sebelum memakai.Di episode 6 selanjutnya kita masuk ke fondasi data: Models & ORM — field types, relasi ForeignKey/ManyToMany/OneToOne, Meta dan constraints, serta QuerySet dengan filtering, chaining, aggregation, dan select_related/prefetch_related. Ini episode yang paling sering menentukan performa aplikasi Django kalian!