3 uses for functools.partial in Django

Python’s functools.partial
is a great tool that I feel is underused.
(If you don’t know what partial
is, check out PyDanny’s explainer.)
Here are a few ways I’ve used partial
in Django projects.
1. Making reusable fields without subclassing
It’s common to have field definitions the same across many models, for example a created_at
field tracking instance creation time. We can do create such a field like so:
from django.db import models
class Book(models.Model):
created_at = models.DateTimeField(
default=timezone.now,
help_text="When this instance was created.",
)
Copying this between models becomes tedious, and makes changing the definition hard.
One solution to this repetition is to use a base model class or mixin. This is fine, but it scatters the definition of a model’s fields, prevents local customization of arguments, and can lead to complex inheritance hierarchies.
Another solution is to create a subclass of DateTimeField
and add it to every model. This works well but can lead to complications with migrations, as the migration files will refer to the subclass by import and we will need to update them all if we refactor.
We can instead use partial
to create a “cheap subclass” of the field class. Our models will still directly use DateTimeField
, but with some arguments pre-filled. For example:
from functools import partial
from django.db import models
CreatedAtField = partial(
models.DateTimeField,
default=timezone.now,
help_text="When this instance was created.",
)
class Book(models.Model):
...
created_at = CreatedAtField()
class Author(models.Model):
...
created_at = CreatedAtField()
partial
also allows us to replace the pre-filled arguments, so we can override them when needed:
class DeletedBook(models.Model):
originally_created_at = CreatedAtField(
help_text="When the Book instance was originally created.",
)
We can also apply this technique to fields in forms, Django REST Framework serializers, etc.
2. Creating many upload_to
callables for FileField
s
When using the model FileField
, or subclasses like ImageField
, one can pass a callable as the upload_to
argument to calculate the destination path. This allows us to arrange the media according to whatever scheme we want.
If we are using multiple such fields that share some logic for upload_to
, we can use partial
to avoid creating many similar functions. For example, if we had a user model that allows uploading two types of image into their respective subfolders:
from functools import partial
from django.db import models
def user_upload_to(instance, filename, category):
return f"users/{instance.id}/{category}/{filename}"
class User(models.Model):
...
profile_picture = models.ImageField(
upload_to=partial(user_upload_to, category="profile"),
)
background_picture = models.ImageField(
upload_to=partial(user_upload_to, category="background"),
)
3. Pre-filling view arguments in URL definitions
Sometimes we might have a single view that changes behaviour based on the URL mapping to it. In such a case we can use partial
to set parameters for the view, without creating wrapper view functions. For example, imagine we have a user profile view, for which we are currently working on “version two” functionality:
def profile(request, user_id, v2=False):
...
if v2:
...
return render(request, ...)
We can map two URL’s to the profile
function view, switching v2
to True
for the a preview URL:
from functools import partial
from django.urls import path
from example.core.views import public
urlpatterns = [
...,
path("profile/<int:user_id>/", public.profile),
path("v2/profile/<int:user_id>/", partial(public.profile, v2=True)),
]
If your Django project’s long test runs bore you, I wrote a book that can help.
One summary email a week, no spam, I pinky promise.
Related posts: