blog

Flask 開発フォーラム - データベース

最後に、最も基本的なホームページを作ったので、ユーザーのログインと登録処理を実装してみましょう。 まず、始める前にデータベースが必要です。ここではMySQLデータベースを選びました。 設定 ......

Jan 27, 2021 · 10 min. read
シェア

このプロジェクトの完全なディレクトリは

最も基本的なホームページを作成した後、ユーザーのログインと登録プロセスを実装します。

まず、始める前にデータベースが必要です。ここではMySQLデータベースを選びました。

データベースの設定が終わったら、データベースに接続します。ここでも、PyMysqlを使って設定するのに時間がかかりました。mysqlのターミナルを開いて

CREATE DATABASE <database-name>;

これらのうち1つを自分のデータベース名に置き換えてから、MySQLターミナルを閉じ、コマンドラインで次のように入力します。 これらのうち1つを自分のデータベース名に置き換えてから、MySQLターミナルを閉じ、コマンドラインで次のように入力します。

pip install flask-sqlalchemy

をインストールして、extensions.py で SQLAlchemy をインスタンス化してください:

from flask_bootstrap import Bootstrap # Bootstrap-Flaskをインポートする
from flask_sqlalchemy import SQLAlchemy
bootstrap = Bootstrap() # 拡張機能をインスタンス化する
db = SQLAlchemy()

プログラムを実行すると、いくつかの警告が表示されるかもしれませんが、これはデータベースがまだセットアップされていないからです。では設定してみましょう。まずコマンドラインを開き

pip install pymysql

pymysql をインストールするには、インストール後、app フォルダに config.py を作成します。config.py と入力してください:

import os
class DevelopmentConfig:
 DEBUG = True # デバッグモードにする
 # データベースの場所を設定する
 SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URI') or 'mysql+pymysql://root:%s@localhost:3306/%s' \
 '?charset'\
 '=utf8mb4' % (os.environ.get('DEV_DATABASE_PASS'),
 os.environ.get('DEV_DATABASE_NAME'))
 # 変更を追跡しない
 SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProductionConfig:
 DEBUG = False # デバッグをオフにする
 # 本番環境で使うためのデータベースをセットアップする
 SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'mysql+pymysql://root:%s@localhost:3306/%s?charset' \
 '=utf8mb4' % (os.environ.get('DATABASE_PASS'), os.environ.get('DATABASE_NAME'))
 SQLALCHEMY_TRACK_MODIFICATIONS = False
# 様々な状況で使えるように設定をする
config = {
 'development': DevelopmentConfig,
 'production': ProductionConfig,
 'default': DevelopmentConfig
}

init.pyに導入しましょう:

import os
from flask import Flask # Flaskをインポートする
from .extensions import * # インスタンス化された拡張モジュールをインポートする
from app.config import config # 設定をインポートする
def create_app():
 app = Flask(__name__) # アプリのインスタンスを作る
 app.config.from_object(config[os.environ.get('FLASK_ENV')]) # configからimportの設定をする
 # ...
 return app # アプリに戻る

config.pyでは2つの環境変数が使われているので、プログラムを実行する前に環境変数を定義する必要があります。しかし、環境変数は現在のセッションでのみ有効なので、それは面倒です。しかし、python-dotenvを使えば簡単にできます。まず、これをインストールします:

pip install python-dotenv

次に、ルート・ディレクトリに環境変数を保存するための別の .env ファイルを作成します:

FLASK_APP=app.py
DEV_DATABASE_PASS=<database-password>
DEV_DATABASE_NAME=<database-name>

andをデータベースのパスワードと名前に置き換えるだけです。テーブルがまだ作成されていないので、アプリを再度実行すると、すべてが前と同じになっているはずです。次に、データベースモデルを保持するために、appの下にmodels.pyを作成しますテーブルがまだ作成されていないので、全ては前と同じはずです。次に、データベースモデルを保持するために、appの下にmodels.pyを作成します:

from .extensions import db # SQLAlchemy をインポートする
class User(db.Model): # Userdbから継承したクラス.Model
 __tablename__ = 'users' # テーブル名を定義する
 id = db.Column(db.Integer, primary_key=True) # idを定義して主キーにする
 username = db.Column(db.String(64)) #  
 email = db.Column(db.String(128)) # メールアドレス
 def __repr__(self): # Userクラスの戻り名を定義する
 return '<User %s>' % self.username #  <User  >

クラスが作成されたので、次はデータベースを更新します。ここではバージョン管理のためにflask-migrateを使っています。

pip install flask-migrate

初期化:

# app/extensions.py
from flask_bootstrap import Bootstrap # Bootstrap-Flaskをインポートする
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# 拡張機能をインスタンス化する
bootstrap = Bootstrap()
db = SQLAlchemy()
migrate = Migrate()
# app/__init__.py
# ...
def create_app():
 app = Flask(__name__) # アプリのインスタンスを作る
 app.config.from_object(config[os.environ.get('FLASK_ENV')]) # configからimportの設定をする
 # 拡張モジュールを初期化する
 # ...
 migrate.init_app(app, db)
 # ...
 return app # アプリに戻る

次に、flask-migrateを初期化します:

flask db init

この時点でエラー:

Traceback (most recent call last):
 File "/Users/sam/Desktop/Python/AttributeError/venv/bin/flask", line 10, in <module>
 sys.exit(main())
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 966, in main
 cli.main(prog_name="python -m flask" if as_module else None)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 586, in main
 return super(FlaskGroup, self).main(*args, **kwargs)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
 rv = self.invoke(ctx)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
 return _process_result(sub_ctx.command.invoke(sub_ctx))
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
 return _process_result(sub_ctx.command.invoke(sub_ctx))
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
 return ctx.invoke(self.callback, **ctx.params)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
 return callback(*args, **kwargs)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/click/decorators.py", line 21, in new_func
 return f(get_current_context(), *args, **kwargs)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 425, in decorator
 with __ctx.ensure_object(ScriptInfo).load_app().app_context():
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 388, in load_app
 app = locate_app(self, import_name, name)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 257, in locate_app
 return find_best_app(script_info, module)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 83, in find_best_app
 app = call_factory(script_info, app_factory)
 File "/Users/sam/Desktop/Python/AttributeError/venv/lib/python3.8/site-packages/flask/cli.py", line 119, in call_factory
 return app_factory()
 File "/Users/sam/Desktop/Python/AttributeError/app/__init__.py", line 12, in create_app
 app.config.from_object(config[os.environ.get('FLASK_ENV')]) # configからimportの設定をする
KeyError: None

これにはコードの変更が必要です:

# app/__init__.py
# ...
def create_app():
 app = Flask(__name__) # アプリのインスタンスを作る
 # Setup は config から、環境が設定されていない場合はデフォルトから設定をインポートする
 app.config.from_object(config[os.environ.get('FLASK_ENV') or 'default'])
 # ...
 return app # アプリに戻る

ここでは、FLASK_ENVがNoneの場合、デフォルトからインポートするように設定します。ここでもう一度初期化します:

flask db init

エラーは報告されません。データベースモデルへの変更を保存するために、ルートディレクトリに migrations というフォルダを作成します。データベースをアップグレードします:

flask db migrate

その理由は、flask-migrate がファイルの import から変更があるかどうかを判断し、どのファイルにも models.py を導入していないからです。理由は、flask-migrateがファイルのインポートから変更があるかどうかを判断し、どのファイルにもmodels.pyを導入しないからです。さて、アプリ内にauthフォルダを作成し、__init__.pyを作成します:

# app/auth/__init__.py
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views

次に views.py を作成します:

# app/auth/views.py
from . import auth
from app.models import User

次に、認証ブループリントを登録します:

# app/__init__.py
# ...
def create_app():
 app = Flask(__name__) # アプリのインスタンスを作る
 # ...
 from .main import main as main_bp # ブループリントをインポートする
 app.register_blueprint(main_bp) # ブループリントをアプリケーションに登録する
 from .auth import auth as auth_bp
 app.register_blueprint(auth_bp)
 return app # アプリに戻る

ここでもう一度 flask db migrate を実行すると、更新ファイルが正常に生成されます。このファイルを有効にするには、データベースをアップグレードする必要があります:

flask db upgrade

では、flaskの対話型シェルを開いてデータベースをテストしてみましょう:

Python 3.8.1 (v3.8.1:1b, Dec , ) 
[Clang 6.0 (clang-)] on darwin
App: app [production]
Instance: /Users/sam/Desktop/Python/AttributeError/instance
>>> from app.models import User
>>> u = User(username='Test', email='test@example.com') # ユーザー名を Test とするユーザーを作成する。
>>> from app.extensions import db
>>> db.session.add(u) # このセッションにユーザーを追加する
>>> db.session.commit() # 変更をデータベースにコミットする
>>> u
<User Test>
>>> u.username # ユーザー名を取得する
'Test'
>>> u.email # ユーザーのメールボックスを取得する
'test@example.com'
>>> u.id # ユーザーIDを取得する
1
>>> db.session.delete(u) # ユーザーを削除する
>>> db.session.commit()
>>> User.query.all() # 全ユーザーを取得する
[]

現在のモデルでは、すべてのユーザーが平等であり、管理者が存在しないことにお気づきでしょう。では、この問題を解決しましょう:

# app/models.py
from .extensions import db # SQLAlchemy をインポートする
class Role(db.Model):
 __tablename__ = 'role'
 id = db.Column(db.Integer, primary_key=True)
 name = db.Column(db.String(64))
 users = db.relationship('User', backref='role', lazy='dynamic') # 関連付けを行う
 def __repr__(self):
 return '<Role %s>' % self.name
class User(db.Model): # Userdbから継承したクラス.Model
 __tablename__ = 'users' # テーブル名を定義する
 id = db.Column(db.Integer, primary_key=True) # idを定義して主キーにする
 username = db.Column(db.String(64)) #  
 email = db.Column(db.String(128)) # メールアドレス
 role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
 def __repr__(self): # Userクラスの戻り名を定義する
 return '<User %s>' % self.username #  <User  >

でデータベースを更新します:

flask db migrate -m "Added role"
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'role'
INFO [alembic.autogenerate.compare] Detected added column 'users.role_id'
INFO [alembic.autogenerate.compare] Detected added foreign key (role_id)(id) on table users
 Generating /Users/sam/Desktop/Python/AttributeError/migrations/versions/93740ecd2a4a_added_role.py ... done
flask db upgrade
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade 8f7a7e6cb8af -> 93740ecd2a4a, Added role

フラスコシェルでテストしてみましょう:

Python 3.8.1 (v3.8.1:1b, Dec , ) 
[Clang 6.0 (clang-)] on darwin
App: app [production]
Instance: /Users/sam/Desktop/Python/AttributeError/instance
>>> from app.models import Role, User
>>> from app.extensions import db
>>> admin = Role(name='Admin')
>>> db.session.add(admin)
>>> user = Role(name='Users')
>>> db.session.add(user)
>>> db.session.commit()
>>> # Role.query.filter_by(name='Users').first(),すべてのロールから、Usersという名前のものを探す。
>>> u1 = User(username='Test User', email='testuser@example.com', role=Role.query.filter_by(name='Users').first())
>>> db.session.add(u1)
>>> db.session.commit()
>>> u1.role
<Role Users>
>>> u2 = User(username='Test Admin', email='testadmin@example.com', role=Role.query.filter_by(name='Admin').first())
>>> db.session.add(u2)
>>> db.session.commit()
>>> u2.role
<Role Admin>
>>> admin.users.all()
[<User Test Admin>]
>>> user.users.all()
[<User Test User>]

さて、今日はここまで。にコードを置いたので、ef00826をgit checkoutしてバージョンを確認してください。データベースをアップグレードするには、新しい.envファイルを作成して環境変数を埋め、flask db upgradeを実行する必要があることに注意してください。

Read next

デザイン・パターンの禅 - ファクトリー・メソッド・パターン

最初のステップは、オブジェクトを作るためのインターフェースを定義することです。 人気のある種類の果物を例にとると、色と名前という一般的な特徴を持っており、一般的な特徴であるため、ここでは果物がインターフェースとなります。 ここでは、処理を簡単にするために、リンゴとバナナの2種類だけをそれぞれ追加して、果物のインタフェースを実装します。 果物のインターフェイスを持っているので、その後、種の果物は、どのような果物のように、ここでは操作する必要はありません...

Jan 26, 2021 · 4 min read