FlaskでWebアプリを作る。その2:Flask-Migrationでデータベースを作る
こんにちは、むさしです。 引き続きflaskでwebアプリを作っていきましょう。
前の記事: playwao.hatenablog.com
データベースを利用したWebアプリを作る
これからデータベースを使ったアプリを作っていきましょう。 はじめに話したようにデータベースはSQLiteを使います。
今回は読書用の日記アプリを作ります。 作る必要があるであろうテーブルは以下の2つになります。
- 書籍
- 書籍名
- 作者・著者
- 出版社
- 読んだ記録
- 日付(自動)
- 読んだ本
- 感想
また、ファイル構造を以下のように構成します。
. ├── manage.py ├── models.py ├── form.py ├── view.py ├── static | └── app.css └── templates ├── index.html └── base.html
manage.py
は名前こそ変えましたがその1で作ったやつのapp.py
と同じです。
model.py
はfalskでデータベースのデータを扱い際の???いい言葉が思いつかない???になります。
view.py
はブラウザなどとやりとりするのに相手のレスポンスに対して特定の関数を呼び出し、適切なレスポンスを返す機能を実装します。
form.py
は書籍や記録などを入力する際のフォーム用のクラスを設定します。
また、header.html
はbase.html
と名前を変えます。変えなくても問題はありません。
staticフォルダにはapp.css
というファイルを入れておきます。
データベース関係の準備
SQLAchemyのインストール
データベースはSQLiteを使います。macにはデフォルトで入いるのでそのままで大丈夫です。 mySQLなど好みのデータベースを使ってください。
ORマッパとしてSQL-Alchemyを使います。ORマッパとはwikipediaによると、
オブジェクト関係マッピング(英: Object-relational mapping、O/RM、ORM)とは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。オブジェクト関連マッピングとも呼ぶ。実際には、オブジェクト指向言語から使える「仮想」オブジェクトデータベースを構築する手法である。
とのことです。 僕の中ではpythonでデータベースにアクセスするのにSQLで記述しなくて良くなる便利なもの程度の認識です^^;
とにかくインストールしましょう。またこの時一緒にFlask-SQLAchemy
もインストールしましょう。SQLAchemyのセッションなど管理できるので自前で作る必要がなく楽できます。
$ pip install sqlalchemy $ pip install flask-sqlalchemy
Flask-Migrateのインストール
データベースを使うのに直接SQLiteを操作してテーブル作るのもいのですが、使うテーブルを変えたい時、新しいテーブルを追加したい時などにサーバーとデータベースをどっちも修正するのは面倒です。
また、ローカル環境で開発して本番のサーバーにあげる時初めからデータベースにテーブルを打ち込まなくていいというメリットもあります。
そこでFlask-Migrate
を使います。これを使えばテーブルの修正や追加などをサポートしてくれます。
まずはpipでインストールします。
$ pip insatll flask-migrate
するとダウンロード完了です。
pip freeze
で確認してみてください。
$ pip freeze alembic==0.7.6 Flask==0.10.1 Flask-Migrate==1.4.0 Flask-Script==2.0.5 Flask-SQLAlchemy==2.0 itsdangerous==0.24 Jinja2==2.7.3 Mako==1.0.1 MarkupSafe==0.23 SQLAlchemy==1.0.6 Werkzeug==0.10.4 wheel==0.24.0
色々増えていますが、Flask-ScriptやMakoなどはFlask-Migrateをインストールした際に一緒にダウンロードされただけです。
ちなみにこのflask-migrateはflaskのオイラリー著者が作ったそうです。
モデルとマイグレーションの設定
モデルを作る準備ができたので早速作っていきましょう。
まずはモデルを作っていきます。
models.py
を以下のよう編集します。
models.py
#!/usr/bin/env python # coding: utf-8 from flask.ext.sqlalchemy import SQLAlchemy from sqlalchemy import Column, Integer, Unicode, UnicodeText, ForeignKey from sqlalchemy.orm import relationship, backref from datetime import datetime db = SQLAlchemy() class Book(db.Model): """ 書籍モデル """ __tablename__ = "books" id = Column(Integer, primary_key=True) title = Column(Unicode(255)) auther = Column(Unicode(255)) publisher = Column(Unicode(255)) #初期化 def __init__(title, auther, publisher): self.title = title.title() self.auther = auther.title() self.publisher = publisher.title() class Diary(db.Model): """ 感想モデル """ __tablename__ = "diaries" id = Column(Integer, primary_key=True) date = Column(Unicode(255)) book_title = Column(Unicode(255), ForeignKey('books.title')) impression = Column(UnicodeText) #書籍とのリレーションを作成 book = relationship("Book", backref=backref('diaries', order_by=id)) #初期化 def __init__(book_title, impression): self.date = datetime.utcnow().strftime( '%Y-%m-%d %H:%M:%S' ) self.book_title = book_title.title() self.impression = impression.title()
まずは
from flask.ext.sqlalchemy import SQLAlchemy db = SQLAlchemy()
として、SQLAchemyを使う準備をします。
次に書籍のモデルを作っていきます。
from sqlalchemy import Column, Integer, Unicode class Book(db.Model): """ 書籍モデル """ __tablename__ = "books" id = Column(Integer, primary_key=True) title = Column(Unicode(255)) auther = Column(Unicode(255)) publisher = Column(Unicode(255)) #生成された時、呼び出される def __init__(title, auther, publisher): self.title = title.title() self.auther = auther.title() self.publisher = publisher.title()
__tablename__
にはデータベースのテーブル名を入れてください。
これでどのテーブルを使うか指定します。
また、今回はmigrateもするのでその際のテーブル名になります。
その下はテーブルのカラムとその定義を書いていきます。
例えばid = Column(Integer, primary_key=True)
ならid
がカラム名に、Integer, primary_key=True
がカラムの定義にあたります。
Integer
はこのカラムに整数が入ることを表しています。primary_key=True
は入る値がカラムに唯一しか存在しないことを表しています。明記していませんがこの値は自動でナンバリングされます。なので、例えば新たにデータを入力すればidが自動で番号付けされます。
title = Column(Unicode(255))
などにあるUnicode(255)
は値にunicodeを取れることを表しています。文字数は255文字までとれます。Unicode
の代わりにString
を使うと日本語などを入力した時エラーがでるので注意が必要です。僕はめんどくさいので全部Unicode
にしています^^;
__init__
とはBookクラスが生成された時呼び出される関数です。今回は引数にtitle, auther, publisher
を取っています。この関数は生成時に呼び出されるものです。
self.title = title.title()
のtitle()
はクラスが生成された時に取った引数を代入します。この例はtitle、titleでこれはわかりにくいですが^^;
つまりnewBook = Book("坊ちゃん", "夏目漱石", "青空文庫")
ならば、
title = "坊ちゃん" auther = "夏目漱石" publisher = "青空文庫"
と各変数に代入されます。
同様に感想についてもモデルを作っていきます。
from sqlalchemy import Column, Integer, Unicode, UnicodeText, ForeignKey from sqlalchemy.orm import relationship, backref from datetime import datetime class Diary(db.Model): """ 感想モデル """ __tablename__ = "diaries" id = Column(Integer, primary_key=True) date = Column(Unicode(255)) book_title = Column(Unicode(255), ForeignKey('books.title')) impression = Column(UnicodeText) #書籍とのリレーションを作成 book = relationship("Book", backref=backref('diaries', order_by=id)) #生成された時、呼び出される def __init__(book_title, impression): self.date = datetime.utcnow().strftime( '%Y-%m-%d %H:%M:%S' ) self.book_title = book_title.title() self.impression = impression.title()
先ほどと同じようなところは説明を省かせていただきます。
book_title = Column(Unicode(255), ForeignKey('books.title'))
とbook = relationship("Book", backref=backref('diaries', order_by=id))
はリレーションを定義しています。今回作成しているWebアプリは書籍一つに関していくつも感想や記録をつけることができます。このように書籍の下にいくつもの感想がくる構造を取ることを一対多の関係にあるといいます。これを関連付けているのがリレーションです。
book_title = Column(Unicode(255), ForeignKey('books.title'))
のForeignKey
は外部キーといいます。このForeignKey
の引数としてリレーション先のテーブル名とそのカラム名を(テーブル名).(カラム名)
を指定します。
book = relationship("Book", backref=backref('diaries', order_by=id))
はBook
とDiary
という2つのクラスのリレーションをrelation()
関数を使って別個に定義します。この時relation()
関数をBookの方に
class Book(db.Model): ... diary = relation("Diary", order_by=Diary.id, backref="books")
と定義も出来ます。どちらで書くこともできますが僕は基本的に子要素のところで定義します。
また、__init__
でdate
についてself.date = datetime.utcnow().strftime( '%Y-%m-%d %H:%M:%S' )
としていますがこれはクラスを生成した時に時間を自動で入力してくれるようにしています。
これ以外にも例えばユーザー登録時にランダムidを発行したくなった時はuuid
を使って
class User(db.Model): id = Column(Integer, primary_key=True) uuid = Column(Unicode(255)) name = Column(Unicode(255)) def __init__(name) self.uuid = uuid.uuid4() self.name = name.title()
とできます。
今度はmange.py
を編集していきます。
manage.py
#!/usr/bin/env python # coding: utf-8 import sys reload(sys) sys.setdefaultencoding('utf-8') from flask import Flask, render_template from model import db from flask.ext.script import Manager, Server from flask.ext.migrate import Migrate, MigrateCommand app = Flask(__name__) #デバッグ app.config['DEBUG'] = True #秘密キー app.secret_key = 'development key' #データベースを指定 app.config['SQLALCHEMY_DATABASE_URI'] = 'SQLite:///diary.db' app.config['SQLALCHEMY_NATIVE_UNICODE'] = 'utf-8' db.init_app(app) db.app = app @app.route("/") def hello(): return render_template("index.html") migrate = Migrate(app, db) manager = Manager(app) manager.add_command('db', MigrateCommand) manager.add_command('runserver', Server(host='localhost', port='8080')) if __name__ == "__main__": manager.run()
まず、はマイグレーションに必要なものをインポートします。
from flask.ext.script import Manager, Server from flask.ext.migrate import Migrate, MigrateCommand
そのあとデータベースを指定します。
app.config['SQLALCHEMY_DATABASE_URI'] = 'SQLite:///diary.sqlite3' app.config['SQLALCHEMY_NATIVE_UNICODE'] = 'utf-8' db.init_app(app) db.app = app
その後、MigrateオブジェクトとManagerオブジェクトを生成します。
add_command
でコマンドラインからマイグレーションの処理を行います。
このときmanager.add_command('runserver', Server(host='localhost', port='8080'))
の部分はこのflask-migrateを適応するとなぜかホストやポート番号を設定できないのでこれで無理やり設定しています^^;
もっといい方法があればいいのですが…
ちなみにapp.config['SERVER_NAME'] = 'localhost'
ではうまくいきませんでした。
マイグレーションを行う
マイグレーションする準備できたので早速マイグレーションを行なっていきましょう。
まず、ちゃんと正しく動いているか確認するためにpython mange.py
を実行させてみましょう。すると以下のように説明が出てきます。これが出てきたら正しくでいている証拠です。
$ python manage.py usage: manage.py [-?] {shell,db,runserver} ... positional arguments: {shell,db,runserver} shell Runs a Python shell inside Flask application context. db Perform database migrations runserver Runs the Flask development server i.e. app.run() optional arguments: -?, --help show this help message and exit
また、python manage.py db --help
を実させましょう。
$ python manage.py db --help usage: Perform database migrations Perform database migrations positional arguments: {upgrade,heads,show,migrate,stamp,current,merge,init,downgrade,branches,history,revision} upgrade Upgrade to a later version heads Show current available heads in the script directory show Show the revision denoted by the given symbol. migrate Alias for 'revision --autogenerate' stamp 'stamp' the revision table with the given revision; don't run any migrations current Display the current revision for each database. merge Merge two revisions together. Creates a new migration file init Generates a new migration downgrade Revert to a previous version branches Show current branch points history List changeset scripts in chronological order. revision Create a new revision file. optional arguments: -?, --help show this help message and exit
この中のmigrate
とupgrade
を主に使います。
まず、データベースを初期化します。
$ python manage.py db init Creating directory ~/flask_app/migrations ... done Creating directory ~/flask_app/migrations/versions ... done Generating ~/flask_app/migrations/alembic.ini ... done Generating ~/flask_app/migrations/env.py ... done Generating ~/flask_app/migrations/env.pyc ... done Generating ~/flask_app/migrations/README ... done Generating ~/flask_app/migrations/script.py.mako ... done Please edit configuration/connection/logging settings in '~/flask_app/migrations/alembic.ini' before proceeding.
こうするとmanage.py
と同じディレクトリにmaigration
というフォルダができています。
ここでマイグレーションを管理、実行します。
マイグレーションファイルを製作しましょう。
$ python manage.py db migrate INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL. INFO [alembic.autogenerate.compare] Detected added table 'books' INFO [alembic.autogenerate.compare] Detected added table 'diaries' Generating ~/flask_app/migrations/versions/299d7e501a6e_.py ... done
するとmigration/versions
になんらかのpythonファイルが生成されています。
最後にこれをupgradeしてsqliteに適応させましょう。
$ python manage.py db upgrade INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL. INFO [alembic.migration] Running upgrade -> 299d7e501a6e, empty message
これで完了です。daiary.db
というデータベースが生成されていると思うので中身を確認してみましょう。
$ sqlite3 diary.db SQLite version 3.8.5 2014-08-15 22:37:57 Enter ".help" for usage hints. sqlite> .table alembic_version books diaries sqlite> .schema CREATE TABLE alembic_version ( version_num VARCHAR(32) NOT NULL ); CREATE TABLE books ( id INTEGER NOT NULL, title VARCHAR(255), auther VARCHAR(255), publisher VARCHAR(255), PRIMARY KEY (id) ); CREATE TABLE diaries ( id INTEGER NOT NULL, date VARCHAR(255), book_title VARCHAR(255), impression TEXT, PRIMARY KEY (id), FOREIGN KEY(book_title) REFERENCES books (title) );
無事出来ているようです。
次回はテンプレートファイルとCRUDを実装していきたいと思います。
次の記事: playwao.hatenablog.com