Django Model Decorators — Tips and Tricks With Implementation | by Tamas K Stenczel | Mar, 2022

Learn to write functionality once and share between models or applications

Photo by Stephen Hamilton on Unsplash | Altered

We intend to apply functionality to a Django model that can be replicated and easily applied to multiple models. We will touch on examples and work through how to solve them by doing the following:

  1. adding fields to the model
  2. adding properties to the model
  3. adding a factory decorator that attaches a default serialiser for REST framework usage

Before we start solving the above problems, let’s quickly refresh a few points on decorators and Django models. If you are completely comfortable with these, feel free to skip ahead.

Photo by Sincerely Media on Unsplash

Decorators are functions applied to functions (or classes), leading to enriched functionality. In essence, they are syntactic sugar for applying functions to functions, “wrapping” a function into another one.

There are numerous great tutorials on decorators out there that explain how they work, so for deep understanding please consult those.

For the sake of this article, we shall highlight one very important piece to understand: decorators act on functions/classes to put a layer around them.

Take a look at this decorator that acts before and after the execution of the wrapped function. Then see two equivalent options on how to use it. Here’s the code:

Running either will lead to the same output, as shown below:

In [2]: func1("Hello World!") # same for func2(...)
I cam do something before the function call!
Hello World!
I can do something after the function call!

Django models are defined as classes that are used for defining the database structure as well as providing an abstract interface to the underlying database. There is a fair bit of “magic” happening in the background when these are used for example when migrations are made. We will understand how some of these work.

See this example:

from django.db import modelsclass DummyModel(models.Model):
number = models.IntegerField()

Which will lead to a table in your database with an integer column and the ID (.pk) of course.

Let’s say we want to put a field on the model, how to do that in a decorator? For a simple class, this would be:

If you try this with a Django model, then it does not work! Well, no error is thrown but there is also nothing happening. For example, with this code:

Is equivalent to the above, the text field 1 is not created and will not be added to the database. But why is that?

There is more to a model’s creation than adding members to a Python class: the magic happens when the field object’s contribute_to_class method is used. Let’s see how that works and what we should do with the above snippet.

This does what we want it to: create a UUID field on the model which will show up in the migrations as well.

This is an alternative to using abstract model classes to derive from, and is presented as a choice for the programmer. Let’s see what more we can do with these decorators.

Let’s say there is some functionality like common derived parameters that you want to add to multiple models. Let’s see how we can extend the above with properties as well.

For example, if you wanted to track the creation and change times of a model, and know the age of the model in the database, then you can do the following:

This will apply a creation date and modification time field to the model. The creation date is recorded at the time of creation and cannot be changed afterward, while the modification time is being updated every time something changes the model.

A caveat of the update time is that it only works for Python code changing the model, not when direct SQL is evaluated on the database [1]. This is due to how Django is handling changes and triggers signals [2].

The age of the model is a derived parameter and depends on the current time, so that is implemented as a property on the model object.

For clarity, this is equivalent to having these fields and properties written to the class directly (see [1] as well):

nb This design is again a choice, there are alternative methods to get to the same result as well, for example by creating an abstract model class to drive from (potentially using multiple inheritances). This choice can make the code perhaps more readable and elegant.

In the case of working with REST frameworks, one may need many serializers, sand some of those are quite simple and similar. Here is a trick to add a default simple serialiser to the models, which can be imported with them. This is more or less like a serializer factory

Here is a simple serializer that would be created over and over again for models:

After about two, one should create a factory function that creates the serializer for any model given. We can also do this with a decorator on the model itself:

This can be extended in many ways, for example providing generic APIViews as well, or adding more functionality to them in case that is used by many models.

We have seen how to apply decorators to Django models, unlocking a wide variety of Pythonic functionality to be added. For most cases, these are choices that one can make, or decide to get to a similar result differently.

Thank you very much for reading this article. I hope you have learned or seen something new!

Photo by Michelle Ding on Unsplash

References:

Leave a Comment