Templates and the template language

Django · Essentials · 14 min

Server-rendered HTML is Django's default output, and the template language keeps it safe and reusable.

render(request, "blog/post_list.html", {"posts": posts}) loads a template, fills it with the context dict and returns an HttpResponse.

With APP_DIRS: True (the default in new projects), Django looks in each installed app's templates/ folder. Put templates in blog/templates/blog/... and refer to them as "blog/..."; the extra folder namespaces them, because all apps share one search path.

The Django Template Language (DTL):

  • {{ post.title }}: variables; dots look up dict keys, attributes, methods without arguments and list indexes
  • {{ post.created|date:"Y-m-d" }}, |default:"none", |truncatewords:30, |length, |linebreaks: filters
  • {% for p in posts %}...{% empty %}No posts{% endfor %} and {% if %}...{% elif %}...{% endif %}
  • {% url 'blog:detail' p.pk %} for links, {% csrf_token %} inside POST forms
  • {% extends "base.html" %} and {% block content %} for layouts; {% include "partials/card.html" %} for snippets
  • Django 6.0 adds template partials: {% partialdef card %}...{% endpartialdef %} defines a named fragment, {% partial card %} reuses it, and render(request, "page.html#card", ...) renders just that fragment, handy for htmx.

Output is autoescaped; |safe and mark_safe turn it off, so use them only on trusted HTML.

The DTL deliberately can't call functions with arguments or run arbitrary Python: prepare data in the view. For more power, Django also supports Jinja as a template backend.

Example

<!-- blog/templates/blog/base.html -->
<!doctype html>
<title>{% block title %}My blog{% endblock %}</title>
<main>{% block content %}{% endblock %}</main>

<!-- blog/templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% block title %}Posts | {{ block.super }}{% endblock %}
{% block content %}
  <ul>
  {% for post in posts %}
    <li>
      <a href="{% url 'blog:detail' post.pk %}">{{ post.title }}</a>
      by {{ post.author.name|default:"anonymous" }}, {{ post.created|date:"M j, Y" }}
      {% if forloop.first %}<strong>newest</strong>{% endif %}
    </li>
  {% empty %}
    <li>No posts yet.</li>
  {% endfor %}
  </ul>
{% endblock %}

<!-- view: return render(request, "blog/post_list.html", {"posts": Post.objects.all()}) -->

Remember

  • render(request, template, context) returns an HttpResponse
  • Namespace templates as app/templates/app/name.html
  • Tags {% %}, variables {{ }}, filters |name:arg; output is autoescaped

Watch out for

  • Putting templates directly in app/templates/ so two apps' list.html collide.

Check yourself

  1. Why put blog templates in blog/templates/blog/ rather than blog/templates/?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.djangoproject.com/en/6.0/topics/templates/