LoginSignup
89
69

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-09-05

はじめての Flask #3 ~htmlをrenderしてみよう~

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

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

PythonからDBを作ってみよう!

では初めに、DBについて簡単に説明しますね (そんなの知ってるよ!という方は飛ばしていただいて結構です。)
DBとは

検索や蓄積が容易にできるよう整理された情報の集まり。 通常はコンピュータによって実現されたものを指すが、紙の住所録などをデータベースと呼ぶ場合もある。コンピュータを使用したデータベース・システムでは、データベース管理用のソフトウェアであるデータベース管理システムを使用する場合も多い。

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

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

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

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

データベースを実際に操作するにはSQLという言語を用いるのですが、それを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番目の人を取り出したいなら、

user = session.query(User).get(2)

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

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

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

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

値の大小で検索

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

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になるものが抽出されます。

つまり

一致で検索

user = session.query(User).filter(User.name == "Python").all()
print(user)
# [User<1, thisisme@test.com, Python>, User<6, ilikeguido@test.com, Python>]

こういうことです。

複数条件

user = session.query(User).filter(User.name == "Python", User.email == "thisisme@test.com").all()
print(user)
# [User<1, thisisme@test.com, 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()を忘れないでくださいね!

データを削除する

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

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

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

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を受け取ろう~,
パスワードの暗号化に関しては 国会でも話に上がった "HASH" をPythonで -- hash()じゃないよ! --
のとおりです。

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
こんな感じになりましたかね?(最後の投稿と、その前の投稿の間の空白の40分間でOVERLORD三期観てました)

おわり

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

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

次回: はじめての Flask #5 ~JSONを返すWebAPIを書こう~

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

89
69
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
89
69