Using formsets for course modules

Django comes with an abstraction layer to work with multiple forms on the same page. These groups of forms are known as formsets. Formsets manage multiple instances of a certain Form or ModelForm. All forms are submitted at once and the formset takes care of the initial number of forms to display, limiting the maximum number of forms that can be submitted and validating all the forms.

Formsets include an is_valid() method to validate all forms at once. You can also provide initial data for the forms and specify how many additional empty forms to display.

You can learn more about formsets at https://docs.djangoproject.com/en/2.0/topics/forms/formsets/ and about model formsets at https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#model-formsets.

Since a course is divided into a variable number of modules, it makes sense to use formsets to manage them. Create a forms.py file in the courses application directory and add the following code to it:

from django import forms
from django.forms.models import inlineformset_factory
from .models import Course, Module

ModuleFormSet = inlineformset_factory(Course,
Module,
fields=['title',
'description'],
extra=2,
can_delete=True)

This is the ModuleFormSet formset. We build it using the inlineformset_factory() function provided by Django. Inline formsets are a small abstraction on top of formsets that simplify working with related objects. This function allows us to build a model formset dynamically for the Module objects related to a Course object.

We use the following parameters to build the formset:

  • fields: The fields that will be included in each form of the formset.
  • extra: Allows us to set the number of empty extra forms to display in the formset.
  • can_delete: If you set this to True, Django will include a Boolean field for each form that will be rendered as a checkbox input. It allows you to mark the objects you want to delete.

Edit the views.py file of the courses application and add the following code to it:

from django.shortcuts import redirect, get_object_or_404
from django.views.generic.base import TemplateResponseMixin, View
from .forms import ModuleFormSet

class CourseModuleUpdateView(TemplateResponseMixin, View):
template_name = 'courses/manage/module/formset.html'
course = None

def get_formset(self, data=None):
return ModuleFormSet(instance=self.course,
data=data)

def dispatch(self, request, pk):
self.course = get_object_or_404(Course,
id=pk,
owner=request.user)
return super(CourseModuleUpdateView,
self).dispatch(request, pk)

def get(self, request, *args, **kwargs):
formset = self.get_formset()
return self.render_to_response({'course': self.course,
'formset': formset})

def post(self, request, *args, **kwargs):
formset = self.get_formset(data=request.POST)
if formset.is_valid():
formset.save()
return redirect('manage_course_list')
return self.render_to_response({'course': self.course,
'formset': formset})

The CourseModuleUpdateView view handles the formset to add, update, and delete modules for a specific course. This view inherits from the following mixins and views:

  • TemplateResponseMixin: This mixin takes charge of rendering templates and returning an HTTP response. It requires a template_name attribute that indicates the template to be rendered and provides the render_to_response() method to pass it a context and render the template.
  • View: The basic class-based view provided by Django.

In this view, we implement the following methods:

  • get_formset(): We define this method to avoid repeating the code to build the formset. We create a ModuleFormSet object for the given Course object with optional data.
  • dispatch(): This method is provided by the View class. It takes an HTTP request and its parameters and attempts to delegate to a lowercase method that matches the HTTP method used: a GET request is delegated to the get() method and a POST request to post(), respectively. In this method, we use the get_object_or_404() shortcut function to get the Course object for the given id parameter that belongs to the current user. We include this code in the dispatch() method because we need to retrieve the course for both GET and POST requests. We save it into the course attribute of the view to make it accessible to other methods.
  • get(): Executed for GET requests. We build an empty ModuleFormSet formset and render it to the template together with the current Course object using the render_to_response() method provided by TemplateResponseMixin.
  • post(): Executed for POST requests. In this method, we perform the following actions:
  1. We build a ModuleFormSet instance using the submitted data.
  2. We execute the is_valid() method of the formset to validate all of its forms.
  3. If the formset is valid, we save it by calling the save() method. At this point, any changes made, such as adding, updating, or marking modules for deletion, are applied to the database. Then, we redirect users to the manage_course_list URL. If the formset is not valid, we render the template to display any errors, instead.

Edit the urls.py file of the courses application and add the following URL pattern to it:

path('<pk>/module/',
views.CourseModuleUpdateView.as_view(),
name='course_module_update'),

Create a new directory inside the courses/manage/ template directory and name it module. Create a courses/manage/module/formset.html template and add the following code to it:

{% extends "base.html" %}

{% block title %}
Edit "{{ course.title }}"
{% endblock %}

{% block content %}
<h1>Edit "{{ course.title }}"</h1>
<div class="module">
<h2>Course modules</h2>
<form action="" method="post">
{{ formset }}
{{ formset.management_form }}
{% csrf_token %}
<input type="submit" class="button" value="Save modules">
</form>
</div>
{% endblock %}

In this template, we create a <form> HTML element, in which we include formset. We also include the management form for the formset with the variable {{ formset.management_form }}. The management form includes hidden fields to control the initial, total, minimum, and maximum number of forms. You can see it's very easy to create a formset.

Edit the courses/manage/course/list.html template and add the following link for the course_module_update URL below the course edit and delete links:

<a href="{% url "course_edit" course.id %}">Edit</a>
<a href="{% url "course_delete" course.id %}">Delete</a>
<a href="{% url "course_module_update" course.id %}">Edit modules</a>

We have included the link to edit the course modules. Open http://127.0.0.1:8000/course/mine/ in your browser. Create a course and click the Edit modules link for it. You should see a formset as follows:

The formset includes a form for each Module object contained in the course. After these, two empty extra forms are displayed because we set extra=2 for ModuleFormSet. When you save the formset, Django will include another two extra fields to add new modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.109.4