[Flask]FizzBuzzの結果を表示する[Jinja2]

Python

このページでは、Flaskを使い、FizzBuzz問題の結果をブラウザで表示します。

そのなかで、

  • HTML内でforとif文を使う方法(Jinja2)
  • CSSファイルを適用させる方法
  • 動的ページの作成方法

を見ていきます。

Pythonの基本的なこと、HTMLとCSSの多少の知識があること、を前提としています。

Flaskが初めての人は↓から。

参考ページ
Flaskへようこそ ―― Flask Documentation
Jinja ―― Jinja Documentation

使用した環境
WSL2 Ubuntu - 20.04
Python 3.10.0
Flask 2.1.3

 Androidアプリを作成しました。
 感情用のメモ帳です。

スポンサーリンク
スポンサーリンク

モジュールとHTMLの用意

下準備としてモジュールを作成します。

from flask import Flask, render_template


app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

モジュールと同階層の「templates」内につぎのHTMLファイルを追加します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>FizzBuzz</title>
  </head>
  <body>
    <!-- これから書いていきます -->
  </body>
</html>

bodyの内容はまだありません。
次の項目で埋めていきます。

HTML内でforとifを使う

FizzBuzz問題を知らない人のために簡単に説明すると、数字を1から順に表示させていきますが、そのうち3の倍数のときは「Fizz」、5の倍数だと「Buzz」、3と5両方の倍数のときは「FizzBuzz」を表示させるというものです。

これを100までHTMLで書こうとすると大変ですが、Flaskではテンプレート(HTML)の中でfor文if文、そして変数を使うことができます。

厳密にはFlaskではなく「Jinja2」というテンプレートエンジンの機能で、Flaskがインストールされるときに自動で追加されています。

$ pip list
Package      Version
------------ -------
click        8.1.3
Flask        2.1.3
itsdangerous 2.1.2
Jinja2       3.1.2 <--
MarkupSafe   2.1.1
pip          22.2.1
setuptools   57.4.0
Werkzeug     2.1.2

Jinja — Jinja Documentation

今回使うものの構文は次のような形です。

forの構文
{% for ~%}

処理
{% endfor %}

ifの構文
{% if 条件式 %}

処理
{% elif 条件式 %}

処理
{% else %}
処理
{% endif %}

変数などの使用
{{ 変数名 }}

ではFizzBuzzを解きます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>FizzBuzz</title>
  </head>
  <body>
  {% for i in range(1, 101) %}
    {% if i % 15 == 0 %}
      <p>FizzBuzz</p>
    {% elif i % 3 == 0 %}
      <p>Fizz</p>
    {% elif i % 5 == 0 %}
      <p>Buzz</p>
    {% else %}
      <p>{{ i }}</p>
    {% endif %}
  {% endfor %}
  </body>
</html>

波カッコや%を使っていて、forとifにはそれぞれend文が必要です。

Pythonにおける構文とそれほど変わらず、難しいことはないと思います。
※ちなみにPythonのときとは違い、インデントをしなくてもエラーにはなりません。

100行分を手打ちするより楽に短く書くことができました。

では開発サーバーにアクセスしてみます。

$ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
FizzBuzzの1から15までの結果
FizzBuzzの86から100までの結果

FizzBuzzの結果が表示されました。

CSSファイルを適用させる

CSSを適用させるにあたってテンプレートに変更を加えました。

主な変更点はクラスの付与です。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <title>FizzBuzz</title>
  </head>
  <body>
    <div>
      <h1>FizzBuzzを表示しよう</h1>
      {% for i in range(1, 101) %}
        {% if i % 15 == 0 %}
          <p class="fizzbuzz">FizzBuzz</p>
        {% elif i % 3 == 0 %}
          <p class="fizz">Fizz</p>
        {% elif i % 5 == 0 %}
          <p class="buzz">Buzz</p>
        {% else %}
          <p>{{ i }}</p>
        {% endif %}
        {% if i % 10 == 0 %}
          <br>
        {% endif %}
      {% endfor %}
    </div>
  </body>
</html>

そして適用させるCSSはこちらを用意しました。

div {
    width: 900px;
    text-align: center;
}

p {
    display: inline-block;
    width: 80px;
    text-align: center;
}

.fizzbuzz {
    font-weight: bold;
    color: red;
}

.fizz {
    color: orange;
}

.buzz {
    color: blue
}

基本的に静的ファイル(CSSや画像など)を置く場所は、アプリケーションモジュールと同階層の「static」という名前のディレクトリ内です。

同名のディレクトリを作ってその中にCSSファイルを入れてください。

$ tree
.
├── app.py
├── static
│   └── style.css <--
└── templates
    └── index.html

2 directories, 3 files

CSSを読み込む設定をHTML内でします。
私のテンプレートでは既に追記済みですが、次の文をheadの中に記述します。

<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">

ここで使う「url_for」というのはFlaskの関数で、第一引数は'static'です。
2つ目の引数に「filename='用意したCSSファイルの名前'」を渡します。

それではサーバーを起動して、どう変化するか確認してみましょう。

$ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
CSSが適用されて文字色等が変わっている

CSSが適用されました。

動的なページの作成

URLとして入力された文字列を使い、それをテンプレートに反映させる、ということをやります。

動的ページの作成です。

具体的にいうと、ルートにアクセスしたときはそのままFizzBuzzを表示し、たとえば「/three/five」にアクセスしたときには、Fizzの代わりに「three」、Buzzの代わりに「five」、FizzBuzzの代わりに「threefive」と表示されるようにします。

まずモジュールを変更します。

from flask import Flask, render_template


app = Flask(__name__)

@app.route('/')
@app.route('/<fizz>/<buzz>')
def index(fizz='Fizz', buzz='Buzz'):
    return render_template('index.html', fizz=fizz, buzz=buzz)
  1. 変数として使いたいURLの部分を<>で挟み、その中で名前を付けます。
  2. 関数の引数にその変数を渡します。上記ではデフォルト値が設定済みです。
  3. render_template関数の引数、ファイル名の次に「HTML内で使う変数名=変数」の形で渡します。
変数の型指定

受け取る変数は型を指定することができます。

  • string 文字列型(/を除く)
  • int 正の整数
  • float 正の浮動小数点
  • path /を含めた文字列
  • uuid UUID文字列(UUIDとは

使用例
@app.route('/<string:fizz>')

次にテンプレートの変更。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <title>FizzBuzz</title>
  </head>
  <body>
    <div>
      {% set fizzbuzz = fizz + buzz %}
      <h1>FizzBuzzを表示しよう</h1>
      {% for i in range(1, 101) %}
        {% if i % 15 == 0 %}
          <p class="fizzbuzz">{{ fizzbuzz }}</p>
        {% elif i % 3 == 0 %}
          <p class="fizz">{{ fizz }}</p>
        {% elif i % 5 == 0 %}
          <p class="buzz">{{ buzz }}</p>
        {% else %}
          <p>{{ i }}</p>
        {% endif %}
        {% if i % 10 == 0 %}
          <br>
        {% endif %}
      {% endfor %}
    </div>
  </body>
</html>

モジュールで定義した変数を使っています。
{{ fizz }} と {{ buzz }} がありますよね。

divタグのひとつ下の行は、Jinja2のテンプレート内で変数を定義や再定義する文でsetキーワード使っています

それでは出来ているか確認してみましょう。

$ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!

ルートページにアクセスしたときは前項と同じなので割愛します。

ブラウザで「http://127.0.0.1:5000/three/five」にアクセスすると、

3の倍数が「three」、5が「five」、15が「threefive」と表示された

意図通りに変わりました。

ちょっと不満なのはFizzとBuzzのように頭文字が大文字になっていないところです。

せっかくなので変えましょう。
divの開始タグの下につぎの文を追加します。

      {% set fizz = fizz.capitalize() %}
      {% set buzz = buzz|capitalize %}

fizzとbuzzの中身の頭文字を大文字にし、同名の変数に代入しています。

ひとつ目がPythonの文字列メソッドによる変換
ふたつ目がJinjaのフィルター(メソッドのようなもの)による変換です。

Pythonの機能のすべてをJinjaで使えるようにしているわけではありません。
※例えば、よく使いそうな関数――enumerateは使えません。代わりにJinjaではfor文の内側で使える「loop.index」という特別な変数が用意されています。

フィルターはJinjaで実装している機能群で、他にもあるので以下を参照してください。

List of Builtin Filters [Jinja Documentation]

変更点を保存し、サーバーを起動します。

3の倍数が「Foo」、5が「Bar」、15が「FooBar」と表示された

「http://127.0.0.1:5000/foo/bar」にアクセスしました。

今度は頭文字が大文字です。

細かいことを言えば、見出しの内容も変えた方が良いでしょうし、titleタグの上で変数を定義すれば、タイトルも変えられますが、ここで止めておきます。

注意事項

URLの一部を変数として使い、それをJinjaを通さずにHTMLとして返すような場合には、インジェクション攻撃を防ぐためにエスケープ処理が必要です。

インジェクション攻撃とは[e-words.jp]

Jinjaを使ってテンプレート内で変数を使うときには必要ありません。
Jinjaがエスケープ処理を自動でやってくれます。

Flaskインストール時に追加されたモジュール「markupsafe」の「escape関数」を使います。

$ pip list
Package      Version
------------ -------
click        8.1.3
Flask        2.1.3
itsdangerous 2.1.2
Jinja2       3.1.2
MarkupSafe   2.1.1 <--
pip          22.2.1
setuptools   57.4.0
Werkzeug     2.1.2
from flask import Flask
from markupsafe import escape


app = Flask(__name__)

@app.route('/<name>')
def print_name(name):
    return f'Your Name: {escape(name)}'

<<前回の記事
Flaskをはじめよう

次の記事>>
フォームとの連携

タイトルとURLをコピーしました