Djangoチュートリアル – その3

Django公式のチュートリアル「Writing your first Django app, part 3」を元に進んでいきます。

チュートリアル – その3では、以下の内容を学びます。

  • データの型について
  • 実用的なviewの書き方
  • htmlファイルを作る場所
  • if文とfor文
  • urlの書き方
  • リレーションシップについて少し
WARN
必ず「仮想環境(myenv)」が有効化されているか確認しましょう。ここからは先頭の(myenv)を省略して書いています。
app1 $ source myenv/bin/activate
(myenv) app1 $ cd mysite
(myenv) mysite $

一般的なサイト

ブログサイトを例にサイトの基本的な構造を見てみましょう。

  • サイトのトップページ(ホームページ)
  • 記事の一覧ページ
  • 記事のページ
  • カテゴリーページ
  • etc…

こんな構造になっていて、それぞれにURLが付与されています。

pollsアプリでは、以下の4つのviewを作成していきます。

  • トップページ(質問をいくつか表示)
  • 質問の詳細ページ(結果は表示しない)
  • 質問の結果ページ(特定の質問の結果を表示)
  • 投票ページ(特定の質問の選択を投票するページ)

viewを書いてみる

polls/views.pyに以下の内容を追加・保存してください。今回の関数には引数を設定しています。

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

# ここから下を新しく追加
# detailは詳細という意味
def detail(request, id):
    return HttpResponse("detailページは質問 %s の詳細を表示します。" % id)

# resultsは結果という意味
def results(request, id):
    response = "resultsページは質問 %s の結果を表示しています。"
    return HttpResponse(response % id)

# voteは投票という意味
def vote(request, id):
    return HttpResponse("voteページは質問 %s に投票します。" % id)

以前書いた、index関数と違う点が2つがあります。

  1. 引数の(request)が、(request, id)になっている
  2. HttpResponseが(“文字列 %s.” % id)になっている

関数の引数にidを設定することで、URLからidを受け取り、関数内でidを変数として使えるようなります

「”文字列 %s.” % id」の部分は、%sの場所に、%の後のidを入れて下さい。といコードです。

この書き方は分かりにくさがあるので、最近はfストリングスという記法が使われたりします。

以下の2つは同じ結果になります。

>>> id = 20

>>> "detailページは質問 %s の詳細を表示します。" % id
detailページは質問 20 の詳細を表示します。

# fストリングス
>>> f"detailページは質問{ id }の詳細を表示します。"
detailページは質問 20 の詳細を表示します。

「f”文字列 { 変数 }”」と書くことで、どこに何の変数を入れているか一目で分かるようになります。

次に、urlからviewに「id」を渡すため、urls.pyを設定していきます。

polls/urls.pyに以下の内容を追加して下さい。メモは削除してもらっても構いません。

from django.urls import path
from . import views

app_name = 'polls' # アプリ名を設定

urlpatterns = [
    path('', views.index, name='index'),

    # detail関数を呼び出すurl
    path('<int:id>/', views.detail, name='detail'),
    # results関数を呼び出すurl
    path('<int:id>/results/', views.results, name='results'),
    # vote関数を呼び出すurl
    path('<int:id>/vote/', views.vote, name='vote'),
]

ここにもidが出てきましたね!

  • detailのurl
    「http://127.0.0.1:8000/polls/1」にアクセスすると、idが1としてdetail関数に渡される
  • resultsのurl
    「http://127.0.0.1:8000/polls/1/results/」にアクセスすると、idが1としてresults関数に渡される
  • voteのurl
    「http://127.0.0.1:8000/polls/1/vote/」にアクセスすると、idが1としてvote関数に渡される

「http://127.0.0.1:8000/polls/好きな数字」にアクセスしてみて下さい。

「detailページは質問好きな数字の詳細を表示します。」が表示されたと思います。

INFO
仮想サーバーを起動していない場合は、python manage.py runserverで起動できます。

データの型

上のurls.pyには「int」が記述されていていますが、これは「整数」を意味するものです。

プログラミングにはデータの型というものがありますので覚えておきましょう。

int型(イント型)

整数を表すものです。Integer(インテジャー)の略です。

>>> int(10)
10
>>> int(3.14)
3

str型(ストリング型)

文字列を表すものです。String(ストリング)の略です。

文字列と数字を連結したい時、数字を文字列として使うためによく使われます。

>>> print(20 + 1)
21

>>> print("年齢:" + 20)
TypeError: can only concatenate str (not "int") to str
# strと連結できるのはstrだけです。とエラーがでます。

# 20を数字ではなく、文字列にしてあげます。
>>> print("年齢:" + str(20))
年齢:20

ちなみに、文字列は「”何かの文字”」のように「”(ダブルクオテーション)」や「’(シングルクオテーション)」で囲ってあげると、システムが勝手に文字列と判断します。先程書いた、viewやfストリングスでも「” “」が使われていましたね!

「”」と「’」の使い分けは好みですが、「’ That’s ~ ‘」のように文字列の中に「’」が使われていたら、どこからどこまでが文字列か分からなくなるので、「” Taht’s ~ “」のように「”」で囲ってあげる必要があります。

float型(フロート型)

浮動小数点数(ふどうしょうすうてんすう)を表すものです。簡単に言えば小数点があるものです。

>>> float(3)
3.0
>>> float(3.14)
3.14

実用的なview

現在のindex関数は、単に文字列を返すだけのものでしたが、今度はQuestionモデルの最新のデータ(オブジェクト)5つを取得して、htmlに表示できるようにします。

HTMLとCSSについて
HTMLとは、ブラウザにどんな構造で表示するかが書いてあるプログラミング言語です。CSSとは、HTMLのデザインをする言語になります。家で例えると、間取りがHTMLで、それに色などをつけるのがCSSです。どちらも難しい言語ではないので、初めての方でも少し学べばすぐ使えるようになると思います。全てのwebサイトにHTMLとCSSは使われています。

from importを追加し、index関数を次のように変更してください。

from django.http import HttpResponse
from django.shortcuts import render # renderを呼び出す

from .models import Question # モデルのQuestionを呼び出す

def index(request):
    # 最新のQuestionのデータ(オブジェクト)を5つ取得
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    # contextの中に入れる
    context = {
        'latest_question_list': latest_question_list
    }
    return render(request, 'polls/index.html', context)

この形はDjangoの基本でもあるので覚えておきましょう。

モデル名.objects.メソッド

Question.objects.order_by()は、Questionの全てのオブジェクト(データ)をorder_by(‘-pub_date’)日付が最新の順に並べ替える、という処理をしています。pub_dateにマイナスがないと古い順になります。

pub_dateはモデルのフィールド名です。models.pyのQuestionモデルでフィールドを設定しているので確認してみてください。

[:5]は、最初から5つ目までを意味します。[5:]だと最後から5つめまでとなります。

objectsの後にあるメソッドは、Djangoが予め用意してくれている関数で、モデル内のobjectsにどういう処理をするか指定します。

よく使うメソッドの例として次の3つがあります。

  • モデル名.objects.all()
    モデルのオブジェクト(データ)全てを取得
  • モデル名.objects.get(条件)
    モデルから条件に一致したものを1つだけ取得(該当するデータが無いとエラー)
  • モデル名.objects.filter(条件)
    モデルから条件に一致したデータをリストで取得
INFO
get()でデータが取得出来なかった場合「500」エラーが出るのですが、これは正しくないので「404」のエラーが出るよう処理する必要があります。しかし、この毎回この処理を書くのは面倒なので、Djangoがget_object_or_404()というメソッドを用意してくれています。 get_object_or_404(Question, id=id)のように書きます。idに該当するものがないと、404エラーを自動で出してくれます。エラーの種類についてはここでは解説しません。

context

context(コンテクスト)の変数名は何でも良いのですが、Djangoでは慣習としてcontextが使われています。

contextには辞書を代入しています。この「辞書」というのが重要なので覚えておいてください。

pythonには、リスト、タプル、辞書が存在します。

  • リスト
    [ ]角括弧で囲ってあるものをリストと言います。
  • タプル
    ( )丸括弧で囲ってあるものをタプルと言います。中身が1つの場合は値の後に「,」を付けます。
  • 辞書
    { }波括弧で囲ってあるものを辞書と言います。

リストは中身の変更・追加ができるが、タプルにはそれができないという違いがあります。

# リスト
>>> list = ["a", "b", "c", "d", "e"]
# 1番目を取得
>>> list[1]
b

# タプル
>>> tuple = ("a",) 
>>> tuple = ("a", "b", "c", "d", "e")
# 1番目を取得
>>> tuple[1]
b

# 辞書
# a=1、b=2, c=3として扱われる
>>> dict = {"a":1, "b":2, "c":3}
# aのデータを取得
>>> dict["a"]
1 
WARN
[1]で1番目を取得しているにも関わらず「b」が返ってきています。これは最初の値が「0番目」として扱われるからです。部分的に中身を取得する際は、違う値を取得してしまわないように注意してください。

辞書であるcontextの場合、”latest_question_list”を指定してあげると最新の5つのオブジェクト(データ)が入ったリストを取得できることになります。

このcontextの中身はhtmlで変数として扱えます。(後述します)

render(request, ‘アプリ名/~~.html’, context)

renderはレンダリングするという意味なのですが、簡単に言うと引数で指定したhtmlとcontextを元に画面を表示してくれます。

関数(def)でページで表示する場合は、return render(request, ‘~~.html’, context)の形がお決まりなので覚えておきましょう。


index.htmlを作る

上で書いたように、renderにはhtmlファイルが必要になるので作成していきます。

pollsアプリの中に「templates」ディレクトリを作成し、さらにその中に「polls」ディレクトリを作成してください。templatesには「s」がついているので忘れないように。

必ずtemplatesディレクトリの中にアプリ名のディレクトリを作るようにして下さい。これをしないとDjangoが正しいhtmlファイルを見つけることが出来ずエラーになります。

templates/pollsディレクトリの中に「index.html」を作成し、次の内容を書いて保存して下さい。

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>投票はありません。</p>
{% endif %}

重要な部分だけ取り出し解説します。

if文

条件を指定して処理を分ける方法です。よく使うので覚えておきましょう

通常、HTMLでif文は使えないのですが、Djangoが{% if 変数 %}で特別に使えるようにしてくれています。

上記の場合以下のような表示になります。

{% if latest_question_list %}
    もし、latest_question_listのデータが「あれば」、このブロックを表示します。
{% else %}
    それ以外は(無い場合も)このブロックを表示します。
{% endif %}

for文

中身を順番に取り出す時に使います。よく使うので覚えておきましょう

for文も通常HTMLでは使えないのですが、Djangoが{% for 変数 in リスト %}で特別に使えるようにしてくれています。

# for文の例
# 名前が入ったリストを用意します
>>> name_list = ["taro", "hana", "yamada", "sasaki"]

# for文でリストから一つずつ取り出します。
# nameの部分は好きな変数でOKです。
>>> for name in name_list:
        print(name)

# name_listの中身が順番に出力されます。
taro
hana
yamada
sasaki

今回の場合、latest_question_listの中身を一つずつquestionに代入して出力しています。

{% for question in latest_question_list %}
    <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}

さらにその中に{{ question.id }}と{{ question.question_text }}がありますね!

これはfor文でオブジェクトを取り出すだびに、そのquestion(オブジェクト)のidとquestion_textを取得しています。

  • {{ question.id }}
    html側で、questionのidを取得できる
  • {{ question.question_text }}
    html側で、questionのquestion_textを取得できる

このように、1つのオブジェクトから特定のフィールドを取り出す場合は、{{ 変数.モデルのフィールド名 }}で取得できます。{% %}と{{ 変数 }}の形はhtml内で非常によく使うので覚えておきましょう!

index.htmlができたので、さっそく「http://127.0.0.1:8000/polls」にアクセスしてみましょう!

まだQuestionには1つのオブジェクト(データ)しか入ってないので、「what’s up?」だけが表示されたと思います。

WARN
「TemplateDoesNotExist at /polls/」のようなエラーがでた場合は、index.htmlが見つからないのが原因です。綴りが間違っていないか、ディレクトリやファイルの場所が間違っていないか、ちゃんと保存したか確認しましょう!

urlを修正する

上記の<a href=”url”>のurlは、ハードコードされていてDjnagoではナンセンスな書き方とされています。

以下の部分を修正しましょう。

# /polls/{{ question.id }}/の部分がハードコードされているのでNG
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

# このように修正
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

urlを呼び出す場合は、{% url ‘アプリ名:pathのname‘ 変数 %}という形を使います。

こうすることで、後でurls.pyのpathを変えたい時など、html側を修正しなくても良くなります。リンクが一つだったら良いですが、様々なページで沢山使われていたら全てのURLを修正しなくてはいけないので大変ですね。

アプリ名とpathのnameはこの部分を指しています。

  • index関数を呼び出すURL
    {% url ‘polls:index’ %}
  • detail関数を呼び出すURL
    {% url ‘polls:detail’ 変数 %}
  • results関数を呼び出すURL
    {% url ‘polls:results’ 変数 %}
  • vote関数を呼び出すURL
    {% url ‘polls:vote’ 変数 %}

detailページを作る

views.pyのdetail関数を修正します。

from django.shortcuts import render, get_object_or_404 # 追加

# detailを修正
def detail(request, id):
    question = get_object_or_404(Question, id=id)
    context = {
        'question': question
    }
    return render(request, 'polls/detail.html', context)

get_object_or_404()というのが出てきましたが、これはQuestion.objects.get(id=question_id)と同じ結果が得られます。オブジェクトが無かった場合のエラーの結果が違うだけです。

慣れないうちは、モデル名.objects.get()を使って、慣れてきたらget_object_or_404()にしていくと良いかもしれません。

detail関数は出来たので、templates/pollsにdetail.htmlを作り以下の内容を保存します。

<h1>{{ question.question_text }}</h1>
<ul>
    {% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
    {% endfor %}
</ul>

{{ question.question_text }}で質問が、for文で質問の選択肢{{ choice.choice_text }}を一つずつ取り出して表示しているコードになります。

for文のquestion.choice_set.allは、この質問に紐付いている選択肢のリストが入っています。こういった紐付けをリレーションシップと言います。choiceモデルのquestionには、ForeignKey(フォーリン・キー)を設定したので紐付けできる状態になっています。

リレーションシップは非常に重要な項目なのですが、少し混乱すると思うので詳しくは別の記事で解説したいと思います。取り敢えず今は名称だけでも覚えておいてください。

  • ForeignKey(フォーリン・キー)
    外部キーとも言います。1対多の関係の時に使います。今回の場合、質問1つに対して、選択肢が多数の関係です。
  • ManyToMany(メニー・トゥー・メニー)
    多対多の関係の時に使います。例えば、ブログのカテゴリーやタグのフィールドを作る場合です。1つの記事は複数のカテゴリー(多数)を持つことができ、カテゴリーの場合も、1つのカテゴリーが複数の記事(多数)を持つことができる関係です。

まとめ

# 基本の形
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {
        'latest_question_list': latest_question_list
    }
    return render(request, 'polls/index.html', context)

# オブジェクトの操作
Question.objects.all() # 全てを取得
Question.objects.get(条件) # 1つしかないオブジェクトを取得
Question.objects.filter(条件) # 複数のオブジェクトをリストで取得

# if文
if ◯◯: # もし◯◯があれば
    ここを実行
else: #それ以外は
    ここを実行

# for文
# リストの中身を一つずつaに代入して出力
for a in list:
    a
<!-- URLの取得 -->
{% url 'app_name:path name' 変数 %}

<!-- htmlでのif文  -->
{% if ◯◯ %} <!-- ◯◯があれば -->
    ここを表示
{% else %} <!-- それ以外の場合 -->
    ここを表示
{% endif %}

<!-- htmlでのfor文  -->
{% for a in list %}
    {{ a }}
{% endfor %}

<!-- 変数の表示 -->
{{ a.id }} <!-- aのidを取得 -->
{{ a.フィールド名 }} <!-- aのフィールドの値を取得できる -->

覚える事が多く感じたかもしれませんが、頻繁に使うのですぐ身につくと思います。

チュートリアル – その4