Alex Heist - Software Developer

Write Better Code in Django with Model Methods

Programming is a fundamentally logical and analytical process. However, there is a niche beauty in writing good code. There is a thriving subculture within software development that is dedicated to creating standards to promote writing beautiful code. You can see this manifesting itself in applications such as prettier.io, whose sole mission is to make code more visually beautiful. Additionally, many languages and frameworks come with their own style guides, including Django.

However, the beauty of code doesn't just refer to the visual appearance, but to the content as well. This aspect of beautiful code is typically represented by "best practices". I have found that programming best practices have not been as easy to find as style guides. Though, in my experience, they are much more valuable.

In this article, I will be taking a look at one of Django's not-so-obvious best practices: model methods. This information will be most useful for beginner programmers, but is not a common topic in how-to tutorials. It took me an embarrassingly long time to learn about and understand this technique, but once I did, it fundamentally changed the way that I wrote applications with Django.

So, let's dive in.


If you're unfamiliar with the term methods, they are simply functions that belong to a class. They are callable from an object (instance) of that class. For example, say we have a class, Dog:

class Dog:
    breed = 'labrador'
    age = 8

    def bark():
        print('woof')

We can then create (instantiate) an object of this class, named buddy, which will possess all of the attributes of the Dog class. These attributes can be called using dot notation:

>>> buddy = Dog()
>>> buddy.breed
'labrador'
>>> buddy.age
8
>>> buddy.bark()
'woof'

Now, take a look at the same class as a Django model:

class Dog(models.Model):
    breed = models.CharField(max_length=32)
    age = models.IntegerField()

    def bark():
        print('bark')

There is not much difference between the two. The model, of course, inherits extra functionality from models.Model (which allow the class to represent tables in a database), but there is really no difference in the way that they behave. So, let's create a new instance of our Dog model:

>>> duke = Dog.objects.create(breed="golden retriever", age=2)
>>> duke.breed
'golden retriever'
>>> duke.age
2
>>> duke.bark()
'woof'

Now, this opens some doors for adding more complex functionality and really helps to clean up my code. I used to write all of my functionality in my views or templates, which made my code very redundant, messy, and difficult to work with. Model methods are incredibly versatile and will save you a ton of time as well as reduce the amount of repetitive code you have to write.

Here are a couple of real use cases for this that I have implemented:

  1. Displaying an image if it exists, or a placeholder image if it doesn't

    Before I started to use model methods, I would typically put this functionality in the template: django <img src=" {% if obj.image %} {{ obj.image }} {% else %} {% static 'img/placeholder.jpg' %} {% endif %}" />

    However, after adding a simple method to the model: python ... def image_or_placeholder(self): if self.image: return self.image return f'{settings.STATIC_URL}/img/placeholder.jpg' ... I could reuse this same functionality throughout all of my templates simply with: django <img src="{{ obj.image_or_placeholder }}" />

  2. Rounding time records to nearest 15 minutes

    In a time tracking application, all time records were supposed to be rounded to the nearest 15 minutes. In the model, I overwrote the save() function to call round_time(), which looked like this: ```python ... def save(self, args, kwargs): self.start = round_time(self.raw_start) self.end = round_time(self.raw_end) return super(HoursReport, self).save(args, **kwargs)

    def round_time(dt):
        """Return datetime object rounded to nearest quarter hour"""
        # round minute to nearest quarter hour (0,15,30,45,60)
        minute = 15*(round((float(dt.minute)+float(dt.second)/60)/15))
        hr = dt.hour
        # correct minute format to work with datetime
        if minute == 60:
            minute = 0
            hr += 1
        # convert input datetime into new (rounded) datetime object
        return datetime.time(hr,minute)
    

    ... `` This could have been done in the view using theform.save(commit=false)` technique. However, putting it in the model allows it to be reused regardless of which view creates the object.

This concept, of course, can be expanded upon to improve the quality of your code in many different ways. Model methods are often the solution to many headaches involving bloated views or template logic.


Whether you write the functionality of your Django application in your models or not is mostly inconsequential. That's part of what makes programming special; there are many ways to approach and solve problems. With that said, though, model functions do more than just implement functionality in a different location. They provide an elegant separation between interactivity and data.

Readability is more important than some developers give credit to, and coupling user logic and data logic is detrimental to the readability of your code. Model methods make the functionality to manipulate and display data inherent to the data itself, and that is beautiful code.