Exploring the Evolution of Django Forms in Version 5.0

Anderson Magalhaes
Django Unleashed
Published in
6 min readDec 6, 2023

Hello, there! In this article, I will delve into the intricacies of Django Forms, highlighting the significant changes introduced in Django 5.0. We’ll begin by exploring various options and discussing their use. Additionally, I recommend checking out our article on microservices and monoliths, which can assist you in making informed decisions between the two architectural approaches.

Okay, let's go!!!

This article was written to help and inform about the changes in Django Forms. The following code examples are purely illustrative, intended to demonstrate the functionality of our solution.

What is the form?

Django Forms are a versatile tool, simplifying various aspects of development. By configuring forms with Python, we can define attributes such as labels, help text, and enablement, among others. Furthermore, the ability to create validations seamlessly translates into visible results on the front-end, almost like magic.

You can choose from a variety of field types, for example:

  • CharField
  • IntegerField
  • DateField
  • TimeField
  • DateTimeField
  • RegexField
  • EmailField
  • FileField
  • ImageField
  • URLField
  • BooleanField
  • ChoiceField
  • MultipleChoiceField
  • MultiValueField
  • FloatField
  • DecimalField
  • SplitDateTimeField
  • GenericIPAddressField
  • JSONField
  • SlugField
  • UUIDField

But where can we apply this knowledge, and how do we go about using it?

Forms offer flexibility in usage; you can integrate them into custom views by creating the HTML structure and incorporating form fields within it. Alternatively, you can leverage views to address issues without significantly altering the front-end.

In the following example, we’ll create the ExampleForm utilizing various Django form fields:

from django import forms

class ExampleForm(forms.Form):
name = forms.CharField(label="Name", initial="Anderson")
blog_url = forms.URLField(label="URL", help_text="This is the map URL (ex.: https://.../abc.png)", initial="https://oandersonbm.medium.com/")

comment = forms.Textarea()

image = forms.ImageField(label="Image", help_text="Select a image", required=False)
json = forms.JSONField(label="JSON", help_text="Input the JSON here", required=False)

date_field = forms.DateField(label="Date", help_text="Date field", required=False,
input_formats=['%Y-%m-%d'],
widget=forms.DateInput(attrs={'type': 'date'}))
time_field = forms.TimeField(label="Time", help_text="Time field", required=False,
input_formats=['%H:%M:%S'],
widget=forms.TimeInput(attrs={'type': 'time'}))
date_time_field = forms.DateTimeField(label="Datetime", help_text="Date and time field", required=False,
input_formats=['%Y-%m-%d %H:%M:%S'],
widget=forms.DateTimeInput(attrs={'type': 'datetime-local'}))
split_datetime_field = forms.SplitDateTimeField(
label='Split Date And Time',
input_date_formats=['%Y-%m-%d'],
input_time_formats=['%H:%M'],
widget=forms.SplitDateTimeWidget(
date_attrs={'type': 'date'},
time_attrs={'type': 'time'},
),
)

slug_field = forms.SlugField(
label='Slug',
max_length=50,
)
regex_field = forms.RegexField(
regex=r'^\d{6}-[A-Z]-\d{1}$',
error_messages={'invalid': 'Please enter a valid pattern like "123456-A-Z" or "123456-A-9".'},
label='Regex'
)

float_field = forms.FloatField(
label='Float field',
widget=forms.TextInput(attrs={'type': 'number', 'step': '0.05'}),
)
decimal_field = forms.DecimalField(
label='Decimal field',
max_digits=10,
decimal_places=2,
widget=forms.TextInput(attrs={'type': 'number', 'step': '0.05'}),
)

uuid_field = forms.UUIDField(required=False)
ip_field = forms.GenericIPAddressField(required=False)

multi_choices_field = forms.MultipleChoiceField(required=False, choices=[("1", "ABC Choice"), ("2", "CDE Choice")], widget=forms.CheckboxSelectMultiple)
combo_box_field = forms.ChoiceField(required=False, choices=[("1", "ABC Box"), ("2", "CDE Box")], widget=forms.RadioSelect)
boolean_field = forms.BooleanField(
required=True,
initial=False,
label='I agree to the terms and conditions'
)

Below, we provide a template (index.html) where we can make use of these fields:

... <!-- SUPRESS -->
<form method="POST">
{% csrf_token %}
<div>
{{form.as_p}}
</div>
<button>SUBMIT</button>
</form>
... <!-- SUPRESS -->

And finally, we can create a view:

from django.shortcuts import render

from django.core.handlers.wsgi import WSGIRequest
from example.forms import ExampleForm

def index(request: WSGIRequest):
if request.method == "POST":
example_form = ExampleForm(request.POST)
example_form.is_valid()
context = {"form": example_form}
else:
context = {"form": ExampleForm()}

return render(request, "index.html", context)

Explain… What's happen when I use "as_p" or "as_div" and the new "as_field_group"?

as_p:

  • When you use as_p on a form instance in Django, it renders the form fields as paragraphs (<p> HTML tags).
  • This is useful for creating a simple, vertically stacked layout of form fields, with each field on a new line.

as_div:

  • Similar to as_p, as_div renders the form fields, but this time it wraps each field in a <div> HTML tag.
  • It provides a layout where each form field is enclosed within a separate division, allowing for more styling flexibility.

as_field_group(new):

  • The new addition, as_field_group, is designed to render form fields grouped together.
  • It organizes the fields in a way that facilitates styling them as a cohesive unit, often used in scenarios where fields are related or belong to a common section in the form.

When we pass our forms.Form via the context with the name 'form,' we have the flexibility to convert all fields at once. Typically, we employ something like {{ form.as_p }} or {{ form.as_div }}, which generates a wrapping structure around each field. In the first case, it produces <p>...</p>, and in the latter, <div>...</div>. This results in a structured representation, as illustrated with a forms.URLField:

<div>
<label for="id_image_url">URL:</label>
<div class="helptext" id="id_image_url_helptext">This is the map URL (ex.: https://.../abc.png)</div>
<input type="url" name="image_url" required="" aria-describedby="id_image_url_helptext" id="id_image_url">
</div>

We also have the option to convert each field individually using one of these two methods, with the newer approach introduced in Django 5.0:

{{ form.image_url.as_field_group }}

Alternatively, we can stick to the conventional method, up until this point:

{{ form.image_url.label_tag }}
{% if form.image_url.help_text %}
<div class="helptext" id="{{ form.image_url.auto_id }}_helptext">
{{ form.image_url.help_text|safe }}
</div>
{% endif %}
{{ form.image_url.errors }}
{{ form.image_url }}

“These two methods yield the same result, resembling something like this:

<label for="id_image_url">URL:</label>
<div class="helptext" id="id_image_url_helptext">This is the map URL (ex.: https://.../abc.png)</div>
<input type="url" name="image_url" required="" aria-describedby="id_image_url_helptext" id="id_image_url">

Validation

After validating a field and encountering an error, the workflow looks something like this:

<ul class="errorlist">
<li>Enter the custom error message.</li>
</ul>

In our example, we encounter a situation like this:

When to choose this approach?

This method is preferable when there’s no requirement to separate the frontend from the backend, making it a practical choice. It proves particularly advantageous when a Proof of Concept (POC) is all that’s needed, expediting the process with minimal effort — perhaps requiring only the addition of some CSS, for instance. On the other hand, if working within a team or necessitating a more customized approach, one can opt to use Django without ‘as_field_group’ or tailor the solution extensively.

Conclusion

This change becomes relevant when leveraging Django for solutions requiring fewer frontend features and more backend validations. With Django’s assistance, it becomes feasible to develop full-stack solutions, implementing validations on both the backend and frontend.

I trust this information proves valuable to you. Thank you for investing your time in reading this. :D

LinkedIn: https://www.linkedin.com/in/andersonbmagalhaes/

CSS (Extra)

If you are here yet, take this css to have a different experience.

* {margin:0; padding:0; font-family:sans-serif;
box-sizing: border-box;}
*:focus {outline: none;}
form{
padding: 1rem;
}
form > div{
position:relative;
}

form > div > p{
margin-bottom: 1rem;
}

form ul.errorlist{
list-style: none;
width: 100%;
text-align: right;
position: absolute;
font-size:.8rem;
color:#e23f3f;
}
form > div > p > label{
display:block;
font-weight: bold;
padding-bottom:2px;
}

button{
display: block;
margin: 1rem auto;
padding: 1rem 3rem;
border-radius: .5rem;
color: #eaf4ff;
background: #307cf6;
border: 0;
}
button:hover{
color: #d2e4f8;
background: #1a60d1;
}
button:active{
color: #d2e4f8;
background: #4c83dc;
}
input:not([type="checkbox"]):not([type="radio"]), textarea{
display:block;
border:1px solid #AAA;
width:100%;
padding: .5rem;
margin-top: .1rem;
border-radius: .3rem;
}
input:not([type="checkbox"]):not([type="radio"])[aria-invalid="true"],textarea[aria-invalid="true"]{
border-color:#e23f3f;
}

.helptext{
color: #999;
font-size: .8rem;
margin-top: 2px;
display:block;
}
label[for="id_boolean_field"]{
display:inline-block;
line-height:1rem;
}

References

--

--

Django Unleashed
Django Unleashed

Published in Django Unleashed

Unleashing the Full Potential of Web Development

Anderson Magalhaes
Anderson Magalhaes

Written by Anderson Magalhaes

Software Engineer | Python | NodeJS | FastAPI | Django | React | Typescript | AWS CLF-C02 | Fullstack Developer

Responses (1)