Building Contact Us Page

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

Open models.py from the blog app and append the Feedback Model class to the end of the file as follows:

TGDB/django_project/blog/models.py

#...

class Feedback(models.Model):
    name = models.CharField(max_length=200, help_text="Name of the sender")
    email = models.EmailField(max_length=200)
    subject = models.CharField(max_length=200)
    message = models.TextField()
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "Feedback"

    def __str__(self):
        return self.name + "-" +  self.email

To commit the changes run makemigration followed by migrate command.

(env) C:\Users\Q\TGDB\django_project>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0012_feedback.py:
    - Create model Feedback
(env) C:\Users\Q\TGDB\django_project>
(env) C:\Users\Q\TGDB\django_project>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Rendering model states... DONE
  Applying blog.0012_feedback... OK

Open blog's urls.py file and add the following URL pattern towards the beginning of the urlpatterns list:

TGDB/django_project/blog/urls.py

#...
urlpatterns = [
    url(r'^feedback/$', views.feedback, name='feedback'),
    url(r'^blog/$', views.test_redirect, name='test_redirect'),
    #...
]

Now create a FeedbackForm class in the blog's forms.py as follows:

TGDB/django_project/blog/forms.py

#...
from .models import Author, Tag, Category, Post, Feedback
from django.template.defaultfilters import slugify
#...

class PostForm(forms.ModelForm):
    #...

class FeedbackForm(forms.ModelForm):

    class Meta:
        model = Feedback
        fields = '__all__'

Next, create a view function called feedback() at the end of the blog app's views.py file as follows:

TGDB/django_project/blog/views.py

#...
from .models import Author, Tag, Category, Post
from django.contrib import messages
from .forms import FeedbackForm
#...

def test_redirect(request):
    c = Category.objects.get(name='python')
    return redirect(c)


def feedback(request):
    if request.method == 'POST':
        f = FeedbackForm(request.POST)
        if f.is_valid():
            f.save()
            messages.add_message(request, messages.INFO, 'Feedback Submitted.')
            return redirect('feedback')
    else:
        f = FeedbackForm()
    return render(request, 'blog/feedback.html', {'form': f})

This view shows feedback form and saves the submitted feedback to the database

Next, we need a template to show the Contact Form. Create a new template called feedback.html inside the blog app i.e blog/templates/blog with the following code:

TGDB/django_project/blog/templates/blog/feedback.html

{% extends "base.html" %}

{% block title %}
    Feedback - {{ block.super }}
{% endblock %}

{% block content %}

    {% if messages %}
    <ul class="messages">
        {% for message in messages %}
        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
        {% endfor %}
    </ul>
    {% endif %}

    <div class="content">
    <div class="section-inner clearfix">

        <h3>Submit Your feedback</h3>       

        <form action="" method="post">
            {% csrf_token %}
            <table>
            {{ form.as_table }}                   
                <tr>
                    <td></td>
                    <td><input type="submit" value="Submit"></td>
                </tr>
            </table>
        </form>

        </div>
    </div>

{% endblock %}

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

contact-form.png

Adding Feedback Model to Django Admin #

Open blog's admin.py file and add FeedbackAdmin class to the file as follows:

TGDB/django_project/blog/admin.py

#...
class TagAdmin(admin.ModelAdmin):
    #...

class FeedbackAdmin(admin.ModelAdmin):
    list_display = ('name', 'email', 'subject','date',)
    search_fields = ('name', 'email',)
    date_hierarchy = 'date'


admin.site.register(models.Post, PostAdmin)
admin.site.register(models.Category, CategoryAdmin)
admin.site.register(models.Author, AuthorAdmin)
admin.site.register(models.Tag, TagAdmin)
admin.site.register(models.Feedback, FeedbackAdmin)

Visit Feedback list page (http://127.0.0.1:8000/admin/blog/feedback/) in the Django Admin and you should get a page like this:

feedback-list-in-django-admin.png

Sending Emails #

Now our site has the ability to accept feedback from the user and site admins can view them using Django Admin. We also want to inform the site admins whenever a new feedback is submitted. To do that, we have to learn how to send email in Django.

Before sending emails in Django, we have to set some variables in the settings.py. Open settings.py and add the following variables towards the end of the file:

TGDB/django_project/django_project/settings.py

#...
########################

# Email settings

SERVER_EMAIL = 'infooveriq@gmail.com'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_PASSWORD = 'passowrd'
EMAIL_HOST_USER = SERVER_EMAIL
EMAIL_PORT = 587
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

ADMINS = [
    ('OverIQ', 'infooveriq@gmail.com'),
]

MANAGERS = ADMINS

########################

Here is a rundown of each variable we have set:

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:

  1. django.core.mail.backends.filebased.EmailBackend
  2. 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. In this case, we are using Gmail SMTP Server i.e smtp.gmail.com.

EMAIL_HOST_USER: It specifies the username of the SMTP Server specified in EMAIL_HOST.

EMAIL_HOST_PASSWORD: Password of the SMTP server specified in the EMAIL_HOST.

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. In our case SERVER_EMAIL, EMAIL_HOST_USER and DEFAULT_FROM_EMAIL is same, so Django will use "infooveriq@gmail.com" for ordinary correspondence as well as for reporting errors.

ADMINS: It specifies a list of people to send error notifications. When the site is in production (i.e DEBUG = False) and views 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:

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.

MANAGERS = [    
    ('name1', 'name1@email.com'),
    ('name2', 'name2@email.com'),
]

To enable this feature you have add 'django.middleware.common.BrokenLinkEmailsMiddleware' middleware in the MIDDLEWARE setting in settings.py. To do so, open settings.py file and append 'django.middleware.common.BrokenLinkEmailsMiddleware' middleware to the MIDDLEWARE list as follows:

TGDB/django_project/django_project/settings.py

#...

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.common.BrokenLinkEmailsMiddleware',
]

#...

Remember that Django will automatically send emails to people listed in ADMINS and MANAGERS only in production (i.e DEBUG=False). That's why, we won't be able to fully test these functionalities until we deploy our site, which we will do in Deploying Django Project lesson.

Testing email using sendtestemail #

Django provides a sendtestemail command which sends a test email to the specified email id. This command is commonly used to test whether everything is working as expected or not. It uses email id specified in DEFAULT_FROM_EMAIL setting to send the email.

In the command prompt or terminal enter the following command:

(env) C:\Users\Q\TGDB\django_project>python manage.py sendtestemail testmail@example.com

The above command would send an email to testmail@example.com from infooveriq@gmail.com. You should replace testmail@example.com with the email where you want to send the test mail.

On my machine, When I tried to run the above command, I got the following error:

(env) C:\Users\Q\TGDB\django_project>python manage.py sendtestemail testmail@example.com
...
    self.connection.login(self.username, self.password)
  File "c:\python34\Lib\smtplib.py", line 652, in login
    raise SMTPAuthenticationError(code, resp)
smtplib.SMTPAuthenticationError: (534, b'5.7.14 <https://accounts.google.com/sig
nin/continue?sarp=1&scc=1&plt=AKgnsbsO\n5.7.14 PsvPm3vLbfhrrNnip0yCTlTTsR5im7B5-
NwDUIjbKHmIw4vQQ0LLZgTXixiWw79qfTjMKJ\n5.7.14 hhK67f2kOZUj_ivkwpI-c1GDcPtCqc28fY
EGgAQvI2RzDTzmkCO1hIxnAkeiVI6wPM-5ZK\n5.7.14 KQ1q2GPLhrPL790icanRXSmdIBh0i5nHySo
rk-8Bsv7amSZDgep9kspYGnqRqHesQE2_Gr\n5.7.14 PLwBgB-RmMcfa53-91-aue4ZywDvw> Please log in via your web browser and\n5.7.14 then try again.\n5.7.14  Learn more at
\n5.7.14  https://support.google.com/mail/answer/78754 b74sm21087287pfl.58 - gsmtp')

Notice the error in the fourth line of the output i.e smtplib.SMTPAuthenticationError. This is a common problem when you try to send email from a Gmail account. This error may occur due to the following reasons:

  1. You have entered wrong username/password in settings.py file.
  2. 2-factor authentication is enabled on your account.
  3. "Allow less secure apps" setting is turned off.

First off, make sure you have entered correct username and password in settings.py file. Next, check whether you have enabled 2-factor authentication on your Gmail account by visiting https://myaccount.google.com/security. If so, disable it. At last, check whether you have "Allow less secure apps" setting turned off by visiting https://myaccount.google.com/security. If so, turn it on. In my case, the culprit was "Allow less secure apps".

After doing these steps you should be able to send a test mail using the sendtestemail command.

If sendtestemail succeeds in sending the email, you should get a response like this:

sendtestemail-success.png

Instead of sending email to the specified id, you can also use sendtestemail to send email to ADMINS or MANAGERS by using the following command:

(env) C:\Users\Q\TGDB\django_project>python manage.py sendtestemail --managers

(env) C:\Users\Q\TGDB\django_project>python manage.py sendtestemail --admins

As the situation stands, our feedback() view just saves the submitted feedback into the database, it doesn't send any email to the site ADMINS or MANAGERS. In the next section, we are going to change this.

The mail_admins() function #

The main_admins() function sends the email to the site admins i.e list of emails specified in the ADMINS list in settings.py. It's syntax is:

Syntax:

mail_admins(subject, message, fail_silently=False)

Subject: The subject of the email.

message: The message.

fail_silently: On error mail_admins() raises gaierror (gai stands for getaddrinfo()) which translates to 500 Internal Server Error on production. If for some reason you want to ignore errors silently set this parameter to True.

The mail_admins() uses SERVER_EMAIL setting as sender's email.

Let's modify feedback() view function in blog's views.py to use mail_admins() function as follows:

TGDB/django_project/blog/views.py

#...
from .forms import FeedbackForm
from django.core.mail import mail_admins
#...

def test_redirect(request):
    #...

def feedback(request):
    if request.method == 'POST':
        f = FeedbackForm(request.POST)

        if f.is_valid():
            name = f.cleaned_data['name']
            sender = f.cleaned_data['email']
            subject = "You have a new Feedback from {}:{}".format(name, sender)
            message = "Subject: {}\n\nMessage: {}".format(f.cleaned_data['subject'], f.cleaned_data['message'])
            mail_admins(subject, message)

            f.save()
            messages.add_message(request, messages.INFO, 'Feedback Submitted.')
            return redirect('feedback')

    else:
        f = FeedbackForm()
    return render(request, 'blog/feedback.html', {'form': f})

Visit feedback page at http://127.0.0.1:8000/feedback/, fill out the form and hit Submit.

feedback-test.png

In addition to saving the feedback to the database this time feedback() view will send an email to the site admins informing them about the submitted feedback. This is how email will like:

mail_admins-test.png

On the other hand, if main_admins() encounters an error while sending emails then it will throw gaierror exception like this:

gaierror.png

In the production, users will get a generic 500 Internal Server Error page.

Note: To checkout this version of the repository type git checkout 22a.