※記事自体が長いですがコピペは全く勉強にならないので絶対にやめてください。
最後にBitbucketのリポジトリのリンクを記載しているので、
そちらを参考にしてください。
Railsに比べてコードが長くなる+設定などが面倒臭いなどの理由で
コードの中にコメントしたりしています。
ブログ記事だけでなく両方見ながら進めてください。
時々説明が足りない部分もあるかと思いますが、その際は直接かコメントで質問を
Flaskを利用してQAサイトを作っていきます
このチュートリアルを通して
- flask_login
- qaサイトの基礎
などを学ぶことができます
複数回に分けて実装していきます
今回は回答者がログインして質問を作成することろまでやります
ユーザーについて
※質問者と回答者は同じユーザータイプで扱います。
- 質問者 → 何か質問したい人
- 回答者 → 回答できる人
- 管理者 → システムを管理する人
Flaskプロジェクトの作成
FlaskはRailsと異なりルールがほとんどないので開発者や企業によってルールがかなり異なります
このチュートリアルでは以下のフォルダ構成で構築を行います
かなりざっくりとした説明ですが、初めから頭に叩き込むのではなく
開発しながら覚えましょう
- app :Flaskプロジェクトの最上層
- models:sqlalchemyを通してデータベースを作成するために作成するclass
- services:modelを通してデータを登録したり取得したりするためのメソッド群
- views:serviceを通してデータを取得したりしてそれをhtmlへ反映させるだけのところ
- __init__.py:全てのファイルをここに取り込み、Flaskを起動させるファイル
- create_db.py:modelを追加しデータベースを追加するときに使うファイル
- run.py:Flaskサーバーを起動するときに使うファイル
それではプロジェクトを作ります
# プロジェクトの大枠作成 $ mkdir qa-site # 移動 $ cd qa-site # flaskのまとまり作成 $ mkdir app # 移動 $ cd app # touch __init__.py # フォルダをそれぞれ作成 $ mkdir models $ mkdir services $ mkdir views $ mkdir templates # 一階層上がる $ cd .. # データベースを作るファイルの作成 $ touch create_db.py # Flaskサーバーを起動するためのファイル作成 $ touch run.py
Flaskサーバーの設定
Flaskサーバーを起動させるために__init__.pyとrun.pyにこのような記述を加えます
__init__.py
# モジュールインポート from flask import Flask # Flaskアプリの生成 app = Flask(__name__) # indexのルート設定 @app.route('/') def index(): return '<h1>Hello world</h1>'
run.py
# appディレクトリにある__init__.pyの中で設定したflaskアプリであるappをimportしている from app import app # flaskサーバーを起動 if __name__ == "__main__": app.run(host='0.0.0.0', debug=True)
これで最低限の設定は完了なので、ターミナルでこちらのコマンドを走らせて実行します
$ python run.py
「http://localhost:5000/」にアクセス
このような画面が見えると思います
これでflaskサーバーの設定は完了です。
SQLAlchemyの追加と設定
flaskにはsqlalchemyというデータベースとflaskを繋いでくれる便利なライブラリがあるので
こちらを利用します。ちなみにほとんどの場合このsqlalchemyが利用されております
__init__.pyを修正
# モジュールインポート from flask import Flask # データベースを利用するために追加 from flask_sqlalchemy import SQLAlchemy # Flaskアプリの生成 app = Flask(__name__) # ここから /// データベースの設定 app.secret_key = "super secret key" app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///qa-site.sqlite3' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # ここまで /// データベースの設定 # sqlalchemyを通してflaskからdbアクセスをするための入り口 db = SQLAlchemy(app) # indexのルート設定 @app.route('/') def index(): return '<h1>Hello world</h1>'
create_db.pyを修正
# appディレクトリの__init__.pyからdbをimport from app import db # データベースを作成するためのコード。pythonコンソールで行うことが多いですが面倒臭いのでターミナルから実行できるようにしました。 db.create_all()
設定が完了したので次はモデルを作成していきます
Modelを追加
ログイン機能を実装していくのでUserに関するモデルを作ります
flask-loginというログイン機能を提供しているライブラリに関連するところも
入っています
modelはmodelsに追加しますが、ついでにmodelsの中に__init__.pyを作成します
ここでは詳細を話しませんが、きになる方は調べてみてください
user.py
from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from app import db # モデルに関する設定 class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(100), unique=True) name = db.Column(db.String(1000)) password = db.Column(db.String(100)) # モデルからインスタンスを生成するときに使います。(利便性を高めるため) # passwordの暗号化も自動で行うことができるので、安全性も高めることができます。 @classmethod def from_args(cls, name: str, email: str, password: str): instance = cls() instance.name = name instance.email = email if password is not None: # passwordがあれば暗号化します。 instance.hash_password(password) return instance # 暗号化するためのメソッド。 def hash_password(self, clean_password): self.password = generate_password_hash(str(clean_password), method='sha256') # 登録したpasswordとユーザーがログインフォームで入力したパスワードが正しいかどうかのチェックを行うメソッド def check_password(self, clean_password): return check_password_hash(self.password, clean_password)
次はこのデータベースを反映させていきます
まずはapp/__init__.pyでuserモデルをimportします
importしないとデータベースに反映されません
# モジュールインポート from flask import Flask # データベースを利用するために追加 from flask_sqlalchemy import SQLAlchemy # Flaskアプリの生成 app = Flask(__name__) # ここから /// データベースの設定 app.secret_key = "super secret key" app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///qa-site.sqlite3' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # ここまで /// データベースの設定 # sqlalchemyを通してflaskからdbアクセスをするための入り口 db = SQLAlchemy(app) # データベースのimport from app.models.user import User # indexのルート設定 @app.route('/') def index(): return '<h1>Hello world</h1>'
次はcreate_db.pyを利用してデータベースを作成します。
ターミナルで以下を実行
$ python create_db.py
問題なく作成されればこのようにデータベースが生成されます
これでmodelとデータベースの設定は完了です
Flask-Loginを追加
次はログイン機能を構築していきます。
ここからは複数ファイルの作業になるので注意しながら開発してください
templates/index.html
templates/layout.html
templates/auth/signup.html
templates/auth/login.html
を作成
templates/index.html
{% extends "layout.html" %} {% block content %} <h1> Index page </h1> {% endblock %}
templates/layout.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>QA-Site</title> </head> <body> <ul> <li><a href="{{ url_for('index') }}">Index</a></li> <li><a href="{{ url_for('auth.signup') }}">新規登録</a></li> <li><a href="{{ url_for('auth.login') }}">ログイン</a></li> </ul> {# flaskのメッセージを表示 #} {% with messages = get_flashed_messages() %} {% if messages %} <div> {{ messages[0] }} </div> {% endif %} {% endwith %} {% block content %} {% endblock %} </body> </html>
templates/auth/signup.html
{% extends "layout.html" %} {% block content %} <div> <h3>Sign Up</h3> <div> <form action="/signup" method="POST"> <div> <input type="text" name="name" placeholder="Name" autofocus=""> </div> <div> <input type="email" name="email" placeholder="Email" autofocus=""> </div> <div> <input type="password" name="password" placeholder="Password"> </div> <button>Sign Up</button> </form> </div> </div> {% endblock %}l
templates/auth/login.html
{% extends "layout.html" %} {% block content %} <div> <h3>Login</h3> <form method="POST" action="/login"> <div> <input type="email" name="email" placeholder="Your Email" autofocus=""> </div> <div> <input type="password" name="password" placeholder="Your Password"> </div> <div> <label> <input type="checkbox"> Remember me </label> </div> <button>Login</button> </form> </div> {% endblock %}
次はservicesを追加します
services/__init__.py
services/auth_service.py
services/__init__.pyは空のファイルを作成します
services/auth_service.pyはこのように作成します
from flask_login import login_user from sqlalchemy.exc import SQLAlchemyError from app import db from app.models.user import User """ servicesのファイルは全てデータベースにデータを送るための準備をしたり、 データに何かしらの処理を与えるコードを記述する場所です。 """ # 新規登録を行うためのメソッドです。引数にはviewsで取得するformデータが送られてきます。 def signup(data: {}) -> User: # -> User:これはreturnする値の型を指定しています。Userはオブジェクトとして出力します。 try: name = data.get('name') email = data.get('email') password = data.get('password') # ユーザーがすでに登録されているかどうかを確認します user = User.query.filter_by(email=email).first() if user: # 同じメールアドレスでユーザーが登録されているのであればユーザーをリターンします return user # ユーザーがいなければ作成します new_user = User.from_args(name, email, password) # データベースに追加するところ db.session.add(new_user) db.session.commit() return user except SQLAlchemyError: raise SQLAlchemyError def login(data: {}) -> User: try: email = data.get('email') password = data.get('password') remember = True if data.get('remember') else False user = User.query.filter_by(email=email).first() # ユーザーとパスワードの確認 if not user and not user.check_password(user.password, password): raise SQLAlchemyError # ログイン。rememberにチェックを入れていればログインが維持される login_user(user, remember=remember) return user except SQLAlchemyError: raise SQLAlchemyError
次はviewsを追加していきます。
viewsはhtmlを表示したりservicesを経由してデータを登録したり取得します
あくまでも呼び出したりするだけで具体的な処理は与えることはありません
views/__init__.py
views/auth.py
views/auth.py
from flask import Blueprint, render_template, redirect, url_for, request, flash from app.services import auth_service auth = Blueprint('auth', __name__) # signupページと、postするページを共通化。 @auth.route('/signup', methods=['GET', 'POST']) def signup(): if request.method == 'GET': return render_template('auth/signup.html') else: user = auth_service.signup(request.form) if user: flash('メールアドレスは既に登録されています。') return redirect(url_for('index')) flash('新規登録に成功しました。') return redirect(url_for('index')) @auth.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('auth/login.html') else: user = auth_service.login(request.form) if not user: flash('メールアドレスもしくはパスワードに誤りがあります。') return render_template('auth.login') flash('ログインしました。') return redirect(url_for('index'))
次はapp/__init__.pyにviewsを取り込みます。
# モジュールインポート from flask import Flask, render_template # データベースを利用するために追加 from flask_sqlalchemy import SQLAlchemy # flaks-loginのライブラリ追加 from flask_login import LoginManager # Flaskアプリの生成 app = Flask(__name__) # ここから /// データベースの設定 app.secret_key = "super secret key" app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///qa-site.sqlite3' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # ここまで /// データベースの設定 # sqlalchemyを通してflaskからdbアクセスをするための入り口 db = SQLAlchemy(app) # flask-loginに関する設定 login_manager = LoginManager() login_manager.login_view = 'auth.login' login_manager.init_app(app) # データベースのimport from app.models.user import User @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # authに関するルーティングを追加 from app.views.auth import auth # authに関するルートをflaskアプリであるappに追加 app.register_blueprint(auth) # indexのルート設定 @app.route('/') def index(): return render_template('index.html')
ここまでの修正をhttp://localhost:5000で確認します
こんな感じでひとまずページが作成されています。
新規登録
ページが作成されたので、新規登録ができるか試してみます
必要な項目よ入力して、「Sign Up」をクリックすると、
indexにリダイレクトされflashメッセージが表示されました
ログイン
先ほど登録したユーザー情報を入力し「Login」ボタンをクリック
無事にログインできました
ログイン制限の追加
次はログインしているときのみindex.htmlにアクセスすることができるように
修正を加えていきます
ログイン制限は下記のコード追加することで実装できます
# htmlに追加 {% if current_user.is_authenticated %} ログインしているときに表示していい項目を記載 {% endif %} #ルーティングに追加 @app.route('/') @login_required # login_requiredを追加するとログインしていないとアクセスができないようになる def index():
app/__init__.pyを下記のように修正します
index.htmlへはログインしていないとアクセスできない修正を加えました
# モジュールインポート from flask import Flask, render_template # データベースを利用するために追加 from flask_sqlalchemy import SQLAlchemy # flaks-loginのライブラリ追加 from flask_login import LoginManager, login_required # Flaskアプリの生成 app = Flask(__name__) # ここから /// データベースの設定 app.secret_key = "super secret key" app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///qa-site.sqlite3' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # ここまで /// データベースの設定 # sqlalchemyを通してflaskからdbアクセスをするための入り口 db = SQLAlchemy(app) # flask-loginに関する設定 login_manager = LoginManager() login_manager.login_view = 'auth.login' login_manager.init_app(app) # データベースのimport from app.models.user import User @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # authに関するルーティングを追加 from app.views.auth import auth # authに関するルートをflaskアプリであるappに追加 app.register_blueprint(auth) # indexのルート設定 @app.route('/') @login_required # ここを追加 def index(): return render_template('index.html')
templates/layout.htmlを下記のように修正
新規登録とログインはログインしていない時のみ表示されます
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>QA-Site</title> </head> <body> <ul> {# ログインしていれば表示 #} {% if current_user.is_authenticated %} <li><a href="{{ url_for('index') }}">Index</a></li> {% endif %} {#ログインしていなければ表示#} {% if not current_user.is_authenticated %} <li><a href="{{ url_for('auth.signup') }}">新規登録</a></li> <li><a href="{{ url_for('auth.login') }}">ログイン</a></li> {% endif %} </ul> {# flaskのメッセージを表示 #} {% with messages = get_flashed_messages() %} {% if messages %} <div> {{ messages[0] }} </div> {% endif %} {% endwith %} {% block content %} {% endblock %} </body> </html>
http://localhost:5000にアクセスするとこのような画面になっています
最後にログアウト機能を追加します
services/auth_service.py
from flask_login import login_user, logout_user from sqlalchemy.exc import SQLAlchemyError from app import db from app.models.user import User """ servicesのファイルは全てデータベースにデータを送るための準備をしたり、 データに何かしらの処理を与えるコードを記述する場所です。 """ # 新規登録を行うためのメソッドです。引数にはviewsで取得するformデータが送られてきます。 def signup(data: {}) -> User: # -> User:これはreturnする値の型を指定しています。Userはオブジェクトとして出力します。 try: name = data.get('name') email = data.get('email') password = data.get('password') # ユーザーがすでに登録されているかどうかを確認します user = User.query.filter_by(email=email).first() if user: # 同じメールアドレスでユーザーが登録されているのであればユーザーをリターンします return user # ユーザーがいなければ作成します new_user = User.from_args(name, email, password) # データベースに追加するところ db.session.add(new_user) db.session.commit() return user except SQLAlchemyError: raise SQLAlchemyError def login(data: {}) -> User: try: email = data.get('email') password = data.get('password') remember = True if data.get('remember') else False user = User.query.filter_by(email=email).first() # ユーザーとパスワードの確認 if not user and not user.check_password(user.password, password): raise SQLAlchemyError # ログイン。rememberにチェックを入れていればログインが維持される login_user(user, remember=remember) return user except SQLAlchemyError: raise SQLAlchemyError def logout(): logout_user() return True
views/auth.py
from flask import Blueprint, render_template, redirect, url_for, request, flash from flask_login import login_required from app.services import auth_service auth = Blueprint('auth', __name__) # signupページと、postするページを共通化。 @auth.route('/signup', methods=['GET', 'POST']) def signup(): if request.method == 'GET': return render_template('auth/signup.html') else: user = auth_service.signup(request.form) if user: flash('メールアドレスは既に登録されています。') return redirect(url_for('index')) flash('新規登録に成功しました。') return redirect(url_for('index')) @auth.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('auth/login.html') else: user = auth_service.login(request.form) if not user: flash('メールアドレスもしくはパスワードに誤りがあります。') return render_template('auth.login') flash('ログインしました。') return redirect(url_for('index')) @auth.route('/logout') @login_required def logout(): auth_service.logout() flash('ログアウトしました。') return redirect(url_for('auth.login'))
templates/layout.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>QA-Site</title> </head> <body> <ul> {# ログインしていれば表示 #} {% if current_user.is_authenticated %} <li><a href="{{ url_for('index') }}">Index</a></li> <li><a href="{{ url_for('auth.logout') }}">ログアウト</a></li> {% endif %} {#ログインしていなければ表示#} {% if not current_user.is_authenticated %} <li><a href="{{ url_for('auth.signup') }}">新規登録</a></li> <li><a href="{{ url_for('auth.login') }}">ログイン</a></li> {% endif %} </ul> {# flaskのメッセージを表示 #} {% with messages = get_flashed_messages() %} {% if messages %} <div> {{ messages[0] }} </div> {% endif %} {% endwith %} {% block content %} {% endblock %} </body> </html>
ログインした状態で画面を確認します
ログアウトが追加されていることが確認できました。
それでは「ログアウト」ボタンを押してみます
問題ないログインされ、ログインページへリダイレクトされました
これでログイン関連は完了です。
Question modelとAnswer modelの関係性について
今回作成するアプリは
- 質問して
- 質問に対しての回答をして
- さらに回答に対して反応を示す
ことができるアプリを作るのでこのようなデータベース設計となります
Userは複数のQuestion(質問)を持つことができ、
Question(質問)は複数のAnswer(回答)を持つことができ
Answer(回答)は複数のReaction(反応)を持つことができます。
ここから先はQeustionモデルをベースに
- model
- service
- template
- view
を全て一気に作成して行きます
最後にBitbucketのリポジトリのリンクを記載しますので動かない場合は確認してください
ユーザーは先ほど作成したのでQuestionを作成していきます
Model
app/models/question.pyを作成
from app import db # モデルに関する設定 class Question(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(1000)) body = db.Column(db.String(255)) # Userに所有されている状態 user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE', name='user_id__question_id_fk')) @classmethod def from_args(cls, title: str, body: str, user_id: int): instance = cls() instance.title = title instance.body = body instance.user_id = user_id return instance
app/models/user.pyを修正
from flask_login import UserMixin # 追加 from sqlalchemy.orm import relationship from werkzeug.security import generate_password_hash, check_password_hash from app import db # モデルに関する設定 class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(1000)) email = db.Column(db.String(100), unique=True) password = db.Column(db.String(100)) # 追加 # user.questions と記述することでhas_manyなquestionを取得することができます questions = relationship('Question') # モデルからインスタンスを生成するときに使います。(利便性を高めるため) # passwordの暗号化も自動で行うことができるので、安全性も高めることができます。 @classmethod def from_args(cls, name: str, email: str, password: str): instance = cls() instance.name = name instance.email = email instance.email = email if password is not None: # passwordがあれば暗号化します。 instance.hash_password(password) return instance # 暗号化するためのメソッド。 def hash_password(self, clean_password): self.password = generate_password_hash(str(clean_password), method='sha256') # 登録したpasswordとユーザーがログインフォームで入力したパスワードが正しいかどうかのチェックを行うメソッド def check_password(self, clean_password): return check_password_hash(self.password, clean_password)
Service
app/services/question_service.py
データベースにデータを保存したり、取得してくるためのメソッドをまとめています
from app import db from app.models.question import Question from sqlalchemy.exc import SQLAlchemyError def find_all() -> [Question]: return Question.query.all() def find_one(question_id: int) -> Question: if question_id is None: raise Exception return Question.query.filter_by(id=question_id).first() def save(question_id: int, user_id: int, data: {}) -> Question: try: if question_id is None: question = Question.from_args( data.get('title'), data.get('body'), user_id ) db.session.add(question) else: question = find_one(question_id) question.title = data.get('title') question.body = data.get('body') question.user_id = user_id db.session.commit() return question except SQLAlchemyError: raise Exception def delete(question_id: int) -> bool: if question_id is None: raise Exception try: question = find_one(question_id) db.session.delete(question) db.session.commit() return True except SQLAlchemyError: raise Exception
View
app/views/questions.py
http://localhost:5000/questions
http://localhost:5000/questions/add
などquestionに関わる特定のリンクにアクセスした際に発火するルーティングの設定です
from flask import Blueprint, render_template, request, flash, redirect, url_for from flask_login import login_required, current_user from app.services import question_service questions = Blueprint('questions', __name__) @questions.route('/') @login_required # ログインしていないと表示できないようにする def find_all(): questions = question_service.find_all() return render_template('questions/index.html', questions=questions) @questions.route('/<question_id>') @login_required # ログインしていないと表示できないようにする def find_one(question_id: int): question = question_service.find_one(question_id) return render_template('questions/show.html', question=question) @questions.route('/add', methods=['GET', 'POST']) @login_required # ログインしていないと表示できないようにする def add(): try: if request.method == 'GET': return render_template('questions/post.html') else: # postとputを一つのメソッドでできるようにquestion_idを入れてあるが、 # 新規作成時はNoneにしておく。二つ目のrequet.formはformから送られてくる情報をそのままserviceに渡す # current_userはflask_loginの機能で、現在ログインしているユーザーの情報を取得することができる。 question = question_service.save(None, current_user.id, request.form) if question is None: flash('Questionを追加することができませんでした。') return redirect(url_for('questions.add')) flash('Questionを追加しました。') return redirect(url_for('questions.find_all')) except Exception: flash('Questionを追加することができませんでした。') return redirect(url_for('questions.add')) @questions.route('/update/<question_id>', methods=['GET', 'POST']) @login_required # ログインしていないと表示できないようにする def update(question_id: int): try: if request.method == 'GET': question = question_service.find_one(question_id) return render_template('questions/update.html', question=question) else: question = question_service.save(question_id, current_user.id, request.form) if question is None: flash('Questionを修正することができませんでした。') return redirect(url_for('questions.update', question_id=question_id)) flash('Questionを修正しました。') return redirect(url_for('questions.find_all')) except Exception: flash('Questionを修正することができませんでした。') return redirect(url_for('questions.update', question_id=question_id)) @questions.route('/delete/<question_id>', methods=['POST']) @login_required # ログインしていないと表示できないようにする def delete(question_id: int): try: question_service.delete(question_id) flash('Questionを削除しました。') return redirect(url_for('questions.find_all')) except Exception: flash('Questionを削除することができませんでした。') return redirect(url_for('questions.find_all'))
Template
登録フォームや個別ページなどのhtmlを作成します
app/templates/questions/index.html
questionの一覧ページです
templatesの中にさらにquestionsというフォルダを作っているので注意してください
{% extends "layout.html" %} {% block content %} <h1>Questions</h1> <ul> <li><a href="{{ url_for('questions.find_all') }}">Index</a></li> <li><a href="{{ url_for('questions.add') }}">追加</a></li> </ul> {% for question in questions %} <p>{{ question.title }} // {{ question.body }} <span><button><a href="{{ url_for('questions.find_one', question_id=question.id) }}">detail</a></button></span> <span><button><a href="{{ url_for('questions.update', question_id=question.id) }}">update</a></button></span> <form action="{{ url_for('questions.delete', question_id=question.id) }}" method="POST"> <button type="submit"><a>delete</a></button> </form> </p> {% endfor %} {% endblock %}
app/templates/questions/post.html
questionの登録ページです
templatesの中にさらにquestionsというフォルダを作っているので注意してください
{% extends "layout.html" %} {% block content %} <div> <h3>Questionの追加</h3> <div> <form action="/questions/add" method="POST"> <div> <div> <input type="text" name="title" placeholder="Title" autofocus=""> </div> </div> <div> <div> <input type="text" name="body" placeholder="Body" autofocus=""> </div> </div> <button>追加する</button> </form> </div> </div> {% endblock %}l
app/templates/questions/show.html
questionの個別ページです
templatesの中にさらにquestionsというフォルダを作っているので注意してください
{% extends "layout.html" %} {% block content %} <h1>Questions</h1> <ul> <li><a href="{{ url_for('questions.find_all') }}">Index</a></li> <li><a href="{{ url_for('questions.add') }}">追加</a></li> </ul> <ul> <p>Title:{{ question.title }}</p> <p>Body:{{ question.body }}</p> </ul> {% endblock %}
app/templates/questions/update.html
questionの編集ページです
templatesの中にさらにquestionsというフォルダを作っているので注意してください
{% extends "layout.html" %} {% block content %} <div> <h3>Questionの追加</h3> <div> <form action="/questions/update/{{ question.id }}" method="POST"> <div> <div> <input type="text" name="title" placeholder="Title" value="{{ question.title }}"> </div> </div> <div> <div> <input type="text" name="body" placeholder="Body" value="{{ question.body }}"> </div> </div> <button>修正する</button> </form> </div> </div> {% endblock %}l
app/templates/layout.html
questionsへリンクを飛ばせるように、ログインしているときにリンクを表示します
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>QA-Site</title> </head> <body> <ul> {# ログインしていれば表示 #} {% if current_user.is_authenticated %} <li><a href="{{ url_for('index') }}">Index</a></li> {#ここに追加#} <li><a href="{{ url_for('questions.find_all') }}">Questions</a></li> <li><a href="{{ url_for('auth.logout') }}">ログアウト</a></li> {% endif %} {#ログインしていなければ表示#} {% if not current_user.is_authenticated %} <li><a href="{{ url_for('auth.signup') }}">新規登録</a></li> <li><a href="{{ url_for('auth.login') }}">ログイン</a></li> {% endif %} </ul> {# flaskのメッセージを表示 #} {% with messages = get_flashed_messages() %} {% if messages %} <div> {{ messages[0] }} </div> {% endif %} {% endwith %} {% block content %} {% endblock %} </body> </html>
__init__.py
flaskアプリのペースファイルです
__init__.pyに作成したMoldeとviewを取り込んでflaskアプリから使えるようにします
# モジュールインポート from flask import Flask, render_template # データベースを利用するために追加 from flask_sqlalchemy import SQLAlchemy # flaks-loginのライブラリ追加 from flask_login import LoginManager, login_required # Flaskアプリの生成 app = Flask(__name__) # ここから /// データベースの設定 app.secret_key = "super secret key" app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///qa-site.sqlite3' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # ここまで /// データベースの設定 # sqlalchemyを通してflaskからdbアクセスをするための入り口 db = SQLAlchemy(app) # flask-loginに関する設定 login_manager = LoginManager() login_manager.login_view = 'auth.login' login_manager.init_app(app) # データベースのimport from app.models.user import User from app.models.question import Question @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # authに関するルーティングを追加 from app.views.auth import auth from app.views.questions import questions # authに関するルートをflaskアプリであるappに追加 app.register_blueprint(auth) # url_prefixに「/questions」を入れると # http://localhost:5000/questions/ というリンクになりわかりやすくなるので設定を追加しました app.register_blueprint(questions, url_prefix='/questions') # indexのルート設定 @app.route('/') @login_required # ここを追加 def index(): return render_template('index.html')
データベースをターミナルからアップデート
追加したQuesetionを適用するためにこちらをターミナルで実行
$ python create_db.py
それでは画面をみていきましょう
http://localhost:5000
Questionsが追加され表示されました
ちなみにリンクはこんな感じです
「Questions」をクリックすると、、
無事に表示されました
ただしデータは一つもいれていないので空のままです
ちなみにquestionsのリンクはこんな感じになっています
複数のurlを作成するときはこのリンクが非常に重要なので
綺麗に分けていきましょう
今回はanswerやreactionもこんな感じで分けていきます
それでは
- 追加
- 個別ページ
- 編集
- 削除
ができるかどうかを確認していきます
追加
「追加」ボタンを押してpost.indexページに飛びます
適当にTitleとBodyを入力して「追加する」ボタンをクリック
questinosの一覧へ遷移し先ほど追加したデータが表示されております
UIは汚いですがひとまず追加と一覧表示は問題なさそうです
個別ページ
一覧の「detail」ボタンをクリックします
こちらも問題なく表示されました
編集
「update」ボタンをクリックして内容を変更してみます
適当にわかりやすい編集を加えて「修正する」ボタンをクリック
編集後は一覧画面へ戻り
問題なく編集が完了しました
削除
「delete」ボタンをクリックします
こちらも問題なく削除されました
まとめ
今回はログイン機能とQuestionを作成しました
templateやservice、viewはAnswerやReactionでもコピペして使えるベースなので
完全に理解できていないという方はなんども復習して理解を深めましょう
Bitbucketのリンク:https://bitbucket.org/Masahiro_Okubo/qa-site/src/phase1/
ブランチは「phase1」で指定してください。
コメントを残す