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()
orserializer.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.