14
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flaskで作るSNS Flask(Model)編

Last updated at Posted at 2021-01-02

#TL;DL
Udemyの下記講座の受講記録

Python+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~
https://www.udemy.com/course/flaskpythonweb/

この記事ではFlaskのView側(Model側)について記載する。

View(Template)側については以下の記事を参照
https://qiita.com/kekosh/items/e4b5d1e3272a20d1c966

##Model(モデル)
モデルとはデータベースのテーブルの内容を定義するもの。

SQLAlchemy
pythonで用いられるORマッパーのライブラリ。要はDBを操作するために利用するライブラリ

マイグレーション
Modelの変更をデータベースのテーブルに反映すること。

###1.テーブルの作成

インストール

pip install flask-sqlalchemy
pip install flask-migrate

モデル

#model.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

base_dir = os.path.dirname(__file__)

app = Flask(__name__)

# sqliteのファイル保存先を指定
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
    os.path.join(base_dir, 'data.sqlite')

# モデルに変更があった場合にシグナル送出の有無を設定する
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# appに設定した内容でDBに接続する
db = SQLAlchemy(app)

# モデルクラスの定義にはdb.Modelクラスを継承する必要がある。
class Person(db.Model):
    # テーブル名を設定(テーブル名はクラス名の複数形が一般的)
    __tablename__ = 'persons'

    # 作成するテーブルのカラムを定義
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    age = db.Column(db.Integer)

モデルからテーブルを作成し、データを追加する。

#crud.py
from model import db, Person

# モデルからテーブルを作成する(インポートされているモデルすべてが対象)
db.create_all()

* モデルで定義したデータクラスのインスタンスを作成する
man1 = Person('Taro', 18)
man2 = Person('Jiro', 18)
man3 = Person('Saburo', 18)

# 作成したテーブルにを追加する(add:追加 add_all:リスト形式の一括追加)
db.session.add_all([man1, man2])
db.session.add(man3)

# テーブルへの変更をcommitで確定する。
db.session.commit()
print(man1, man2, man3)

crud.pyファイルを実行後、Modelフォルダ内にsqliteのデータファイルが作成されていることを確認
スクリーンショット 2020-11-24 22.39.30.png

###2.テーブルのマイグレーション
マイグレーションとは・・・
データベースにモデルファイルに定義したテーブルの更新情報を反映し、その情報を履歴として管理しておくこと。これにより、別環境などに同内容のDBを構築したい場合に履歴に従ってDBを更新していくことで上手く同じ内容のDBを作成することができる。

注意点は、前の項のようにコントロールファイル内で「db.create_all()」メソッドを実行してテーブルを作成した場合はマイグレーションを利用するのに適していないこと。

from model import db, Person

# この場合はマイグレーションによる管理は適さない。
db.create_all()

マイグレーション管理する場合のモデル

import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

base_dir = os.path.dirname(__file__)

app = Flask(__name__)

# sqliteのファイル保存先を指定
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
    os.path.join(base_dir, 'migrate_data.sqlite')

# sqlalchemyのデータに変更があった場合にトレースする設定のONOFF
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# DBのインスタンス作成(接続)
db = SQLAlchemy(app)

# マイグレーションの準備(引数はFlaskAppとSQLAlchemyのインスタンス)
# 2021-01-10:
# 初期化はinit_appメソッドでも可能(Flask拡張機能標準メソッド)
# [Migrationインスタンス].init_app([flaskアプリ名],[db名]
# 例:
# migrate = Migrate()
# migrate.init_app(app, db)
Migrate(app, db)

# db.Modelクラスを継承することでテーブルを定義できるようにする
class Person(db.Model):
    # テーブル名を設定(テーブル名はクラス名の複数形が一般的)
    __tablename__ = 'persons'

    # 作成するテーブルのカラムを定義
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    gender = db.Column(db.Text)
    age = db.Column(db.Integer)

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "id={},name={},age={}".format(self.id, self.name, self.age)

マイグレーションの実行
1.環境変数を設定する
command : export FLASK_APP = [Flaskインスタンスを作成しているファイル]

"Flaskインスタンスを作成しているファイル"とは、"app=Flask()"が実行されているファイルのこと。(書かれているところではなくて実行されているところ)

(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# export FLASK_APP=migrate_model.py

2.DBを初期化する。これにより、マイグレーションの設定情報を保存するMigratinフォルダが作成される。
command : flask db init

(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# flask db init  Creating directory /home/venv/flaskenv/Model/migrations ...  done
  Creating directory /home/venv/flaskenv/Model/migrations/versions ...  done
  Generating /home/venv/flaskenv/Model/migrations/README ...  done
  Generating /home/venv/flaskenv/Model/migrations/alembic.ini ...  done
  Generating /home/venv/flaskenv/Model/migrations/env.py ...  done
  Generating /home/venv/flaskenv/Model/migrations/script.py.mako ...  done
  Please edit configuration/connection/logging settings in
  '/home/venv/flaskenv/Model/migrations/alembic.ini' before proceeding.

3.DBにモデルの変更内容を反映するためのマイグレーションファイルを作成する。
command : flask db migrate -m 'コメント'

(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# flask db migrate -m 'add Person'
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'persons'
  Generating /home/venv/flaskenv/Model/migrations/versions/aa0223688c58_add_person.py
  ...  done

4.マイグレーションファイルの内容をDBに反映する。
command : flask db upgrade

(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade aa0223688c58 -> c881e626270e, persons db add new column "gender"

マイグレーションを実行することで、modelファイル上でのテーブルに対する変更内容がDBに反映される。
また、マイグレーションに伴い、DBに「alembic_version」テーブルが自動的に追加される。このテーブルは、マイグレーションのバージョンを管理するために追加されるテーブルで、右クリックのShow_Tableメニューから内容を確認すると、適用されたマイグレーションのバージョン情報が確認できる。
スクリーンショット 2020-12-13 0.37.53.png

###3.制約の提示、インデックス作成

オプション 制約 コード
primary_key 主キー制約 db.Column(db.Integer, primary_key = True)
unique ユニーク制約 db.Column(db.Integer,unique = True)
nullable NOT NULL制約 db.Column(db.Integer,nullable = False)
index インデックスを作成 db.Column(db.Text,Index = True)
db.Index クラスの外でインデックスを定義することが可能。第1引数にインデックス名、第2引数にインデックスの関数を定義する db.Index('some_index',func_lower(Person.name))
server_default カラムのデフォルト値を設定 db.Column(db.Text, server_default='xxxx')
CheckConstraint ※ チェック条件を自由に設定 __table_args__=(CheckConstraint('update_at > create_at'),)

※CheckConstraintモジュールををsqlalchemyからインポートする必要がある。

db.config['SQLALCHEMY_ECHO'] = Trueの場合
スクリーンショット 2020-12-13 21.51.13.png
以下のように実行されたSQLの内容がコンソールに表示される。
スクリーンショット 2020-12-13 21.51.23.png

###4.SQLAlchemyの基本操作

コード 概要
db.session.add(object) レコードの挿入
db.session.add_all([List]) 複数レコード一括追加
db.session.delete()  Table.query.filter_by(条件).delete() レコードの削除
Table.query.get(primary_key) 主キーで抽出
Table.query.all() データをリスト化してすべて抽出
Table.query.first() データの最初の要素だけ取得
Table.query.filter_by(name='A') 絞り込み
Table.query.filter(Table.name.startswith('A')) 前方一致検索
Table.query.filter(Table.name.endswith('z')) 後方一致検索
Table.query.limit(1) 件数を指定して抽出
Table.query.update({'column': 'value'}) アップデート
  • 複数レコードを取得した場合、個別のレコードを参照するためにはfor文で一つずつ取得する必要がある。

その他参考
https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query%20limit#sqlalchemy.orm.Query.limit

###5.外部キー
複数テーブルのカラム間で紐付けする。
外部参照キーを設定しているテーブルのデータをSELECTした場合、結果に参照先テーブルの紐づくデータも一緒に取得されるようになる。

概要 テンプレート サンプル
参照先設定 db.relationship(参照先モデル名, backref='外部参照時テーブル名' projects = db.relationship('Project', backref='employees', [layzy=xxxx])
被参照設定 db.Column(データ型, db.ForeignKey(キー項目) employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))

○lazyオプション
テーブルを紐付けるときの処理方式を設定する。

オプション 概要
select デフォルトの設定。都度SELECT文を実施する。
joined JOIN句でテーブルを紐付ける
subquery サブクエリでテーブルを紐付ける
dynamic 紐付けたテーブルでクエリ実行用オブジェクトを作成する。
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

base_dir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = \
    'sqlite:///' + os.path.join(base_dir, 'data.sqlite')

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = True

db = SQLAlchemy(app)


class Employee(db.Model):
    __tablename__ = 'employees'

    """カラム定義"""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    # 外部キーの宣言
    # One to Many
    projects = db.relationship('Project', backref='employees')
    # One to One
    company = db.relationship('Company', backref='emoployees', uselist=False)

    def __init__(self, name):
        self.name = name

    def __str__(self):
        if self.company:
            return f'Employee name {self.name} company is {self.company.name}'
        else:
            return f'Employee name = {self.name}, has no company'

    def show_projects(self):
        for project in self.projects:
            print(project)


class Project(db.Model):
    __tablename__ = 'project'

    """カラム定義"""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))

    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id


class Company(db.Model):
    __tabelname__ = 'companies'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))

    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id


# テーブル作成
db.create_all()

###6.トランザクション

  • トランザクションとは、DBに対する処理を1つの単位としてまとめたもの。
  • コミットするまでを1トランザクションとするため、トランザクション内でエラーが発生した場合は、トランザクション内の処理すべてがロールバックされる。
  • 一連の処理結果がすべて完了するかエラーですべてなかったことになるかのどちらかになるので、トランザクションを利用することで、処理の原子性が保たれる。
# 構文
with db.sessino.begin(subtransactions=True):
    DB処理
db.session.commit()
# データ更新処理
# 対象レコード取得
record = Model.query.get(1)
# 更新したい項目に代入
record.name = '更新'
# db.sessionに更新後のレコードオブジェクトを追加
db.session.add(record)
# コミット処理
db.session.commit()
14
13
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
14
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?