Andreas L.
Andreas L.

Reputation: 4531

Django contact e-mail form does not render/show up in template home.html

Django project structure

Parent directory: personal_portfolio_project Main sub-direcory (containing the settings): personal_portfolio Apps: portfolio, blog

Directory structure of the entire project with all its apps (printed out in the console via tree):

├── personal_portfolio_project
│   ├── blog
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-39.pyc
│   │   │   ├── admin.cpython-39.pyc
│   │   │   ├── models.cpython-39.pyc
│   │   │   ├── urls.cpython-39.pyc
│   │   │   └── views.cpython-39.pyc
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   ├── 0002_auto_20210117_1306.py
│   │   │   ├── 0003_auto_20210117_1309.py
│   │   │   ├── 0004_auto_20210117_1432.py
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       ├── 0001_initial.cpython-39.pyc
│   │   │       ├── 0002_auto_20210117_1306.cpython-39.pyc
│   │   │       ├── 0003_auto_20210117_1309.cpython-39.pyc
│   │   │       ├── 0004_auto_20210117_1432.cpython-39.pyc
│   │   │       └── __init__.cpython-39.pyc
│   │   ├── models.py
│   │   ├── templates
│   │   │   └── blog
│   │   │       ├── all_blogs.html
│   │   │       └── detail.html
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── db.sqlite3
│   ├── manage.py
│   ├── media
│   │   └── portfolio
│   │       └── images
│   │           ├── DSC_0004a.jpg
│   │           └── DSC_0010a.jpg
│   ├── personal_portfolio
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-39.pyc
│   │   │   ├── settings.cpython-39.pyc
│   │   │   ├── urls.cpython-39.pyc
│   │   │   └── wsgi.cpython-39.pyc
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── portfolio
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-39.pyc
│   │   │   ├── admin.cpython-39.pyc
│   │   │   ├── forms.cpython-39.pyc
│   │   │   ├── models.cpython-39.pyc
│   │   │   └── views.cpython-39.pyc
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── forms.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       ├── 0001_initial.cpython-39.pyc
│   │   │       └── __init__.cpython-39.pyc
│   │   ├── models.py
│   │   ├── static
│   │   │   └── portfolio
│   │   │       ├── CV_Andreas_Luckert.pdf
│   │   │       ├── DSC_0010a.jpg
│   │   │       ├── Logo_Andreas_Luckert.png
│   │   │       ├── Logo_ZappyCode_DjangoCourse.png
│   │   │       ├── custom.css
│   │   │       └── main.js
│   │   ├── templates
│   │   │   └── portfolio
│   │   │       ├── about.html
│   │   │       ├── base.html
│   │   │       └── home.html
│   │   ├── tests.py
│   │   └── views.py
│   └── test_sendgrid.py

Story - line

I have a portfolio/forms.py - file in the "portfolio" - app with the following content:

from django import forms

class ContactForm(forms.Form):
    from_email = forms.EmailField(required=True)
    subject = forms.CharField(required=True)
    message = forms.CharField(widget=forms.Textarea, required=True)
    # From docs: https://docs.djangoproject.com/en/3.1/topics/forms/#more-on-fields
    cc_myself = forms.BooleanField(required=False)

In the portfolio/views.py I have the following relevant part:

from .forms import ContactForm  # import module from same parent folder as this script
import General.Misc.general_tools as tools  # custom module to import special-print function

def contactView(request, admin_email='[email protected]'):

    if request.method == 'GET':
        sendemail_form = ContactForm()
    else:
        sendemail_form = ContactForm(request.POST)

        if sendemail_form.is_valid():
            # * Retrieve data and set up e-mail (subject, sender, message)
            subject = sendemail_form.cleaned_data['subject']
            from_email = sendemail_form.cleaned_data['from_email']
            message = sendemail_form.cleaned_data['message']
            # From docs: https://docs.djangoproject.com/en/3.1/topics/forms/#more-on-fields
            cc_myself = sendemail_form.cleaned_data['cc_myself']

            recipients = [admin_email]
            if cc_myself:
                recipients.append(from_email)

            # * Send email
            try:
                # General Django docs: https://docs.djangoproject.com/en/3.1/topics/email/#send-mail
                # NOTE on passed list as 4th parameter: recipient-list

                ## i) How to set up an email contact form - docs: https://learndjango.com/tutorials/django-email-contact-form
                send_mail(subject,
                          message,
                          from_email,
                          recipients,
                          fail_silently=False)

            # * Exceptions * #
            # NOTE on scope: security
            except BadHeaderError:
                return HttpResponse('Invalid header found.')
            # General exception to be printed out
            except Exception as e:
                tools.except_print(f"ERROR:\n{e}")

            # Return success (if this code is reached)
            return redirect('success')

    # Send the completed (filled-in) form to the homepage - HTML - document
    return render(request, "portfolio/home.html",
                  {'sendemail_form': sendemail_form})


def successView(request):
    return HttpResponse('Success! Thank you for your message.')

This is the contact form I included at the very end of my portfolio/templates/portfolio/home.html (the rest of the HTML works perfectly fine and generally Django does not show any errors in the console while displaying the localhost-webpage):

<!-- ** E-Mail contact form **
Docs: https://learndjango.com/tutorials/django-email-contact-form-->

{% if sendemail_form %}
    <h2 class="mt-5">Contact Me</h2>
    <hr>
    <form method="post">
        {% csrf_token %}
        <!-- NOTE on ".as_p": https://docs.djangoproject.com/en/3.1/topics/forms/#form-rendering-options
        Possible attributes: as_table, as_p, as_ul -->
        {{ sendemail_form.as_p }}
        <div class="form-actions">
            <button type="submit">Send</button>
        </div>
    </form>
{% endif %}

The content of my personal_portfolio/urls.py is the following:

from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings  # get info from the settings.py - file
from portfolio import views  # other views could also be imported (e.g. from the blog-app)

urlpatterns = [
    path("", views.home, name="home"),

    # Docs: https://learndjango.com/tutorials/django-email-contact-form
    path("contact/", views.contactView, name='contact'),
    path("success/", views.successView, name='success'),

    # Add "About me" for additonal info about the creator of the webpage
    path("about/", views.about, name="about"),

    # Standard admin page
    path('admin/', admin.site.urls),

    # NOTE on include: forwarding all /blog/ - related requests to the blog-app
    path('blog/', include('blog.urls')),
]

# * Add a static link to the MEDIA - files * #
# NOTE on processing: import certain variables defined in the settings.py of this project
# Docs: google django images (or media (folder)) etc.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

I tried to register the form in the portfolio/admin.py just like classes defined in portfolio/models.py, but first of all I don't know if that's necessary and secondly it didn't even work due to the TypeError: 'DeclarativeFieldsMetaclass' object is not iterable, so I removed the following code from the portfolio/admin.py afterwards:

from django.contrib import admin
# !!! CAUTON: TypeError: 'DeclarativeFieldsMetaclass' object is not iterable
from .forms import ContactForm

# NOTE on registering form: doesn't work due to the following error:
# TypeError: 'DeclarativeFieldsMetaclass' object is not iterable
admin.site.register(ContactForm)

By and large, I'm confused as to how to make the form finally appear on my portfolio/templates/portfolio/home.html. As the {% if sendemail_form %} scans for a non-None form to be passed, this indicates that the form is not passed correctly as it should.


Test to check whether the function contactView() in views.py calling the contact-form is reached with a printout in it:

$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 19, 2021 - 14:16:37
Django version 3.1.5, using settings 'personal_portfolio.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[19/Jan/2021 14:16:42] "GET / HTTP/1.1" 200 31617
[19/Jan/2021 14:16:42] "GET /static/portfolio/custom.css HTTP/1.1" 304 0
[19/Jan/2021 14:16:42] "GET /static/portfolio/main.js HTTP/1.1" 304 0
[19/Jan/2021 14:16:43] "GET /static/portfolio/Logo_ZappyCode_DjangoCourse.png HTTP/1.1" 200 442438

--> According to the console it is not reached, since the printout does not appear.

Upvotes: 1

Views: 199

Answers (2)

Ralf
Ralf

Reputation: 16515

You need to create the ContactForm in the home view that renders the home page. We need to do this because the form appears in the home page template; if we don't add the form in this view, then the contact form part of the template never will work because it receives None.

def home(request):
    ... your code here ...

    # send empty contact form to template
    sendemail_form = ContactForm()

    return render(
        request,
        "portfolio/home.html",
        {'sendemail_form': sendemail_form})

Then the contactView should never render a template, just redirect. The job of the contactView is to get the POST data and process it, and the redirect to a differnt view; it should never render a template.

def contactView(request, admin_email='[email protected]'):
    if request.method == 'POST':
        sendemail_form = ContactForm(request.POST)

        if sendemail_form.is_valid():
            subject = sendemail_form.cleaned_data['subject']
            from_email = sendemail_form.cleaned_data['from_email']
            message = sendemail_form.cleaned_data['message']
            cc_myself = sendemail_form.cleaned_data['cc_myself']

            recipients = [admin_email]
            if cc_myself:
                recipients.append(from_email)

            try:
                send_mail(subject,
                          message,
                          from_email,
                          recipients,
                          fail_silently=False)
            except BadHeaderError:
                return HttpResponse('Invalid header found.')
            except Exception as e:
                tools.except_print(f"ERROR:\n{e}")
                return HttpResponse('errors.')

            # Return success (if this code is reached)
            return redirect('success')

    # return to home page
    return redirect('home')

Also, point your contact form (which is located in the home page template) to the correct URL (which is the contact URL, not the home URL); you could change:

{% if sendemail_form %}
    ...
    <form method="post">
    ...
{% endif %}

to

{% if sendemail_form %}
    ...
    <form method="post" action="{% url 'contact' %}">
    ...
{% endif %}

Upvotes: 2

Ralf
Ralf

Reputation: 16515

I think you need to add the correct URL to the contact HTML form.

You say that the HTML form is located in the home page, but the HTML form does not specify a different action (the target URL), so it will post (send a HTTP POST request) to the home page URL, which uses the view views.home, and that is not what you need.

By specifying a different HTML action attribute in the form, the page will send the data to a different URL (should be contact), which should then use your view views.contactView.

Try changing your HTML code for the contact form (in the home page template) from

{% if sendemail_form %}
    ...
    <form method="post">
    ...
{% endif %}

to

{% if sendemail_form %}
    ...
    <form method="post" action="{% url 'contact' %}">
    ...
{% endif %}

Also, add the ContactForm() in the view of your home page as well.

Upvotes: 1

Related Questions