Exploring the Evolution of Django Forms in Version 5.0
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;
}