Develop your first web application in Django 1.10 – Part 4

Learn how to use Django Forms to submit user input data into Database.

In previous post I discussed about Db interaction with a Django application and how to pass data in a html view and display it. Now we will move onto next step and will learn how to use HTML forms to store data in database.

Django Forms

The form I am going to deal is.. yeah, the project form so that I can submit project details. I will create a new folder under tracker/templates and will call it project. The reason to do is that I want to view file separate w.r.t modules. In the file I am going to add following HTML:

{% extends "layouts/master.html" %}
{% block content %}
    <h2>Add New Project</h2>
    <form method="post">
        {% csrf_token %}
      <div class="form-group">
        <label for="name">Name:</label>
        <input type="text" class="form-control" id="name">
      </div>
      <div class="form-group">
        <label for="description">Description:</label>
          <textarea name="description" id="description" cols="30" rows="10" class="form-control"></textarea>
      </div>

      <button type="submit" class="btn btn-primary btn-block">Submit</button>
</form>
{% endblock %}

The first line is extended the template and enclosing the FORM html in block content. This is a typical bootstrap form. You should notice {% csrf_token %}. If you have worked on MVC frameworks before then this should not be alien for you but if you have not, CSRF stands for Cross-Site Request Forgery  is a kind of attack that let makes site to submit information on behalf of user and since the server has no idea, it believes it was the user who submitted the info. CSRF Token is a kind of ticket or pass for each request; when a form is submitted, the server issues a token for that particular request and when the user submits form, if it finds that token, it welcomes otherwise kick the user out by considering it a hijacked request.

The form is ready, next up, we need to create a new method in views.py.

def add_new_project(request):
    return render(request, 'project/add.html')

render accepts the form I just created so that it can be viewed on browser. But, how? I mean which URL should I use to access it? hmm. Yeah right, we need to add another entry in tracker/urls.py. Before I do that, a little correction in main urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('tracker.urls')),
]

Earlier there was a $ sign in the end for tracker urls which will not let append new subfolders since $ closes the boundary. I just removed it and then I move on to tracker/urls.py and add the following entry:

urlpatterns = [
    url('^$', views.index, name='index'),
    url('^project/new/$', views.add_new_project, name='add_new_project')
]

Now.. if I go to http://127.0.0.1:8000 then I will be seeing the following screen:

 

If you notice I added a new description field. We need this field in our database as well. So, let’s make changes in models.py file:

description = models.TextField(default=None)

Then run makemigrations command:

Adnans-MacBook-Pro:ohbugztracker AdnanAhmad$ python manage.py makemigrations
Migrations for 'tracker':
  tracker/migrations/0004_project_description.py:
    - Add field description to project
Adnans-MacBook-Pro:ohbugztracker AdnanAhmad$ 

Migration file is generated. Now it’s time to run python manage.py migrate. On running it gave the error:

Running migrations:
  Applying tracker.0004_project_description...Traceback (most recent call last):
  File "/anaconda3/anaconda/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/anaconda3/anaconda/lib/python3.5/site-packages/django/db/backends/sqlite3/base.py", line 337, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: NOT NULL constraint failed: tracker_project.description

The thing is, I did not let the field accept null. So I changed it to:

description = models.TextField(max_length=2000, blank=True, null=True, default='')

And it worked. I was setting blank but not null and there’s definitely difference between a null and blank.

Now I am going to set up Form class in forms.py. Create tracker/forms.py and in the file write:

from django import forms
from .models import Project


class ProjectForm(forms.Form):
    title = forms.CharField(label='Enter Title', help_text='Custom CMS',
                            widget=forms.TextInput(attrs={'class': 'form-control'}))
    description = forms.CharField(required=False,
                                  widget=forms.Textarea(attrs={'cols': "80", 'rows': "10", 'class': 'form-control'}))

If you notice it is similar to models.py. Here I mentioned labels, help text etc. In widget attribute we pass supporting attributes details like class etc. widget=forms.Textarea tells how to render a certain field. Now open views.py and make changes in add_new_project.

def add_new_project(request):
    form = ProjectForm()
    if request.method == 'POST':
        form = ProjectForm(request.POST)
        if form.is_valid():
            title = form.cleaned_data['title']
            description = form.cleaned_data['description']
            # All set, now enter Data iin DB
    return render(request, 'project/add.html', {'form': form})

First import the from .forms import ProjectForm. We created the form instance and then checking whether the request is GET or POST. If yes, send all POSTed vars to ProjectForm and then check validity, if valid then sanitize input fields. Before I go further and save the record, let’s discuss the render() method here which is not unfamiliar by now. Pass form instance to your template and now your template should be rendered like it:

{% extends "layouts/master.html" %}
{% block content %}
    <h2>Add New Project</h2>
    <form method="post">
        {% csrf_token %}
        {% for field in form %}
              <div class="form-group">
                <label for="{{field.label}}">{{field.label}}:</label>
                  {{field}}
              </div>
         {% endfor %}

      <button type="submit" class="btn btn-primary btn-block">Submit</button>
</form>
{% endblock %}

I am iterating the form fields, label is being set for labeling and the html itself is being set by mentioning {{field}}. If all goes well your form will look like:

Now we have required data, it’s time to save it. I will be using plain old objects.create here.add_new_project will now look like this:

def add_new_project(request):
    form = ProjectForm()
    if request.method == 'POST':
        form = ProjectForm(request.POST)
        if form.is_valid():
            title = form.cleaned_data['title']
            description = form.cleaned_data['description']
            p = Project.objects.create(title=title, description=description)
            if p.pk > 0:
                return redirect('index')
    return render(request, 'project/add.html', {'form': form})

Once I have the cleaned data, I m checking whether record inserted by getting pk field of the new record, if exist, I am returning to index. Time to test. I filled up the form:

And if it’s submitted well then I am seeing the result on main page:

Yay!!

So you saw how to add a new field in model and then rendering and submitted a form by creating a Form class. This should be enough to figure out form submission in Django.

In next post which will probably be the last post of this series, I will discuss Django’s most powerful feature, the Admin Panel

As usual code is available on Github.

 

If you like this post then you should subscribe to my blog for future updates.

* indicates required