(Comments)
Forms are the most generic way of accepting input from the sites users. A form is a set of HTML input elements enclosed in the <form>...</form>
tags. An HTML form submits the data using either GET
or POST
HTTP methods and the data is of the form of key-value pairs. These values can be accessed from the request
object's GET/POST attributes in the view.
Form being the most common way of input on the Web, any sensible web-framework must provide sophisticated ways of handling form inputs. Form inputs also pose most of the security threats a web application can face. Hence it is essential to understand how to properly handle form inputs on any framework.
The Django's Form class gives a logical representation of an HTML form, defines how it should behave and how it appears in the HTML.
Before we start working with the Form class of Django, let us build a basic HTML form - a user login form with just two input fields. The form in HTML will look like this:
<form action="/login/" method="post"> <label for="username">Username: </label> <input id="username" type="text" name="username"> <label for="password">Password: </label> <input id="password" type="password" name="password"> <input type="submit" value="Login"> </form>
There are few things to notice. The above form has two inputs - a text field named username (the name attribute in the html input field is what determines the name of input field) and a password field named password - and a submit button. The form uses POST method to submit form data to server. The action attribute of the form determines the url to which the form data has to be submitted.
To handle this data on the server-side we define a form as follows.
from django import forms class LoginForm(forms.Form): username = forms.CharField(max_length=20) password = forms.CharField(max_length=20, widget=forms.PasswordInput)
Though the password input in the html is of password type in the html, it is just text and must be processed as a CharField
.
Once a form is submitted to a view, we create the form object from the POST data (or GET) as follows:
from .forms import LoginForm def loginView(request): if request.method == 'POST': form = LoginForm(request.POST) # Do something with form and form data
Observe that the fieldnames in the form are same as the name of the input fields of the HTML forms. This lets us directly initialize the form with the request.POST
dictionary (otherwise we need to create a dictionary that maps form field names to the HTML input names).
Creating form object looks fancier but what advantage does that give us? The first advantage that the Forms provide us is the validation of input. Input validation is extremely important in terms of security of web applications. The OWASP documentation describes extensively why we need to validate the input data. And the field types allowed in forms are documented here.
Form data can be validated using the is_valid()
method. The method returns True
if the data is valid and False
otherwise. In our example, we have defined our login form with two fields each with argument max_length
set to 20. When we execute the is_valid()
method of the form, the form checks that the each field's value length is less than 20. If any of the field value is greater than 20, the method returns false. Once the is_valid()
method is called and the data is valid, the data is made available as cleaned_data
attribute of the class.
Based on the is_valid()
method our login view will develop like this.
from .forms import LoginForm def loginView(request): if request.method == 'POST': form = LoginForm(request.POST) if form.is_valid(): # Form data is valid username = form.cleaned_data['username'] password = form.cleaned_data['password'] # Do something with data pass # return some response else: # The data is invalid pass # Respond with some error messages
We might need to do some additional checks on the data validity which we will discuss on a separate blog post how to add validators and write custom validators.
The another advantage that forms provide is handling the appearance of HTML forms. Earlier we have created a login form in HTML. In practice we do not to write any of that HTML. All we need to do is to use the form in our templates with {{ form }}
, and jinja (Django's templating engine) will take care of generating all the HTML necessary. Our template rendering the form will look like this:
<form action="/login" method="post"> {% csrf_token %} {{form}} <input type="submit" value="Login" /> </form>
This renders in the template as
<form action="/login" method="post"> <input type="hidden" name="csrfmiddlewaretoken" value="rBZCng998fLdLTIxUftlFQw4JV0OoNfaCqutdDH9SB8XkyvZh1zVlGRnccQRUBZH"> <label for="id_username">Username:</label> <input type="text" name="username" maxlength="20" id="id_username" required=""> <label for="id_password">Password:</label> <input type="text" name="password" maxlength="20" id="id_password" required=""> <input type="submit" value="Login"> </form>
The rendering of the form fields is controlled with widgets. The widget argument in our login form specifies that the password field must be a of type password
even though the form field type is CharField()
.
Most of the times our user inputs are very close to our models, after all the input data is stored in models. Django provides us with Model Forms, the forms that can be built from models.
Building a form from a model is really easy. We just need to extend the django.forms.ModelForm
class, specify the model (that we want to built the form from) in the Meta
class and specify the fields to include. We will first define a model before building our model form. Here is our model.
class Author(models.Model): name = models.CharField(max_length=40) dob = models.DateField(verbose_name='Date of birth')
Now, we define our model form like:
from django.forms import ModelForm class AuthorForm(ModelForm): class Meta: model = Author fields = '__all__'
The form's model Meta attribute specifies the model to use to build the form and the fields attribute specifies the fields that need to be included in the form. The fields
attribute takes a list (or a tuple) of model field names. The special string '__all__'
specifies that all the fields in the model needs to be included. We can also specify only the fields that need to be excluded with exclude
attribute which will be a list (or a tuple) of field names that should be excluded. Only one of fields
and exclude
attributes can be used in model form.
The table on model form fields states how each model field type is mapped as a form field type. Validation rules of the form fields are automatically built based on the rules on the model fields (length limitations, foreign key fields etc.).
Model forms provide a close binding of forms to the models and allow us to save the data into model directly with the save()
method. In our example of AuthorForm
model form, once the data is validated with the is_valid()
method we can use the form.save()
method to create a model instance (saved to database). The method will return a model instance if it is successful.
Using the model form AuthorForm
to create a model instance of Author
, our view would look like this.
from .forms import AuthorForm def createAuthorView(response): if response.method == 'POST': form = AuthorForm(response.POST) if form.is_valid(): author = form.save() # Do something with the author (model instance) return render(response, 'posts/message.html', {'message': 'Successful'}) else: return render(response, 'posts/create.html', {'form': form}) else: form = AuthorForm() return render(response, 'post/create.html', {'form': form})
The save()
method creates a model instance and saves it to the database. We are, of course, allowed to override the save()
method to create the model instance that suits our requirement. In another blog post, I will explore more on how to validate form data, adding custom validators, control the redered HTML format with widgets etc.
We develop web applications to our customers using python/django/angular.
Contact us at hello@cowhite.com
Comments