下町エンジニアの雑多ブログ

東京の端っこでITエンジニアやってます。テックブログを中心に書いていきたいと思います

Flask入門~公式ドキュメントを読み解く~ Day 5 ~

f:id:usktkt:20181019225022p:plain

前回の内容

前回の記事では、Blueprintを使用して認証機能の実装を行いました。

今回はMVTモデルのTにあたる、Templateの実装を行います。

テンプレートの作成

前回の記事でも少し触れましたが、テンプレートはWebアプリケーションの画面描画を担当します。作成すべきものはhtmlファイルになります。Flaskでは、Jinjaというテンプレートライブラリを使用することで、htmlを動的に生成します。(余談ですが、Jinjaの名前の由来はTemplateがTempleに似ているからだそうです。Templeは神社ではなく寺なのですが・・・)

前回のauth.pyの中でrender_templateメソッドを使用しました。render_templateの引数で指定したhtmlを呼び出すことができます。

Jinjaの構文はシンプルで、変数のレンダリングには{{ 変数名 }} を使い、if や for などの制御構文は{% if %}のように使います。また、Flaskではアプリケーション配下のtemplatesディレクトリに入っているファイルは自動的にテンプレートファイルだと認識します。そのため、先にflaskr配下にtemplatesフォルダを作ってしまいましょう。

まずは全てのベースとなるテンプレートである、base.htmlをtemplatesディレクトリ配下に作成します。

<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
  <h1>Flaskr</h1>
  <ul>
    {% if g.user %}
      <li><span>{{ g.user['username'] }}</span>
      <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
    {% else %}
      <li><a href="{{ url_for('auth.register') }}">Register</a>
      <li><a href="{{ url_for('auth.login') }}">Log In</a>
    {% endif %}
  </ul>
</nav>
<section class="content">
  <header>
    {% block header %}{% endblock %}
  </header>
  {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
  {% block content %}{% endblock %}
</section>

base.htmlには、<!doctype html>などのhtml宣言や、参照するCSSやJSについて、ヘッダー・フッダーなどの全ページで共通の内容を記述します。 基本的な文法はhtmlと同じですが、ところどころに先に説明した{{ }}や{% %}が出てきます。ここで特に重要なのは、以下の3つになります。

  • {% block title %}{% endblock %} : 他ページのtitleブロックで置き換え
  • {% block header %}{% endblock %} : 他ページのheaderブロックで置き換え
  • {% block content %}{% endblock %} : 他ページのcontentブロックで置き換え

これらのブロックはbase.htmlを参照するページ内に同じ名前のブロックがあれば、base.htmlのブロックがそのブロックに置き換えられます。下線を引いたブロック中の名前は自由に決めることができますが、参照元と先で揃えておく必要があります。

では次にbase.htmlを使用する登録画面を作成します。template/auth/register.htmlを作成してください。

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Register">
  </form>
{% endblock %}

1行目の{% extends 'base.html' %}によって、base.htmlのブロックをこのテンプレートのブロックで置き換えることができます。次の{% block header %}では、その中に{% block title %}を含んでいます。このように、ブロックを入れ子にすることも可能です。 {% block content %}ではフォームタグでユーザー情報入力欄とサブミットボタンを作成しています。register.htmlが呼び出されると、base.htmlのブロックがregister.htmlのブロックで置き換えられるので、実際には以下のhtmlが作成されることになります。

<!doctype html>
<title>Register- Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
  <h1>Flaskr</h1>
  <ul>
    {% if g.user %}
      <li><span>{{ g.user['username'] }}</span>
      <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
    {% else %}
      <li><a href="{{ url_for('auth.register') }}">Register</a>
      <li><a href="{{ url_for('auth.login') }}">Log In</a>
    {% endif %}
  </ul>
</nav>
<section class="content">
  <header>
    <h1>Register</h1>
  </header>
  {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
    <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Register">
  </form>
</section>

続いてログイン画面も一気に作ってしまいます。template/auth/login.htmlを作成してください。

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Log In">
  </form>
{% endblock %}

register.htmlとやっていることは同じですね。説明は割愛します。

登録とログインの画面ができたので、ここで一旦アプリを起動させてブラウザから接続してみましょう。flaskrの1つ上の階層(本記事に沿っているならflask-tutorial)で以下のコマンドを入力し、アプリを起動します。

export FLASK_APP=flaskr
export FLASK_ENV=develop
flask run --host=0.0.0.0

起動が完了したら、ブラウザからhttp://192.168.33.10/auth/registerに接続します。うまくいっていれば以下のような画面が表示されるはずです。ログインリンクからログインページも表示してみましょう。

f:id:usktkt:20181025225002p:plain

登録画面とログイン画面の作成が完了しましたが、このままでは少し味気ないですね・・・ ということで、続いて画面のスタイルを整えていきます。

スタイルシートの作成

皆様ご存知の通り、Webページのスタイルを整えるためには、CSSを使います。flaskでCSSを使うには、base.htmlに<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">を記載することで、flaskr/staticディレクトリのstyle.cssを参照するようになります。今回は扱いませんが、JavaScriptを使用したい場合も同様に<link rel="stylesheet" href="{{ url_for('static', filename='ファイル名.js') }}">のようにすれば使用できます。CSSの中身は解説しませんが、flaskr/static/style.cssを作成してください。

html { font-family: sans-serif; background: #eee; padding: 1rem; }
body { max-width: 960px; margin: 0 auto; background: white; }
h1 { font-family: serif; color: #377ba8; margin: 1rem 0; }
a { color: #377ba8; }
hr { border: none; border-top: 1px solid lightgray; }
nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; }
nav h1 { flex: auto; margin: 0; }
nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; }
nav ul  { display: flex; list-style: none; margin: 0; padding: 0; }
nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; }
.content { padding: 0 1rem 1rem; }
.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; }
.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; }
.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; }
.post > header { display: flex; align-items: flex-end; font-size: 0.85em; }
.post > header > div:first-of-type { flex: auto; }
.post > header h1 { font-size: 1.5em; margin-bottom: 0; }
.post .about { color: slategray; font-style: italic; }
.post .body { white-space: pre-line; }
.content:last-child { margin-bottom: 0; }
.content form { margin: 1em 0; display: flex; flex-direction: column; }
.content label { font-weight: bold; margin-bottom: 0.5em; }
.content input, .content textarea { margin-bottom: 1em; }
.content textarea { min-height: 12em; resize: vertical; }
input.danger { color: #cc2f2e; }
input[type=submit] { align-self: start; min-width: 10em; }

これでスタイルシートが適用されたはずなので、もう一度ブラウザから接続してみましょう。 正しくできていれば以下のように表示されます。

f:id:usktkt:20181025231513p:plain

まとめ

今回は登録、ログインテンプレートの作成と、CSSの適用を行いました。 次回はいよいよこのアプリのメイン機能であるブログ投稿機能、記事の編集・削除機能を作成していきます。 前回の記事のBlueprintから今回の記事のテンプレートまで含んだ盛りだくさんの内容となりますので、今一度復習しておきましょう。