はじめての Flask #4 ~データベースをSQLAlchemyでいじってみよう~

[はじめての Flask #3 ~htmlをrenderしてみよう~](https://qiita.com/nagataaaas/items/4662237cfb7b92f0f839)

今回は、実際にサイト内での処理を基にデータベースを作ってみたいと思います！

サイトの構築になくてはならないデータベースをいじれるようになりましょう！

# PythonからDBを作ってみよう！

では初めに、**DB**について簡単に説明しますね (そんなの知ってるよ！という方は飛ばしていただいて結構です。)
**DB**とは 
> 検索や蓄積が容易にできるよう整理された情報の集まり。 通常はコンピュータによって実現されたものを指すが、紙の住所録などをデータベースと呼ぶ場合もある。コンピュータを使用したデータベース・システムでは、データベース管理用のソフトウェアであるデータベース管理システムを使用する場合も多い。

ありがとうwikipediaさん！ということで、**データのまとまりを使いやすくまとめたもの**です。
データベースというファイルの中にいくつかテーブルという枠を作ります。そのテーブルには、決まった形式のデータしか入れられません。
例えば、< users >というテーブルを作って、そこにはユーザに関する情報のみを入れる。といった感じですね。テーブルの中にはいくつもデータを入れることができます。

百聞は一見に如かずということで、実際に触り始めてみましょう！

まずコンソール画面で
` $ pip install sqlalchemy`
と入力してください。

Python用の**SQLAlchemy**というライブラリをインストールしました。

データベースを実際に操作するにはSQLという言語を用いるのですが、それをPython内からやってくれる、というライブラリになっています。

では、ユーザ情報の入るデータベースを作って、実際に一つ入れてみましょう。

```python

from sqlalchemy import create_engine, Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///user.db')  # user.db というデータベースを使うという宣言です
Base = declarative_base()  # データベースのテーブルの親です


class User(Base):  # PythonではUserというクラスのインスタンスとしてデータを扱います
    __tablename__ = 'users'  # テーブル名は users です
    id = Column(Integer, primary_key=True, unique=True)  # 整数型のid をprimary_key として、被らないようにします
    email = Column(String)  # 文字列の emailというデータを作ります
    name = Column(String)  # 文字列の nameというデータを使います

    def __repr__(self):
        return "User<{}, {}, {}>".format(self.id, self.email, self.name)


Base.metadata.create_all(engine)  # 実際にデータベースを構築します
SessionMaker = sessionmaker(bind=engine)  # Pythonとデータベースの経路です
session = SessionMaker()  # 経路を実際に作成しました

user1 = User(email="thisisme@test.com", name="Python")  # emailと nameを決めたUserのインスタンスを作りましょう(idは自動で1から順に振られます)
session.add(user1)  # user1 をデータベースに入力するための準備をします
session.commit()  # 実際にデータベースにデータを入れます。
```

これで、**user.db というファイル内の users というテーブルの中に [id=1, email="thisisme@test,com", name="Python"] という値を持つデータが入りました**

では、今度はデータを取得してみましょう！

| id | email | name |
|:--:|:-----:|:----:|
|1|thisisme@test.com|Python|
|2|iloveruby@test.com|Ruby|
|3|cobolisnotold@test.com|Cobol|
|4|cplusminus@test.com|C|
|5|kotlinsocute@test.com|Kotlin|
|6|ilikeguido@test.com|Python|

というデータがusersの中にあったとします。

いろんなユーザのデータを取り出してみましょう！！

# データの操作

### primary_keyで検索
例えば、idが2番目の人を取り出したいなら、

```python
user = session.query(User).get(2)
```
としましょう。
`session.query(table名)`で、テーブルを指定して検索がかけられます。
primary_keyがidになっているので、**get()**の引数がidに合致するデータが取り出されます。

ただ、1番目の人を取り出すなら、

```python
session.query(User).get(1) == session.query(User).first()  # True
user = session.query(User).first()
```

のほうが良いでしょう。こっちの方が早いですしね。

### 値の大小で検索

idが3以上の人を探したいときには

```python
user = session.query(User).filter(User.id >= 3).all()
print(user)
# [User<3, cobolisnotold@test.com, Cobol>, User<4, cplusminus@test.com, C>, User<5, kotlinissocute@test.com, Kotlin>, User<6, ilikeguido@test.com, Python>]

```
`session.query(User).filter(cond)`とすることで、condがTrueになるものが抽出されます。

つまり

### 一致で検索
```python
user = session.query(User).filter(User.name == "Python").all()
print(user)
# [User<1, thisisme@test.com, Python>, User<6, ilikeguido@test.com, Python>]
```

こういうことです。


### 複数条件
```python
user = session.query(User).filter(User.name == "Python", User.email == "thisisme@test.com").all()
print(user)
# [User<1, thisisme@test.com, Python>]
```

複数の条件を持たけることもでき、そのすべてが当てはまるもののみ返します。

いったんはこの程度で十分だと思います。

### 変更を加える

```python
user = session.query(User).filter(User.name == "Python", User.email == "thisisme@test.com").all()
user.name = "Pythonista"
print(user)
# [User<1, thisisme@test.com, Pythonista>]
session.commit()
```
こうすることで、ユーザのnameがPythonistaに書き換わりました。インスタンスの様に扱えるのですね。

`session.commit()`を忘れないでくださいね！

### データを削除する

```python
user = session.query(User).filter(User.name == "Python", User.email == "thisisme@test.com").all()
session.delete(user)
session.commit()
```

とすることでuserをデータベースから削除できます。

では、いままでの知識+αで簡単な掲示板を作ってみましょう！

# 実際にアプリを作ってみよう！

とにかくシンプルな実装で、簡単に作ってみましょう！
では、

```
root/
|__main.py
|__templates
    |__mainpage.html
```

という風にファイルを構築して、アプリを作り始めましょう。

今回は掲示板ということなので、まずはユーザーと、投稿のデータベースを作りましょう！

```python
from sqlalchemy import create_engine, Column, String, Integer, DATETIME
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

engine = create_engine('sqlite:///app.db')
Base = declarative_base()


class User(Base):
    __tablename__ = 'users'
    name = Column(String, primary_key=True, unique=True)
    passw = Column(String)

    def __repr__(self):
        return "User<{}, {}, {}>".format(self.name)


class Content(Base):
    __tablename__ = 'contents'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    content = Column(String)
    timestamp = Column(DATETIME)

    def __repr__(self):
        return "Content<{}, {}, {}, {}>".format(self.id, self.name, self.content, self.timestamp)

Base.metadata.create_all(engine)
SessionMaker = sessionmaker(bind=engine)
session = scoped_session(SessionMaker)
```

**nameとpasswという値を持つUser**と、**id, name, content, timestampという値を持つContent**というクラスを作成しました。

投稿の際に自動でアカウントを作り、名前がかぶっていればそのUserとpasswでの照合を行います。両方同じであればそのアカウントでContent(投稿)を作成し、合致しなければ何もしない。

という一連の流れを作ってみましょう。

```main.py
import hashlib
import datetime

from flask import *

from sqlalchemy import create_engine, Column, String, Integer, DATETIME
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

app = Flask(__name__)
engine = create_engine('sqlite:///app.db')
Base = declarative_base()


class User(Base):
    __tablename__ = 'users'
    name = Column(String, primary_key=True, unique=True)
    passw = Column(String)

    def __repr__(self):
        return "User<{}, {}, {}>".format(self.name)


class Content(Base):
    __tablename__ = 'contents'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    content = Column(String)
    timestamp = Column(DATETIME)

    def __repr__(self):
        return "Content<{}, {}, {}, {}>".format(self.id, self.name, self.content, self.timestamp)


Base.metadata.create_all(engine)
SessionMaker = sessionmaker(bind=engine)
session = scoped_session(SessionMaker)


@app.route("/", methods=["GET", "POST"])
def main_page():
    cont = session.query(Content).all()
    if request.method == "GET":
        return render_template("mainpage.html", cont=cont)
    user = session.query(User).get(request.form["name"].strip())
    if user:
        if user.passw != str(hashlib.sha256(request.form["pass"].strip().encode("utf-8")).digest()):
            return render_template("mainpage.html", cont=cont)
    else:
        user = User(name=request.form["name"], passw=str(hashlib.sha256(request.form["pass"].strip().encode("utf-8")).digest()))
        session.add(user)
    mess = Content(name=request.form["name"], content=request.form["content"], timestamp=datetime.datetime.now())
    session.add(mess)
    session.commit()
    cont = session.query(Content).all()
    return render_template("mainpage.html", cont=cont)


if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=8888, threaded=True)

```
データの受け取りに関しては [はじめての Flask #2 ~POSTを受け取ろう~](https://qiita.com/nagataaaas/items/3116352da186df102d96), 
パスワードの暗号化に関しては [国会でも話に上がった "HASH" をPythonで -- hash()じゃないよ! --](https://qiita.com/nagataaaas/items/eb85b4e9b6e2c5dd73ce)
のとおりです。

contというデータで投稿のリストをjinja2に渡しています。

そして、htmlの方ですが

```mainpage.html
<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <table border="1">
            <tr>
                <th>ID</th>
                <th>時間</th>
                <th>名前</th>
                <th>内容</th>
            </tr>
            {% for co in cont %}
                <tr>
                    <th>{{ co.id }}</th>
                    <th>{{ co.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</th>
                    <th>{{ co.name }}</th>
                    <th>{{ co.content }}</th>
                </tr>
            {% endfor %}
        </table>
        <form action="/" method="POST">
            <table>
                <tr>
                    <th>名前</th>
                    <th>パスワード</th>
                    <th>内容</th>
                </tr>
                <tr>
                    <th><input type="text" value="" placeholder="名前を入力してください" name="name"></th>
                    <th><input type="text" value="" placeholder="パスワードを入力してください" name="pass"></th>
                    <th><input type="text" value="" placeholder="内容を入力してください。" name="content"></th>
                </tr>
            </table>
            <input type="submit" value="sub">
        </form>
    </body>
</html>
```
このような形でよいと思います。
さて、実際に動かしてみてください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/236785/3dde17d8-e09d-2313-5456-e0a9c91441e5.png)
こんな感じになりましたかね？(最後の投稿と、その前の投稿の間の空白の40分間で[OVERLORD三期](http://overlord-anime.com/)観てました)


# おわり

どうですか？きちんと動いたと思います。

今回も楽しんでいただけたら幸いです。

[次回: はじめての Flask #5 ~JSONを返すWebAPIを書こう~](https://qiita.com/nagataaaas/items/24e68a9c736aec31948e)

ではみなさん、たのしいPython Lifeを！！