クウネルマナブ

個人的なメモです(笑) 最近は、もっぱら趣味。WEBシステムを作るべくpython,HTML,PHPあたりの勉強を中心にしています!

Django公式 チュートリアル その3

Djangoをマスターしたいので、

チュートリアルにて一連の作業を学びます。

 

関連記事

 

(個人的な学習ノートです)

 

 

 

前回Djangoのインストールから仮想環境の設定を行いました。

apist.hateblo.jp

 

Django公式 チュートリアル その1の記事です↓ 

apist.hateblo.jp

 

Django公式 チュートリアル その2の記事です↓  

apist.hateblo.jp

 

 

 

そのままチュートリアルに進みます!

 

「はじめてのDjangoアプリ作成 その3」

 

学習環境

macOS Sierra 10.12

python3.6

Django1.11

XAMPP

  MySQLMariaDB10.1.21)

テキストエディッタ:ATOM

 

参考サイト

 

はじめての Django アプリ作成、その3 | Django documentation | Django

 

上記サイトに沿って進めます。

 

 恋も熱湯もヤケドに注意だ!

チュートリアル3まできましたが・・・。

学習用のノート何で、あとで見直しが絶対に必要に感じます。

当たり前ちゃー当たり前ですけどw

 

オーバービュー

 ビューとは、 Django のアプリケーションにおいて特定の機能を提供するウェブペー ジの「型 (type)」のことで、各々のテンプレートを持っている。

例えばブログアプリケーションなら、以下のようなビューがある。

  • Blog ホームページ - 最新エントリーをいくつか表示

  • エントリー詳細ページ - 1エントリーへのパーマリンク (permalink) ページ

  • 年ごとのアーカイブページ - 指定された年のエントリーの月を全て表示

  • 月ごとのアーカイブページ - 指定された月のエントリーの日をすべて表示

  • 日ごとのアーカイブページ - 指定された日の全てのエントリーを表示

  • コメント投稿 - エントリーに対するコメントの投稿を受付

 

 投票アプリケーションでは、以下4つのビューを作成。

  • 質問 “インデックス” ページ – 最新の質問をいくつか表示

  • 質問 “詳細” ページ – 結果を表示せず、質問テキストと投票フォームを表示

  • 質問 “結果” ページ – 特定の質問の結果を表示

  • 投票ページ – 特定の質問の選択を投票として受付

 

Django へは各ページとコンテンツはビューによって提供される。

各ビューは単純に Python 関数 (クラスベースビューの場合はメソッド) として実装され、 Django はビューをリクエストされたURLから決定する(正確には、URLのドメイン移行の部分)。

 

インターネットサーフィンをしてるときなどに、 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” のような美しいURLを見かけることがあるでしょう。

Django はこんなものよりもっとエレガントな “URLパターン” を提供している(?)。

 

URLパターンはURLをシンプルに一般化したものです。

例えば “/newsarchive/<year>/<month>/” などです。

URLからビューには、 Django は ‘URLconfs’ を使います。

URLconf は (正規表現で表される) URLパターンとビューを紐付けます。

 

 

ビューの作成

下記をpolls/views.py に追加

 (polls/views.py) 単純な文字列を返すだけのビュー

def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)

def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)

 

url()コールを追加して、新しいviewを polls.urls と結びつける。

ルーティング(URLconf)の設定

(polls/urls.py) 下記の内容に書き換え

from django.conf.urls import url

from . import views

urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

 

ブラウザで、http://127.0.0.1:8000/polls/34/を見ると、

You're looking at question 34.』と表示される。

detail() メソッドが実行され、URLで提供したIDが表示される。

 

http://127.0.0.1:8000/polls/34/results/

→『You're looking at the results of question 34.

 

http://127.0.0.1:8000/polls/34/vote/

→『You're voting on question 34.

 

結果と投票ページのプレースホルダがそれぞれ表示される。

 

<仕組み>

Webサイトの “/polls/34/” をリクエストすると、 Django は  ROOT_URLCONF に指定されている、 Python モジュール mysite.urls をロードし、そのモジュール内の urlpatterns という変数を探し、その中に入っている正規表現を順に確認。

'^polls/' に合致した箇所を見つけた後、一致した文字列 ("polls/") を除き、残りの文字列である "34/" を次の処理のために ‘polls.urls’ の URLconf に渡します。

 r'^(?P<question_id>[0-9]+)/$' に合致し、結果として下記のように detail() が呼ばれる。

 

detail(request=<HttpRequest object>, question_id='34')

 

question_id='34' の部分は、 (?P<question_id>[0-9]+) から来ています。

パターンの前後に括弧を使用すると、そのパターンにマッチしたテキストを “キャプチャ” し、ビュー関数の引数として、それを送信します。

?P<question_id> はマッチしたパターンを識別するために使用する名前を定義します。

[0-9]+ は一桁以上の数字(すなわち、数)にマッチする正規表現です。

URL パターンは正規表現であるため、正規表現で実現できる限り制限はありません。

また、 .html のような文字列を URL に追加する必要もありません。

 

 

実際に動作するビューを書いてみっぺ

各ビューには二つの役割:

  • 一つはリクエストされたページのコ ンテンツを含む HttpResponse オブジェクトを返す

  • Http404 のような例外の送出

それ以外の処理はユーザ次第。

 

ビューはデータベースからレコードを読みだしても、読み出さなくてOK。

Django のテンプレートシステム、あるいはサードパーティPython テンプ レートシステムを使っても可、使わなくてもOK。

PDF ファイルを生成しても、 XML を出力しても、 ZIP ファイルをその場で生成してもOK。

Python ライブラリを使ってやりたいことを何でも実現可能。

 

Django にとって必要なのは HttpResponse か、あるいは例外となる。

 

簡単のため、 チュートリアルその 2 で解説した Django のデータベース API を使ってみましょう。

 

index() ビューを、システム上にある最新の 5 件の質問項目をカンマで区切り、日付順に表示させてみます:

 

(polls/views.py) 下記に全て書き換え

from django.shortcuts import render
from django.http import HttpResponse
from .models import Question

def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)

def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)

def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)

 

このコードには問題があります。

(問題のあるコード載せるなよw)

 

ビューの中で、ページのデザインがハードコードされています。

ページの見栄えを変更するたびに、 Python コードを編集する必要があります。

Django のテンプレートシステムを使って、ビューから使用できるテンプレートを作成し、Python からデザインを分離しましょう。

 

最初に、 polls ディレクトリの中に、 templates ディレクトリを作成します。

polls

 ├── __init__.py

 ├── __pycache__

 ├── admin.py

 ├── apps.py

 ├── migrations

 ├── models.py

 ├── templates ←作成

 ├── tests.py

 ├── urls.py

 └── views.py

 Django はそこからテンプレートを探します。

Django がどのようにテンプレートをロードしレンダリングするかについて、プロジェクトの TEMPLATES の設定を説明します。

デフォルトの DjangoTemplates の設定ファイルは、 APPS_DIRS のオプションが True に設定されています。

設定により、 DjangoTemplates は INSTALLD_APPS のそれぞれのサブディレクトリの “templates” を検索します。

 

先ほど作成した templates ディレクトリ内では、 polls と呼ばれる別のディレクトリを作成し、その中に index.html というファイルを作成します。

 

つまり、テンプレートは polls/templates/polls/index.html にする必要があります。

 

そのため、 app_directories テンプレートローダは前述したように、Django 内でこのテンプレートを polls/index.html として参照することができます。

 

テンプレートの名前空間

作ったテンプレートを polls という別のサブディレクトリを作らずに、直接 polls/templates の中に置いてもいいのではないか、と思うかもしれませんね。しかし、それは実際には悪い考えです。Django は、名前がマッチした最初のテンプレートを使用するので、もし 異なる アプリケーションの中に同じ名前のテンプレートがあった場合、Django はそれらを区別することができません。そのため、Django に正しいテンプレートを教えてあげる必要がありますが、一番簡単な方法は、それらに 名前空間を与える ことです。アプリケーションと同じ名前をつけた もう一つの ディレクトリの中にテンプレートを置いたのは、そういうわけなのです。 

 

テンプレートには次のコードを書きます

( polls/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>No polls are available.</p>
{% endif %}

 

テンプレートを使用するために polls/views.pyindex ビューを更新

( polls/views.py )
from django.http import HttpResponse
from django.template import loader

from .models import Question

def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))

def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)


def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)


def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)

 

このコードは、 polls/index.html というテンプレートをロードし、コンテキストを渡す。

コンテキストは、テンプレート変数名を Python オブジェクトへのマッピングしている辞書。

ブラウザで “/polls/” を開くと、チュートリアルその2 で作った、 “What’s up” という質問の入ったブレットリストを表示。リンクは質問の詳細ページ。

 

ショートカット: render()

テンプレートをロードしてコンテキストに値を入れ、テンプレートをレンダリングした結果を HttpResponse オブジェクトで返す、というイディオムは非常によく使われる。 Django はこのためのショートカットを提供。

これを使って index() ビューを書き換え:

( polls/views.py )
from django.shortcuts import render

from .models import Question


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)
 

この作業によって、 loder や HttpResponse ( detail 、 results や vote のスタブメソッドがある場合は HttpResponse のままにします) を import する必要はなくなる。

 

render() 関数

  • 第1引数として request オブジェクト
  • 第2引数としてテンプレート名
  • 第3引数としてその他のオプション辞書

 

この関数はテンプレートを指定のコンテキストでレンダリングし、 HttpResponse オブジェクトを返す。

 

 404エラーの送出

指定された投票の質問文を表示するページの詳細ビュー

ビューは次のようになります:

( polls/views.py )

from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

 

このビューはリクエストした ID を持つ質問が存在しないときに Http404 を送出

 ぱぱっと上記を表示したいときは下記のようになる。

 

( polls/templates/polls/detail.html )

 {{ question }}

 

 

ショートカット: get_object_or_404

get() を実行し、オブジェクトが存在しない場合には Http404 を送出することは非常によく使われるイディオム。

Django はこのためのショートカットを提供。

ショートカットを使って、 detail() ビューを書き換えて見ると:

 

( polls/views.py )

from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

 

The get_object_or_404() function takes a Django model as its first argument and an arbitrary number of keyword arguments, which it passes to the get() function of the model’s manager. It raises Http404 if the object doesn’t exist.

 

わからないので・・・和訳↓

get_object_or_404()関数は、最初の引数としてDjangoモデルを取り、任意の数のキーワード引数を取り、モデルのマネージャのget()関数に渡します。オブジェクトが存在しない場合、Http404が生成されます。

(15ヘェ〜)

 

get_list_or_404 という関数もあり、この関数は get_object_or_404() と同じように動きますが、get()ではなく、 filter() を使用。

リストが空の場合は Http404 を送出。

 

 

テンプレートシステムを使う

投票アプリの detail() ビューに戻り、コンテキスト変数 question とすると、 polls/detail.htmlテンプレートは次のようなる:

 

( polls/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 }} を例にすると、はじめに Django は `` question`` オブジェクトを辞書検索を行いこれには失敗する。

今度は属性として検索を行い、この場合は成功。

仮に、属性の検索に失敗すると、リストインデックスでの検索の実施。

メソッドの呼び出しは {%_for_%} ループの中で行われています。 question.choice_set.all は、 Python コードの question.choice_set.all() と解釈。

その結果、Choice オブジェクトからなるイテレーション可能オブジェ クトを返し、 {%_for_%} タグで使えるようになる。

 

 

テンプレート内のハードコードされたURLを削除

polls/index.html テンプレートで質問へのリンクを書いたとき、リンクの一部は次のようにハードコードされていたため変更。

 

( polls/index.html )

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

 

の部分は、 polls.urls モジュール内の url() 関数の name 引数を定義したため、テンプレートタグの {% url %} を用いることで、 URL 設定で定義された特定の URL パスへの依存をなくすことが可能なため、次のように書き換えます。

 

( polls/index.html )

【変更箇所】

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

   ↓

【変更後】

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

 

( polls/index.html ) 変更後全て

{% if latest_question_list %}
  <ul>
  {% for question in latest_question_list %}
    <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
  {% endfor %}
</ul>
{% else %}
  <p>No polls are available.</p>
{% endif %}

 

投票の詳細ビューの URL を何か他のものに変更したい場合

polls/specifics/12/ のようにしたいとき、対象となる(複数の)テンプレートを変更する代わりに、 polls/urls.py を変更します:

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

 

 

URL 名の名前空間

チュートリアルプロジェクトは「polls」というただ一つのアプリとなる。

本番でのプロジェクトでは、多くのアプリになるでしょう。

Djangoはどうやってこれらの間のURL 名を区別するのか?

 

例えば、「polls」アプリは詳細ビューを含み、同じプロジェクトにブログのためのアプリがあるかもしれない。

どうすれば、Djangoは {% url %} テンプレートタグを使ってurlに対してどのアプリのビューを作成すればいいと知ることができるのか?

URLconfに名前空間を追加すること、が答え。

なので、polls/urls.py ファイル内の、アプリケーションの名前空間を設定するため app_name の箇所に向かい追加。

 

(  polls/urls.py )

from django.conf.urls import url

from . import views

# アプリケーションの名前空間を設定 app_name = 'polls'
urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'), url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]

 

polls/index.html テンプレートを変更 

 ( polls/templates/polls/index.html )

【変更前】
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>



【変更後】
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> 

 

 ( polls/templates/polls/index.html ) 変更後全て

{% if latest_question_list %}
  <ul>
  {% for question in latest_question_list %}
    <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
  {% endfor %}
  </ul>
{% else %}
  <p>No polls are available.</p>
{% endif %}

 

 

そろそろしんどい(笑)

 

次回は、「初めてのDjangoアプリ作成、その4」に 取り掛かかります。

 

関連記事