Ajax+Flaskを用いたコンタクトフォーム作成

# 経緯
ウェブ制作の一環として、コンタクトフォームを作ることになったのですが、既にある物を使うくらいなら勉強にもなるし、1から作ろうと考え他のがきっかけです。
実際、phpなどを使う方法もあるのですが、pythonに慣れていたため、flaskを使おうと思いました。　

# アーキテクチャ

![Untitled Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/234222/10c6e206-7353-ce72-57f3-e35261300324.png)

# 参考にしたサイト
CSSフレームワーク：
- [Bulma](https://bulma.io/)

Flaskでは：
- [Jinja2の使い方がわかるとFlaskを用いた開発がよりスマートになる](https://qiita.com/bookun/items/7ae5de21307d101b4759)
- [Flask-Mail documentation](https://pythonhosted.org/Flask-Mail/)
- [Attaching content and links to messages (Slack)](https://api.slack.com/docs/message-attachments)

# HTMLの中身


HTMLで必要になってくるのは`formタグ`内の`method`と`action`の指定であり、コンタクトフォームであるので下記のように記します。

```html
<form id="contact-form" method="POST" action="/127.0.0.1:5000">
```
今回はFlaskを使用しているため、actionの指定先はFlaskを立ち上げるサーバになります。
したがって、htmlの中身は下記のようになります：

```html
<form id="contact-form" method="POST" action="/127.0.0.1:5000">
	<div>
		<div class="field">
			<label class="label">Name</label>
			<div class="control">
				<input name="name" class="input" type="text" placeholder="Name" required>
			</div>
		</div>
		<div class="field">
			<label class="label">Email</label>
			<div class="control">
			<input id="email" name="email" class="input" type="email" placeholder="Email" required>
			</p>
			<p id="email-error" class="help is-danger email-success">
				This email is invalid
			</p>
		</div>
		<div class="field">
			<label class="label">Message</label>
			<div class="control">
				<textarea name="message" class="textarea" placeholder="Textarea" required></textarea>
			</div>
		</div>
		<div>
			<div class="control">
				<button id="submit" class="button is-link">Submit</button>
			</div>
			<div class="control">
				<button id="cancel" class="button is-text">Cancel</button>
			</div>
		</div>
	</div>
</form>
<div id="result"></div>
```
簡略化のために氏名・メールアドレス・メッセージだけにしました。ここで重要になるのが、`input`と`textarea`と`button`なので、他の要素はとりあえずは無視します。

# jsの中身
まず、フォームの中身をオブジェクト型で取ってきます

```js

var form = document.forms.contactForm;
```
これを使用して、もしsubmitボタンが押された時、それらをajaxで処理します。
## ajaxの部分について
初めに、デフォルトでは`submit`をした時にデフォルトでPOSTするようになっているのでこれをしないように明言する必要があります。

```js
$(form).submit(function(e) {
   // prevent it from POSTing
   e.preventDefault();
   
   //ここに必要なコードを記述していく
}
```
では実際に内容を書いていきます。
ajaxの`data`は正規化されていないといけないので、下記のように正規化を行う。

```js
var formData = $(form).serialize();
```
そしてこれを用いて、ajaxに渡す内容は下記のようになります。

```js

$.ajax({
  type: 'POST',
  url: 'http://127.0.0.1:5000',
  data: formData
})
```
`url`はflaskで指定する番号になるため、とりあえずはこのままにしておきます。実際にサーバがあるのならそれをここに記述します。

### 成功時

```js

.done(function(response) {
    // ここに成功時の動作を記述する
})
```

### 失敗時

```js

.fail(function(response) {
    // ここに失敗時の動作を記述する
});
```

## jsのまとめ

したがって、以上のコードをまとめると下記のようになります：

```js

// Object of the form
var form = document.forms.contactForm;

/*
 * This function receives the form and sends it
 * using ajax.
 */
function sendForm(e) {
  // When 'submit' was clicked
  $(form).submit(function(e) {
    // prevent it from POSTing
    e.preventDefault();

    var formData = $(form).serialize();

    $.ajax({
      type: 'POST',
      url: 'http://127.0.0.1:5000',
      data: formData
    })
    // If succeeded, show a popup message
    .done(function(response) {
      // reset form inputs
      $('form :input').val('');
    })
    .fail(function(response) {
      // 失敗時の記述
    });
  });
};

form.name.addEventListener('change', sendForm, false);
```
となる。私は成功時は更にポップアップメッセージを追加し、失敗時はそのエラーを吐くようにしました。
色々工夫の方法はあると思うので、自由に書いてください。

# Flaskの中身

pythonコードのでは実際にslackやメールを送信するという動作を行います。  
メールは `flask_mail` を使用すると実装ができ、slackは `slackweb` というパッケージを使用すると実装ができます。

## flask_mail

上記でも参考サイトであげさせていただいた[Flask-Mail documentation](https://pythonhosted.org/Flask-Mail/)を参考に送信するメールのコードを記述するとできます。
単純にメールを送信するコードは下記のようになります：

```python
from flask_mail import Message

@app.route("/")
def send():

    msg = Message('Hello',
                  sender='from@example.com',
                  recipients=['to_1@example.com', 'to_2@example.com'])
```

しかし、これでは同期処理となり、時間がかかるため、非同期処理にメールを送信する方法があります。
これを行うには、 `threading`パッケージの `Thread`メソッドを用います。

```python
from threading import Thread

from flask_mail import Message, Mail


def send_async_mail(app, msg):
    """
    Send mail asynchronously
    """
    mail = Mail(app)
    with app.app_context():
        mail.send(msg)

def send_mail(email, app, html, sender, mail_title):
    """
    Send mail to receiver
    """
    msg = Message(
            mail_title,
            sender=('name', 'sender@example.com'),
            recipients=[email],
            bcc=[sender],
            )
    msg.html = html
    thr = Thread(target=self.send_async_mail, args=[app, msg])
    thr.start()

    return True


```
とすれば、メールを非同期処理で送信することができます！

ここで、メールをhtmlでフォーマットしたい場合、Flaskはjinja2との相性が良いので[Flask-Mail documentation](https://pythonhosted.org/Flask-Mail/)を参考にすると綺麗に作成できると思います。

## Slack

今回、Slackには簡単にコンタクトフォームの内容を送信してみることにした。こちらはSlackが提供しているAPIですぐ実装ができます。
その前に、Slackにメッセージが送れるようにWebhook URLの発行が必要で、下記のサイトを参考にすると簡単にできます。
[SlackのWebhook URL取得手順](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)

URLを取得できればあとは簡単です。
[Attaching content and links to messages (Slack)](https://api.slack.com/docs/message-attachments)を参考に、好きな形でメッセージを送信します。
まず、メッセージの作成です。

```python

def create_message():
        attachments = []
        attachment = {
                'fallbacks': 'New Contact Has Arrived!',
                'title': "New Contact Has Arrived!",
                'color': '#anycolor',
                'fields': [
                    {
                        'title': 'Date',
                        'value': date,
                        'short': True
                        },
                    {
                        'title': 'Name',
                        'value': name,
                        'short': True
                        },
                    {
                       # Any other fields
                        },
                    ]
                }
        attachments.append(attachment)

        return attachments
```

これでメッセージの作成ができました。あとは先ほど取得したwebhook urlを用いてメッセージの送信を行います。

```python
import slackweb

slack = slackweb.Slack(url=WEBHOOK_URL)


def send_to_slack(slack):
        slack.notify(attachments=format_attachment())


if __name__ == '__main__':
        send_to_slack(slack)
```

以上で、メールとslackにメッセージを送信することができました！

# 最後に
コンタクトフォームを作る方法は色々あると思いますが、flaskを用いると結構簡単に作れてしまうことがわかりました。実際、色々な機能を追加するとまた違いますが、、、