【Django】問い合わせフォームの作成

#はじめに
Djnagoで個人サービスを開発しているのですが、
問い合わせフォームを作成してユーザから私にメールで問い合わせできるようしよう！と、
ふと思いましたので備忘がてら今回の記事作成しました。

Django初心者ですので何か指摘がありましたらぜひご連絡ください！

##実装
では、実装していきます。
プロジェクトやアプリケーションは既に作成済みである程で記述していきます。

また、私が実際に開発中のプロジェクト内で実装したので、
一部説明を省いている箇所などがあります。

なので、この記事通りに記述していっても実装はできません。
ただ、飛ばしているところは簡単な箇所のみで、
必須となるような箇所については説明しています。

本記事は参考程度にご覧ください。

###forms.pyの作成

まずは問い合わせフォームを作るため、forms.pyの修正を行います。

デフォルトでは作成されていないため、アプリケーション配下に作成してください。

今回は、Formクラスを継承したContactFormを作成しました。
入力するためのフィールドと、send_emailメソッドを記述します。

```python:アプリケーション名/forms.py
from django import forms
from django.core.mail import BadHeaderError, send_mail
from django.http import HttpResponse

class ContactForm(forms.Form):
    subject = forms.CharField(
        label="件名",
        max_length=100, 
        widget=forms.TextInput(
            attrs={
                "class": "form-control",
                "placeholder": "件名",
            }
        )
    )

    message = forms.CharField(
        label="問い合わせ内容",
        widget=forms.Textarea(
            attrs={
                "class": "form-control",
                "placeholder": "問い合わせ内容"
            }
        )
    )

    def send_email(self, username, email):
        subject = "【お問い合わせ" + self.cleaned_data["subject"]
        message = self.cleaned_data["message"] + f"\n\nBy {username}."
        recipient_list = ["onishi.ry3s3k4@gmail.com"]   ### 送信先
        try:
            send_mail(subject, message, email, recipient_list)
        except BadHeaderError:
            return HttpResponse("無効なヘッダが検出されました。")
```
今回は件名(subject)と問い合わせ内容(message)の二つの入力フィールドを作ります。
ユーザ名(name)や送信元(email)なども入力するようにしたい場合はそれらを追加してください。

私の場合は、ユーザ名と送信元アドレスは、
ログインしているユーザから自動で取得するようにしていますので記述していません。

その代わり、`send_email`メソッドの引数に、ユーザ名とメールアドレスを指定しています。
フォームに入力された内容は、`self.cleand_data["キー"]`で取得できます。

cleand_dataは、データに不整合がないデータを取得するらしいです。すごい！

Django標準機能のsend_mailメソッドをtry-exceptで囲んで実行します。
send_mailメソッドの引数には、件名・本文・送信元・送信先の順で渡します。

例外に`BadHeaderError`を指定していますが、
Headerを改ざんする攻撃的なのを検出したらエラーを出すらしいです。（合ってるかわからない）

フォームは完成したのでViewの作成を行います。

###views.pyの修正
今回作成するのは、問い合わせフォームのViewと、
問い合わせ後に表示するViewの二つです。

```python:アプリケーション名/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls.base import reverse_lazy
from django.views.generic import TemplateView
from django.views.generic.edit import FormView

from .forms import ContactForm


class ContactFormView(LoginRequiredMixin, FormView):
    template_name = "mainApp/contact.html"
    form_class = ContactForm
    success_url = reverse_lazy("mainApp:contact_result")

    def form_valid(self, form):
        form.send_email(
            username=self.request.user.username, 
            email=self.request.user.email
        )
        return super().form_valid(form)

class ContactResultView(LoginRequiredMixin, TemplateView):
    template_name = "mainApp/contact_result.html"
```

`ContactFormView`は、`LoginRequiredMixin`と
`FormView`の二つのクラスを継承しています。

`form_class`に先程作成した`ContactForm`を指定し、
送信後のURLは、`success_url`で`mainApp:contact_result`を指定しています。

`LoginRequiredMixin`はログインしていないとアクセス出来ないようにする画面です。

`form_valid`プロパティはフォームに正しく情報が入力された際に呼ばれるメソッドで、
ここではform.send_emailで先程作成したフォームのメソッドを呼び出します。

引数でusernameとemailが必須なので、
self.request.user.usernameなどで情報を取得しています。

`ContactResultView`は、表示するためのHTMLファイルを指定しているだけです。

###urls.pyの修正

urls.pyは、デフォルトではアプリケーション配下に存在しないため作成します。
1つ目のPATHは、問い合わせ画面に行くためのリンクが記載されているViewのPATHです。
2つ目が問い合わせ画面のPATHで、3つ目が問い合わせ後の画面です。

```python:アプリケーション名/urls.py
from django.urls import path

from . import views


app_name="アプリケーション名"
urlpatterns = [
    path("", views.HomeView.as_view(), name="home"),
    path("contact/", views.ContactFormView.as_view(), name="contact"),   ### 追加
    path("contact/result", views.ContactResultView.as_view(),name="contact_result"),    ### 追加
]
```


次に、templateファイルを作成します。

アプリケーション配下にtemplatesフォルダを作成し、
その下にアプリケーションと同じ名前のフォルダを作成します。

ベースにするHTMLのbase.htmlと、ホーム画面を表示するためのhome.htmlの説明は省きます。
なお、base.htmlではBootstrap4を使えるように記述しています。

今回作成するのは、先ほどのViewの`template_name`で記述したHTMLファイルです。

まず、問い合わせフォームの方のHTMLファイル。

```python:アプリケーション名/templates/アプリケーション名/contact.html
{% extends "<アプリケーション名>/base.html" %}    ### ベースのHTML
{% block title %}問い合わせ{% endblock %}    ### titleタグ

{% block content %}    ### contentタグ
<div class="container">
    <h2 class="text-secondary my-5">お問い合わせフォーム</h2>
    <form action="" method="POST">
        {% csrf_token %}
        {{ form.non_field.errors }}

        {% for field in form %}
        {{ field }}
        <br>
        {% endfor %}

        <button class="btn btn-danger mt-3 px-4">送信</button>
    </form>
</div>
{% endblock %}
```
フォームを作成する時はformタグで囲み、method="POST"の場合は、
脆弱性対策で`{% csrf_token %}`を記述します。

その後、for文で書くフィールドを記述するようにしています。
ボタンは送信ボタンのみ記述しています。

ボタンをクリック後は、views.pyで記述した、
reverse_lazyのURLに飛ぶようになっているので注意。


次に、問い合わせ後のページです。

こっちは正直言って適当です。。。
送信できたのがわかればOKです。

```python:アプリケーション名/templates/アプリケーション名/contact_result.html
{% extends "mainApp/base.html" %}
{% block title %}問い合わせ完了{% endblock %}

{% block content %}
<div class="container">
    <h2 class="text-secondary my-5">お問い合わせ完了</h2>
    <p>メールが送信されました。ありがとうございます</p>
    <a href="{% url 'アプリ名:home' %}" class="btn btn-outline-danger mt-5">ホームに戻る</a>
</div>
{% endblock %}
```

ここまでである程度OKですが、最後に一つ。`settings.py`の修正を行います。

以下を追加するだけです。

これは何かというと、smtpなどを利用してメールを送らないで、
コンソールに内容を表示すると言った開発環境向けの記述です。

なので本番環境では、また別の記述にしないといけません。
（それについては別途記事を作成すると思います。）

```python:setting.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
```

以上で実装できたと思いますので確認していきます。![スクリーンショット 2021-07-29 21.17.26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/5775abe0-0eae-c60f-ef72-bfee7b3312c5.jpeg)


まず、ホーム画面からお問い合わせ画面に遷移します。
（私の画面は開発中のサイトのためみなさんと異なります。）
![スクリーンショット 2021-07-29 21.15.43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e1942346-6339-f727-5293-510d8e979b7e.jpeg)

お問い合わせフォームはしっかりと作られていました！
少し綺麗に見えるのはforms.pyで`class`に`form-control`を指定したからです。

それとプレースホルダーも同じく指定したからですね。
![スクリーンショット 2021-07-29 21.16.17.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/da4310d6-564c-e00a-1526-2e31288c1bbc.jpeg)


では、適当に文字を入力して送信をしてみたいと思います。
いざ！送信！
![スクリーンショット 2021-07-29 21.17.16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e0b7c00f-bc59-ae3d-55cc-3289f0270d21.jpeg)

お問い合わせ完了画面が表示されました。
とりあえず流れとしてはOKそうですね。
![スクリーンショット 2021-07-29 21.17.26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/601176eb-2783-7652-c940-362c02a39401.jpeg)

では、実際にメールは送信されているのか確認します。
今回は、settings.pyに記述した通り、コンソール出力です。

コンソールは以下の通りでした。

```
[29/Jul/2021 21:16:09] "GET /analytics/contact/ HTTP/1.1" 200 6176   ### 問い合わせフォームアクセス
Content-Type: text/plain; charset="utf-8"   ### 文字コード
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject:  =?utf-8?b?44CQ44GK5ZWP44GE5ZCI44KP44Gb44OG44K544OI44Gu44Oh44O844Or44Gn44GZ?= =?utf-8?b?44CC?= ### 件名
From: ********@gmail.com    ### 送信元
To: *********@gmail.com    ### 宛先
Date: Thu, 29 Jul 2021 12:17:19 -0000
Message-ID: <162756103906.7645.10870385695471281163@ryosukenombp.elecom>

テストメールはしっかり送れるか、緊張の一瞬です。
よろしくお願いします。

By admin.
-------------------------------------------------------------------------------
[29/Jul/2021 21:17:19] "POST /analytics/contact/ HTTP/1.1" 302 0
[29/Jul/2021 21:17:19] "GET /analytics/contact/result HTTP/1.1" 200 5954
[29/Jul/2021 21:17:27] "GET /analytics/ HTTP/1.1" 200 12773
```

しっかり送れてそうです！

#さいごに

最後までご覧いただきありがとうございました。

結構説明を省いてしまいましたがいかがでしょうか？
メール送信機能を実装したいと思う方は私レベルの知識があると仮定して記述しました。
（私自身まだDjango歴数ヶ月なので・・・。）

もし不明な箇所や間違っている箇所がありましたらご連絡ください！