Often not all fields specified in a model are specified through a form. These for example originate for example through the path, or we make use of the logged in user. Take for example the following model:
from django.conf import settings
from django.db import models
class Comment(models.Model):
= models.ForeignKey(Post, on_delete=models.CASCADE)
post = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
author = models.CharField(max_length=1024)
comment = models.DateTimeField(auto_now_add=True) posted_at
The form will normally only take the comment
as field, not the post
and author
. We can for example specify the primary key of the post in the path, and the author is normally the logged in user. The form thus looks like:
from django import forms
class CommentForm(forms.ModelForm):
class Meta:
= Comment
model = ['comment'] fields
and the path to the view will have a primary key that specifies to what post we comment:
from django.urls import path
from app_name.views import CreateCommentView
urlpatterns = [
path('post/<int:pk>/comment', CreateCommentView.as_view()),
]
What problems are solved with this?
A class-based view is powerful, but it is often not entirely clear where to add certain logic to alter the code flow slightly. Often people will alter the entire .post(…)
method. This destroys most of the boilerplate code in the view. This does not only mean the user needs to define a long view, but it is also likely that the user will not think about everything in advance. For example people often forget to pass request.FILES
to the form.
It also makes it easy to wrap the logic to "inject" data into the form instance through a mixin: this makes the logic to inject data more reusable.
What does this pattern look like?
We override the .form_valid(…)
method [Django-doc] of the view with the FormMixin
, and there we can alter the .instance
wrapped in the form, for example:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from app_name.forms import CommentForm
class CommentCreateView(LoginRequiredMixin, CreateView):
form_class = CommentForm
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post_id = self.kwargs['pk']
return super().form_valid(form)
If we need the logic in multiple views, we can easily encapsulate this in a mixin, for example:
from django.contrib.auth.mixins import LoginRequiredMixin
class SetAuthorMixin(LoginRequiredMixin):
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
then the mixin can be used, for example in both views that create a Post
and a Comment
:
from django.views.generic.edit import CreateView
from app_name.forms import CommentForm, PostForm
class PostCreateView(SetAuthorMixin, CreateView):
form_class = PostForm
class CommentCreateView(SetAuthorMixin, CreateView):
form_class = CommentForm
def form_valid(self, form):
form.instance.post_id = self.kwargs['pk']
return super().form_valid(form)
This not only makes it easier to reuse logic. It also makes it easier to fix a mistake: if the mistake is only made in one mixin, it is easy to fix the problem for all views with that logic, instead of searching for all views that implemented (variants) of that logic.
For the Django admin, we can override the .save_model
method [Django-doc]:
from django.contrib import admin
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)
Extra tips
For DateTimeField
s and DateField
s we can make use of the auto_now_add=…
[Django-doc] or auto_now=…
[Django-doc] can be used to automatically specify the current timestamp to specify when to create or update the object.