Photo by Dan Meyers on Unsplash

Top 10 must-know Django code tips to level up your skills

Adrien Van Thong
Django Unleashed
Published in
7 min readJul 17, 2024

--

Django provides myriad of major features to make our lives easier as developers, which I’ve written articles about in the past. In today’s article, I wanted to bring some focus on the smaller quality-of-life coding shortcuts that Django also provides that don’t get as much attention as the shiny back-of-the-box features as some of these smaller tips can be absolute game-changers in our day-to-day coding.

Below are my personal list of absolute top 10 must-know coding tips in Django, which help produce simpler, more beautiful code.

Tip #1: Built-in QuerySet methods

In addition to the extremely common filter(), exclude() and annotate() methods, Django also provides other lesser-known methods, such as first() , last(), as syntactic sugar to help keep code more readable.

As the name implies, first() returns the first record in the QuerySet whereas last() returns the last record. Personally, I tend to use these most frequently in unit tests.

For example consider the following code, which grabs the first Fruit record:

>>> Fruit.objects.all()[:1][0]
<Fruit: Banana ($0.99)>

The same code can be converted to using the first()method:

>>> Fruit.objects.first()
<Fruit: Banana ($0.99)>

You’ll note right away that the code is way more readable, but an additional benefit with the first() and last() methods is they return a None object if the QuerySet is empty, whereas slicing requires more exception handling:

# With slicing, exception handling is required in case the QuerySet in empty:
>>> Fruit.objects.filter(price__gt=5.00)[:1][0]
Traceback (most recent call last):
File "<console>", line 1, in <module>
File ".../site-packages/django/db/models/query.py", line 318, in __getitem__
return qs._result_cache[0]
IndexError: list index out of range

# With first() and last(), a None object is returned:
>>> Fruit.objects.filter(price__gt=5.00).first()
None

For more on these methods as well as others, consult the Django docs.

Tip #2: get_or_create and create_or_update

Two other extremely useful QuerySet methods which I use constantly are get_or_create() and create_or_update().

In both cases these methods can save a ton of boilerplate code while also improving code readability:

# Without using get_or_create:
try:
fantasy = Category.objects.get(name='Fantasy')
except Category.DoesNotExist:
fantasy = Category.objects.create(name='Fantasy')
created = True

# Using get_or_create:
fantasy, created = Category.objects.get_or_create(name='Fantasy')

Both methods return a (record, bool) tuple where the first item is the record in question, and the second item is True if the operation resulted in the record being created.

A similar example using create_or_update():

# Without using create_or_update:
try:
horror = Category.objects.get(name='Horror')
horror.name = 'Scary'
horror.save()
except Category.DoesNotExist:
horror = Category.objects.create(name='Scary')
created = True

# With create_or_update:
horror, created = Category.objects.update_or_create(name='Horror', defaults={'name': 'Scary'})

Here again, the entire try-except clause is reduced to a single method call, which returns a (record, bool) tuple functioning in the exact same way as the prior example.

Corresponding Django docs

Tip #3: get_FOO_display in templates

When using a Django model with a field with the choices attribute defined, I frequently want to print out the user-friendly value for that field in my templates. For example, given the following model:

class Book(models.Model):
STATUSES = [
(1, "Available"),
(2, "Signed out"),
(3, "On Backorder"),
(4, "To Be Shelved"),
(5, "Out of Circulation"),
]
title = models.CharField(max_length=128)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
status = models.IntegerField(choices=STATUSES)

On a template using that model, I want to print a book’s status but using {{book.status}} would print an unhelpful integer, i.e. 2. To translate that value back to the user-friendly status string, we can leverage the get_FOO_display method, for example:

<h1>Book Entry: {{ book.title }}</h1>
<b>Author:</b> {{ book.author }}<br />
<b>Status:</b> {{ book.get_status_display }}<br />

Results in the following HTML:

<h1>Book Entry: Moby Dick</h1>
<b>Author:</b> Herman Melville<br />
<b>Status:</b> Signed Out<br />

Corresponding Django docs

Tip #4: Bulk creates and updates

Bulk creates and updates in Django are not only powerful because of how easy they are to use and how much easier it makes the code to read, but also because the entire operation is done atomically.

Doing a bulk create in Django is quite simple — instantiate a list containing all the model records to be created, then feed it into the bulk_create() method, as shown below:

new_fruits = [
Fruit(name='Banana', price=0.80, description='Bunch of 4'),
Fruit(name='Banana (organic)', price=0.99, description='Bunch of 4'),
Fruit(name='Apple', price=0.75, description='Honeycrisp'),
Fruit(name='Strawberry', price=0.10),
]
fruits = Fruit.objects.bulk_create(new_fruits)

Meanwhile, bulk updates can be performed using QuerySets:

# Update the description for all fruits costing more than $1.00:
>>> Fruit.objects.filter(price__gt=1.00).update(description='Too expensive!')
5

The integer returned represents the number of records which were updated.

Corresponding Django docs

Tip #5: auto_now and auto_now_add

Django provides two extremely useful attributes on fields which can help us manage dates: auto_now and auto_now_add

Whereas auto_now sets the field to the current date whenever the record is updated, auto_now_add only sets the value on the field when the record is created.

An example of a model using these would look like this:

class Article(models.Model):
title = models.CharField(max_length=128)
created_on = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)

Watching this model in action, notice how the last_modified field is updated, but the created_on field is not:

>>> a = Article.objects.create(title='This is an example')
>>> a.created_on
datetime.datetime(2024, 7, 15, 21, 12, 44, 580527, tzinfo=<UTC>)
>>> a.last_modified
datetime.datetime(2024, 7, 15, 21, 12, 44, 580628, tzinfo=<UTC>)

# Let's update the object:
>>> a.title = 'I have been updated'
>>> a.save()

# Note how the last_modified field has been updated:
>>> a.created_on
datetime.datetime(2024, 7, 15, 21, 12, 44, 580527, tzinfo=<UTC>)
>>> a.last_modified
datetime.datetime(2024, 7, 15, 21, 13, 01, 538356, tzinfo=<UTC>)

Corresponding Django Docs

Tip #6: Useful unit test assert methods

Beyond the generic assert methods which come standard with all Python unit testing frameworks, such as assertEqual, assertTrue, assertNone, assertIn, etc, Django also adds a bunch of very useful new assert methods that can be used in our unit tests. My favourite ones are below:

  • assertContains — useful to check that a particular model record is contained in a larger QuerySet object. Conversely, use assertNotContains for the opposite result.
  • assertURLEqual — extremely useful for comparing URLs since the GET params ordering can be different in two URLs and functionally still be the same URL.
  • assertFormError — useful for verifying that form submits return errors when they are supposed to. This comes in very handy when getting test coverage on form validators.
  • assertTemplateUsed — useful when testing views (either functional or CBVs) to ensure a specific template is used when rendering the page.

See the complete list of Django-specific assert methods in the docs.

Tip #7: Unit test tag decorator

Staying on the subject of unit tests, Django provides the tag decorator which allows the app developer to categorize each unit tests into one or multiple test suites.

The goal is when running tests, the developer can choose to target the subset of the tests which is most relevant to them.

Tagging a test case is extremely simple — use the @tag decorator to specify one or more tags to assign to the test case:

from django.test import tag

@tag("api", "core"):
class ExampleAPITest(TestCase):
def setUp(self):
pass

def test_example(self):
# Your test logic here

Then, when executing the unit tests, provide the --tag argument to specify which tags to run, and only tests with that tag specified will run:

python manage.py test --tag=api

Corresponding Django docs

Tip #8: Lesser-known template tags

Django provides some extremely useful tags out of the box. Below I’ve highlighted some of the less common ones that are still extremely useful to remember:

  • join functions exactly like Python’s join method for strings.
  • first and last return the corresponding values in the given list.
  • floatformat controls how many digits to show past the decimal point, extremely useful for showing price values (for example:
    {{ price|floatformat:2 }})
  • intcomma Adds commas to numbers larger than 1000 for easier readability, i.e. 1000000 becomes 1,000,000
  • linebreaksbr Automatically converts newline characters to <br /> HTML entities.
  • urlize Automatically converts a URL string into an anchor tag to that same URL.
  • yesno Converts boolean values into user-readable strings, for example: {{ record.is_active|yesno:"Active,Disabled" }} Will convert True values to the string Active and False values to Disabled.

Complete list of template tags and filters

Tip #9: refresh_from_db

This one gets its own tip because I only learned it about myself way too late and it’s an absolute time-saver.

As the name implies, if you have an instance of a model record saved and you suspect it might be outdated and want to ensure freshness, call this method to update the local instance:

fruit = Fruits.objects.last()

# Without refresh_from_db:
fruit = Fruits.objects.get(pk=fruit.pk)

# With refresh_from_db:
fruit.refresh_from_db()

This is one which I widely use across my REST API unit tests.

Corresponding Django docs

Tip #10: Admin register decorator

Last one is an easy one - Django provides a register decorator which helps keep relevant admin code together and makes the code easier to read:


from django.contrib import admin

# Without decorator:
class FruitAdmin(admin.ModelAdmin):
search_fields = ['name', 'description']
list_filter = ['name']
list_display = ('name', 'price', 'description')

admin.site.register(Fruit, FruitAdmin)


# With decorator:
@admin.register(Fruit)
class FruitAdmin(admin.ModelAdmin):
search_fields = ['name', 'description']
list_filter = ['name']
list_display = ('name', 'price', 'description')

While being only a modest shortcut, it does improve code readability significantly and ensures the register stays close to the class definition.

Corresponding Django Docs

It was difficult to narrow down the list to just ten of my top coding shortcuts in Django, as there are so many good ones! In the end I selected the ones I used the most often, and the ones that save me the most boilerplate code. Do you have a favourite coding trick or tip that didn’t make the list? Sound off in the comments below!

--

--