Hello! Welcome to your next lesson on the Django framework.
In our last session, we saw how CreateView and UpdateView dramatically simplify our code by handling the entire GET/POST cycle for creating and editing model objects. We configured them with attributes like model and form_class, and they automatically saved the data and redirected on success.
But what happens when the default behavior isn't quite enough? For example, how do you associate a new blog post with the currently logged-in user who created it? The view needs to intervene in the process.
Today's lesson focuses on exactly that. Our goal is to process form submissions and handle validation within Class-Based Views (CBVs). You'll learn how to hook into the form processing lifecycle to execute custom logic, which is a crucial skill for building real-world applications. Given your background in object-oriented programming, you'll find the method overriding approach here quite natural.
The Form Processing Lifecycle in a CBV
When you submit a form to a CreateView or UpdateView, a POST request is sent to the server. The view doesn't just blindly save the data. It follows a precise, predictable sequence of steps. Understanding this sequence is key to customizing it.
The process is managed by methods inherited from Django's ProcessFormView class, which is an ancestor of CreateView and UpdateView.
To see the core logic, let's explore a resource that digs into the source code.
Digging up Django class-based views - 3
This article, 'Digging up Django class-based views - 3' by The Digital Cat Online, provides an excellent, clear look under the hood. It will show you the exact post method that orchestrates form handling.
Please read the section titled 'Processing GET and POST requests'. Focus on the code block showing the post method of ProcessFormView. This is the heart of today's lesson.
As you saw in the article, the post method's logic can be summarized as:
# Simplified logic within a CBV's post method
form = self.get_form() # Instantiates the form with request.POST data
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
This reveals two critical "hook" methods:
form_valid(form): This method is called only when the submitted data passes all validation rules. Its default job is to save the model instance and redirect to thesuccess_url. This is where we will add our custom logic.form_invalid(form): This method is called when the data fails validation. Its default job is to re-render the template, passing back the form object which now contains the validation errors to be displayed to the user.
This flow is beautifully illustrated in the following diagram.

Handling Successful Submissions with form_valid()
The most common reason to customize a form-handling view is to perform an action after the form is successfully validated but before the final redirect. The form_valid() method is the perfect place for this.
A classic example is assigning the currently logged-in user to a created_by field on a new object. CreateView doesn't know about your specific user relationships, so you have to tell it what to do.
The official Django documentation provides a clear, concise example of this.
Form handling with class-based views
The official Django documentation on 'Form handling with class-based views' provides the canonical example for this task. It shows how to add a created_by field to a model and then populate it in the view.
Read the section titled 'Models and request.user'. It's short and directly to the point. Pay close attention to how the form_valid() method is overridden.
Let's break down the implementation for our portfolio project.
Step 1: Update the Model
First, we'd add a ForeignKey to the User model in our Project model (don't forget to import User).
# portfolio/models.py
from django.db import models
from django.contrib.auth.models import User
class Project(models.Model):
# ... other fields
author = models.ForeignKey(User, on_delete=models.CASCADE)
After changing the model, you would need to run makemigrations and migrate.
Step 2: Override form_valid in the View
Now, we override the form_valid method in our ProjectCreateView.
# portfolio/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from .models import Project
class ProjectCreateView(LoginRequiredMixin, CreateView):
model = Project
fields = ['title', 'description', 'technology'] # 'author' is excluded!
template_name = 'portfolio/project_form.html'
# success_url or get_absolute_url() on model...
def form_valid(self, form):
# Assign the currently logged-in user to the author field
form.instance.author = self.request.user
# Now, let the parent class do its work: save the object and redirect
return super().form_valid(form)
Let's analyze this:
LoginRequiredMixin: We add this mixin to ensure only logged-in users can access this view.fields: Notice that'author'is not in the list of fields. We don't want the user to choose the author from a dropdown; we want to set it programmatically.def form_valid(self, form):: We define our own version of the method.form.instance.author = self.request.user: This is the core of our custom logic. Theformobject passed toform_validhas aninstanceattribute, which is theProjectobject about to be saved. We can modify it directly. The current user is available viaself.request.user.return super().form_valid(form): This is extremely important. After we're done with our custom logic, we call theform_validmethod from the parent class (CreateView). This parent method contains the logic that actually saves the model (form.save()) and returns theHttpResponseRedirect. If you forget this line, the form will not be saved!
To see this exact pattern in a live-coding context, the following video is very helpful.
Django Class Based Views Tutorial
This video from JustDjango demonstrates converting a function-based view to a CreateView and overriding form_valid to set the author. It's a great practical walkthrough of the concept we just discussed.
Watch from 21:15 to 24:20. The instructor implements a PostCreateView and overrides form_valid to set the post's author, which is identical to the user-project association we're discussing.
Handling Validation Errors with form_invalid()
What happens when form.is_valid() returns False? The form_invalid() method is called.
By default, form_invalid() simply re-renders the template (self.render_to_response()) with the same context, which includes the form object. However, this time the form object is "bound" with the submitted data and also contains a dictionary of errors. The template can then access these errors and display them next to the corresponding fields.
You've already built a ModelForm, so you know that Django automatically provides validation based on your model's field types (e.g., CharField with max_length, IntegerField, etc.). Let's see how FormView (the parent of CreateView) handles these errors automatically.
Learn Django Class Based Views - FormView - Theory and Examples
This video from Very Academy provides a clear demonstration of Django's default validation error handling within a form view.
Watch from 07:00 to 08:46. The presenter intentionally submits invalid data (a string into an integer field) and shows how the view automatically redisplays the form with a validation error message, without any custom code.
This default behavior is usually exactly what you want. While you can override form_invalid() to add custom logging or flash a special message, it's far less common than overriding form_valid(). The key takeaway is that Django's CBVs handle the validation failure case for you out of the box.
Test your understanding!
You have a ProjectCreateView. A user tries to submit the form but leaves the title field blank, which is a required field in your Project model. Which method in your view gets called, and what is its default action?
Show answer
The form_invalid(form) method gets called because the form fails validation (title is required). Its default action is to re-render the same template, passing the form context variable back. The template can then display the error message associated with the title field.
The same principles for overriding form_valid() apply directly to UpdateView as well. You might override it to set a last_modified_by field or to create a log entry every time an object is changed. The pattern remains the same: modify form.instance and then call super().form_valid(form).
Conclusion
You've now unlocked the ability to customize the form processing logic within Django's powerful Class-Based Views. This moves you beyond basic configuration and into building more complex, real-world application features.
Key Takeaways:
- CBVs like
CreateViewandUpdateViewuse apostmethod that orchestrates form validation. - If a form is valid, the
form_valid()method is called. This is the primary hook for adding custom logic. - A common pattern in
form_valid()is to modify the unsaved model instance (form.instance) before calling the parent method withsuper().form_valid(form)to complete the save and redirect operations. - If a form is invalid, the
form_invalid()method is called, which by default redisplays the form with error messages.
In our next lesson, we will build upon this foundation to tackle another common requirement: handling file uploads using forms and models. You'll see that the form_valid() method is, once again, a valuable place to perform custom actions.