In this post, I will share some tips to help you improve the design of your Django Models. Many of those tips are related to naming conventions, which can improve a lot the readability of your code.
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 Company(models.Model): name = models.CharField(max_length=30) vat_identification_number = models.CharField(max_length=20)
Always name your models using singular. Call it Company
instead of Companies
. A model definition is therepresentation of a single object (the object in this example is a company), and not a collection of companies.
This usually cause confusion because we tend to think in terms of the database tables. A model will eventually betranslated 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 Company.objects
. We can renamed the objects
attribute by defining a models.Manager
attribute:
from django.db import models class Company(models.Model): # ... companies = models.Manager()
So with that we would access the collection of companies as Company.companies.filter(name='Google')
. But I usuallydon’t go there. I prefer keeping the objects
attribute there for consistency.
The Django Coding Style suggests the following order of inner classes, methods and attributes:
class Meta
def __str__()
def save()
def get_absolute_url()
Example:
from django.db import models from django.urls import reverse class Company(models.Model): # CHOICES PUBLIC_LIMITED_COMPANY = 'PLC' PRIVATE_COMPANY_LIMITED = 'LTD' LIMITED_LIABILITY_PARTNERSHIP = 'LLP' COMPANY_TYPE_CHOICES = ( (PUBLIC_LIMITED_COMPANY, 'Public limited company'), (PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'), (LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'), ) # DATABASE FIELDS name = models.CharField('name', max_length=30) vat_identification_number = models.CharField('VAT', max_length=20) company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES) # MANAGERS objects = models.Manager() limited_companies = LimitedCompanyManager() # META CLASS class Meta: verbose_name = 'company' verbose_name_plural = 'companies' # 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('company_details', kwargs={'pk': self.id}) # OTHER METHODS def process_invoices(self): do_something()
The related_name
attribute in the ForeignKey
fields is extremely useful. It let’s us define a meaningful namefor 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 Company: name = models.CharField(max_length=30) class Employee: first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees')
That means the Company
model will have a special attribute named employees
, which will return a QuerySet
with
all employees instances related to the company.
google = Company.objects.get(name='Google') google.employees.all()
You can also use the reverse relationship to modify the company
field on the Employee
instances:
bharat = Employee.objects.get(first_name='bharat') google = Company.objects.get(name='Google') google.employees.add(bharat)
This kind of relationship also applies to query filters. For example, if I wanted to list all companies that employs people named ‘design’, I could do the following:
companies = Company.objects.filter(employee__first_name='bharat')
If you want to customize the name of this relationship, here is how we do it:
class Employee: first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) company = models.ForeignKey( Company, on_delete=models.CASCADE, related_name='employees', related_query_name='person' )
Then the usage would be:
companies = Company.objects.filter(person__first_name='bharat')
To use it consistently, related_name
goes as plural and related_query_name
goes as singular.
form.is_valid()
.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.
Example:
# The default values of `null` and `blank` are `False`. class Person(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)