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 admins email addresses.

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

...

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:

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

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

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

class FeedbackForm(forms.ModelForm):

    class Meta:
        model = models.Feedback
        fields = '__all__'

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

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 allows to handle Contact Us page 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:

{% 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 cretion visit http://127.0.0.1:8000/feedback/ and try submitting one or two feedback.

[]

Adding Feedback Model to Django Admin

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

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

class TagAdmin(admin.ModelAdmin):
    ...

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

admin.site.register(Post, PostAdmin)
...
admin.site.register(Feedback, FeedbackAdmin)

At this point if you visit Feedback list page (http://127.0.0.1:8000/admin/blog/feedback/) in the Django Admin you should get a page like this:

[]

Sending Emails

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

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

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

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

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 add 'django.middleware.common.BrokenLinkEmailsMiddleware' middleware as follows:

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. So, we will not be able to fully test these functionalities until we deploy our site which we will do in Deploying Django Project.

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 help@overiq.com

The above command would send an email to help@overiq.com from infooveriq@gmail.com.

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 help@overiq.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 forth line of the output i.e smtplib.SMTPAuthenticationError. This is a common problem when you try to send email from a gmail account. There are two main cause of this error.

  1. 2-factor authentication.
  2. Allow less secure apps.

First, you have to disable 2-factor authentication for this to work. Further, you would also need to Allow less secure apps. By Default Allow less secure apps is turned off, click on the button to turn it on. To change these settings login to your Google account and visit https://myaccount.google.com/security page.

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 prateekrc3@g
mail.com

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

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

[screen short of gmail]

Instead of sending email to the specified id, you can tell 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 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 add modify or view function 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'])

            try:
                mail_admins(subject, message)
                f.save()
                print("success")
                messages.add_message(request, messages.SUCCESS, 'Feedback sent!')
            except:
                print("failed")
                messages.add_message(request, messages.INFO, 'Unable to send feedback. Try agian')

            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/ and fill out the form and hit subject, you will get a page like this:

[]

This is how email will like:

[gmail screen shot]

On the other hand if main_admins() encounters an error while sending emails then you will get a notification like this:

[mail_error]