Laughing Blog Tutorial Part 2 - Handling Django Forms

Laughing Blog Tutorial

https://achiengcindy.com/

published on: | by cindy In categories: Tutorial Series , Django

Forms provide a rich way of interacting with users. However, the form handling process is complex. Forms accept user input which poses a major security threat. I like to think of it as an unlocked door. You definitely need some security measures. The rule of the thumb is to never trust a user input or worse malicious attackers taking advantage of unsuspecting users. Caution is required to ensure that the user's data is sanitized and field validation is taken into consideration.

From Class

Django has an inbuilt form Class that allows you to easily create forms. You define the fields of your form, specify the layout, display widgets labels, specify how they have to validate user input and the messages associated with invalid fields. It also provides methods for rendering itself in templates using predefined formats such as tables, list or paragraphs.

In this tutorial, we will focus on creating, processing forms and rendering to the template. The task is to create a form that enables users to subscribe to the newsletter app and then send the new subscribers a welcome email.

Creating Newsletter App

In Django, apps are small components used to build projects For an app to be active, add it to the list of settings.INSTALLED_APPS. .Apps perform a specific function and can be reused in various projects. Since we are creating a newsletter, we will name it a newsletter. Make sure you are in the same directory as manage.py and type:

python manage.py startapp newsletter

When Django creates the newsletter app, it also creates some files and the structure look like this:

 newsletter/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
       views.py

In order to use the app, we must activate it by adding it to settings.INSTALLED_APPS

INSTALLED_APPS = [
        ...
        'newsletter',
    ]

Now our app is installed and activated, let make it do something!

Creating Models

Next, we going to create our model in models.py. For each model defined Django will create a table for it in the database. Let's define our model and name it NewsUsers and give it 3 fields name, email and date_added write the following code in your newsletter/models.py

from django.db import models
class NewsUsers(models.Model):
    name = models.CharField(max_length = 30)
    email = models.EmailField()
    date_added = models.DateField(auto_now_add=True)
        def __str__(self):
            return self.email
  • import the models.
  • Next,I have defined 3 fields:name,email and the date_added

Achieng Bytes

The __str__() method simply tell python to display a human-readable representation of the model

Make and Run Migrations.

python manage.py makemigrations newsletter
python manage.py migrate

Note

Anytime you make changes to the models remember to run migrations

Adding Models to Admin Site

We will create a simple admin site to manage the newsletters users

from django.contrib import admin
from .models import NewsUsers
class NewsletterAdmin(admin.ModelAdmin):
    list_display =('email','date_added',)
admin.site.register(NewsUsers, NewsletterAdmin)

Create a Superuser and runserver

To Interact with the admin site, we must create a user with admin privileges

python manage.py createsuperuser

Then input the credentials. Below is a snippet

Username: laughing-blog
Email address: "email Adress"
Password: 
Password (again): 

Run the server and navigate to http://127.0.0.1:8000/admin on the browser.Log in using superuser credentials you created above.

 python manage.py runserver

If you are successful then you will see an interface similar to this:

Note

Note: Django is 'smart' and naturally pluralizes the model In admin

We have NewsUserss and that is not cool! To avoid such a scenario, define meta class in models and set the verbose _name=' singular' and verbose_name_plural. Your models should look like this

from django.db import models
class NewsUsers(models.Model):
    name = models.CharField(max_length = 30)
    email = models.EmailField()
    date_added = models.DateField(auto_now_add=True)

    class Meta:
        verbose_name = "NewsUser"
        verbose_name_plural = "NewsUsers"
        def __str__(self):
            return self.email

Note

always run migrations after modifying the models

After applying migrations, run the server again and navigate to the admin interface and the extra s should be gone and if you click to add the user, you should be able to add users in the newsletter list

Creating the Newsletter Form

Django has two base classes. The form class and modelForm for creating forms. With the form you get to create standard forms and the ModelForm allows you to build forms from the model's fields.

Depending on your needs you can choose either of the two methods. The standard forms are best in scenarios where the forms do not interact with the database directly.

Form vs modelForm

  • Forms: Allows you to build Standard Forms
  • ModelForm: Allows you to build forms from a model by defining fields using the Meta class

In our case we will create form using the ModelForm Create a new file called forms.py in newsletter app.

Note

it's a general practice to create Django forms in a file called forms.py

Lets go ahead and declare the form.To create our ModelForm, import the form class and the model class

from django import forms
from .models import NewsUsers
class NewsUserForm(forms.ModelForm):
    class Meta:
        model = NewsUsers
        fields = ['name', 'email']
  1. Import the forms library
  2. derive from the ModelForm class
  3. declare the Fields we need in the meta section

Handling Django forms in views

It's time for the heavy lifting

from django.shortcuts import render
from .forms import NewsUserForm
from . models import NewsUsers
def newsletter_subscribe(request):
  if request.method == 'POST':
    form = NewsUserForm(request.POST)
    if form.is_valid():
      instance = form.save()
      print('your email is already added to our database')
  else:
    form = NewsUserForm()
  context = {'form':form}
  template = "newsletter/subscribe.html"
  return render(request, template, context)

huh! what is all that?

First things first,let us deal with the importations. Import NewsUserForm from form.py and NewsUsers from models.

The first step in handling forms in Django is to display an empty form to the user. When our view is initially loaded with a GET request, we create a new form instance that will be used to display the empty form in the template.

form = NewsUserForm()

Note

A form may be blank in the case where you creating a new record or may be pre-populated with initial values. At this stage, the form is unbound because it is not associated with any user input although it may have initial values. At this point, the form is unbound

The second step is to check if the user has filled the form and submit it via POST. So, If it is a POST request, we create a new form instance and populate it with data from the request. This process is called binding and the form is said to be bound

if request.method == 'POST':
    form = NewsUserForm(request.POST)

Note

binding allows us to validate data. I mean it's meaningless validating a form with no data right?

Next, Check for form validation and clean the data. The method is_valid(), is used to check if the form is valid.Form validation means the data accepted is as specified when defining the fields. In our case, the name fieldaccepts up to a maximum of 30 characters only, theemail` field must be a valid email and so on. This method will return true if the fields contain valid data or false if invalid throwing errors.

If the form is valid, we clean the data. This means performing sanitization of the input i.e removing invalid characters that might be used to send malicious content to the server.

If we have a valid form as in our case, we process the form. To save the data we call save() method.

save()method

it is worth mentioning that only modelForms have this method and not form

Creating newsletter/urls.py

Okay, our data is saved in the database. We now want to create the url that will display the form we just created. Create a new file in newsletter app and name it urls.py then place the code below:

from django.urls import path 
from .views  import  newsletter_subscribe
app_name = 'newsletter'
urlpatterns = [
        path('subscribe', newsletter_subscribe, name='subscribe'),
     ]

In the main urls.py include the uurl confi from the newsletter app

Main urls.py

from django.contrib import admin
from django.urls import path,include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('newsletter/',include('newsletter.urls' , namespace='newsletter')),
]

To check if out if the form is being displayed, run server and follow the link bash python manage.py runserver

When you navigate to http://127.0.0.1:8000/newsletter on the browser, it will return an error that the template does not exist.

template

Do not panic just yet, we just need to create the template subscribe.html

Rendering the Form in Templates

Create a templates directory in newsletter app called templates and inside it create another directory called newsletter which will hold the file subscribe.html file. The structure should look like this:

newsletter
 templates
 newsletter
   subscribe.html

subscribe.html

    {% extends "base.html" %}
    {% block content %}
    <form action="{% url 'newsletter:subscribe' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
    </form>
    {% endblock %}

Note

you must include cross-site request forgery {% csrf_token %} tag for POST requests

Run the Server again and navigate to http://127.0.0.1:8000/newsletter/subscribe

 $ python manage.py runserver

you should see the form below:

Fill in your name and email and when you hit submit, it saves check the admin interface and you should see the user you just added.

Recap

So far we have created

Task

try using the same email more than once and it will save

We do not want that to happen right? so let's add some queries to filter the email. If the email already exists we notify the user it already exists else save. Update your views to look like this:

from django.shortcuts import render
from .forms import NewsUserForm
from .models import NewsUsers
def newsletter_subscribe(request):
  if request.method == 'POST':
    form = NewsUserForm(request.POST)
    if form.is_valid():
      instance = form.save(commit=False) #we dont want to save just yet
      if NewsUsers.objects.filter(email=instance.email).exists():
        print( "your Email Already exists in our database")
      else:
        instance.save()
        print( "your Email has been submitted to our database")    
  else:
    form = NewsUserForm()
  context = {'form':form}
  template = "newsletter/subscribe.html"
  return render(request, template, context)

Create the model instance with the save() method, but don't save it to the database just yet by calling commit = False

save()

  • The save() method creates an instance of the model that the form is linked to and saves it to the database.
  • If called with commit=False, it creates the model instance but doesn't save to the database just yet!

Task

fill the form using a new email and check if it saves. We have added a notification message on the console, so check your console for guidance. Use the same email again and check the console

Now it validates our emails,Great Work!

Sending Emails

Sending Emails with Django is quite simple.All we need is an Email service Provider.You can use gmail or write to console for testing .Refer to
Sending Emails with mailjet because I will be using mailjet

Remember the .env file we created in Project Structure and update it with your smtp credentials

EMAIL_HOST=in-v3.mailjet.com
EMAIL_HOST_USER="Your Username"
EMAIL_HOST_PASSWORD="Your Password"
EMAIL_PORT=587
EMAIL_USE_TLS=True

Replace "Your Username" with the actual username and "Your Password" with your actual password.

settings.py

EMAIL_HOST = config('EMAIL_HOST')
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
EMAIL_PORT = config('EMAIL_PORT', cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool)
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL')

newsletter/views.py

We want to start sending emails, Import send_mail .

from django.shortcuts import render
from .forms import NewsUserForm
from . models import NewsUsers
from django.core.mail import send_mail
def newsletter_subscribe(request):
   if request.method == 'POST':
    form = NewsUserForm(request.POST)
    if form.is_valid():
      instance = form.save(commit=False) #we do not want to save just yet
      if NewsUsers.objects.filter(email=instance.email).exists():
        print('your email Already exists in our database')
      else:
        instance.save()
        print('your email has been submitted to our database')
        send_mail('Laughing blog tutorial series', 'welcome', 'mail@achiengcindy.com',[instance.email], fail_silently=False) 
  else:
    form = NewsUserForm()
  context = {'form':form}
  template = "newsletter/subscribe.html"
  return render(request, template, context)

What is the difference between Bound and Unbound Form?

A bound form has data associated with it. Unbound form has no data associated with it.It is either empty or contain default values.To check if a form is bound use is_bound() method.In our case form.is_bound() return false while passing even an empty dictionary binds the form.form.is_bound({}) returns true

We have successfully created a basic newsletter app and managed to send a welcome message to new subscriptions.The code for this tutorial is available on Github . In the next tutorial, we will continue to build our newsletter app by making it more readable! Connect with me on Twitter