Django FormView

Summary: in this tutorial, you’ll learn how to use the Django FormView class to create a registration form for the Todo app.

This tutorial begins where the Django LoginView tutorial left off.

The Django FormView class allows you to create a view that displays a form. We’ll use the FormView class to create a registration form for the Todo App.

Creating a signup form

Create form.spy file in the users app and define the RegisterForm class as follows:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class RegisterForm(UserCreationForm):
    email = forms.EmailField(max_length=254)

    class Meta:
        model = User
        fields = ('username',  'email', 'password1', 'password2', )Code language: Python (python)

The RegisterForm uses the User model and displays the username, email, password1, and password2 fields.

Defining a RegisterView class

Define the RegisterView class in views.py of the users.py app:

from django.urls import reverse_lazy
from django.views.generic.edit import FormView
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login 
from django.contrib.auth.models import User
from .forms import RegisterForm


class RegisterView(FormView):
    template_name = 'users/register.html'
    form_class = RegisterForm
    redirect_authenticated_user = True
    success_url = reverse_lazy('tasks')
    
    def form_valid(self, form):
        user = form.save()
        if user:
            login(self.request, user)
        
        return super(RegisterView, self).form_valid(form)Code language: Python (python)

The RegisterView class inherits from the FormView class and has the following attributes and methods:

  • template_name specifies the name of the template for rendering the signup form.
  • form_class specifies the form (RegisterForm) used in the template.
  • redirect_authenticated_user is set to True to redirect the user once authenticated.
  • success_url specifies the URL to redirect once the user signs up successfully. In this example, it redirects the user to the task list.
  • form_valid() method is called once the form is submitted successfully. In this example, we save the User model and log the user in automatically.

Defining the registration route

Define a route that maps the registration URL register/ with the result of the as_view() method of the RegisterView class in the urls.py of the users app:

from django.urls import path
from django.contrib.auth.views import LogoutView
from .views import MyLoginView, RegisterView
 

urlpatterns = [
    path('login/', MyLoginView.as_view(),name='login'),
    path('logout/', LogoutView.as_view(next_page='login'),name='logout'),
    path('register/', RegisterView.as_view(),name='register'),
]
Code language: Python (python)

Create a registration template

Create a register.html in the templates/users directory of the users app with the following code:

{%extends 'base.html'%}

{%block content%}
	<div class="center">
	  <form method="post" novaldiate class="card">
	  	{% csrf_token %}
	    <h2 class="text-center">Create your account</h2>
		{% for field in form %}
	    		{{ field.label_tag }} 
	        	{{ field }}
	        	{% if field.errors %}
	        		<small class="error">{{ field.errors|striptags  }}</small> 
	        	{% endif %}
		{% endfor %}
		
		<input type="submit" value="Register" class="btn btn-primary full-width">
		<hr>
		<p class="text-center">Already have an account? <a href="{% url 'login'%}">Login Here</a></p>
		</form>
	</div>
{%endblock content%}Code language: HTML, XML (xml)

The form in the users/register.html is the RegisterForm class that we defined in the forms.py file.

Adding registration link to the login and navigation

Modify the login.html template by adding the registration link:

{%extends 'base.html'%}

{%block content%}
  <div class="center">
	  <form method="post" class="card" novalidate>
	  	{% csrf_token %}
	    <h2 class="text-center">Log in to your account</h2>
		{% for field in form %}
	    		{{ field.label_tag }} 
	        	{{ field }}
	        	{% if field.errors %}
	        		<small>{{ field.errors|striptags  }}</small> 
	        	{% endif %}
		{% endfor %}
		
		<input type="submit" value="Login" class="btn btn-primary full-width">
		<hr>
		<p class="text-center">Forgot your password <a href="#">Reset Password</a></p>
		<p class="text-center">Don't have a account? <a href="{%url 'register'%}">Join Now</a></p>
	</form>
</div>

{%endblock content%}
Code language: HTML, XML (xml)

Also, modify the base.html template by adding the registration link to the navigation and homepage:

{%load static %}
<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="{% static 'css/style.css' %}" />
        <title>Todo List</title>
    </head>

    <body>
        <header class="header">
            <div class="container">
            	<a href="{%url 'home'%}" class="logo">Todo</a>
            	<nav class="nav">
	            	<a href="{%url 'home'%}"><i class="bi bi-house-fill"></i> Home</a>
	                {% if request.user.is_authenticated %}
	    	              	<a href="{% url 'tasks' %}"><i class="bi bi-list-task"></i> My Tasks</a>
	    	              	<a href="{% url 'task-create' %}"><i class="bi bi-plus-circle"></i> Create Task</a>
		                <a href="#">Hi {{request.user | title}}</a>
	    	                <a href="{% url 'logout' %}" class="btn btn-outline">Logout</a>
	                {% else %}
				<a href="{% url 'login' %}" class="btn btn-outline">Login</a>
	            	        <a href="{% url 'register' %}" class="btn btn-primary">Join Now</a>
	                {% endif %}
	             </nav>
            </div>
        </header>
        <main>
            <div class="container">
            	{% if messages %}
			{% for message in messages %}
			<div class="alert alert-{{message.tags}}">
			       {{message}}
		        </div>
		        {% endfor %}
		{% endif %}
            
             {%block content %}
             {%endblock content%}
            </div>
        </main>
        <footer class="footer">
            <div class="container">
                <p>© Copyright {% now "Y" %} by <a href="https://www.pythontutorial.net">Python Tutorial</a></p>
            </div>
        </footer>
    </body>

</html>
Code language: HTML, XML (xml)

home.html

{%extends 'base.html'%}

{%load static %}

{%block content%}
	<section class="feature">
		<div class="feature-content">
			<h1>Todo</h1>
			<p>Todo helps you more focus, either work or play.</p>	
			<a class="btn btn-primary cta" href="{% url 'register' %}">Get Started</a>
		</div>
		<img src="{%static 'images/feature.jpg'%}" alt="" class="feature-image">
	</section>
{%endblock content%}
Code language: HTML, XML (xml)

If you open the registration URL:

http://127.0.0.1:8000/register/Code language: Python (python)

you’ll see the registration form:

Once registered successfully, you’ll be logged in automatically:

However, we have one issue. Jane can view, update, and delete other users’ tasks.

To fix this, you need to filter the tasks that belong to the currently logged user in all the classes by adding the get_queryset() methods to the TaskList, TaskDetail, TaskCreate, TaskUpdate, TaskDelete classes.

from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from .models import Task


class TaskList(LoginRequiredMixin, ListView):
    model = Task
    context_object_name = 'tasks'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['tasks'] = context['tasks'].filter(user=self.request.user)
        return context
    
class TaskDetail(LoginRequiredMixin, DetailView):
    model = Task
    context_object_name = 'task'
    
    def get_queryset(self):
        base_qs = super(TaskDetail, self).get_queryset()
        return base_qs.filter(user=self.request.user)  
    
    
class TaskUpdate(LoginRequiredMixin, UpdateView):
    model = Task
    fields = ['title','description','completed']
    success_url = reverse_lazy('tasks')
    
    def form_valid(self, form):
        messages.success(self.request, "The task was updated successfully.")
        return super(TaskUpdate,self).form_valid(form)
      
    def get_queryset(self):
        base_qs = super(TaskUpdate, self).get_queryset()
        return base_qs.filter(user=self.request.user)
 

class TaskDelete(LoginRequiredMixin, DeleteView):
    model = Task
    context_object_name = 'task'
    success_url = reverse_lazy('tasks')
    
    def form_valid(self, form):
        messages.success(self.request, "The task was deleted successfully.")
        return super(TaskDelete,self).form_valid(form)
      
    def get_queryset(self):
        base_qs = super(TaskDelete, self).get_queryset()
        return base_qs.filter(user=self.request.user)
Code language: Python (python)

Now, if you log in as Jane, you’ll see an empty todo list:

django formview - empty todo list

If you create a new task, you’ll see only that task in the todo list:

If you attempt to access John’s tasks while logging in as Jane, you’ll get a 404 error like this:

django formview - 404

You can download the complete code here.

Summary

  • Use Django FormView to create a view that displays a form.
Did you find this tutorial helpful ?