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 site 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 changes run makemigration followed by migrate command.

(env) C:\Users\K\TGDB\django_project>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0012_feedback.py:
    - Create model Feedback

(env) C:\Users\K\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

(env) C:\Users\K\TGDB\django_project>

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'),
    ...
]

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 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 .forms import FeedbackForm
...

def feedback(request):
    if request.method == 'POST':
        f = FeedbackForm(request.POST)
        if f.is_valid():
            f.save()
            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>{{ 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

...
from .models import Post, Author, Category, Tag, Feedback

...


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


...
admin.site.register(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 to 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 = [
    ('Puneet', 'punnet652@gmail.com'),
]

MANAGERS = ADMINS

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

Here is a run down 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.
'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

'django.core.mail.backends.filebased.EmailBackend' allows us to write the email to a file instead of forwarding it to a SMTP server.

Similarly, 'django.core.mail.backends.console.EmailBackend' 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 account to use with the host defined in EMAIL_HOST.

EMAIL_HOST_PASSWORD: Password of the account to defined in the EMAIL_HOST.

EMAIL_PORT: Port to use to connect to the SMTP server in EMAIL_HOST.

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 site is in production i.e DEBUG = False and 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's 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 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\K\TGDB\django_project>python manage.py sendtestemail punnet652@gmail.com

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

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

(env) C:\Users\K\TGDB\django_project>python manage.py sendtestemail punnet652@gmail.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> Pleas
e 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 - gsm
tp')

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 curlprit was "Allow less secure apps".

After doing these steps you should be able to send test email using sendtestemail command.

(env) C:\Users\K\TGDB\django_project>python manage.py sendtestemail punnet652@gmail.com

(env) C:\Users\K\TGDB\django_project>

If sendtestemail succeeds sending 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) H:\TGDB\django_project>python manage.py sendtestemail --managers

(env) H:\TGDB\django_project>python manage.py sendtestemail --admins

As situation stands, our feeback() 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.

mail_admins() function #

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 this parameter to True.

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:

...
from django.core.mail import mail_admins
...


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()
            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