Django: Keeping logic out of templates (and views)

Last updated 2 years ago by Open Folder


When I first started dabbling with Django and web-development, a good friend with a little more experience advised that I should keep logic away from my templates. “Templates should be dumb”.

I didn’t really understand what that meant until I started suffering the consequences of having logic in my .html files. After 3 years with Django, I now try to keep business-logic away not only from templates, but also from views.

In this post I’ll gradually go over from the least to the most recommended path and outline the advantages that each one offers.

Our app: a simple blog

Let’s start with extracting logic from the templates first. As is the case with most real-world apps, the project usually starts simple and plain in its specifications and requirements, and starts growing gradually.

Given this model:


from django.db import models from django.utils import timezone

class Post(models.Model): title = models.CharField(maxlength=90, blank=False) content = models.TextField(blank=False) slug = models.SlugField(maxlength=90) isdraft = models.BooleanField(default=True, null=False) ishighlighted = models.BooleanField(default=False) published_date = models.DateTimeField( likes = models.IntegerField(default=0)

class Meta:
    ordering = ('-published_date',)

def __str__(self):
    return self.title

def is_in_past(self):
    return self.published_date <


The worst: logic in templates

In our blog’s index.html, we want to display the latest 10 posts’ titles and their publication date. The title should also be a link to the post-detail view, where the post content is presented.

While we do want to see our drafts so we can preview how they look on the website, we certainly don’t want them visible to other visitors.


def all_posts(request): context = {} posts = Post.objects.all()[:10] context['posts'] = posts return render(request, 'index.html', context) ```

```hljs {# index.html #} {% for post in posts %} {% if request.user.is_superuser %}

{{ post.title }}

    {% if post.is_draft %}
      <span class="alert alert-info small">Draft</span>
    {% endif %}

    {% if not post.is_in_past %}
      <span class="alert alert-info small">Future Post</span>
    {% endif %}
  <span class="text-muted"> Date: {{ post.published_date }}</span>

{% elif not request.user.issuperuser and not post.isdraft %}

{{ post.title }}

Date: {{ post.published_date }}
{% endif %} {% endfor %} ```

In index.html, we’re checking if request.user is an admin, and if they are, we’re not filtering any posts. in the elif block that applies to all other visitors, we’re making sure the is_draft property is False before displaying the post:

hljs {% elif not request.user.is_superuser and not post.is_draft %}

We’re also adding some Bootstrap markup so an admin can see clearly if a certain post is a draft or one that is scheduled in the future. We don’t need this markup for regular visitors because they’re not supposed to see these posts in the first place.

Read full Article