Skip to main content

Submitting And Validating Forms

Previously we mentioned that an HTTP method is included in every request that the web browser makes to our Django application. The GET method is the most used, as it indicates that you simply want to fetch the content of a particular page. The POST method, on the other hand, indicates that you want to send data to the server. This method is the one used when working with forms (hence the method="post" attribute used in the previous section.) From a Django view you can know which HTTP method the current request includes via the request.method attribute.

At the moment, our new_course() view is responsible for generating the form and sending it to the template, but it is not prepared to receive and validate the submitted data. Keep in mind that when the form is submitted, the web browser makes a new request to the same view, but using the POST method instead. Thus, in order to distinguish when our view is being invoked via GET (which implies that you should display the form) or via POST (which implies receiving and validating the data), we are going to add the following conditional:

def new_course(request: HttpRequest) -> HttpResponse:
if request.method == "POST":
form = forms.CourseForm(request.POST)
if form.is_valid():
return HttpResponse("Course created successfully!")
else:
form = forms.CourseForm()
ctx = {"form": form}
return render(request, "myapp/new_course.html", ctx)

Strictly speaking, this conditional only checks that when the HTTP method is POST, the form data must be received and validated. For any other method (PUT, DELETE, HEAD, etc.) the form will be displayed.

To validate the data received from a form, which Django saves in the request.POST object, you must first pass this dictionary as an argument to the form which you want to work with: forms.CourseForm. Next, make the form actually perform validation via the is_valid() function. If this function returns True, it means that the data has been validated correctly, so we return a success message.

Now enter http://127.0.0.1:8000/myapp/new-course and check that everything works correctly:

Submit Form

At the bottom of the image the Firefox's developer tools panel is open, which you can access by pressing CTRL + SHIFT + E. From there you can see how the browser first makes a GET request when entering the form page for the first time and then a POST request when pressing the “Add course” button.

Django validations occur when the data has already been received by the server. These are called server-side validations. However, whenever possible, validations will also be implemented in the browser, i.e., client-side. For example, fields of type forms.IntegerField generate an HTML input tag with the type="number" attribute, which implies that the browser itself will be in charge of validating the data entered in that field. However, HTML code can be altered by the user (unlike Python code, which runs on the server), in which case Django validation will show the error messages next to each field. Let's see:

Submit Form

Here you can see how the browser invalidates the submission of the form on the first occasion, since the value entered in the “Students” field was not a number. However, when deleting the HTML code responsible for that validation (type="number"), Django itself invalidated the submission (that is, form.is_valid() returned False and added the error messages to the form, then the form followed its natural path to the template.) Django error messages can be customized, both their text and their style.

Now, once you called form.is_valid() and it returned True, the data of the form fields will be stored in the form.cleaned_data dictionary, in which the keys correspond to the names of the fields defined in the CourseForm class. That is, you will have two keys: form.cleaned_data["name"] and form.cleaned_data["enrolled_students"]. Note that the values of form.cleaned_data have already been converted to their respective Python data types by Django at this point. Therefore, form.cleaned_data["registered"] is an integer, not a string. Considering this, let's add the code that actually adds a new course to the database:

def new_course(request: HttpRequest) -> HttpResponse:
if request.method == "POST":
form = forms.CourseForm(request.POST)
if form.is_valid():
conn = sqlite3.connect("courses.db")
cursor = conn.cursor()
cursor.execute(
"INSERT INTO courses VALUES (?, ?)",
(form.cleaned_data["name"], form.cleaned_data["enrolled_students"])
)
conn.commit()
conn.close()
return HttpResponse("Course created successfully!")
else:
form = forms.CourseForm()
ctx = {"form": form}
return render(request, "myapp/new_course.html", ctx)

Lastly, it would be nice if after the form has been successfully validated and the course created, the user would be sent back to the courses list. To do so, instead of returning HttpResponse, we are going to use HttpResponseRedirect. Additionally, we are going to need the django.urls.reverse() function, which is useful to generate URLs in the views (just as we used the {% url ... %} tag in the templates.) So, first add the following lines to the beginning of views.py:

from django.http import HttpResponseRedirect
from django.urls import reverse

Afterwards, instead of:

return HttpResponse("Course created successfully!")

Put:

return HttpResponseRedirect(reverse("courses"))

Recall that the "courses" given as argument is the name we had set for the URL http://127.0.0.1:8000/myapp/courses in urls.py.

Now check that everything works as expected:

Submit Form With Redirect