Accessing a model’s verbose names in Django templates

I love building web applications with Django and I love its ORM. Defining models is fairly simple and rendering forms and list is (more or less) straight forward.

However, if you want to access the properties of the model’s fields in templates, then you might find a dead end really fast. This is especially hard when you’re trying to access the verbose names of the model or its fields.

Example

The model

As mentioned before, defining models is quite simple. Here’s an example of a model:

from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _


class Host(models.Model):
    '''
    Example model of a host.
    '''
    hostname = models.CharField(
        max_length=64,
        verbose_name=_('hostname'),
    )

    os = models.CharField(
        max_length=32,
        verbose_name=_('operating system'),
    )

    class Meta:
        '''
        Meta class for the model.
        '''
        verbose_name        = _('Host')
        verbose_name_plural = _('Hosts')

As you can see, a Host has a hostname and an os field.

The list view

Now let’s say we want to display a list of hosts.

First of all we need our ListView:

from django.views.generic.list import ListView

class HostListView(ListView):
    '''
    View which displays a list of all hosts.
    '''
    model = Host

And of course our host_list.html template, which is something like:

{% extends 'project/layout/base.html' %}
{% load i18n %}

{% block content %}
<h1>{% trans 'Hosts' %}</h1>
<table>
    <thead>
        <tr>
            <th>{% trans 'hostame' %}</th>
            <th>{% trans 'operating system' %}</th>
        </tr>
    </thead>
    <tbody>
    {% for host in host_list %}
        <tr>
            <td>
                <a href="{{ host.get_absolute_url }}">
                    {{ host.hostname }}
                </a>
            </td>
            <td>
                {{ host.os }}
            </td>
        </tr>
    {% endfor %}
    </tbody>
</table>
{% endblock %}

The problem

This is how I’ve defined my ListViews in the past. Of course this works and there’s no problem with it, except for one little and probably important thing:

We’ve already defined verbose name of the model and its field in the model definition, for example the os field:

    os = models.CharField(
        max_length=32,
        verbose_name=_('operating system'),
    )

In our template we had to define and translate the exactly same strings again, for example:

<th>{% trans 'operating system'</th>

Sure this would work fine, but this isn’t very nice and doesn’t meet my DRY expectations. This problem occurs only when we’re using ListViews or DetailViews. As soon as we’re using Forms, we no longer have these problems, as forms already access the verbose names out of the box.

I had to change a lot of verbose names lately. Unfortunately I’ve defined all the table headers manually as described above, so eventually I had to search and replace all the occurences in the templates by hand. To be honest, I’m not a big fan of repetetive tasks and I was going nuts.

The solution

I came up with a solution, because I thought there must be a better way to deal with this kind of problem.

Adding the model to the context data

First of all I’ve added the model to the context data to all of my ListView instances. I didn’t want to repeat myself, so I created a new ListView class inherited from Django’s ListView:

from django.views.generic.list import ListView as DjangoListView

class ListView(DjangoListView):
    '''
    Enhanced ListView which also includes the ``model`` in the context data,
    so that the template has access to its model class.
    '''

    def get_context_data(self):
        '''
        Adds the model to the context data.
        '''
        context          = super(ListView, self).get_context_data()
        context['model'] = self.model
        return context

Then I’ve updated all my existing view modules by importing my ListView instead of the origin Django ListView:

#from django.views.generic.list import ListView
from myproject.lib.views import ListView

Of course you can also go for method decorators, but in this case you always have to define get_context_data() in your views just to decorate them.

Adding template tags

Now as I had access to the models in all my list templates, I was going to create some template tags:

from django import template

register = template.Library()


@register.simple_tag
def model_name(value):
    '''
    Django template filter which returns the verbose name of a model.
    '''
    if hasattr(value, 'model'):
        value = value.model

    return value._meta.verbose_name.title()


@register.simple_tag
def model_name_plural(value):
    '''
    Django template filter which returns the plural verbose name of a model.
    '''
    if hasattr(value, 'model'):
        value = value.model

    return value._meta.verbose_name_plural.title()


@register.simple_tag
def field_name(value, field):
    '''
    Django template filter which returns the verbose name of an object's,
    model's or related manager's field.
    '''
    if hasattr(value, 'model'):
        value = value.model

    return value._meta.get_field(field).verbose_name.title()

I saved them as model_name.py.

Accessing the verbose names in templates

Now I finally used the magic sauce I’ve put together:

{% extends 'project/layout/base.html' %}
{% load model_name %}

{% block content %}
<h1>{% model_name_plural model %}</h1>
<table>
    <thead>
        <tr>
            <th>{% field_name model 'hostname' %}</th>
            <th>{% field_name model 'os' %}</th>
        </tr>
    </thead>
    <tbody>
    {% for host in host_list %}
        <tr>
            <td>
                <a href="{{ host.get_absolute_url }}">
                    {{ host.hostname }}
                </a>
            </td>
            <td>
                {{ host.os }}
            </td>
        </tr>
    {% endfor %}
    </tbody>
</table>
{% endblock %}

As you can see with the load of model_name and the model itself, you can now get the model and field names by using:

{% model_name        model %}
{% model_name_plural model %}
{% field_name        model field_name %}

Conclusion

Adding the model to the ListView context is a clean way to access the model’s and fields verbose names in list views. By accessing the existing model and field properties, you can define the verbose names in one single place – the model definition.

You don’t necessary need the template tags, as you can now access the model’s attributes directly in the template itself. However, using template tags is more convenient and you can now add some additional logic to the tags (e.g. fallback to the name when no verbose name is defined).

 

6 Comments

  • Angel Reply

    Great Dominique! thaks per your approach!

    • Dominique Barton Reply

      You’re welcome, hope it helps 🙂

  • Bruno Oliveira Reply

    I loved your approach, too. Good job!

  • hans li Reply

    thank you very much, this is what i need

  • udanieli Reply

    Why passing the model class to the template and get it back in the templatetag?
    You could simpy do
    “`
    @register.simple_tag
    def get_verbose_field_name(instance, field_name):
    return instance._meta.get_field(field_name).verbose_name.title()
    “`
    and from the template
    “`
    {% get_verbose_field_name object ‘my_field’ %}
    “`

  • wuxiaworld Reply

    Which incorporates the build though information is packaged with the classes, articles, techniques and capacities working on that information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.