Flask-PyMongoを使って簡単なウェブアプリケーション

# 概要
FlaskとMongoDBを組み合わせて、何かを作るための前準備。
ウェブからMongoDBへの読み書きを行うだけの簡単なウェブアプリケーションを作成。Flask-PyMongoというモジュールを使用しています。これは、FlaskとPyMongoをセットにしてプラスアルファされている感じ?そもそもPyMongoを単独で使ったことがないため、利点は今のところちゃんと理解できていません…。
[Git](https://github.com/KI1208/Flask-PyMongo-Test)  にコードあり。

# 環境
ホストマシン: Windows10
Python: 3.6.4
IDE: PyCharm　2017.3.2 Community Edition
MongoDBのためのコンテナ環境: Docker Toolbox(17.10.0-ce)

# MongoDBの用意
オフィシャルのコンテナイメージをそのまま使用。

```shell-session
$ docker pull mongo:3.6.1-jessie
$ docker run --name my-mongo -d -p 27017:27017 mongo:3.6.1-jessie
```

Docker Toolbox環境なので、MongoDBのクライアントが入っていれば、以下のようにアクセスできる。
Out of boxでは3つのデータベースがあるらしい。  

```shell-session
[root@localhost myapp]# mongo 192.168.99.100 # DockerホストのIPアドレス
MongoDB shell version v3.6.1
connecting to: mongodb://192.168.99.100:27017/test
MongoDB server version: 3.6.1
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```

# PyCharmの準備
PyCharmはなくてもなんら問題ないが、Flask-PyMongo,python-dateutilのインストールだけは必要。また、一応仮想環境として分離しておく。
1. 適当なフォルダを作成してPyCharmでそこに新規Projectを作成(C:\test)
2. その中にvirtualenv用のフォルダを作成(C:\test\venv)
3. PyCharmのFile->Settings->Project InterpreterからAdd Local
4. Interpreterを選択して、virtualenv作成
5. 今度は⁺サイン(Install)からFlask-PyMongo,python-dateutilをインストールしておく

![image.png](https://qiita-image-store.s3.amazonaws.com/0/159980/33fb6576-0d37-1094-cf4e-9c3f47f756af.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/159980/c9d0ae1b-4a89-d373-c919-19f0d3d100a6.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/159980/f95c71b9-e907-790d-26c3-17f93e95d5ea.png)

# フォルダ構成の準備
上記プロジェクトフォルダにgit cloneしてしまえばそれでOKなはずですが、以下のような構成になります。

C:\test
* venv\
* static\
** style.css
* templates\
** layout.html
** toppage.html
** result.html
* testapp.py

# コード作成

まずは、アプリケーション本体。
parseはHTMLから文字列として送られてくる日付を、datetime型に変換するために使用しています。
基本的にはmongo.dbが指定したデータベースになります。ここではtestdb。次のuserがコレクション名で、コレクションに対して、find,insertといったメソッドが使用可能です。
注意点としては、mongodb(pymongo)では時刻はdatetime型、しかもUTCで入れるのがよいとのこと。
From [公式](http://api.mongodb.com/python/current/examples/datetimes.html)

```py:testapp.py
from flask import Flask, current_app, request, flash,redirect,url_for,render_template
from flask_pymongo import PyMongo
from dateutil.parser import parse

app = Flask(__name__)
app.secret_key = 'secret'
# 以下でMongoDBの場所を指定。testdb(データベース)やuser(コレクション、SQLでいうテーブル)はあらかじめ作る必要なし。
app.config['MONGO_HOST'] = '192.168.99.100'
app.config['MONGO_PORT'] = 27017
app.config['MONGO_DBNAME'] = 'testdb'
mongo = PyMongo(app, config_prefix='MONGO')


@app.route('/', methods=['GET'])
def show_entry():
    users = mongo.db.user.find()
    entries = []
    for row in users:
        entries.append({"name": row['name'], "birthday": row['birthday'].strftime("%Y/%m/%d")})

    return render_template('toppage.html', entries=entries)


@app.route('/add', methods=['POST'])
def add_entry():
    mongo.db.user.insert({"name": request.form['name'], "birthday": parse(request.form['birthday'])})
    flash('New entry was successfully posted')
    return redirect(url_for('show_entry'))


@app.route('/search', methods=['POST'])
def filter_entry():
    start = parse(request.form['start'])
    end = parse(request.form['end'])
    cur = mongo.db.user.find({'birthday': {'$lt': end, '$gte': start}})
    results = []
    for row in cur:
        results.append({"name": row['name'], "birthday": row['birthday'].strftime("%Y/%m/%d")})

    return render_template('result.html', results=results)


if __name__ == '__main__':
    app.run(host='0.0.0.0')

```

あとはHTML。layout.htmlは他のHTMLのひな形になるテンプレート。公式のチュートリアルからログイン部分を削ったくらい。

```html:layout.html
<!doctype html>
<title>TestApp</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> <!-- static/style.cssを読み込む -->
<div class=page>
  <h1>Test App</h1>
  {% for message in get_flashed_messages() %}
    <div class=flash>{{ message }}</div>
  {% endfor %}
  {% block body %}{% endblock %}    <!-- このブロックが参照時に置き換えられる -->
</div>
```

toppage.htmlが最初にアクセスされるページ。

```html:toppage.html
{% extends "layout.html" %}    <!-- これで元になるHTML(laytout.html)を指定 -->
{% block body %}    <!-- layout.htmlのbodyというブロックを以下で置き換える -->
  <form action="{{ url_for('add_entry') }}" method=post class=add-entry>
    <dl>
      <dt>Name:
      <dd><input type=text size=20 name=name>
      <dt>Birthday:
      <dd><input type=date name=birthday>
      <dd><input type=submit value=Submit>
    </dl>
  </form>

  <form action="{{ url_for('filter_entry') }}" method=post class=add-entry>
    <h3>Search X whose birthday is </h3>
    <dl>
      <dt>From:
      <dd><input type=date name=start>
      <dt>End:
      <dd><input type=date name=end>
      <dd><input type=submit value=Search>
    </dl>
  </form>

  <ul class=entries>
  {% for entry in entries %}
    <li><h2>{{ entry.name }}</h2>{{ entry.birthday }}
  {% else %}
    <li><em>No entries.</em>
  {% endfor %}
  </ul>
{% endblock %}
```

特定のレンジに誕生日が入る人をピックアップする機能を入れたので、その結果を表示するためのページ。

```html:result.html
{% extends "layout.html" %}    <!-- これで元になるHTML(laytout.html)を指定 -->
{% block body %}    <!-- layout.htmlのbodyというブロックを以下で置き換える -->
  <h2>The result is </h2>
  <ul class=entries>
  {% for result in results %}
    <li><h2>{{ result.name }}</h2>{{ result.birthday }}
  {% else %}
    <li><em>No match.</em>
  {% endfor %}
  </ul>
{% endblock %}
```

最後にCSS。これは公式のチュートリアルのものそのまま。

```css:style.css
body            { font-family: sans-serif; background: #eee; }
a, h1, h2       { color: #377BA8; }
h1, h2          { font-family: 'Georgia', serif; margin: 0; }
h1              { border-bottom: 2px solid #eee; }
h2              { font-size: 1.2em; }

.page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
                  padding: 0.8em; background: white; }
.entries        { list-style: none; margin: 0; padding: 0; }
.entries li     { margin: 0.8em 1.2em; }
.entries li h2  { margin-left: -1em; }
.add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl   { font-weight: bold; }
.metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
                  margin-bottom: 1em; background: #fafafa; }
.flash          { background: #CEE5F5; padding: 0.5em;
                  border: 1px solid #AACBE2; }
.error          { background: #F0D6D6; padding: 0.5em; }
```

これらを作成して、python testapp.pyで起動すれば、あとはブラウザでhttp://localhost:5000にアクセスすればOK。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/159980/22d22060-fe63-a847-c5b4-575b6c9b9cbd.png)