Working with forms: creating items
This episode covers :-
1) How to create forms with ModelForm.
- Setup :-
Terminal
cp -fr 15-Base-Project 21-Forms-Create
cd 21-Forms-Create
source ../venv/bin/activate
- Creating the edit form :-
Create an edit.html file in the myapp templates folder:
Template location
├── myapp
│ ├── templates
│ │ └── myapp
│ │ ├── edit.html # < here
│ │ ├── index.html
Fill it with these lines:
myapp/templates/myapp/edit.html
{% extends 'base/base.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<div class="row justify-content-center">
<div class="col-6">
{{ form }}
<hr class="mb-3">
<button class="btn btn-primary btn-lg btn-block" ty\
pe="submit">Submit</button>
</div>
</div>
</form>
{% endblock %}
We will use this template to create and edit flower items.
- Creating the form class :-
Create forms.py file in the myapp folder:
forms.py location
├── myapp
..
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py # < here
Fill it with these lines:
myapp/forms.py
from django import forms
from django.forms import ModelForm
from .models import Flower
class MyForm(ModelForm):
title = forms.CharField(label='Title',
widget= forms.TextInput(attrs={'class': 'form-contr\
ol '}))
class Meta:
model = Flower
fields = ['title']
- Updating urlpatterns :-
Edit mysite app urls.py file and add the create path:
mysite/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', myapp_views.index, name='index'),
path('flower/create/', myapp_views.create, name='create\
'), # < here
]
- Creating the view function :-
Edit myapp views.py file and add a create view below the index view:
myapp/views.py
from django.shortcuts import render
from .models import Flower
from django.http import HttpResponseRedirect # < here
from .forms import MyForm # < here
def index(request):
...
def create(request): # < here
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/')
else:
form = MyForm()
return render(request, 'myapp/edit.html', {'form': form\
})
- Adding a menu item :-
Edit base app base.html file and add a menu link to the flower creation form:
base/templates/base/base.html
<ul>
<li><a>Home</a></li>
<li class="nav-item"> <!-- here -->
<a class="nav-link" href="flowercreate/">
Create Flower
</a>
</li>
</ul>
I removed unimportant CSS classes for the book. The complete markup is available at the GitHub repository.
Visit flowercreate/ and create a flower:
The new flower will now show up on the frontpage:
Note that the bootstrap class card-columns creates a masonry like arrangement, not a grid.
In Details :-
- Protecting against cross site request forgeries :-
In the myapp edit.html file we define a CSRF token:
myapp/templates/myapp/edit.html
<form action="" method="post">
{% csrf_token %} # < here
...
</form>
This token adds protection against Cross Site Request Forgeries where malicious parties can cause visitor’s browser to make a request to your website. The cookies in the visitor browser make the app think that the request came from an authorized source.
Use the token only in POST requests. You don’t need it with GET requests. Any request that has a potential to change the system shoud be a POST request. Like when we add flowers to the database.
GET requests are often used in situations where the system state is not changed, like when we query database with the search form. The q search word parameter is public data we don’t need to hide. You want to be able to share links like this: https://samulinatri.com/search?q=Django.
Also you shouldn’t use the token with forms that point to external URLS. This introduces a vulnerability as the token is leaked. action="" in the form means that the POST data is sent to the current internal URL (flowercreate/).
- Adding form fields :-
Easiest way to generate HTML markup for the form fields is to use the {{ form }} template variable:
myapp/templates/myapp/edit.html
<div class="col-6">
{{ form }}
</div>
This will produce the following HTML:
Generated HTML
<div class="col-6">
<label for="id_title">Title:</label>
<input type="text" name="title" maxlength="255" class="\
form-control" required="" id="id_title">
</div>
- Using the Form class :-
Form class represents a form. It describes a form in a similar way the Flower model describes how fields map to database fields. With forms the fields map to HTML elements.
ModelForm is a helper class that creates that Form class from a Model:
myapp/forms.py
class MyForm(ModelForm):
title = forms.CharField(label='Title',
widget= forms.TextInput(attrs={'class': 'form-contr\
ol '}))
class Meta:
model = Flower
fields = ['title']
With ModelForm we don’t need to specify the fields again. We already add the fields in the Flower model:
Fields are already specified in the models.py file
class Flower(models.Model):
title = models.CharField(max_length=255, default='')
description = models.TextField(default='')
This would be enough to create a form to edit all Flower fields:
myapp/forms.py
class MyForm(ModelForm):
class Meta:
model = Flower
fields = '__all__' # < here
It’s recommended to explicitly specify all the fields like this though:
Fields should be explicitly specified
fields = ['title', 'description']
Otherwise you could unintentionally expose fields to users when you add them to the model.
A form field is represented as an HTML “widget” that produces some default markup. We can modify that widget in the form definition:
Adding CSS class for Bootstrap
title = forms.CharField(label='Title',
widget = forms.TextInput(attrs={'class': 'form-cont\
rol '}))
The only reason we did this is because we wanted to add the form-control CSS class to the title input element. This way we can take advantage of the Bootstrap textual form control styling.
- Examining the view function :-
In the myapp views.py file we added the create view function:
myapp/views.py
def create(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/')
else:
form = MyForm()
return render(request, 'myapp/edit.html', {'form': form\
})
First we check if the request is POST. If it’s not, we create an empty form that we pass to the edit.html template:
Empty form is passed to the template
if request.method == 'POST':
..
else
form = MyForm()
return render(request, 'myapp/edit.html', {'form': form})
This is the default scenario when you first visit the flowercreate/ page. We need to create the form object so that the form HTML can be generated using the template tags.
If the request is POST, we create the form object and populate it with the data from the request:
Populating the form object with the POST data
if request.method == 'POST':
form = MyForm(request.POST)
Then we check if the form data is valid and save the flower:
Validating and saving the data
if form.is_valid():
form.save()
return HttpResponseRedirect('/')
Django has built-in validators that it uses internally. For example EmailValidator for email addresses and validate_slug for slugs. If the input doesn’t satisfy the validator, a ValidationError is raised.
The save() method creates the flower object from the data bound to the form and stores it in the database.
When we submit a form using a POST request, our create view will instantiate the form object and populate it with the form data from the request. We “bind” the data to the form. It’s now a “bound” form.
The validated data can be accessed in the form.cleaned_data dictionary:
Accessing validated data
if form.is_valid():
print(form.cleaned_data['title']) # < here
form.save()
return HttpResponseRedirect('/')
This will print the validated title field data in the terminal:
And finally HttpResponseRedirect('/') redirects the visitor to the frontpage.
Summary :-
- Use {% csrf_token %} with internal POST forms to protect against Cross Site Request Forgeries.
- {{ form }} template variable generates markup for all form fields.
- Form class represents a form. Its fields map to HTML elements.
- ModelForm is a helper class that allows us create the Form class from a Django model.
- A form field is represented as an HTML “widget”. You can modify this widget in the form definition.
- The submitted form is processed in the create view.
- Django has built-in validation that triggers a ValidationError when the data doesn’t validate.
- validated data is stored in the form.cleaned_data dictionary.
- In the create view we bind the form data to the form instance.
- form.save() method creates a database object using the bound data.
PRINT THIS POST




No comments:
Post a Comment
If you have any doubts. Please let me know.