Django Designing Better Models

In this post, We will learn some tips to improve the design of Django Models. Many of those tips are related to naming conventions, which can improve a lot the readability of your code.

The PEP8 is widely used in the Python ecosystem (Django included). So it’s a good idea to use it in the projects.

Besides PEP8, Django also provide the Django’s Coding Style which is a guideline for people writing code for inclusion in the Django code base itself.


Let’s start

1. Naming Models

The model definition is a class, so always use CapWords convention (no underscores). E.g. User, Permission, ContentType, etc.

For the model’s attributes use snake_case. E.g. first_name, last_name, etc.

Example:

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=30)
    enrollment_number = models.CharField(max_length=20)

Always name the models using singular. Call it Student instead of Students. A model definition is the representation of a single object (the object in this example is a student), and not a group of students.

This usually cause confusion because we tend to think in terms of the database tables. A model will eventually be translated into a table. The table is correct to be named using its plural form because the table represents a collection of objects.

In a Django model, we can access this collection via Student.objects. We can also renamed the objects attribute by defining a models.Manager attribute inside the model:

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=30)
    enrollment_number = models.CharField(max_length=20)
    students = models.Manager()

So with that we would access the group of students as Student.students.filter(name=’Ram’). To maintain consistency in our project we should keep the objects itself so that it would be easy understandable for the other developers too.


2. Model Style Ordering

The Django Coding Style suggests the following order of inner classes, methods and attributes:

  • If choices is defined for a given model field, define each choice as a tuple of tuples, with an all-uppercase name as a class attribute on the model.
  • All database fields
  • Custom model manager attributes
  • class Meta
  • def __str__()
  • def save()
  • def get_absolute_url()
  • Any custom methods

Example:

from django.db import models
from django.urls import reverse

class Student(models.Model):
    # CHOICES
    MALE = 'M'
    FEMALE = 'F'
    OTHERS = 'O'
    GENDER_CHOICES = (
        (MALE, 'Male'),
        (FEMALE, 'Female'),
        (OTHERS, 'Others'),
    )

    # DATABASE FIELDS
    name = models.CharField('name', max_length=30)
    enrollment_number = models.CharField('VAT', max_length=20)
    gender = models.CharField('gender', max_length=I, choices=GENDER_CHOICES)

    # MANAGERS
    objects = models.Manager()
    male_students = MaleStudentManager()

    # META CLASS
    class Meta:
        verbose_name = 'student'
        verbose_name_plural = 'students'

    # TO STRING METHOD
    def __str__(self):
        return self.name

    # SAVE METHOD
    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

    # ABSOLUTE URL METHOD
    def get_absolute_url(self):
        return reverse('student_details', kwargs={'pk': self.id})

    # OTHER METHODS
    def generate_enrollment(self):
        do_something()

3. Reverse Relationships

related_name

The related_name attribute in the ForeignKey fields is extremely useful. It let’s us define a meaningful name for the reverse relationship.

Rule of thumb: If you are not sure what would be the related_name, use the plural of the model holding the ForeignKey.

class University(models.Model):
    name = models.CharField(max_length=30)

class Student(models.Model):
    name = models.CharField(max_length=30)
    university = models.ForeignKey(University, on_delete=models.CASCADE, related_name='students')

That means the University model will have a special attribute named students, which will return a QuerySet with all students instances related to the university.

ait = University.objects.get(name='AIT')
ait.students.all()

You can also use the reverse relationship to modify the university field on the Student instances:

ram = Student.objects.get(name='Ram')
ait = University.objects.get(name='AIT')
ait.students.add(ram)

related_query_name

This kind of relationship also applies to query filters. For example, if I wanted to list all universities that students named ‘Ram’, I could do the following:

universities = Unversity.objects.filter(student__name='Ram')

If you want to customize the name of this relationship, here is how we do it:

class Student(models.Model):
    name = models.CharField(max_length=30)
    university = models.ForeignKey(
        University,
        on_delete=models.CASCADE,
        related_name='students',
        related_query_name='std'
    )

Then the usage would be:

universities = University.objects.filter(std__name='Ram')

To use it consistently, related_name goes as plural and related_query_name goes as singular.


4. Blank and Null Fields

  • Null: It is database-related. Defines if a given database column will accept null values or not.
  • Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid() or serializer.is_valid() in DRF.

Do not use null=True for text-based fields that are optional. Otherwise, you will end up having two possible values for “no data,” that is: None and an empty string. Having two possible values for “no data” is redundant. The Django convention is to use the empty string, not NULL.

# The default values of `null` and `blank` are `False`.
class Student(models.Model):
    name = models.CharField(max_length=255)  # Mandatory
    bio = models.TextField(max_length=500, blank=True)  # Optional (don't put null=True)
    birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True)

Models definition is one of the most important parts of your application. Something that makes all the difference is defining the field types properly. Make sure to review the Django models field types to know your options. You can also define custom field types.

Explore More Django Posts

Efficient Django Project Settings with Split Settings Library

Learn how to efficiently manage your Django project settings with the Split Settings library. Use environment variables, keep sensitive information i…

Read More
Integrating Flake8 with Django: Best Practices

Learn how to integrate Flake8 with Django projects and enforce code quality. Follow our step-by-step guide and optimize your Django workflow with Fla…

Read More
Django Authentication and Authorization with JWT

Learn how to implement JSON Web Token (JWT) based authentication and authorization in Django web applications with step-by-step guide and code exampl…

Read More
Best Practices for Django Development: Tips and Tricks

Learn the best practices for Django development, including project structure, code organization, testing, and deployment. Build high-quality web apps.

Read More
Django Middleware: Tips, Tricks and Examples

Learn how to use Django Middleware to improve your app's performance and security. Includes examples and best practices.

Read More
Django Production Deployment: Best Practices & Checklist

Learn the best practices and checklist for deploying a Django application to production. Includes tips on web servers, databases, caching, security, …

Read More