Creating a Contact Form

To create our ContactForm class, we first create a new file called contact.py. As the contact form is a part of the website, we will add it to the site app (new file):

  1. # myclub_root\myclub_site\contact.py
  2. 1 from django import forms
  3. 2
  4. 3 class ContactForm(forms.Form):
  5. 4 yourname = forms.CharField(max_length=100, label='Your Name')
  6. 5 email = forms.EmailField(required=False, label='Your Email Address')
  7. 6 subject = forms.CharField(max_length=100)
  8. 7 message = forms.CharField(widget=forms.Textarea)

This is like the SimpleForm class we created in the shell, with some differences:

  • Line 4. If you don’t specify the label attribute, Django uses the field name for the field label. We want the label for the yourname field to be more readable, so we set the label attribute to “Your Name”.
  • Line 5. We don’t want the email address to be a required field, so we set the required attribute to False, so the person submitting the form can leave the email field blank. We are also changing the default label of the email field to “Your e-mail address”.
  • Line 7. The message field must allow the person submitting the form to enter a detailed message, so we are setting the field widget to a Textarea, replacing the default TextInput widget.

Now we have created our ContactForm class, we have a few tasks to complete to get it to render on our website:

  1. Add our form to the site URLs;
  2. Add navigation to our site template;
  3. Create a template for the contact form; and
  4. Create a new view to manage the contact form.

Add Contact Form URL to Site App

To show our contact form, we start by creating a URL for it. To do that, we need to modify our site’s urls.py file (changes in bold):

  1. # myclub_root\myclub_site\urls.py
  2. 1 from django.contrib import admin
  3. 2 from django.urls import include, path
  4. 3 from django.contrib.auth import views as auth_views
  5. 4 from . import contact
  6. 5
  7. 6 urlpatterns = [
  8. 7 path('admin/', admin.site.urls),
  9. 8 path('contact/', contact.contact, name='contact'),
  10. # ...

In line 4 we’ve imported the contact module and in line 8 we have added a URLconf that will direct the URL ending in “contact” to the new contact view we will write shortly.

Add Navigation to Site Template

We added a placeholder for the contact page in the top menu when we created the base template. Now we will turn the placeholder into a link for our contact form (changes in bold):

  1. # myclub_site\templates\base.html
  2. # ...
  3. 1 <header id="header">
  4. 2 <div id="logo"><img src="{% static 'logo.png' %}" alt="" /></div>
  5. 3 <div id="top_menu">
  6. 4 Home | Calendar | About |
  7. 5 <a href="/contact">Contact Us</a>
  8. 6 </div>
  9. # ...

I have made one change to the base.html template—in line 5 we have turned the placeholder into a link to the contact page. I’ve also reformatted the code slightly.

Create the Contact Form Template

For our contact form to render, it needs a template. In your site templates folder, create a new folder called “contact”. Inside the new folder, create a file called contact.html and enter the following template code (new file):

  1. # \myclub_site\templates\contact\contact.html
  2. 1 {% extends "base.html" %}
  3. 2
  4. 3 {% block title %}Contact Us{% endblock title %}
  5. 4
  6. 5 {% block content %}
  7. 6 <h1>Contact us</h1>
  8. 7
  9. 8 {% if submitted %}
  10. 9 <p class="success">
  11. 10 Your message was submitted successfully. Thank you.
  12. 11 </p>
  13. 12
  14. 13 {% else %}
  15. 14 <form action="" method="post" novalidate>
  16. 15 <table>
  17. 16 {{ form.as_table }}
  18. 17 <tr>
  19. 18 <td>&nbsp;</td>
  20. 19 <td><input type="submit" value="Submit"></td>
  21. 20 </tr>
  22. 21 </table>
  23. 22 {% csrf_token %}
  24. 23 </form>
  25. 24 {% endif %}
  26. 25 {% endblock content %}

In the contact template, we are extending the base template, and replacing the title and content blocks with new content for our contact form. Some other things to note:

  • Line 8. We are using the {% if %} template tag for the first time. submitted is a boolean value passed in from the view. The {% if %} / {% else %} / {% endif %} tags (Lines 8, 13 and 24) are creating a logical branch that is saying “if the form has been submitted, show the thank you message, otherwise show the blank form.”
  • Line 14. Is the start of our POST form. This is standard HTML. Note the novalidate attribute in the <form> tag. When using HTML5 in some of the latest browsers (notably Chrome), form fields will be automatically validated by the browser. As we want Django to handle form validation, the novalidate attribute tells the browser not to validate the form.
  • Line 16. This is the line that renders the form fields. The as_table method will render the form fields as table rows. Django doesn’t render the table tags or the submit button, so we are adding these on line 15 and lines 17 to 21.
  • Line 22. All POST forms targeted at internal URLs must use the {% csrf_token %} template tag. This is to protect against Cross-Site Request Forgeries (CSRF). A full explanation of CSRF is beyond the scope of this book; just rest assured that adding the {% csrf_token %} tag is a Good Thing.

Create the Contact Form View

Our last step is to create the new contact view. Open your contact.py file and add the contact view code (changes in bold):

  1. # myclub_root\myclub_site\contact.py
  2. 1 from django import forms
  3. 2 from django.shortcuts import render
  4. 3 from django.http import HttpResponseRedirect
  5. 4
  6. 5
  7. 6 class ContactForm(forms.Form):
  8. 7 yourname = forms.CharField(max_length=100, label='Your Name')
  9. 8 email = forms.EmailField(required=False,label='Your e-mail address')
  10. 9 subject = forms.CharField(max_length=100)
  11. 10 message = forms.CharField(widget=forms.Textarea)
  12. 11
  13. 12
  14. 13 def contact(request):
  15. 14 submitted = False
  16. 15 if request.method == 'POST':
  17. 16 form = ContactForm(request.POST)
  18. 17 if form.is_valid():
  19. 18 cd = form.cleaned_data
  20. 19 # assert False
  21. 20 return HttpResponseRedirect('/contact?submitted=True')
  22. 21 else:
  23. 22 form = ContactForm()
  24. 23 if 'submitted' in request.GET:
  25. 24 submitted = True
  26. 25
  27. 26 return render(request,
  28. 27 'contact/contact.html',
  29. 28 {'form': form, 'submitted': submitted}
  30. 29 )

Let’s step through the important bits of this code:

  • Line 2. Import the render() shortcut function from django.shortcuts.
  • Line 3. Import the HttpResponseRedirect class from django.http.
  • Line 13. The beginning of our new contact view.
  • Line 15. Check if the form was POSTed. If not, skip down to line 22 and create a blank form.
  • Line 17. Check to see if the form contains valid data. Notice there is no cruft for handling invalid form data. This is what’s really cool about the Form class. If the form is invalid, the view drops right through to line 26 and simply re-renders the form as Django has already automatically added the relevant error messages to the form.
  • Line 18. If the form is valid, Django will normalize the data and save it to a dictionary accessible via the cleaned_data attribute of the Form class. In this context, normalizing means changing it to a consistent format. For example, regardless of what entry format you use, Django will always convert a date string to a Python datetime.date object.
  • Line 19. We’re not doing anything with the submitted form right now, so we put in an assertion error to test the form submission with Django’s error page.
  • Line 20. Once the form has been submitted successfully, we are using Django’s HttpResponseRedirect class to redirect back to the contact view. We set the submitted variable to True, so instead of rendering the form, the view will render the thank you message.
  • Line 26. Renders the template and data back to the view.

To test the contact form, uncomment line 19, save the contact.py file and then navigate to http://127.0.0.1:8000/contact to see your new contact form. First, note there is a link to the contact form in the top menu.

Next, submit the empty form to make sure the form validation is working. Django should show the error messages (Figure 8-3).

Creating a Contact Form - 图1

Figure 8-3: The contact form showing errors for required fields.

Now, fill out the form with valid data and submit it again. You should get an assertion error triggered by the assert False statement in the view (line 19). When we wrote our contact form view, we told Django to put the contents of the cleaned_data attribute into the variable cd (line 18).

With the assert False active in our view, we can check the contents of cd with the Django error page. Scroll down to the assertion error and open the Local vars panel. You should see the cd variable containing a dictionary of the complete form submission (Figure 8-4).

Creating a Contact Form - 图2

Figure 8-4: Using the assert False statement allows us to check the contents of the submitted form.

Once you have checked the submitted data is correct, click the back button in your browser and then click on the “Contact Us” link in the menu to take you back to the empty form.

Add Styles to the Contact Form

Our contact form is working great, but it still looks a bit plain—the fields don’t line up well, and the error messages don’t stand out. Let’s make the form prettier with some CSS. Add the following to the end of your main.css file:

  1. # \myclub_site\static\main.css
  2. # ...
  3. ul.errorlist {
  4. margin: 0;
  5. padding: 0;
  6. }
  7. .errorlist li {
  8. border: 1px solid red;
  9. color: red;
  10. background: rgba(255, 0, 0, 0.15);
  11. list-style-position: inside;
  12. display: block;
  13. font-size: 1.2em;
  14. margin: 0 0 3px;
  15. padding: 4px 5px;
  16. text-align: center;
  17. border-radius: 3px;
  18. }
  19. input, textarea {
  20. width: 100%;
  21. padding: 5px!important;
  22. -webkit-box-sizing: border-box;
  23. -moz-box-sizing: border-box;
  24. box-sizing: border-box;
  25. border-radius: 3px;
  26. border-style: solid;
  27. border-width: 1px;
  28. border-color: rgb(169,169,169)
  29. }
  30. input {
  31. height: 30px;
  32. }
  33. .success {
  34. background-color: rgba(0, 128, 0, 0.15);
  35. padding: 10px;
  36. text-align: center;
  37. color: green;
  38. border: 1px solid green;
  39. border-radius: 3px;
  40. }

Once you have saved the changes to your CSS file, refresh the browser and submit the empty form. You may have to clear the browser cache to reload your CSS file. Not only should your form be better laid out, but showing pretty error messages too (Figure 8-5).

Creating a Contact Form - 图3

Figure 8-5: Adding some CSS changes our rather plain contact form into something to be proud of.

Emailing the Form Data

Our contact form works well and looks good, but it’s not much use right now because we aren’t doing anything with the form data.

As this is a contact form, the most common way to deal with form submissions is to email them to a site administrator or some other contact person within the organization.

Setting up an email server to test emails in development can be painful. Luckily, this is another problem for which the Django developers have provided a handy solution. Django provides several email backends, including a few designed for use during development.

We will use the console backend. This backend is useful in development as it doesn’t require you to set up an email server while you are developing a Django application. The console backend sends email output to the terminal (console). You can check this in your terminal window after you submit your form.

There are other email backends for testing: filebased, locmem and dummy, which send your emails to a file on your local system, save it in an attribute in memory or send to a dummy backend respectively.

You can find more information in the Django documentation under Email Backends.

So, let’s modify the contact view to send emails (changes in bold):

  1. # myclub_root\myclub_site\contact.py
  2. 1 from django import forms
  3. 2 from django.shortcuts import render
  4. 3 from django.http import HttpResponseRedirect
  5. 4 from django.core.mail import send_mail, get_connection
  6. 5
  7. 6
  8. 7 class ContactForm(forms.Form):
  9. 8 yourname = forms.CharField(max_length=100, label='Your Name')
  10. 9 email = forms.EmailField(required=False,label='Your e-mail address')
  11. 10 subject = forms.CharField(max_length=100)
  12. 11 message = forms.CharField(widget=forms.Textarea)
  13. 12
  14. 13
  15. 14 def contact(request):
  16. 15 submitted = False
  17. 16 if request.method == 'POST':
  18. 17 form = ContactForm(request.POST)
  19. 18 if form.is_valid():
  20. 19 cd = form.cleaned_data
  21. 20 # assert False
  22. 21 con = get_connection('django.core.mail.backends.console.EmailBackend')
  23. 22 send_mail(
  24. 23 cd['subject'],
  25. 24 cd['message'],
  26. 25 cd.get('email', 'noreply@example.com'),
  27. 26 ['siteowner@example.com'],
  28. 27 connection=con
  29. 28 )
  30. 29 return HttpResponseRedirect('/contact?submitted=True')
  31. 30 else:
  32. 31 form = ContactForm()
  33. 32 if 'submitted' in request.GET:
  34. 33 submitted = True
  35. 34
  36. 35 return render(request, 'contact/contact.html', {'form': form, 'submitted': submitted})

Let’s have a look at the changes we’ve made:

  • Line 4. Import the send_mail() and get_connection() functions from django.core.mail.
  • Line 20. Comment out the assert False statement. If we don’t do this, we will keep getting Django’s error page.
  • Lines 21 to 28. Open a connection to the email backend and use the send_mail() function to send the email.

This is all you need to send an email in Django. To switch to production, you only need to change the backend and add your email server settings to settings.py.

Test the view by filling out the form and submitting. If you look in the console window (PowerShell or command prompt) you will see the view sent the coded email straight to the console. For example, when I submitted the form, this was what Django output to PowerShell (I’ve shortened some of the longer lines):

  1. Content-Type: text/plain; charset="utf-8"
  2. MIME-Version: 1.0
  3. Content-Transfer-Encoding: 7bit
  4. Subject: this is the subject
  5. From: nigel@masteringdjango.com
  6. To: siteowner@example.com
  7. Date: Fri, 22 May 2020 23:50:39 -0000
  8. Message-ID: <...@DESKTOP.home>
  9. This is the message
  10. ------------------------------------------

Now the form is complete, submit the form with valid data and the contact view will redirect to the contact page with “submitted=True” as a GET parameter—http://127.0.0.1:8000/contact?submitted=True. With submitted set to True, the contact.html template will execute the first {% if %} block and render the success message instead of the form (Figure 8-6).

Creating a Contact Form - 图4

Figure 8-6: Once a valid form has been submitted, the thank you message is shown instead of the form.