Django Best Practices: Models
Tags: django, python, tutorial, beginners
Published: 2020-02-21 19:39:13

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](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-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`.

```python
# 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](https://docs.djangoproject.com/en/dev/ref/models/instances/#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()](https://docs.djangoproject.com/en/dev/ref/models/instances/#get-absolute-url) method sets a canonical URL for the model. This is required when using the [reverse()](https://docs.djangoproject.com/en/dev/ref/urlresolvers/#django.urls.reverse) function. It is also the correct way to refer to a model in your templates rather than hard-coding them.

```html
<!-- 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.

```python
# 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()](https://docs.djangoproject.com/en/dev/ref/models/instances/#saving-objects) 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](https://www.python.org/dev/peps/pep-0020/), 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:

```python
# 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)
```

```python
# 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.

```python
# 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"](https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects). By default, Django sets this in our example to `student_set` and any filter names to `student__first_name`.

```python
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**.

```python
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](https://docs.djangoproject.com/en/dev/ref/models/options/) is incredibly powerful and has a [long list of features](https://docs.djangoproject.com/en/dev/ref/models/options/).

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.

```python
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](https://docs.djangoproject.com/en/dev/topics/db/optimization/#don-t-order-results-if-you-don-t-care) 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.

```python
# 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.

```python
# 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()](https://docs.djangoproject.com/en/dev/ref/models/instances/#extra-instance-methods).

Here's an example of adding and then displaying a new University in the Django shell.

```python
>>> 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](https://github.com/django-extensions/django-extensions/) and its `shell_plus` command.


## Step 6 - Blank vs Null
By default whenever you define a new database model both [null](https://docs.djangoproject.com/en/dev/ref/models/fields/#null) and [blank](https://docs.djangoproject.com/en/dev/ref/models/fields/#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.

<!-- ## Step 7 - Model Managers


## Step 8 - Custom Methods
 -->


## Further Learning
With more time I'd like to cover custom methods, [managers](https://docs.djangoproject.com/en/dev/topics/db/managers/), and a fuller list of [available field types](https://docs.djangoproject.com/en/dev/ref/models/fields/#model-field-types). However for now this guide should hit the major points.

The book [Two Scoops of Django](https://www.twoscoopspress.com/) 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](https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html) is a treasure trove of good tips on Django, including model design.

Haki Benita has a wonderful article on [Bullet Proofing Django Models](https://medium.com/@hakibenita/bullet-proofing-django-models-c080739be4e) and [Steel Kiwi](https://steelkiwi.com/blog/best-practices-working-django-models-python/) also has a good list.

**Note**: Special thanks to [Jeff Triplett](https://twitter.com/webology) and [Patrick Cloke](https://twitter.com/clokep) for feedback and suggestions.