ブログの各処理を実装するところまで来ました。本格的になってきました。
今まで勉強した技術もふんだんに盛り込まれているね~。
flaskのチュートリアルでシンプルなブログアプリflaskrを作成しています。今回進めるチュートリアルのページは以下のページとなります。
こんな人の役に立つかも
・PythonでWebアプリケーションを作成したい人
・flaskのチュートリアルを行なっている人
・flaskのブループリントについて勉強をしている人
ブログの各機能について
チュートリアルでは突然プログラミングが始まりましたので、これからどんな機能を実装していくのかをまとめてみました。どんな機能を作成するのかをイメージできると良いと思いましたので、作成アプリの機能を整理してい見たいと思います。
次の4つの処理を実装していきます。
①表示処理(index)
投稿をすべて表示して、最新のものから並べる処理です。index.htmlに表示するようにします。
②投稿作成処理(Create)
記事投稿の入力画面を作成して、記事を追加できるようにします。もちろんログインしていないと、投稿機能にはアクセスできません。
③投稿のアップデート(Update)
ユーザーは「自分の投稿を」編集できるようにします。
④投稿の削除(Delete)
ユーザーは「自分の投稿を」削除できます。これは、テンプレートがupdate.htmlからの処理となりますので、viewに削除処理のみをプログラミングすることになります。
UpdateとDeleteはユーザーの情報が必要なんだね。
ブログ機能のBlueprint
まずは、先ほどの4つの機能を作成していくために、アプリの構成を整えていきます。4つのブログの機能を「blog.py」に作成するために、blueprintでアプリに登録します。
「blog.py」を次のプログラムで作成します。もちろん、階層は「flaskr」フォルダの直下です。
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
from werkzeug.exceptions import abort
from flaskr.auth import login_required
from flaskr.db import get_db
bp = Blueprint('blog', __name__)
bp = Blueprint(‘blog’, __name__)と「bp」に「blog」という名前のブループリントを追加しています。
Blueprintは2回目で、以前「auth.py」を作成するときも利用しました。前回は、「url_prefix」の引数を与えていましたが、今回は、引数にurl_prefixがありません。
bp = Blueprint('auth', __name__, url_prefix='/auth')
url_prefixがないので、「/」にそのままurlがぶら下がってくることになります。例えば、「blog.py」で「@bp.route(‘create’)」とルーティングすると、「/create」のURLでアクセスできることになります。
それでは、アプリにブログのblueprintを追加します。次の3行のプログラムを「__init__.py」に追加します。
def create_app():
#略
#以下3行が追加するプログラム
from . import blog
app.register_blueprint(blog.bp)
app.add_url_rule('/', endpoint='index')
return app
今回のflaskrブログアプリでは、「/」にアクセスすると、viewで定義した「index」のview関数が呼び出されるように設定するため、「app.add_url_rule(‘/’, endpoint=’index’) 」と指定します。
このようにすることで、アプリケーションのURLルートに登録することができるようです。以下のページも参考になります。
アプリケーションファクトリを利用したWebアプリの構造では、URLルートに何かしらのURLを指定するにはこのように設定する必要がありそうです。
①表示処理(index)
まずは①の表示処理を作成していきます。ここでは、viewとしてindex関数を、テンプレートとしてindex.htmlを作成していきます。
view関数であるindexを次のように「blog.py」に追加します。
@bp.route('/')
def index():
db = get_db()
posts = db.execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' ORDER BY created DESC'
).fetchall()
return render_template('blog/index.html', posts=posts)
index関数では、データベースコネクションを作成し、posts変数に、SQL文でアクセスしたデータベースの情報を全行取得するようにしています。fetchall()は、SQLでデータベースから取得したすべての行を取得するというようなメソッドのようです。投稿はたくさんの情報の取得になりますので、すべての情報を取得しているのですね。
SQL文は、次のようなイメージです。
上の図の右、userテーブルで今のユーザー(1のpanda)に一致するpostテーブルの「author_id」のデータ行を取得してきます(緑色)。ユーザーテーブルに紐づけて外部キーであるauthor_idの投稿データ(黄色)を取得してくる、という寸法です。
そして、最後にorder by句で「created」(投稿日時)の降順に並べ替えます。
次に、テンプレートであるindex.htmlを作成します。
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Posts{% endblock %}</h1>
{% if g.user %}
<!-- ①ユーザーがログイン状態のとき、Newリンクの表示 -->
<a class="action" href="{{ url_for('blog.create') }}">New</a>
{% endif %}
{% endblock %}
{% block content %}
<!-- ②データベースから取得した行数文のループ=投稿数文のループ -->
{% for post in posts %}
<article class="post">
<header>
<div>
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
</div>
{% if g.user['id'] == post['author_id'] %}
<!-- ③ユーザーが一致する投稿のとき、Editリンクの表示 -->
<a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ post['body'] }}</p>
</article>
<!-- ④JINJAループの特別な変数loop.last -->
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
今まで作成したテンプレートと同じように、「base.html」を引き継ぎ、それぞれ「title」「header」「content」ブロックを定義しています。
①でユーザーがログインしていると、(g.userに値が存在している)ヘッダーには「New」という投稿作成へのリンクが表示されます。
②の、content部分には、投稿が表示されます。投稿の表示には、postsというデータベースから取得したすべての行が格納された変数がview関数から渡されるので、これをfor文で回すことですべての投稿数分のループが行われるようになっています。
③のそれぞれの投稿の表示処理では、ユーザーがログインしているものと一致した場合、「Edit」という投稿編集へのリンクを作成しています。
④である最後の以下の表記は、「JINJAのforループで特別な変数」である「loop.last」が利用されています。
{% if not loop.last %}
これは、JINJAのforループの最後を示す変数なので、この条件では、最後の投稿ではないときに<hr>、水平線を引くHTML表示が行われる、というものになっています。
まだプログラムは完全にできていないので、添付したイメージを参考にしていただけるとありがたいです。