DjangoのHTTPメソッド処理を理解しよう
"Django を使ってみよう" で作成した、ビューとモデル、テンプレートの関係と、DjangoがHTTPメソッドをどう処理するのかを理解するために、ユーザが入力した文字列を表示する、アプリケーション hello 作ってみましょう。 code: bash
$ python manage.py startapp hello
テンプレート
Djangoにはテンプレートエンジン Django Template があります。
アプリケーション hello のためのテンプレートを保存するディレクトリを作成します。
code: bash
$ mkdir hello/templates
テンプレートファイルが置かれるディレクトリは、プロジェクトの設定ファイルに依存します。 django-admin startproject でプロジェクトを作成したデフォルトのsettings.pyでは、TEMPLATES の定義は次のようになっています。
code: sayhello/settings.py の抜粋
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
APP_DIRS: True に設定されているのでアプリケーションディレクトリにある templatesにテンプレートファイルがあるものとして検索されます。
テンプレートファイルの検索パスを追加したいときは、DIRSに登録します。
例えばプロジェクト全体で共通したテンプレートを使いたいというようなときは、次のようにします。
code: python
BASE_DIR は settings.py で事前に定義されていて、プロジェクトのトップディレクトリになります。
補足説明1
BASE_DIR は settings.py で次のように定義されています。
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
つまり、settings.py のファイルがあるひとつ上のディレクトリで、
プロジェクトのトップディレクトリとなります。
テンプレートファイルを作成しましょう。
code: hello/templates/index.htnml
<html>
<head>
<title>Hello Django</title>
</head>
<body>
<p>Hello Django!</p>
</body>
</html>
テンプレートといっても、ファイルの配置場所とレンダリングする方法を理解するために、
今の段階ではHTMLファイルそのものです。
アプリケーション hello/views.py にビュー関数index()を追加します。
code: hello/views.py
from django.shortcuts import render
from django.http import HttpResponse
def myview(request):
return HttpResponse('Hello World')
def index(request):
return render(request, 'index.html')
アプリケーションのURLパターン hello/urls.py を新しく作成します。
code: hello/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('index/', views.index, name='index'),
]
次に、プロジェクトのURLパターン sayhello/urls.py を修正します。
code: sayhello/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', include('hello.urls')),
]
さぁ、サーバーを起動して確かめてみましょう。
https://gyazo.com/a8d13480c860df54610c0d9455e67d7e
うまくいきましたね。
せっかくのテンプレートエンジンなので変数をレンダリングしてみましょう。
code: hello/templates/index.html
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<p>Hello {{ name }}!</p>
</body>
</html>
ここで、使用した変数name や title はどこで定義するのがよいのでしょうか?
レンダリングさせているのはビュー関数ですね。
ここに定義してみましょう。
code: hello/views.py
from django.shortcuts import render
from django.http import HttpResponse
def myview(request):
return HttpResponse('Hello World')
def index(request):
values = {
'title': 'Hello Django',
'name': 'Python'
}
return render(request, 'index.html', values)
ここでは、ビュー関数index() に辞書型オブジェクトとして values を定義して、
render() に渡しています。
https://gyazo.com/cd8f2463604ed423195b1447c02353ad
Django Template は、Jinja2 とよく似た書式ですね。実は、Jinja2 はDjango Template に強く影響をうけて開発されています。
forや if も同じように記述することができます。
Django Template のif文
テンプレート index.html を次のようにif文を使って条件判断させてみます。
code: hello/templates/index.html
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{% if name != '' %}
<p>Hello {{ name }}!</p>
{% else %}
<p>Hello Jack!</P>
{% endif %}
</body>
</html>
いま時点では静的に変数を定義するだけにします。
code: hello/views.py
from django.shortcuts import render
from django.http import HttpResponse
def myview(request):
return HttpResponse('Hello World')
def index(request):
values = {
'title': 'Hello Django',
'name': ''
}
return render(request, 'index.html', values)
https://gyazo.com/8ad6ebaa0f0ad1661908575f4babb56e
Django Template の for文
Django Template も for文を使うことができます。
ただし、render() に渡せるのは辞書型オブジェクトなので注意してください。
code: hello/templates/artist_list.html
<html>
<body>
<h1>Artist Birthday(Artist: {{ artist_list|length }})</h1>
<table>
<tr><td> Name </td>
<td> Birthday </td>
<td> Born place </td>
</tr>
{% for artist in artist_list %}
<tr>
<td> {{ artist.firstname }} {{ artist.lastname }} </td>
<td> {{ artist.born_date }} </td>
<td> {{ artist.born_place }} </td>
</tr>
{% endfor %}
</table>
</body>
</html>
hello/views.py にビュー関数 artist_list() を追加します。
code: hello/views.py
from django.shortcuts import render
from django.http import HttpResponse
def myview(request):
return HttpResponse('Hello World')
def index(request):
values = {
'title': 'Hello Django',
'name': ''
}
return render(request, 'index.html', values)
def artist_list(request):
values={
'artist_list': [ {
'firstname': 'Freddie',
'lastname': 'Mercury',
'born_place': 'Farrokh Bulsara',
'born_date': '1946-9-5'
},
{
'firstname': 'David',
'lastname': 'Bowie',
'born_place': 'Brixton, London, England',
'born_date': '1947-1-8'
},
]
}
return render(request, 'artist_list.html', values)
hello/urls.py を修正します。
code: hello/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('index/', views.index, name='index'),
path('artist_list/', views.artist_list, name='artist_list'),
]
https://gyazo.com/46c8d44764119b6ad7700cc34aee35b2
テンプレートの継承もJinja2 と同じです。(Jinja2が真似ているわけなので当然ですが...)
POST処理をさせてみよう
ここまでの例は、GETメソッドでページを表示させているものでした。
次は、フォームにユーザが入力した文字列を受け取って表示させるようにしてみましょう。
URL/login をGETメソッドでアクセスされると、フォーム画面が表示される
フォームでユーザが入力した文字列をPOSTメソッドで送信
URL/login をPOSTメソッドでアクセスされると、ユーザが入力した文字列をGETパラメタにして /indexにリダイレクト
まず、HTMLのINPUTタグで入力フィールドを作ります。
code: hello/templates/base.html
<html>
<head>
{% if title %}
<title>{{ title }} - Django Example </title>
{% else %}
<title>Django Example</title>
{% endif %}
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
code: hello/templates/login.html
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{% csrf_token %}
<p> Enter Name:</p>
<p> <input type="text" name="name"></p>
<p> <input type="submit" name="submit"></p>
</form>
{% endblock %}
hello/view.py を index() を次のように修正します。
code: hello/view.py からの抜粋
from django.urls import reverse
app_name = 'hello'
# ...
def login(request):
if request.method == 'POST':
redirect_url = reverse('hello:index')
parameters = urlencode({'name': request.POST.get('name')})
url = f'{redirect_url}?{parameters}'
return redirect(url)
else:
return render(request, 'login.html')
def index(request):
values = {'name': request.GET.get('name') }
return render(request, 'index.html', values)
アプリケーションのURLパターン を定義
code: hello/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('index/', views.index, name='index'),
path('login/', views.login, name='login'),
path('artist_list/', views.artist_list, name='artist_list'),
]
https://gyazo.com/9d1765abf645fa0d718e5bf4a840fd02
{% csrf_token %} がフォーム内にないと、
Submitボタンをクリックしたときにエラーになってしまいます。
https://gyazo.com/5ed0c6fbaaff41b595ce1031b4053f22
CSRFとは
過去に訪れたWebサイトから、製品やサービスのプロモーションメールを受け取ることがよくあります。 こうした電子メールのコンテンツには、以前にログインしたWebサイトに移動するリンクが興味を引きそうな画像の後ろ隠されていることがあります。 こうしたハイパーリンクの中にはデータを盗もうとする悪意があるものもあります。これをCSRF(Cross-Site Request Forgery)攻撃と呼ばれます。
Django WebアプリケーションでのCSRF保護
Webアプリケーションでは、基本的に、Webフォームはユーザーから入力を受け取り、サーバー側コンポーネントに送信して処理します。 サーバー側のコンポーネントは通常、HTTP経由でデータを受け入れるためのPOST、PUT、DELETEメソッドとしてサービスを公開します。
このとき、固有のCSRFトークンを生成し、それをHTTPリクエストと一緒に送信するようにしておくべきです。
Djangoは、デフォルトでアプリケーションでCSRFトークンを構成する方法を提供していて、
テンプレートに{% csrf_token %} を記述するだけです。
フォームにこのCSRFトークンがない場合、Djangoは単にHTTPエラー403を返します。
ここまでの要点
フォームをPOSTメソッドで送信するときは {% csrf_token %}が必要
HTTPメソッドはrequest.methodを参照して知ることができる
reverse()に、アプリケーション名:urlパターン名を与えてURLを生成させる
reverse()を使うときは、app_name=でアプリケーション名を定義する
INPUTタグのデータはrequest.メソッド.get('フィールド名') で取得する
POSTメソッドのとき:request.POST.get('name')
GETメソッドのとき:request.GET.get('name')
リダイレクトさせるときは、redirect()にリダイレクトさせるURLを与える