Django Model Best Practices: Tips and Tricks for Clean and Efficient Code

Learn how to optimize our Django model design with best practices for field selection, database design, querying techniques, clean code, effective testing, advanced optimization, and key takeaways to help you build scalable and maintainable web applications.

Sagar Chopade
7 min readFeb 9, 2023
Django model best practices

In this blog post, we’ll explore some of the key best practices for working with django models🙌.

Hello and welcome to the Python and Django worlds! 😃 You are now entering a journey to learn one of the most popular and widely used programming languages and frameworks. With Python, you can build a wide range of applications, from simple scripts to complex web applications with Django. The possibilities are endless, and the learning experience will be both challenging and rewarding. Best wishes on your journey, and please do not hesitate to ask for assistance when you need it.

At ScaleReal we work extensively on Python/Django Applications and have built a lot of highly scalable applications for various clients of ours.

What is the django model?🤔 Why do we follow model writing best practices?

The Django Model is an object-relational mapper (ORM) that allows developers to interact with databases using python(using classes and methods) code instead of writing raw SQL. By following best practices when writing models, we can ensure that our code is efficient, secure, and maintainable. This helps us to create applications that are robust and reliable. Additionally, following best practices can help us to avoid common pitfalls and ensure that we optimize our code for performance.🤗
So let’s start with it,

▪ Keep your models simple and clear

Models should describe a single entity in the database and should have only the fields that are necessary for that entity🫶. Avoid adding unnecessary fields or methods. For example,

class BlogPost(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
publication_date = models.DateTimeField(auto_now_add=True)

In this example, the BlogPost the model has only four fields that are necessary to store the information about a blog post. The fields are of the correct type, such as CharField for the title and TextField for the content, and they are defined with the minimum necessary parameters. The ForeignKey field to the User the model ensures that each blog post is associated with an author, but it does not create an overly complex relationship between the models. This makes the model simple, easy to understand, and straightforward to use.

▪ Model Naming

Models should be named using singular nouns, not plural, and written in CamelCase.
For example “Blog” instead of “Blogs” or “BlogPost” instead of “Blogposts”

▪ Relationship Field Naming

To define relationships between models using fields like ForeignKey, OneToOneKey, and ManyToManyField field names should be snake_case and use expressive names.
For example, “blog_author” for the relationship between a blog and its author

▪ Apply Indexing on the model field

Frequently used queries that require a high degree of performance, such as large data sets with complex filtering. It’s important to choose the right fields to index and to keep the number of indexes to a minimum.

Note✍️: Indexing can improve query performance. It can also slow down the performance of write operations, such as creating, updating, and deleting records in the database. In addition, adding too many indexes can lead to large disk seeks, which can slow down database performance.

Use help_text as documentation

It’s a useful tool for providing clear and concise documentation for Django models, which can help improve the overall quality and maintainability of the code. For example,

class BlogPost(models.Model):
title = models.CharField(max_length=100, help_text='Enter the title of the blog')
...

▪ Related-Name

should be a descriptive name that specifies the relationship it represents
ex.

from django.db import models
class Tag(models.Model):
name=models.CharField(max_length=100)
class Post(models.Model):
tags = models.ManyToManyField(Tag, related_name='blog_posts')

With this setup, you can use the custom related_query_name in querysets to filter on the reverse relation, like this

posts = Post.objects.filter(tags__blog_posts__name='python')

▪ Writing order of django Model

  • Define constants first or the best way you can import them from the constants.py file.
  • model field names
  • custom manager initializer
  • define metadata options for the model using a subclass class Meta
  • string representation of a model object using def __str__
  • model property methods define using “@property”
  • other special methods of class
  • def save
  • other methods

Note✍️: Order can be changed according to your comfort level

Let’s see it by using the example:

class User(AbstractBaseUser, PermissionsMixin):
USERNAME_FIELD = "email"
email = models.EmailField(_("email address"), unique=True, blank=False, null=False, max_length=254)
first_name = models.CharField(_("First Name"), max_length=254, blank=True)
last_name = models.CharField(_("Last Name"), max_length=254, blank=True)

objects = UserManager()
class Meta:
verbose_name_plural = "users"
ordering = ["email"]
indexes = [models.Index(fields=["email"])]
def __str__(self):
return f"{self.id}-{self.email}"
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
def save(self, *args, **kwargs):
self.email = self.email.lower()
super(User, self).save(*args, **kwargs)

▪ Use custom manager

This provides more control over the data returned from the model and can simplify common querying patterns. Custom managers can also be used to extend or override the default manager and change the behaviour of the model’s default manager. By using custom managers, you can improve the maintainability, readability, and testability of your code.🫶
You can find more information in my previous blog post
Custom Model Managers In Django🔥

▪ Do not use null=True or blank=True for BooleanField

A BooleanField is used to represent a true/false value and allow it to be null or blank in the database, create ambiguity and can lead to unexpected behaviour

▪ Handel the database exception

Use try-except blocks in the Django custom manager methods to handle exceptions. You should also use specific classes for different exceptions in order to handle the behaviours appropriately.
Example: if a specific in the model, then handle that exception using ModelName.DoesNotExist

▪ Calculate the length of the model

Use the ModelName.objects.count() method to calculate the length of the database instead of len(ModelName.objects.all())

▪ Use _id to fetch foreign key id

While you use a foreign key in a model, Django creates a related field that can be accessed through the model’s name, followed by _id. This related field represents the primary key of the related object. so instead of accessing the object using.
For example, let’s say you have two models, User and Blog, where Blog has a foreign key to User. You can access the primary key of the related author using user_id instead of user.id.

▪ Check the object in the model

The simplest method of finding the object is using the exists() querysets method in place of get() or filter(), and then checking through the if conditions and returning none or raising an error.

▪ Model Inheritance

Use Abstract Base Classes for shared fields like created_at and updated_at fields used in every model, Multi-Table Inheritance for models with unique fields, and Proxy Models for customizing model behaviour.

Note✍️: Strongly recommend against using the multi-table inheritance why?
It can lead to complex queries, increased database size, inefficient table joins, and poor scalability, making it difficult to manage and maintain. It’s usually recommended to use alternative inheritance types such as Abstract Base Classes or Proxy Models instead.

▪ When to use the null and blank

null defines whether it can save a field with a NULL value in the database, while blank defines whether the field is required when creating a form or model instance. Use null=True for optional fields that can be saved as NULL, and blank=True for optional fields that can be left empty.

It’s important to keep in mind the implications of allowing NULL values in the database and make sure to properly handle them in your code

▪ Define the primary key in the Model

  • Benefit primary_key=True on a single field as the unique identifier for each instance. By default, we used the auto-incrementing primary keys.
  • don't make ForeignKey as primary keys
  • Use the UUIDField if you want to use unique identifiers across multiple databases.

▪ Use constraints on the model field

Adding constraints ensures data integrity and prevents incorrect data from being saved to the database, serving more powerful and justifiable applications. For example, adding a unique constraint to a field ensures that no duplicates are allowed in that field and a custom validator, etc...

▪ keep a safe relationship between the models

You can use the models.PROTECT option, which will raise a ProtectedError exception if you try to delete a parent object that is referenced by related objects.

Note✍️: Based on your need, you can try the other options
models.CASCADE- When the referenced object is deleted, all related objects will also be deleted.
models.SET_NULL- When the referenced object is deleted, the foreign key in the related object will be set to NULL, if the field allows null values.
models.SET_DEFAULT- When the referenced object is deleted, the foreign key in the related object will be set to its default value.
models.SET_ON_DELETE- When the referenced object is deleted, call the callable defined in the SET_ON_DELETE option.
models.DO_NOTHING- This option is not recommended, as it allows the database to be left in an inconsistent state with references to non-existent objects.

▪ Store files using the customized path

Specifies where uploaded files will be stored within MEDIA_ROOT. This should be allowed you to organize and manage the files and serve them easily. you can achieve this by using upload_to in the model field ofFileField or ImageField .
For example,


def _upload_to(instance, filename):
epoch_time = epoch_milliseconds()
name, extension = os.path.splitext(filename)
file = "{}_{}{}".format(name, epoch_time, extension)
return "{}/{}".format(instance.__class__.__name__, file)
class Post(models.Model):
banner_image = models.ImageField(upload_to=_upload_to)
...

That’s it from my side folks 🧑‍💻. I hope this blog has been helpful in giving you a better understanding of the best practices of the django model. Until next time, stay safe and keep learning!

At ScaleReal We believe in Sharing and Open Source.

Thanks for reading!🫡 If you enjoyed reading this article, please click the 👏 button and share it with everyone. Feel free to leave a comment 💬 below.

Sharing is Caring!

Thank You :) 🙏❤️

Please don’t hesitate to get in touch if you need any support, I can be reached through LinkedIn or Github.

~Sagar Chopade

--

--