本記事では「初めてのDjango」(入門編)として、初めてDjangoというフレームワークを使用してWebアプリケーションを作ってみたい!という方向けに、簡単なアプリケーションを作成しながら基本的な知識をご紹介していきます。
・とりあえず手を動かしてWebアプリケーションを作成してみたい!
・pythonはある程度知識ある!
本記事は連作になっております!
- (入門編 #01)単一ページのアプリケーション作成まで
- (入門編 #02)CSSの適用からベーステンプレートの作成まで
- (入門編 #03)コンテキスト情報とテンプレートタグの活用 ←イマココ
- (入門編 #04)お問合せフォームの設置、メール送信処理まで
- (入門編 #05)お問い合わせ送信後にメッセージを表示させる
- (入門編 #06)アプリケーションをGAEにデプロイ
- (発展編 #01)データベースの設定を行う
- (発展編 #02)カスタムユーザーモデルの定義、マイグレーション
- (発展編 #03)django-allauthで認証機能を実装する
- (発展編 #04)ListViewを用いてモデル情報を描写する
- (発展編 #05)CreateViewを使用してデータベースを更新
- (発展編 #06)DetailViewを用いて詳細表示
- (発展編 #07)UpdateViewを用いてデータベース編集
- (発展編 #08)DeleteViewを用いてデータベースのレコード削除
新規ページを作成する
現在のトップ画面がどんな感じか改めて確認してみましょう。
「python manage.py runserver」とコマンドを入力してから、http://127.0.0.1:8000 にアクセスしましょう!
現在は、右上のメニューをクリックしても特にページ遷移は発生しません。
そこで新たにページを作成し、右上の項目をクリックしたらページ移動させるようにします!
具体的には「MENU」をクリックしたら、飲み物のメニュー一覧ページへと遷移します。
ルーティングの設定
まずはルーティングの設定をするのですが、ルーティングとはなにか覚えていますか??
ルーティングとは…
どのURLにアクセスしたらどのページを表示させるかの設定
今回は https://<ホスト名>/menu にアクセスしたらメニュー一覧が表示されるようにしたいので、アプリケーション内の urls.py のurlpatternを下記のように編集します(プロジェクトの urls.py ではないので注意しましょう)。
urlpatterns = [
path('', views.IndexView.as_view(), name="index"),
path('menu', views.MenuView.as_view(), name="menu"), # 追加
]
このように記述することで「https://<ホスト名>/menu にアクセスした際に、ページの描写する役割をviews.pyのMenuViewクラスに渡す」という意味になります。
ページが増えるたびにこのリストに追加していきます。
ビューの編集
urls.py にルーティングの設定を追加すだけではページ描写まで担ってもらえませんので、views.py内に下記を追加しましょう!
class MenuView(generic.TemplateView):
template_name = "menu.html"
これでtemplates内にある menu.html のファイル内容を描写してくれます(まだ作成していません)。
views.pyはページの描写の役割を担っているんですね!
メニューページの作成
まだ menu.html を作成していないので、cafe_app/templates内に新規作成しましょう。
新規作成した menu.html を下記のように編集しましょう。
<!-- base.htmlに組み込むためのおまじない -->
{% extends 'base.html' %}
<!-- 静的ファイルの読み込み -->
{% load static %}
<!-- base.htmlのtitleブロックに組み込み -->
{% block title %}トップページ{% endblock title %}
<!-- base.htmlのcontentsブロックに組み込み -->
{% block contents %}
<section class="menu">
<div>
<h2>MENU</h2>
</div>
</section>
{% endblock contents %}
これで材料は整いました!ローカルサーバーを立ち上げた状態で http://127.0.0.1:8000/menu にアクセスしてみましょう!
このようなページが表示されればOKです!
先ほど新規作成した menu.html の内容がちゃんと描写されています。
ただし現時点だと、URLを直接検索欄で打ち込まないとページの移動をすることができませんので、このページへのリンクを作成します。
リンクの作成
index.html の一部を下記のように編集します。
<nav>
<ul>
<li><a href="#">HOME</a></li>
<li><a href="{% url 'cafe_app:menu' %}">MENU</a></li> <!-- 編集 -->
<li><a href="#">CONTACT</a></li>
</ul>
</nav>
編集した<a>タグのみに注目してみます。
<a href="{% url 'cafe_app:menu' %}">MENU</a>
このように記述することで、cafe_appアプリケーションにあるurls.pyにおけるmenuという名前のルーティングを参照すると言う意味になります。
この「menuという名前」というのは、urls.py内のurlpatternsの要素であるpath()メソッドの第三引数に該当します。
urlpatterns = [
path('', views.IndexView.as_view(), name="index"),
path('menu', views.MenuView.as_view(), name="menu"),
]
したがってトップページへのリンクを作成したい場合はこのように記述します。
<a href="{% url 'cafe_app:index' %}">TOP</a>
やっと path() の第三引数の name=”…” の存在意義が判明しましたね。
きちんと内部リンクが機能していることを確認してみましょう!
完璧すぎて怖いですね、、、。
メニューページを作り込む
新規ページを作成し、リンクを作成することでトップページから移動することができるようになりました。続いてはこの作成したページを作り込んで、カフェのメニュー風にしていきます。
編集後はこのようになります。
menu.html、mystyle.cssそれぞれを下記のように編集してください。
menu.html
{% extends 'base.html' %}
{% load static %}
{% block title %}トップページ{% endblock title %}
{% block contents %}
<section class="sectionMenu">
<div class="wrapperHeader">
<h2>MENU</h2>
</div>
<div class="wrapperMenu">
<ul class="ulMenu">
<li class="menuContent">
<img src="{% static 'assets/img/brend-coffee.jpg' %}" alt="brend-coffee">
<h3>ブレンドコーヒー</h3>
<p class="price">420円</p>
<p class="description">
当店おすすめの一杯です。<br>
迷った場合はぜひこちらをご堪能ください。
</p>
</li>
<li class="menuContent">
<img src="{% static 'assets/img/cafe-au-lait.jpg' %}" alt="cafe-au-lait">
<h3>カフェオレ</h3>
<p class="price">500円</p>
<p class="description">
産地直送の贅沢ミルクを使用しています。<br>
オリジナルのラテアートも自慢です。
</p>
</li>
<li class="menuContent">
<img src="{% static 'assets/img/black-tea.jpg' %}" alt="black-tea">
<h3>紅茶</h3>
<p class="price">480円</p>
<p class="description">
圧倒的な茶葉使用。<br>
故郷でもない外国の景色に想いを馳せることができます。
</p>
</li>
</ul>
</div>
<a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
mystyle.css
/* 共通 */
:root {
--dark-color: #442f1e;
--semi-dark-color: #614039;
--base-color: #d3c2bb;
--accent-color: #598493;
--footer-height: 100px;
}
* {
margin: 0;
padding: 0;
list-style: none;
font-family: -apple-system, Calibri、Candara、Segoe、Segoe UI、Optima、Arial、sans-serif;
box-sizing: border-box;
}
a:link, a:visited, a:hover, a:active {
color: inherit;
text-decoration: none;
}
.wrapperHeader {
text-align: center;
padding: 3rem;
}
/* トップページ */
.topPage {
position: relative;
}
.topMainImg {
width: 100vw;
}
.wrapperNav {
position: absolute;
top: 50px;
right: 100px;
}
.wrapperNav ul {
display: flex;
flex-direction: row;
}
.wrapperNav li {
color: var(--base-color);
letter-spacing: .3rem;
margin: 0 .5rem;
font-weight: 800;
}
.welcome {
background-color: var(--base-color);
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.welcome p {
display: block;
margin-top: 1rem;
}
/* フッター */
footer {
background-color: var(--semi-dark-color);
height: var(--footer-height);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
footer div {
text-align: center;
position: relative;
}
footer p {
color: var(--base-color);
font-size: 12px;
}
/* メニュー */
.sectionMenu {
background-color: var(--base-color);
top: 0;
color: var(--dark-color);
min-height: calc(100vh - var(--footer-height));
}
.wrapperMenu {
width: fit-content;
margin: 0 auto;
}
.ulMenu {
width: fit-content;
margin: 0 auto;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.menuContent {
width: 30vw;
max-width: 500px;
margin: 30px 15px;
}
.menuContent img {
width: 100%;
height: auto;
}
.menuContent h3 {
display: block;
margin: 10px 0;
font-style: italic;
}
.menuContent .price {
font-weight: 600;
max-width: 100%;
word-wrap: break-word;
}
.menuContent .description {
display: block;
margin-top: 5px;
max-width: 100%;
word-wrap: break-word;
}
.backToHome {
display: block;
width: fit-content;
margin-top: 30px;
margin-left: 20px;
}
特にDjangoに関する新しい知識は出てきていないので、脳死でコピペでOKです。
ただのページの作り込みです。
このままでは画像ファイルがないため読み込まれません。こちらから画像をダウンロードし、static/assets/img/ ディレクトリに格納してください。
もちろんご自身でお好きな画像を用意していただいても大丈夫です。またメニューの価格や説明も好き勝手いじっちゃってください。
ただし画像の名前を変える場合は、<img>タグのsrc属性値もそれに合わせて編集しましょう。
この時点でのプロジェクト全体のコードをGithubに載せてありますので、よかったら確認用としてご覧ください。
テンプレートにコンテキスト情報を渡す
ここまでは、ルーティングに設定した静的なテンプレート(htmlファイル)の内容が表示されるだけでした。しかしWebアプリケーションである以上、動的にページ内容を変化させたいところです。
動的なページとは
いつ・誰がアクセスするかでページの内容が変化するようなページ。
そんな動的なページを作成するための第一歩として、ビューからテンプレートに変数(厳密にはオブジェクト)を渡して使用する方法をご紹介します。
コンテキストとは?
そもそもコンテキストとは何でしょうか??
上記で少し触れましたが、Djangoにおけるコンテキストとは辞書型のデータのことを指します。
sample_context = {
"name": "Tom",
"age": 27
}
sample_context["name"] # Tom
sample_context["age"] # 27
このコンテキストをテンプレートに渡すことで、その中身を取り出すことができます。例えば上記の sample_context をテンプレートに渡した場合、下記のように取り出すことができます。
<p>{{ name }}<p> >> Tom
<p>{{ age }}<p> >> 27
あとでご紹介しますが、{{}}で囲むことでそのキーに対する値を使用することができます。
{{}}で囲むことで、pythonと同じ要領で扱うことができます。
ビューからコンテキスト情報を渡す
今回のようなクラスベースビュー(※1)においてはコンテキストが自動で生成されます。
(※1)クラスベースビュー
クラスベースビューとは、Djangoが用意している django.generic.view を使用もしくは継承して作成したビューのことを指します。ほとんどがクラスベースビューを使用して作成されます。クラスベースビュー以外に関数ベースビューがあります。
詳しくはこちらの記事なんかをご覧ください。
get_context_data()メソッドを行うことで、自動生成されたコンテキストを取得することができます。
現在作成している views.py のMenuViewクラスを次のように編集しましょう。
class MenuView(generic.TemplateView):
template_name = "menu.html"
# コンテキストを取得
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
継承元であるTemplateViewのget_context_data(**kwargs)メソッドを実行し(6行目)、その返り値をcontext変数に格納しています。
また7行目にてテンプレートにコンテキストを渡しています。
print()で出力し、どんな中身なのか確認してみましょう。
class MenuView(generic.TemplateView):
template_name = "menu.html"
# コンテキストを取得
def get_context_data(self, **kwargs):
# 継承元であるTemplateViewのget_context_data()メソッド
context = super().get_context_data(**kwargs)
# 中身確認
print(context)
return context
上記のようにprint文を追加した後に、メニュー一覧ページに再度アクセスしてみましょう。すると、ローカルサーバーを立ち上げているターミナルにこのような一文が吐き出されるかと思います。
{'view': <cafe_app.views.MenuView object at 0x102f58610>}
‘view’というキーに対する値を含んだ辞書型のデータであることが確認できます。
今度は適当な内容をコンテキストに追加してみましょう。
class MenuView(generic.TemplateView):
template_name = "menu.html"
# コンテキストを取得
def get_context_data(self, **kwargs):
# 継承元であるTemplateViewのget_context_data()メソッド
context = super().get_context_data(**kwargs)
# 値追加
context['weather'] = '晴れ'
# 中身確認
print(context)
return context
先ほどと同様に、メニュー一覧ページを更新した後にターミナルを見てみましょう。
{'view': <cafe_app.views.MenuView object at 0x102b3fb20>, 'weather': '晴れ'}
‘weather’キーに対して’晴れ’という値が追加されていることが確認できます!
get_context_data()が呼び出されるタイミング
urls.py内のurlpatternsのas.view()メソッドが実行されたタイミングでget_context_data()メソッドが実行されます。
コンテキストをテンプレート側で表示させる
先ほどviews.pyにてコンテキストをテンプレートに渡す処理を実装しましたので、今度はテンプレート側で受け取ったコンテキストの中身を表示させてみます。
テンプレート側で {{ <キー> }} と記述することで、任意の値を表示させることができます。試しに menu.html の見出しの下に、このように1行追加してみましょう。
<div class="wrapperHeader">
<h2>MENU</h2>
<p>{{ weather }}</p>
</div>
この状態で保存し、メニュー一覧を更新するとこのように表示されます。
見出し「MENU」の下に「晴れ」という文字が表示されています。すなわち、コンテキストの’weather’キーに対する値が表示できていることが確認できます。
やっとWebアプリケーション感がでてきましたね、、、。
簡単に図で表すとこんな感じです。
感の良い方ならお分かりかと思いますが、コンテキストの中身を都度変化させることで、ページに表示させる内容も変化させることができます。すなわち動的なページを作成することができます。
テンプレートタグの使い方
ここまでで、テンプレート内で {{ <キー> }} と記述することで、コンテキスト内の値を使用することができると述べました。
これの特性を活かして、もう少し現在のメニューをスマートに表現したいと思います。
おさらい
現在の menu.html の一部を改めて見てみます。
<ul class="ulMenu">
<li class="menuContent">
<img src="{% static 'assets/img/brend-coffee.jpg' %}" alt="brend-coffee">
<h3>ブレンドコーヒー</h3>
<p class="price">420円</p>
<p class="description">
当店おすすめの一杯です。<br>
迷った場合はぜひこちらをご堪能ください。
</p>
</li>
<li class="menuContent">
<img src="{% static 'assets/img/cafe-au-lait.jpg' %}" alt="cafe-au-lait">
<h3>カフェオレ</h3>
<p class="price">500円</p>
<p class="description">
産地直送の贅沢ミルクを使用しています。<br>
オリジナルのラテアートも自慢です。
</p>
</li>
<li class="menuContent">
<img src="{% static 'assets/img/black-tea.jpg' %}" alt="black-tea">
<h3>紅茶</h3>
<p class="price">480円</p>
<p class="description">
圧倒的な茶葉使用。<br>
故郷でもない外国の景色に想いを馳せることができます。
</p>
</li>
</ul>
この部分って、ただ単に次の内容を繰り返し記述してるだけというのが分かります。
<li>
<img src="<画像のパス>">
<h3>メニューの名前</h3>
<p>価格</p>
<p>説明</p>
</li>
これって、普通にpythonで記述するときのようにループ処理で記述できたら楽ちんじゃないですか?
イメージとしてはこんな感じです。
menu_items = [<商品に関する情報のオブジェクトのリスト>]
# ループさせて中身を取り出す
for item in menu_items:
print(f'商品名: {item.name}')
print(f'価格: {item.price}')
print(f'説明: {item.description}')
これをそのままテンプレート内で記述することは勿論できません。しかし、テンプレートタグと呼ばれるものを使用することで、同じ機能を実現することができます。
テンプレートタグを使用してみる
ではどのようにテンプレートタグを使用するのか解説していきます。
if文
テンプレートタグは基本的に {% %} で囲って記述します。
コンテキストの中身を取り出す {{}} との違いに注意しましょう。
menu.htmlの一部を下記のように編集してみましょう。
<div class="wrapperHeader">
<h2>MENU</h2>
<p>{{ weather }}</p>
{% if weather == '晴れ' %}
<p>外出日和です</p>
{% else %}
<p>家に篭りましょ</p>
{% endif %}
</div>
感覚的になんとなく意味がお分かりかと思いますが、変数 weather の値が’晴れ’ならば、「外出日和です」を表示させ、もし違う場合は「家に篭りましょ」を表示させる、という処理を追加しました。
メニュー一覧ページはこのようになります。
変数 weather の中身は ‘晴れ’ なので、「外出日和です」が表示されています。
pythonとは異なり、テンプレートタグを用いる場合は最後に {% endif %} が必要です。
for文
続いてはテンプレートタグを用いてfor文を表現する方法です。
まずviews.pyを次のように編集し、テンプレートにリストを渡します。
class MenuView(generic.TemplateView):
template_name = "menu.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['weather'] = '晴れ'
context['loop_item'] = [1, 2, 3] # 追加
print(context)
return context
続いて、menu.htmlの一部を下記のように編集しましょう。
<div class="wrapperHeader">
<h2>MENU</h2>
<p>{{ weather }}</p>
<!-- loop_sample = [1, 2, 3] -->
{% for i in loop_sample %}
<p>{{ i }}</p>
{% endfor %}
</div>
if分の時と同様に {% endfor %} を忘れないようにしましょう。
メニュー一覧はこのように表示されます。
このようにリストの中身を1つずつ取り出せているのが確認できます。
より詳しい使い方や機能などについてはこちらなんかで分かりやすくまとめられています。
テンプレートタグを活用した形に作り替える
ここまでにご紹介したテンプレートタグ(if文、for文)を使用して、よりスマートに記述していきます。
views.py
まずはviews.pyを次のように編集してください。
...
class MenuView(generic.TemplateView):
template_name = "menu.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['menu_items'] = [
{
'name': 'ブレンドコーヒー',
'price': 420,
'description': '当店おすすめの一杯です。\n迷った場合はぜひこちらをご堪能ください。',
'img_path': 'brend-coffee.jpg'
},
{
'name': 'カフェオレ',
'price': 500,
'description': '産地直送の贅沢ミルクを使用しています。\nオリジナルのラテアートも自慢です。',
'img_path': 'cafe-au-lait.jpg'
},
{
'name': '紅茶',
'price': 480,
'description': '圧倒的な茶葉使用。\n\n故郷でもない外国の景色に想いを馳せることができます。',
'img_path': 'black-tea.jpg'
},
]
return context
8-27行目にて、メニューの各要素(名前、価格、説明、画像のパス)が格納されたオブジェクトのリストをコンテキストに追加しています。これをテンプレート側でループさせるという寸法です。
menu.html
menu.htmlも下記のように編集します。
{% extends 'base.html' %}
{% load static %}
{% block title %}トップページ{% endblock title %}
{% block contents %}
<section class="sectionMenu">
<div class="wrapperHeader">
<h2>MENU</h2>
</div>
<div class="wrapperMenu">
<ul class="ulMenu">
{% if menu_items %}
{% for item in menu_items %}
<li class="menuContent">
<img src="{% static 'assets/img/'|add:item.img_path %}" alt="black-tea">
<h3>{{ item.name }}</h3>
<p class="price">{{ item.price }}円</p>
<p class="description">{{ item.description }}</p>
</li>
{% endfor %}
{% endif %}
</ul>
</div>
<a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
14行目にてコンテキスト内に menu_itemsキーに対する値があるかを確認しています。
あった場合は、15~22行目にてリストをループさせ各オブジェクトを取り出し、メニューを表現しています。
※17行目の記述方法についてはこちらを参考にさせていただきました。ありがとうございます。
このように記述することで、圧倒的に少ないコード量で表現することができます。今回はメニューの数が3つだけなのでアレですが、メニューの個数が増えれば増えるほど恩恵が大きくなり、なにより可読性がよくなりバグも起きにくくなります。
この時点でのプロジェクト全体のコードをGithubに載せておりますので、確認などにご活用ください。
まとめ
本セクションでは、新規ページ・リンクの作成方法から、コンテキストやテンプレートタグを使用して動的なページを作成するための基本知識をご紹介させていただきました。
次のセクションでは、お問い合わせフォームを設置し、実際にユーザーがメールを送信する処理を実装するまでをご紹介します。
ご覧いただきありがとうございました!
コメント
コメント一覧 (10件)
[…] 【初めてのDjango】(入門編 #03)コンテキスト情報とテンプレートタグの活… […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]
[…] (入門編 #03)コンテキスト情報とテンプレートタグの活用 […]