Form in Django plays a key part in other components. It glues the data from the web form to the database or other services. It serves as the bridge between user input and the database or other services. A Django Form takes user input from web forms, transforms it into Python objects, prepares the data for storage, and saves it to the database. Conversely, forms can retrieve data from the database or other services, converting Python objects back into HTML/HTTP for user interaction. Additionally, Django Forms provide essential validation and error-handling mechanisms to ensure data integrity.

Forms are also responsible for rendering UI widgets that users interact with, Form is composed of Fields and Field is composed Widget. Django Form allows theming to a certain extent. For example, the Field attribute in the Python object maps to the HTML input attribute.

Browser and HTTP History

The HTTP protocol and web browsers emerged in the late 1980s, evolving substantially throughout the 1990s, and so today. During this era, web browsers were developed as tools for interaction with web servers. Initially, browsers lacked JavaScript capabilities, and UI widgets were limited to basic HTML elements such as input, select, and textarea. These components were fundamental for user interaction with the web server.

Browsers have evolved, CSS styles, JavaScript, and AJAX have been introduced. These technologies have changed the landscape of web development. Django overall design is a multi-page web-app and the frontend web components are designed to be stateless.

Modern Frontend frameworks allow front-end developers to create more interactive UI widgets which are web components and stateful.

There is also another route in-between, Django + frameworkless frontend. This looks welcomed by a majority of Django developers. With simple JS libraries like jQuery, HTMX, Alpine.js, Bootstrap CSS. Django developers can create (I guess) 80% of the web frontend requirement with no steep learning obstacles.

VS. REST API

It is crucial to understand that Django Forms are designed to handle HTTP web forms, which can be viewed as a form of data serialization akin to JSON or XML. The web form format is well-defined within the HTTP protocol, and Django Forms are specifically intended to work with application/x-www-form-urlencoded data.

Web Forms are primarily meant for human input via web browsers, making them less suitable for data exchanges between services. In scenarios requiring service integration and data exchange, REST APIs utilizing JSON are the preferred method (Though there exist other alternatives like GraphQL). Consequently, Django Forms cannot directly handle AJAX JSON requests. For applications with extensive REST API usage, frameworks like Django REST Framework (DRF) and Django Ninja are recommended.

In parallel, REST API layer works a similar role as Django Form. DRF emphasizes on the data serialization, validation, and rate limit too.

Basic Form

Form is a class inheriting form classes under "django.forms". Common examples include django.forms.Form and django.forms.ModelForm. These classes define fields and widgets that represent the form's structure and user interface:

from django import forms
class aForm(forms.Form):
  aField = forms.DateField(hidden=True,widget=forms.HiddenInput)
  bField = forms.CharField(max_length=100,widget=forms.Textarea)

  def clean_aField(self):
    data = self.cleaned_data['aField']
    if data < datetime.date.today():
      raise ValidationError(_('Invalid date'))
    return data

Django Form basically is a Python class. There is a hierarchical structure of Django Form. Form, Field, and Widget are Python classes:

  • Form: consists of fields
    • Field: map Python value(string, number, date, bytes) to HTML Web Form field. Also allows to set the attributes such as height, width, hidden, and so on. The attributes reflect on HTML and correspond to HTML element standards.
      • Widget: a partial HTML template element. The widgets are extensible and essentially some HTML template files inside the Django package. There are third-party packages that augment the widgets even support responsive and advanced UI web widgets.

The above example demonstrates a basic form class aForm with two fields, aField and bField. The aField is a DateField with a hidden widget, while bField is a CharField with a Textarea widget. The clean_aField method is a validation method that checks if the date is valid. If the date is invalid, a ValidationError is raised.

This short example showed that Form is responsible for playing the joint between web frontend and the data integrity. The validation is done by the Form class.

Model Form

Model Form is a variant of Form that is designed to work with Django models easily plus a few more features dealing with HTTP, Database API, and Validation. With Model Form, use META class to declare the connections to the Model class. The form is then passed as context to the template.

# models.py:
class ProfileImage(models.Model):
    pic = models.ImageField(upload_to="profilepics/") # a Model Field
    owner = models.
         OneToOneField(User, on_delete=models.CASCADE) # a Model Field

    def __str__(self):
        return self.owner.username + "-" + self.pic.name

# forms.py:
from .models import ProfileImage
class ProfileImageForm(forms.ModelForm):
    # a Form Field mapping to the model field "pic"
    pic = forms.ImageField(label="Profile Picture") 

    through_ajax = False # not a Field
    ajx_upload_url = reverse_lazy("forms_upload") # not a Field

    class Meta: # Python Meta class syntax
        fields = ["pic"]
        model = ProfileImage

The above example is mapping a Model Form to a Django Model "ProfileImage". The Meta class has two attributes, fields and model. The fields attribute specifies the fields to be included in the form, while the model attribute specifies the model to be used. The pic field is an ImageField with a label "Profile Picture".

Validation

Data integrity is crucial for any web application. Inputting data without validation can lead to significant issues, making it essential to validate all user inputs. In most typical use cases, a Model Form serves as the primary interface for the CRUD (Create, Read, Update, Delete) operations associated with a model in the database.

A Model Form in Django is designed to facilitate seamless interaction with HTTP requests, templates, and Django models. It maintains several key internal states that enable it to function effectively, including:

  • member attributes (dictionaries):
    • initial: The value of the Model object initialized to be served in web form, but not the input data from user
    • is_bound: Indicates whether the form has been bound to data (i.e., whether it has received input).
    • data: The data submitted with the form.
    • cleaned_data: A dictionary containing the validated and cleaned data after the form has been processed.
    • errors: Error Handling If validation fails, the form's errors attribute will contain the validation errors, providing a mechanism for feedback to the user.
  • member methods:
    • has_changed(): A method to check if any data has been modified since the form was initialized.
    • is_valid(): Trigger full_clean(). Validates the form data and returns a boolean indicating whether the form is valid. It checks if the form has errors and is bound
    • full_clean(): A method that performs full validation on the form, the entry point for checking errors. This method also calls models.full_clean() to ensure that the model-level validation is executed.
    • clean(): The main method that is called to perform validation on the entire form.
    • clean_<field>: Individual field validation methods can be defined to handle specific validation rules for each field.
    • save(): Pretty the same as "Model.save()", but "commit:boolean" allow Form to populate Model's field value which was not included in Form fields.

A full example looks like:

# models.py:
from django.db import models
from django.core.exceptions import ValidationError

class ExampleModel(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()

    def clean(self):
        # Custom validation logic for the entire model
        if self.age < 0:
            raise ValidationError("Age cannot be negative.")

    def validate_constraints(self):
        # Additional constraints can be validated here
        if not self.name:
            raise ValidationError("Name cannot be empty.")

# forms.py:
from django import forms
from .models import ExampleModel

class ExampleModelForm(forms.ModelForm):
    class Meta:
        model = ExampleModel
        fields = ['name', 'age']

    def clean(self):
        # Call the parent class's clean method to ensure validation
        cleaned_data = super().clean()

        # Custom validation logic for the form
        name = cleaned_data.get('name')
        age = cleaned_data.get('age')

        if age is not None and age < 0:
            self.add_error('age', "Age cannot be negative.")

        return cleaned_data

    def clean_name(self):
        # Specific field validation for the 'name' field
        name = self.cleaned_data.get('name')
        if not name:
            raise forms.ValidationError("Name cannot be empty.")
        return name

    def clean_age(self):
        # Specific field validation for the 'age' field
        age = self.cleaned_data.get('age')
        if age is not None and age < 0:
            raise forms.ValidationError("Age cannot be negative.")
        return age

# Usage Example
def save_example_model(form_data):
    form = ExampleModelForm(form_data)
    if form.is_valid():
        example_model_instance = form.save(commit=False)
        # some more processing
        example_model_instance.save()
    else:
        print(form.errors)  # Handle form errors

For more information, refer to Django Model Form validation

Validator

Django provides built-in validators as well as the capability to create custom validators, ensuring that data meets specific criteria before being processed or saved.

Theming

Form is just an abstract concept. By itself has no information about how it visually appears. As mentioned above, Form is composed fields and fields corresponds with web widgets.

There are CSS and JavaScript to style the form. Full-fledged form components take a significant amount of effort to design and implement. In the communities, Bootstrap is a popular CSS framework, and minimal JS libraries like jQuery, Alpine.js, and HTMX are popular too.

If using React.js, Django would be used as a REST API backend instead.

There are plenty of extensions that allow Django Form to work in more modern approaches.

  • django-crispy-forms: Facilitates the creation of beautiful forms using various CSS frameworks.
  • render form components with bootstrap, tailwindcss, bulma, foundation, materializecss, semantic-ui.
  • There are also some packages that interpolate Django Form with React, Vue, and Angular.
  • django-bootstrap4, django-bootstrap3, django-bootstrap5 not recommended...

Conclusion

Django Form is a tough topic to deal with.

Your thoughts...