Django
前回、XserverにDjangoで作成したウェブサイトをアップロードした際に起こった画像ファイルのアドレスのずれを修正しました。
今回は前回に少しお話ししたテンプレートの継承を試していきたいと思います。
何故テンプレート継承が必要かと言うと、これまでトップページと3つのアプリを作成してきました。
そしてそれぞれの「index.html」には、そのページを表示するための全ての情報が記載されています。
具体的に言うと、ロゴを表示する部分、コンテンツを表示する部分、サイドバー(リンク)部分、そしてコピーライトを表示する部分です。
そのうちコンテンツを表示する部分(HTML上では<!–main–>の部分)はそのコンテンツによって内容が変化します。
しかし、他の部分に関しては現在のところトップページを含め、どのページにおいても共通です。
もしこの共通部分を変えようと思ったら、トップページ+3つのアプリの全てのHTMLファイルを修正しなければいけません。
今のところ4ページなので、一つ一つ修正していっても、それほど大した手間ではありません。
しかし、これが何十ページ、何百ページ(実際、3PySciは400ページを超えています)となった時に、一つ一つ修正していくのは現実的ではありません。
そこで共通の部分をテンプレートとして、記事をそこにはめ込むという仕組みや、さらにはページの中のそれぞれの部分を部品化して、それを組み合わせて表示することで、一つのファイルを修正するだけで全てのページを修正することが可能になります。
ということで今回のテンプレートの継承に関しては、Djangoでウェブサイトを作成する際、なるべく早い段階でやっておくほうがいいでしょう。
ということで始めていきましょう。
テンプレートの継承の基本〜extends〜
まずはどのようにするとコンテンツの部分と他の共通の部分を分離できるのか、基本を見ていきましょう。
今回使用するのは「BMI計算アプリ」です。
/webapp/bmiapp/templates/bmiapp/index.htmlはこんな感じです。
<!doctype html>
{% load static %}
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}">
<title>{{ title }}|ライフ:BMI</title>
</head>
<body class="container">
<!-- title -->
<div class="row">
<div class="col" style="text-align: left; margin:2rem 0rem 0rem 0rem;">
<a href={{index_url}}><img class="image_logo_m" src="{% static '3pysci_logo.png' %}"></a>
</div>
</div>
<!-- /title -->
<!-- main&link-->
<div class="row">
<!-- main -->
<div class="col-12 col-lg-10" style="margin:2rem 0rem 0rem 0rem;">
<h1>ライフ:BMI(Body Mass Index)</h1>
<p>BMI(Body Mass Index)は体重と身長から計算される肥満度を表す指数。</p>
<p>計算式は <font size="6">\(BMI=\frac{w}{h^2}\)</font> w:体重(kg) h:身長(m)</p>
<p><font size="4"> </font></p>
<table class="mx-auto">
<form action="/bmiapp/" method="post">
{% csrf_token %}
<tr>
<td style="text-align: right;">{{ forms.height.label }}</td><td>{{ forms.height }}</td><td>cm</td>
</tr>
<tr>
<td style="text-align: right;">{{ forms.weight.label }}</td><td>{{ forms.weight }}</td><td>kg</td>
</tr>
<tr>
<td></td><td style="text-align: right;"><input type='submit' value="click"></td><td></td>
</tr>
</form>
<tr><td><br></td><td></td><td></td></tr>
<tr><td style="text-align: right;">BMI値:</td><td style="text-align: center;">{{ bmi }}</td><td></td></tr>
<tr><td style="text-align: right;">適正体重:</td><td style="text-align: center;">{{ optimal }}</td><td>kg</td></tr>
</table>
<p style="text-align: center;"><font size="6">{{ result }}</font></p>
<table class="table table-striped mx-auto" style="width:auto">
<tr><td colspan="3"><font size="4"><b>日本肥満学会の基準</b></font></td></tr>
<tr><td>状態</td><td colspan="2">BMIの指標</td></tr>
<tr><td>低体重(痩せ)</td><td>18.50未満</td><td>低体重</td></tr>
<tr><td>普通体重</td><td>18.50以上25.00未満</td><td>標準</td></tr>
<tr><td>肥満(1度)</td><td>25.00以上30.00未満</td><td rowspan="2">肥満</td></tr>
<tr><td>肥満(2度)</td><td>30.00以上35.00未満</td></tr>
<tr><td>肥満(3度)</td><td>35.00以上40.00未満</td><td rowspan="2">高度肥満</td></tr>
<tr><td>肥満(4度)</td><td>40.00以上</td></tr>
</table>
</div>
<!-- /main -->
<!-- link -->
<div class="col-12 col-lg-2" style="margin:2rem 0rem 0rem 0rem;">
{% for cat, urls in link.items %}
<p style="font-weight: bold; ">・{{cat}}</p>
{% for name, url in urls.items %}
<p style="text-indent: 1rem;"><a href={{index_url}}{{url}}>{{name}}</a></p>
{% endfor %}
{% endfor %}
</div>
<!-- /link -->
</div>
<!-- /main&link -->
<!-- copyright -->
<div class="row">
<div class="col" style="text-align: center; margin:2rem 0rem 0rem 0rem;">
{{copyright}}
</div>
</div>
<!-- /copyright -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
</body>
</html>
かなり長いですが、まずはこれを共通部分と非共有部分(コンテンツの部分)に分けます。
そして共有部分のうち、コンテンツを表示する場所に次のコマンドを記載し、別ファイルとして保存します。
{% block content %}
{% endblock %}
別ファイルの名前を今回は「base.html」としましょう。
ということでこうなります。
<!doctype html>
{% load static %}
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}">
<title>{{ title }}|ライフ:BMI</title>
</head>
<body class="container">
<!-- title -->
<div class="row">
<div class="col" style="text-align: left; margin:2rem 0rem 0rem 0rem;">
<a href={{index_url}}><img class="image_logo_m" src="{% static '3pysci_logo.png' %}"></a>
</div>
</div>
<!-- /title -->
<!-- main&link-->
<div class="row">
<!-- main -->
<div class="col-12 col-lg-10" style="margin:2rem 0rem 0rem 0rem;">
{% block content %}
{% endblock %}
</div>
<!-- /main -->
<!-- link -->
<div class="col-12 col-lg-2" style="margin:2rem 0rem 0rem 0rem;">
{% for cat, urls in link.items %}
<p style="font-weight: bold; ">・{{cat}}</p>
{% for name, url in urls.items %}
<p style="text-indent: 1rem;"><a href={{index_url}}{{url}}>{{name}}</a></p>
{% endfor %}
{% endfor %}
</div>
<!-- /link -->
</div>
<!-- /main&link -->
<!-- copyright -->
<div class="row">
<div class="col" style="text-align: center; margin:2rem 0rem 0rem 0rem;">
{{copyright}}
</div>
</div>
<!-- /copyright -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
</body>
</html>
次にコンテンツ部分(非共有部分)ですが、1行目にこちらのコマンドを記載します。
{% extends "bmiapp/base.html" %}
次にコンテンツ部分の前後にこのようにコマンドを記載します。
{% block content %}
コンテンツ部分
{% endblock %}
そして「index.html」として保存します。
今回の場合こうなります。
{% extends "bmiapp/base.html" %}
{% block content %}
<h1>ライフ:BMI(Body Mass Index)</h1>
<p>BMI(Body Mass Index)は体重と身長から計算される肥満度を表す指数。</p>
<p>計算式は <font size="6">\(BMI=\frac{w}{h^2}\)</font> w:体重(kg) h:身長(m)</p>
<p><font size="4"> </font></p>
<table class="mx-auto">
<form action="/bmiapp/" method="post">
{% csrf_token %}
<tr>
<td style="text-align: right;">{{ forms.height.label }}</td><td>{{ forms.height }}</td><td>cm</td>
</tr>
<tr>
<td style="text-align: right;">{{ forms.weight.label }}</td><td>{{ forms.weight }}</td><td>kg</td>
</tr>
<tr>
<td></td><td style="text-align: right;"><input type='submit' value="click"></td><td></td>
</tr>
</form>
<tr><td><br></td><td></td><td></td></tr>
<tr><td style="text-align: right;">BMI値:</td><td style="text-align: center;">{{ bmi }}</td><td></td></tr>
<tr><td style="text-align: right;">適正体重:</td><td style="text-align: center;">{{ optimal }}</td><td>kg</td></tr>
</table>
<p style="text-align: center;"><font size="6">{{ result }}</font></p>
<table class="table table-striped mx-auto" style="width:auto">
<tr><td colspan="3"><font size="4"><b>日本肥満学会の基準</b></font></td></tr>
<tr><td>状態</td><td colspan="2">BMIの指標</td></tr>
<tr><td>低体重(痩せ)</td><td>18.50未満</td><td>低体重</td></tr>
<tr><td>普通体重</td><td>18.50以上25.00未満</td><td>標準</td></tr>
<tr><td>肥満(1度)</td><td>25.00以上30.00未満</td><td rowspan="2">肥満</td></tr>
<tr><td>肥満(2度)</td><td>30.00以上35.00未満</td></tr>
<tr><td>肥満(3度)</td><td>35.00以上40.00未満</td><td rowspan="2">高度肥満</td></tr>
<tr><td>肥満(4度)</td><td>40.00以上</td></tr>
</table>
{% endblock %}
これでテンプレートの継承は完了です。
実際に表示したのがこちらですが、テンプレートを分割し、それを再度統合して表示しているだけなので見た目的には変わりません。
アクセスから表示までの流れ
ここで一度、アクセスから表示までの流れを整理しておきましょう。
まずこのページ(今回ならBMI計算アプリのページ)にアクセスがあった場合、「index.html」にアクセスされます。
ただしここで「{% extends ‘bmiapp/base.html’ %}」とあった場合、「bmiapp/base.html」を読みにいきます。
そこで同じ名称のブロック、つまり{% block content %}の場合「content」という名称のブロックがあった場合、index.htmlからbase.htmlへその情報が挿入され、表示されます。
ここで疑問が浮かびます。
「同じ名称のブロック」とありましたが、複数のブロックでも可能なのでしょうか?
個々のアプリで異なる点として、「タイトル」もそれぞれ異なるため、こちらも継承してみましょう。
複数の情報を継承する場合
先ほどは「content」という名称のブロックを使ったので、今回は「title」という名称のブロックを作成してみましょう。
その場合、base.html(<head></head>部分のみ)はこうなります。
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}">
{% block title %}
{% endblock %}
</head>
次にindex.html(途中は省略)はこうなります。
{% extends "bmiapp/base.html" %}
{% block title %}
<title>{{ title }}|ライフ:BMI</title>
{% endblock %}
{% block content %}
<h1>ライフ:BMI(Body Mass Index)</h1>
<p>BMI(Body Mass Index)は体重と身長から計算される肥満度を表す指数。</p>
<p>計算式は <font size="6">\(BMI=\frac{w}{h^2}\)</font> w:体重(kg) h:身長(m)</p>
(中略)
<tr><td>肥満(4度)</td><td>40.00以上</td></tr>
</table>
{% endblock %}
この場合、index.htmlの「title」と「content」の二つのブロックの情報がbase.htmlに渡され、表示されます。
これでテンプレートの継承の基本的なことは分かりました。
しかし今のところ、同じフォルダ内にあるindex.html、base.htmlが情報のやりとりをしているだけなので、他のアプリケーションと共有したテンプレートというわけではありません。
ということで次回はプロジェクト内で同一のテンプレートを使うということを試してみたいと思います。
ではでは今回はこんな感じで。
コメント