Django forms

  • In web development, forms are fundamental components that facilitate user interaction and data submission.
  • Django, a powerful web framework in Python, provides a robust mechanism for handling forms efficiently.

HTML forms

  • an HTML form is an essential part of web development, enabling users to input data and submit it to a server.
  • HTML forms consist of various elements such as text inputs, checkboxes, radio buttons, and dropdowns, defined using HTML tags like <form>, <input>, <textarea>, and <select>.

login.html

<form action="/submit/" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required>
    <button type="submit">Submit</button>
</form>

In this basic example:

  • <form> defines the form.
  • <input> with type="text" creates a text input field.
  • <input> with type="password" creates a password input field.
  • required attribute enforces field validation on the client side.

Django forms

  • Django abstracts the complexity of form handling in web applications by providing Django forms.
  • These are Python classes that generate HTML forms based on model definitions or custom form logic

Django provides two types of forms:

  1. Forms: Used to create forms not directly linked to models.
  2. ModelForms: Used to create forms that are directly linked to models.

Creating a basic form

  • import django forms - from django import forms
  • define a form class
from django import forms

class LoginForm(forms.Form):
    username = forms.EmailField(max_length=63, label='Email')
    password = forms.CharField(widget=forms.PasswordInput, label='Password')
  • forms.CharField field generates HTML input field when rendered
  • forms.PasswordInput widget generates HTML input with type="password"
  • more form fields can be find at django official documentation

Get HTML from Django Form

  • Django form has below render methods

form as div tag - as_div()

form = LoginForm()
html = form.as_div()
print(html)
  • form.as_div() generates below html code
<div>
  <label for="id_username">Username:</label>
  <input type="email" name="username" maxlength="63" required id="id_username">
</div>
<div>
  <label for="id_password">Password:</label>
  <input type="password" name="password" required id="id_password">
</div>

form as p tag - as_p()

form = LoginForm()
html = form.as_p()
print(html)
* form.as_p() generates below html code

<p>
  <label for="id_username">Username:</label>
  <input type="email" name="username" maxlength="63" required id="id_username">
</p>
<p>
  <label for="id_password">Password:</label>
  <input type="password" name="password" required id="id_password">
</p>

form as ul tag - as_ul()

form = LoginForm()
html = form.as_ul()
print(html)
  • form.as_ul() generates below html code
<li>
  <label for="id_username">Username:</label>
  <input type="email" name="username" maxlength="63" required id="id_username">
</li>
<li>
  <label for="id_password">Password:</label>
  <input type="password" name="password" required id="id_password">
</li>

form as table tag - as_table()

form = LoginForm()
html = form.as_table()
print(html)
  • form.as_table() generates below html code
<tr>
  <th><label for="id_username">Username:</label></th>
  <td>
    <input type="email" name="username" maxlength="63" required id="id_username">
  </td>
</tr>
<tr>
  <th><label for="id_password">Password:</label></th>
  <td>
    <input type="password" name="password" required id="id_password">
  </td>
</tr>

Render form in a template

  • Create a simple view that uses LoginForm

my_app/views.py

from django.shortcuts import render
from .forms import LoginForm

def login_view(request):
    context = {"form": LoginForm()}
    return render(request, "login.html", context)
  • use the form with template

templates/login.html

<form action="" method="post">
    {% csrf_token %}
    {{ form.as_div }}
    <input type="submit" value=Submit">
</form>
  • other html form methods like as_p, as_ul, as_table can also be used.
  • csrf_token template tag generates a secure random token that prevents prevent CSRF attacks

Working with form data

django form - initial data

  • We need to pass the initial data data to django form using kwarg initial like below.
from .forms import LoginForm

initial = {"username": "rockstar007"}

form = LoginForm(initial=initial)
print(form.as_p())
  • It generates below HTML code
<p>
  <label for="id_username">Username:</label>
  <input type="email" name="username" value="rockstar007" maxlength="63" required id="id_username">
</p>
<p>
  <label for="id_password">Password:</label>
  <input type="password" name="password" required id="id_password">
</p>
  • from the above HTML code, we can find that the username input has value rockstar007.

django form - validate data

  • django forms also used to validate the data
  • when user submits the data to the app, it can be validated like below.
data = {"username": "rockstar007", "password": "secret"}
form = LoginForm(data=data)
is_form_valid = form.is_valid()
print(is_form_valid)
  • above code gives the output as False.
  • because, the username is an email field it expects the value to be an email.
  • we can have custom field validations as well.

django form - detect data changes

  • If we want to find the initial data changed when form submitted then we can detect it.
  • It can be done using the attribute - is_changed
initial_data = {"username": "[email protected]", "password": "secret"}
data = {"username": "[email protected]", "password": "secret"}
form = LoginForm(data=data, initial=initial_data)
is_form_changed = form.has_changed()
print(is_form_changed)
  • Above code gives the output as True. Because, the data is different from initial data.

django form - custom field validation

  • we can add field-level validation to forms by defining methods that start with clean_ followed by the name of the field.
  • lets look at the form below
from django import forms

class LoginForm(forms.Form):
    username = forms.EmailField(max_length=63, label='Email')
    password = forms.CharField(widget=forms.PasswordInput, label='Password')

    def clean_password(self):
        password = self.cleaned_data.get('password')
        if len(password) < 8:
            raise forms.ValidationError('Password must be at least 8 characters long.')
        return password
  • above form will raise the error if the password value length is below 8 characters
  • is_valid() method is used to validate the form data.

django form - custom data validation

  • clean method is used to add validation that depends on multiple fields or other logic that doesn't fit into a single field's validation.
  • let's look at the example below.
from django import forms

class RegistrationForm(forms.Form):
    username = forms.CharField(max_length=100)
    password = forms.CharField(widget=forms.PasswordInput)
    confirm_password = forms.CharField(widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        confirm_password = cleaned_data.get('confirm_password')

        if password and confirm_password:
            if password != confirm_password:
                raise forms.ValidationError('Passwords do not match.')

        return cleaned_data
  • above form validates the form data and raises validation error if password doesn't match with confirm_password.
  • is_valid() method is used to validate the form data.

django form - form errors

  • if the form is not valid then it will raise the validation errors.
  • we can use the attribute errors to get the form errors.
from django import forms

class RegistrationForm(forms.Form):
    username = forms.CharField(max_length=100)
    password = forms.CharField(widget=forms.PasswordInput)
    confirm_password = forms.CharField(widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        confirm_password = cleaned_data.get('confirm_password')

        if password and confirm_password:
            if password != confirm_password:
                raise forms.ValidationError('Passwords do not match.')

        return cleaned_data

data = {"username": "rockstart007", "password": "secret", "confirm_password": "sec"}
form = RegistrationForm(data=data)
is_valid = form.is_valid()
print(form.errors)
  • It will give the below errors
<ul class="errorlist"><li>__all__<ul class="errorlist nonfield"><li>Passwords do not match.</li></ul></li></ul>
  • To get errors as json then use the method forms.errors.as_json()

Rendering form fields in template

Iterate over form fields

  • Lets consider the form RegistrationForm
  • Can use for loop to iter through form fields
<div>
  <p>{{ form.non_field_errors }}</p>
{% for field in form %}
  <div class="fieldWrapper">
      {{ field.errors }}
      {{ field.label_tag }}
      {{ field }}
  </div>
{% endfor %}
</div>
  • form.non_field_errors - renders non-field error messages
  • field.errors - renders error messages if any
  • field.label_tag - renders label tag
  • field - renders the form field [i.e input, textarea, select, etc.]

Manually render form fields

  • Lets consider the form RegistrationForm
<div>
  <!-- non field errors -->
  {{ form.non_field_errors }}
  <!-- username -->
  <div class="fieldWrapper">
    {{ form.username.errors }}
    <label for="{{ form.username.id_for_label }}">Username</label>
    {{ form.username }}
  </div>
  <!-- password -->
  <div class="fieldWrapper">
    {{ form.password.errors }}
    <label for="{{ form.password.id_for_label }}">Password</label>
    {{ form.password }}
  </div>
  <!-- confirm password -->
  <div class="fieldWrapper">
    {{ form.confirm_password.errors }}
    <label for="{{ form.password.id_for_label }}">Confirm Password</label>
    {{ form.confirm_password }}
  </div>
</div>

References