Introduction
Developers are introduced to Django as "The web framework for perfectionists with deadlines"; This is not just a catch phrase. Django offers features that help developers write clean, maintainable and efficient code. It achieves this, among other things, by following a set of conventions that are often overlooked.
In this post, we'll review some of these conventions and how you can use them to reduce boilerplate and remain consistent across projects.
Legend: CBV = Class Based Views / GCBV = Generic Class Based Views
GCBV - Template Name
Instead of this:
class PostListView(ListView):
model = Post
template_name = 'blog/posts_template.html'
You can write this:
class PostListView(ListView):
model = Post
When using GCBV, you can omit the template_name
attribute by naming your templates accordingly. For a model named Post
in an app named blog
, the template names should be as follows:
View | Template |
---|---|
ListView | blog/post_list.html |
DetailView | blog/post_detail.html |
CreateView | blog/post_form.html |
UpdateView | blog/post_form.html |
DeleteView | blog/post_confirm_delete.html |
Django will infer the template name based on the model and app name. Developers reading your code will be able to discern what template will be used as well as infer the model from the template names.
GCBV - Context Object Name
Instead of this:
class PostListView(ListView):
model = Post
context_object_name = 'posts'
You can write this:
class PostListView(ListView):
model = Post
When using GCBV, you can omit the context_object_name
attribute altogether. Django will automatically set the context object name based on the model name. For a model named Post
, the context object names are as follows:
View | Context Object Name | Or |
---|---|---|
ListView | post_list | object_list |
DetailView | post | object |
CreateView | post | object |
UpdateView | post | object |
DeleteView | post | object |
Django will infer the context object name based on the model, so you don't need to specify it explicitly. I myself prefer to use the object
version instead of the "explicit" version so that the template can be used with another view without having to make any changes.
GCBV - PK & Slug URL Parameters
Instead of this:
class PostDetailView(DetailView):
model = Post
pk_url_kwarg = 'post_id'
slug_url_kwarg = 'post_slug'
# urls.py
path('post/<int:post_id>/<slug:post_slug>/', PostDetailView.as_view(), name='post_detail')
You can write this:
class PostDetailView(DetailView):
model = Post
# urls.py
path('post/<int:pk>/<slug:slug>/', PostDetailView.as_view(), name='post_detail')
When using GCBV, you can omit the pk_url_kwarg
and slug_url_kwarg
attributes altogether as long as you follow the convention of naming your primary key kwarg as pk
and your slug kwarg as slug
.
CBV - Extra Context
Instead of writing this:
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Post Detail'
return context
You can write this:
class PostDetailView(DetailView):
model = Post
extra_context = {'title': 'Post Detail'}
When using Class Based Views, you can use the extra_context
attribute to add static context instead of overriding the get_context_data
method. This will only work for static context and does not support callables.
GCBV - Date Based Views
While not a convention, it's worth mentioning that Django provides a set of date-based views that can be used to display objects based on their date fields. These views are:
- DayArchiveView
- WeekArchiveView
- MonthArchiveView
- YearArchiveView
- TodayArchiveView
These views are particularly useful when you want to display objects based on their date fields. For example, if you have a Post
model with a created_at
field, you can use the DayArchiveView
to display all posts created on a particular day. For example:
# views.py
class PostDayArchiveView(DayArchiveView):
model = Post
date_field = 'created_at'
# urls.py
path(
'post/<int:year>/<int:month>/<int:day>/',
DayArchiveView.as_view(),
name='post_archive_day'
)
Notice how slim the view is compared to doing this manually? This is the power of Django's generic class-based views. Year, month and day are automatically passed and used by the view because they were "named" correctly.
Models - Related Name
Instead of this:
class Post(Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
...
You can write this:
class Post(Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
...
When defining a ForeignKey
field, you do not have to specify the related_name
attribute. Django will automatically set the related name based on the model name. For a model named Post
, the related name will be post_set
.
This point is quite controversial. You'll find that an overwhelming majority will argue against this and say that you should always specify the related_name
attribute because it's more pythonic and readable to have user.posts
versus user.post_set
. I'd go as far as saying that for the very same readability reason, an indicator such as _set
will help the readers distinguish a related from a none-related attribute[1]. This will also completely [2] eliminate misnomers like:
class Post(Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author')
# or
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='post')
Which most of the time end up staying in repositories having been overlooked by code reviewers.
-
[1] Arguably, "Types" may improve readability, but that's a whole other topic. How many devs/projects actually use them?
[2] This is not always the case, given you might have numerous foreign keys to the same model, in which case you'd have to specify the related_name
attribute. I'd still argue in this particular case, you're better off explicitely specifying the _set
suffix to all related names. Try to be consistent in your decision.