OverIQ.com

Building Contact Us Form

Last updated on July 27, 2020


In this lesson, we are going to create a Contact form. This will allow our visitors to send feedback directly to the admin email address.

The forms we have built in the last few chapters were closely related to the models class. However, It is also possible (and sometimes more feasible) to create forms that stand on its own without having any relationship with the model. To give you an example of the complete process, the Contact form we will build in this chapter will inherit from forms.Form class instead of forms.ModelForm. But before we do that, we have to learn how to send emails with Django.

Sending Email with Django #

To send email, Django requires you to add some configurations. The following is a list of some common configuration options provided by Django:

  • SERVER_EMAIL: It specifies the email address which Django will use to send error messages to ADMINS and MANAGERS.

  • EMAIL_BACKEND: It specifies the name of the backend to use for sending emails. The django.core.mail.backends.smtp.EmailBackend means that Django will use SMTP Server to send emails. Django has many other backends. Here are other two commonly used backends:

    • django.core.mail.backends.filebased.EmailBackend
    • django.core.mail.backends.console.EmailBackend

    The former allows us to write the email to a file instead of forwarding it to an SMTP server. And the latter prints the email directly to the console.

  • EMAIL_HOST: It specifies the address of the Email Server or SMTP Server.

  • EMAIL_HOST_USER: It specifies the username of the SMTP Server.

  • EMAIL_HOST_PASSWORD: Password of the SMTP server.

  • EMAIL_PORT: Port to use to connect to the SMTP server.

  • EMAIL_USE_TLS: It specifies whether to use TLS secure or not.

  • DEFAULT_FROM_EMAIL: It specifies the default email address to use for ordinary correspondence from the site managers.

  • ADMINS: It specifies a list of people to send error notifications. When the site is in production (i.e DEBUG = False) and any view raises an exception then Django will send an email to all the to all the people specified in the ADMINS list. Each item in ADMINS list is a tuple. For example:

    1
    2
    3
    4
    ADMINS = [    
        ('name1', 'name1@email.com'),
        ('name2', 'name2@email.com'),
    ]
    
  • MANAGERS: It specifies a list of people to send broken link emails for 404 NOT FOUND errors. It accepts emails in the same format as ADMINS.

    1
    2
    3
    4
    MANAGERS = [    
        ('name1', 'name1@email.com'),
        ('name2', 'name2@email.com'),
    ]
    

    To enable this feature you'll have add django.middleware.common.BrokenLinkEmailsMiddleware middleware in the MIDDLEWARE setting in settings.py file.

The following listing shows configurations required to send emails via Gmail SMTP server.

1
2
3
4
5
6
7
8
SERVER_EMAIL = 'infooveriq@gmail.com'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_PASSWORD = 'password'
EMAIL_HOST_USER = SERVER_EMAIL
EMAIL_PORT = 587
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

We are in the development phase and just want to send test emails. As a result, we will use the console backend. Open settings.py and add the following options at end of the file:

djangobin/django_project/django_project/settings.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#...
MEDIA_ROOT  = os.path.join(BASE_DIR, 'media')

MEDIA_URL = '/media/'

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

SERVER_EMAIL = 'infooveriq@gmail.com'
DEFAULT_FROM_EMAIL = SERVER_EMAIL

ADMINS = (
    ('OverIQ', 'admin@overiq.com'),
)

MANAGERS = (
    ('OverIQ', 'manager@overiq.com'),
)

Testing email using sendtestemail #

Django provides sendtestemail command which sends a test email to the specified email id. It uses email id specified in DEFAULT_FROM_EMAIL setting to send the email.

In the terminal enter the following command:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ ./manage.py sendtestemail test@example.com
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Test email from pc on 2018-04-14 08:02:32.394195+00:00
From: infooveriq@gmail.com
To: test@example.com
Date: Sat, 14 Apr 2018 08:02:32 -0000
Message-ID: <20180414080232.16065.81775@pc>

If you're reading this, it was successful.
-------------------------------------------------------------------------------

If you get the same output as above, then it means everything is working as expected.

Instead of sending email to the specified email id, you can also have sendtestemail to send emails to ADMINS or MANAGERS by using --admins or --managers options:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ ./manage.py sendtestemail --managers
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Test email from pc on 2018-04-14 08:05:12.545568+00:00
From: infooveriq@gmail.com
To: manager@overiq.com
Date: Sat, 14 Apr 2018 08:05:12 -0000
Message-ID: <20180414080512.16961.91759@pc>

This email was sent to the site managers.
-------------------------------------------------------------------------------
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ ./manage.py sendtestemail --admins
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Test email from pc on 2018-04-14 08:08:31.878881+00:00
From: infooveriq@gmail.com
To: admin@overiq.com
Date: Sat, 14 Apr 2018 08:08:31 -0000
Message-ID: <20180414080831.17122.89062@pc>

This email was sent to the site admins.
-------------------------------------------------------------------------------

send_mail() function #

The send_mail() is the easiest way to send emails in Django. It's syntax is:

send_mail(subject, message, from_email, recipient_list)

Subject: Subject of the email.

message: Message.

from_email: Sender's email

recipient_list: A list of recipients

On success send_mail() returns 1, otherewise 0.

mail_admins() function #

The main_admins() function sends email to the administrators specified in the ADMINS setting. It's syntax is:

mail_admins(subject, message, fail_silently=False)

Subject: the subject of the email.

message: message to send.

It uses email specified in the SERVER_EMAIL setting as the sender's email.

Since we are interested in sending email to the admins, this is the function we are going to use while building the Contact Us page.

Creating Contact Us Page #

Open forms.py file and add ContactForm class to wards the end of the file as follows:

djangobin/django_project/djangobin/forms.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#...

class ContactForm(forms.Form):
    BUG = 'b'
    FEEDBACK = 'fb'
    NEW_FEATURE = 'nf'
    OTHER = 'o'
    purpose_choices = (
        (FEEDBACK, 'Feedback'),
        (NEW_FEATURE, 'Feature Request'),
        (BUG, 'Bug'),
        (OTHER, 'Other'),
    )

    name = forms.CharField()
    email = forms.EmailField()
    purpose = forms.ChoiceField(choices=purpose_choices)
    message = forms.CharField(widget=forms.Textarea(attrs={'cols': 40, 'rows': 5}))

In the views.py file, add a view named contact as follows:

djangobin/django_project/djangobin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#...
from django.core.mail import mail_admins
import datetime
from .forms import SnippetForm, ContactForm
from .models import Language, Snippet, Tag
from .utils import paginate_result
#...


def trending_snippets(request, language_slug=''):
    #...


def contact(request):
    if request.method == 'POST':
        f = ContactForm(request.POST)
        if f.is_valid():

            name = f.cleaned_data['name']
            subject = "You have a new Feedback from {}:<{}>".format(name, f.cleaned_data['email'])

            message = "Purpose: {}\n\nDate: {}\n\nMessage:\n\n {}".format(
                dict(f.purpose_choices).get(f.cleaned_data['purpose']),
                datetime.datetime.now(),
                f.cleaned_data['message']
            )

            mail_admins(subject, message)
            messages.add_message(request, messages.INFO, 'Thanks for submitting your feedback.')

            return redirect('djangobin:contact')

    else:
        f = ContactForm()

    return render(request, 'djangobin/contact.html', {'form': f})

This view shows the Contact form and sends the submitted response to all the admins.

Notice how we are setting the purpose in the in the email body. The data of the purpose field will be the first element of the tuple in purpose_choices, that is b, fb, nf or o. These letters are not very helpful. That's why we first convert the tuple into a dictionary and then use the key stored in cleaned_data to access the more readable value.

Next, create a template named contact.html in the templates directory with the following code:

djangobin/django_project/djangobin/templates/djangobin/contact.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{% extends 'djangobin/base.html' %}

{% block title %}
    Contact Us - {{ block.super }}
{% endblock %}

{% block main %}

    <h4>Contact </h4>
    <hr>

    {% if messages %}
        {% for message in messages %}
            <p class="alert alert-info">
                {{ message }}
            </p>
        {% endfor %}
    {% endif %}

    <form method="post">

        {% csrf_token %}

        <div class="form-group row">
            <div class="col-lg-5">
                {{ form.name.errors }}
                {{ form.name.label_tag }}
                {{ form.name }}
            </div>
        </div>

        <div class="form-group row">
            <div class="col-lg-5">
                {{ form.email.errors }}
                {{ form.email.label_tag }}
                {{ form.email }}
            </div>
        </div>

        <div class="form-group row">
            <div class="col-lg-5">
                {{ form.purpose.errors }}
                {{ form.purpose.label_tag }}
                {{ form.purpose }}
            </div>
        </div>

        <div class="form-group row">
            <div class="col-lg-5">
                {{ form.message.errors }}
                {{ form.message.label_tag }}
                {{ form.message }}
            </div>
        </div>

        <button type="submit" class="btn btn-primary">Submit</button>
    </form>

{% endblock %}

Add a new URL pattern in urls.py as follows:

djangobin/django_project/djangobin/urls.py

1
2
3
4
5
6
7
8
#...
urlpatterns = [
    #...
    url('^tag/(?P<tag>[\w-]+)/$', views.tag_list, name='tag_list'),
    url('^download/(?P<snippet_slug>[\d]+)/$', views.download_snippet, name='download_snippet'),
    url('^raw/(?P<snippet_slug>[\d]+)/$', views.raw_snippet, name='raw_snippet'),    
    url('^contact/$', views.contact, name='contact')    
]

Next, add a link to Contact us form in the base.html as follows:

djangobin/django_project/djangobin/templates/djangobin/base.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{# ... #}
        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li {% if request.path == '/' %}class='active'{% endif %} >
                    <a href="{% url 'djangobin:index' %}">Add new</a>
                </li>
                <li {% if request.path == '/trending/' %}class='active'{% endif %}>
                    <a href="{% url 'djangobin:trending_snippets' %}">Trending<span class="sr-only">(current)</span></a>
                </li>
                <li {% if request.path == '/about/' %}class='active'{% endif %}>
                    <a href="">About</a>
                </li>
                <li {% if request.path == '/contact/' %}class='active'{% endif %}>
                    <a href="{% url 'djangobin:contact' %}">Contact</a>
                </li>
            </ul>
{# ... #}

To view our creation visit http://127.0.0.1:8000/contact/ and try submitting a feedback or two.

After submission you will get a success message like this:

In the shell running the server you should get output as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[10/May/2018 14:19:05] "GET /contact/ HTTP/1.1" 200 9875
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] You have a new Feedback from spike:<spike@mail.com>
From: infooveriq@gmail.com
To: admin@overiq.com
Date: Thu, 10 May 2018 14:19:30 -0000
Message-ID: <20180510141930.6825.79585@pc>

Purpose: Feedback

Date: 2018-05-10 14:19:30.825265

Message:

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet, animi assumenda eos exercitationem incidunt molestias nihil non quas soluta voluptatibus! Architecto blanditiis eos nulla quas! A odit optio quibusdam voluptatibus!
-------------------------------------------------------------------------------