Skip to main content

Models And Forms

In the forms chapter we saw how to generate this form:

Options list

By using the following code in forms.py:

class CourseForm(forms.Form):
name = forms.CharField(label="Name", max_length=128)
enrolled_students = forms.IntegerField(label="Students")
TIMES_OF_DAY = (
(1, "Morning"),
(2, "Afternoon"),
(3, "Evening")
)
time = forms.ChoiceField(label="Time", choices=TIMES_OF_DAY)

Now, compare how similar this form definition is to the definition of our Course model in models.py:

class Course(models.Model):
name = models.CharField(max_length=128)
enrolled_students = models.IntegerField()
TIMES_OF_DAY = (
(1, "Morning"),
(2, "Afternoon"),
(3, "Evening")
)
time = models.PositiveSmallIntegerField(choices=TIMES_OF_DAY, null=True)

Django provides a feature called ModelForm to generate forms from models with the aim of avoiding code repetition: both of the fields and their respective types, and of their validations (max_length, choices, etc.)

So, let's replace the previous forms.py code with this one:

from django.forms import ModelForm

from .models import Course


class CourseForm(ModelForm):

class Meta:
model = Course
fields = ["name", "enrolled_students", "time"]

The procedure to define a ModelForm is simple:

  1. Create a class that inherits from django.forms.ModelForm.
  2. Inside this class, create another one named Meta.
  3. Inside Meta, specify the model from which you want to generate the form, and the list of fields of that model that you want the form to display.

That's enough for the form definition. What how do we process this form? Remember that the function in views.py that we used to fetch the data submitted in CourseForm and then to insert it into the Course model was the following:

def new_course(request: HttpRequest) -> HttpResponse:
if request.method == "POST":
form = forms.CourseForm(request.POST)
if form.is_valid():
Course.objects.create(
name=form.cleaned_data["name"],
registered=form.cleaned_data["registered"]
)
return HttpResponseRedirect(reverse("courses"))
else:
form = forms.CourseForm()
ctx = {"form": form}
return render(request, "myapp/new_course.html", ctx)

Since CourseForm is now a ModelForm, in which we specified its source model as Course, it is no longer necessary to fetch the submitted data manually via form.cleaned_data nor to insert it in the model using Course.objects.create(). Forms that inherit from ModelForm will perfom this operations automatically when invoking the save() method. So the previous view can be thus simplified:

def new_course(request: HttpRequest) -> HttpResponse:
if request.method == "POST":
form = forms.CourseForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse("courses"))
else:
form = forms.CourseForm()
ctx = {"form": form}
return render(request, "myapp/new_course.html", ctx)

Go to http://127.0.0.1:8000/myapp/new-course and test the new implementation:

ModelForm

Note that the label of each of the form fields ("Name", "Enrolled students", "Time") was built by Django from the names of the fields (name, enrolled_students, time). This is useful during development, but generally you'll want to make those names explicit in the model definition:

class Course(models.Model):
name = models.CharField("Name", max_length=128)
enrolled_students = models.IntegerField("Students")
TIMES_OF_DAY = (
(1, "Morning"),
(2, "Afternoon"),
(3, "Evening")
)
time = models.PositiveSmallIntegerField("Time", choices=TIMES_OF_DAY, null=True)