Will Vincent
Posted on February 21, 2020
Properly defining database models is arguably the most important part of a new project, however Django provides us with tremendous flexibility around how we structure our models.
There is an official Django coding style which provides a recommended ordering for models.
- choices
- database fields
- custom manager attributes
Meta
def __str__()
def save()
def get_absolute_url()
- custom methods
But in practice it can be hard to go from a simple model to a massive one that you'd likely find in the real-world. In this tutorial we will create a progressively more complex University
example to demonstrate building out a "best practice" Django model. You don't need to add all of the features we'll cover but it's good to be aware of them as these are the most common ones you'll see.
Step 1 - Model and Field Names
The simplest possible model has only 4 lines of code. It imports models
, defines a model class University
, and has a single field full_name
.
# schools/models.py
from django.db import models
class University(models.Model):
full_name = models.CharField(max_length=100)
Models should always be Capitalized (eg. University
, User
, Article
) and singular (eg. University
not Universities
) since they represents a single object, not multiple objects.
Fields should be all lowercase using underscores not camelCase so full_name
not FullName
.
Step 2 - Common Methods
After defining a model and fields, the next step is often adding __str__()
and get_absolute_url
methods.
The str method defines a human-readable representation of the model that is displayed in the Django admin site and in the Django shell.
The get_absolute_url() method sets a canonical URL for the model. This is required when using the reverse() function. It is also the correct way to refer to a model in your templates rather than hard-coding them.
<!-- BAD template code. Avoid! -->
<a href="/university/{{ object.id }}/">{{ object.full_name }}</a>
<!-- Correct -->
<a href="{{ object.get_absolute_url }}/">{{ object.full_name }}</a>
And the Django admin will add a helpful "View on site" link to your models.
# schools/models.py
from django.db import models
from django.urls import reverse # new
class University(models.Model):
full_name = models.CharField(max_length=100)
def __str__(self): # new
return self.full_name
def get_absolute_url(self): # new
return reverse('university_detail', args=[str(self.id)])
Note: This example assumes a university_detail
named URL referencing a detail view.
A third common method is save() method which lets us customize how a model is saved. A common example is for a blog app that needs to automatically set the author
of a blog post to the current logged-in user. You'd implement that functionality with save()
.
Step 3 - Explicit Naming
Django makes a number of implicit naming choices that often trip up beginners. Since explicit is better than implicit, it's a good idea to set these values manually.
Django implicitly sets the human readable name for each field by converting underscores to spaces. So full_name
is represented as full name
. There are two ways to set this manually: you can use verbose_name()
or as long as the field type is not ForeignKey
, ManyToManyField
, or OneToOneField
, you can set it as an optional first positional argument.
Thus the following three lines of code are identical as far as Django is concerned:
# All 3 are equivalent!
full_name = models.CharField(max_length=100)
full_name = models.CharField('full name', max_length=100)
full_name = models.CharField(verbose_name='full name', max_length=100)
# schools/models.py
from django.db import models
class University(models.Model):
full_name = models.CharField(
max_length=100,
verbose_name='university full name', # new
)
def __str__(self):
return self.full_name
def get_absolute_url(self):
return reverse('university_detail', args=[str(self.id)])
When a ForeignKey
relationship exists, Django again implicitly defines two more names we'll often need. Let's add a Student
model to illustrate this.
# schools/models.py
...
class Student(models.Model): # new
first_name = models.CharField('first name', max_length=30)
last_name = models.CharField('last name', max_length=30)
university = models.ForeignKey(
University,
on_delete=models.CASCADE,
related_name='students',
related_query_name='person',
)
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
def get_absolute_url(self):
return reverse('student_detail', args=[str(self.id)])
Working with a ForeignKey
means following a relationship "backward". By default, Django sets this in our example to student_set
and any filter names to student__first_name
.
dartmouth = University.objects.get(full_name='Dartmouth')
dartmouth.student_set.all() # returns all students at Dartmouth
dartmouth.objects.filter(student__first_name='william') # returns all Dartmouth students named William
Pay attention to the one underscore _
vs two underscores __
in this. It is an easy mistake to make!
A better approach is to explicitly set these values. related_name()
should be the plural of the model containing the ForeignKey
(students
for the Student
model) and related_query_name()
should be singular.
yale = University.objects.get(full_name='Yale')
yale.students.all() # returns all students at Yale
yale.objects.filter(person__first_name='ashley') # returns all Yale students named Ashley
Step 4 - Meta class
The Meta class is incredibly powerful and has a long list of features.
A good first step is to explicitly name your model too, not just your fields. This can be done with verbose_name
and verbose_name_plural
. Otherwise Django would just add an s
to make it plural, universitys
, which is wrong.
class Meta:
verbose_name = 'university'
verbose_name_plural = 'universities'
Another common feature is ordering
which sets the default order of a list of objects. If we wanted to have a webpage where we listed all universities alphabetically, this is how we'd set it.
Be aware though that there can be a performance hit to ordering results so don't order results if you don't need to.
However if you need ordered results, consider adding an index to speed things up. An index usually is a good idea but maintaining it also requires some overhead so you'll want to test which implementation is more performant.
To add an index we can either add a db_index=True
to a field or set it via indexes
in our Meta
class. We'll implement the latter.
# schools/models.py
from django.db import models
from django.urls import reverse
class University(models.Model):
full_name = models.CharField(
max_length=100,
verbose_name='university full name',
)
class Meta: # new
indexes = [models.Index(fields=['full_name'])]
ordering = ['-full_name']
verbose_name = 'university'
verbose_name_plural = 'universities'
def __str__(self):
return self.full_name
def get_absolute_url(self):
return reverse('university_detail', args=[str(self.id)])
class Student(models.Model):
first_name = models.CharField('first name', max_length=30)
last_name = models.CharField('last name', max_length=30)
university = models.ForeignKey(
University,
on_delete=models.CASCADE,
related_name='students',
related_query_name='person',
)
class Meta: # new
verbose_name = 'student'
verbose_name_plural = 'students'
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
def get_absolute_url(self):
return reverse('student_detail', args=[str(self.id)])
Step 5 - Choices
If choices are 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.
Here we've added choice fields to our University
model and a new university_type
field.
# schools/models.py
from django.db import models
from django.urls import reverse
class University(models.Model):
UNIVERSITY_TYPE = ( # new
('PUBLIC', 'A public university'),
('PRIVATE', 'A private university')
)
full_name = models.CharField(
verbose_name='university full name',
max_length=100
)
university_type = models.CharField( # new
choices=UNIVERSITY_TYPE_CHOICES,
max_length=1,
verbose_name='type of university',
)
class Meta:
indexes = [models.Index(fields=['full_name'])]
ordering = ['-full_name']
verbose_name = 'university'
verbose_name_plural = 'universities'
def __str__(self):
return self.full_name
def get_absolute_url(self):
return reverse('university_detail', args=[str(self.id)])
If you want to refer to the "human-readable" value of a choice field, use get_FOO_display().
Here's an example of adding and then displaying a new University in the Django shell.
>>> u = University(full_name='College of William and Mary', university_type='PUBLIC')
>>> u.save()
>>> u.university_type # 'PUBLIC'
>>> u.get_university_type_display() # 'A public university'
If you're going to spend much time using the Django shell you'll like django-extensions and its shell_plus
command.
Step 6 - Blank vs Null
By default whenever you define a new database model both null and blank are set to false. These two values are easy to confuse but quite different.
null
is database-related. When a field has null=True
it can store a database entry as NULL
, meaning no value.
blank
is validation-related, if blank=True
then a form will allow an empty value, whereas if blank=False
then a value is required.
The tricky part is that the field type dictates how to use these values. Do not use null
with string-based fields like CharField
or TextField
as this leads to two possible values for "no data". The Django convention is instead to use the empty string ''
, not NULL
.
An exception is that if CharField
has both unique=True
and blank=True
then null=True
is also required to avoid unique constraint violations when saving multiples objects with blank values.
Further Learning
With more time I'd like to cover custom methods, managers, and a fuller list of available field types. However for now this guide should hit the major points.
The book Two Scoops of Django has a very complete section on model design including an easy-to-use guide to best practices with blank
and null
. It is a recommended resource for all Django developers.
The website Simple Is Better Than Complex is a treasure trove of good tips on Django, including model design.
Haki Benita has a wonderful article on Bullet Proofing Django Models and Steel Kiwi also has a good list.
Note: Special thanks to Jeff Triplett and Patrick Cloke for feedback and suggestions.
Posted on February 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.