Three more uses for functools.partial() in Django

I remain convinced that Python’s functools.partial()
is underappreciated. Following my previous post, here are three more ways to use partial()
with Django.
(For an explanation of partial()
see PyDanny’s post.)
1. Customizing render()
Django’s render()
shortcut simplifies generating an HTTP Response from rendering a template. If several views need to pass the same arguments, we can wrap the shortcut with partial()
to avoid repetition.
For example, imagine we have several views that return text, rather than HTML. We could create our own render_text
shortcut that calls render
with the content_type
argument pre-filled:
from functools import partial
from django.shortcuts import render
render_text = partial(render, content_type="text/plain")
def robots_txt(request):
...
return render_text(request, "robots.txt")
def security_txt(request):
...
return render_text(request, "security.txt")
(See my posts on robots.txt and security.txt.)
We can use this approach with other shortcut functions, or even HttpResponse
& its subclasses.
2. transaction.on_commit()
callbacks
Django’s examples for transaction.on_commit()
use functions, or lambdas:
from django.db import transaction
# With a function
def send_email():
send_admin_email(
subject="Author created",
# ...
)
transaction.on_commit(send_email)
# With a lambda
transaction.on_commit(
lambda: send_admin_email(
subject="Author created",
# ...
)
)
In the wild, most on_commit()
callbacks only call another function with pre-filled arguments. Using lambda
can make this easy, but lambda functions have their drawbacks. We can instead use partial()
to bind the arguments to the function:
transaction.on_commit(
partial(
send_admin_email,
subject="Author created",
# ...
)
)
3. Database functions
Django’s database functions represent functions that will run in the database. As such, in Python they are classes, rather than functions, but this doesn’t stop us from using partial()
with them.
For example, to create a specialized SubStr
that extracts the first character from a string, we can do:
from functools import partial
from django.db.models.functions import Substr
FirstChar = partial(Substr, pos=1, length=1)
In use, this looks just like a normal database function:
In [1]: from example.core.models import Author, FirstChar
In [2]: Author.objects.annotate(first_char=FirstChar("name")).filter(first_char="C")
Out[2]: <QuerySet [<Author: Author 'Charles Dickens' (1)>]>
For wrappers using more than one database function, we can’t use partial()
. Instead we can combine them with a function. For example, to take the first character and uppercase it:
from functools import partial
from django.db.models.functions import Substr, Upper
def FirstCharUpper(expression):
return Upper(Substr(expression, pos=1, length=1))
Even though this is a function, I like to stick to the CamelCaes naming convention so it matches the database functions.
Learn how to make your tests run quickly in my book Speed Up Your Django Tests.
One summary email a week, no spam, I pinky promise.
Related posts: