Django Forms and Model Forms

(Comments)

What is a form?

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 Forms

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.

Initialilzing forms in views

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).

Validation of data with forms

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.

Rendering Forms as HTML

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().

Model forms

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.).

Saving model forms in to database

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.

Comments

Recent Posts

Archive

2022
2021
2020
2019
2018
2017
2016
2015
2014

Tags

Authors

Feeds

RSS / Atom