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): post = models.ForeignKey(Post, on_delete=models.CASCADE) author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) comment = models.CharField(max_length=1024) posted_at = models.DateTimeField(auto_now_add=True)
The form will normally only take the
comment as field, not the
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: model = Comment fields = ['comment']
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()), ]
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.
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
from django.views.generic.edit import CreateView from app_name.forms import CommentForm, PostForm class CommentCreateView(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)
DateFields 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.