Django基礎 #4 URL と Views (FBV)
#3 Models と ORM 基礎 で Post モデルとデータを揃えました。今回はそのデータを URL で公開 し、view 関数がどのようにレスポンスを作るかを見ます。関数ベースビュー (FBV) から — クラスベースビュー (CBV) は 中級 #1 で。
URLconf — URL と view のマッピング #
Django の URL ルーティングは urls.py が担当します。1 ファイルの urlpatterns リストがパターンを集めたものです。
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="post_list"),
path("<int:post_id>/", views.post_detail, name="post_detail"),
path("new/", views.post_new, name="post_new"),
path("<int:post_id>/edit/", views.post_edit, name="post_edit"),
]3 つの主要関数:
path(route, view, name=...)— URL パターン 1 行include("blog.urls")— 別モジュールの urlpatterns を差し込むapp_name = "blog"— 名前空間。別アプリと URL 名が衝突するのを防ぐ
URL パラメータ — <int:...>、<slug:...>
#
path() の第一引数に <タイプ:変数名> を書くと view 関数の引数として渡されます。
urlpatterns = [
path("<int:post_id>/", views.post_detail, name="post_detail"),
path("category/<slug:slug>/", views.by_category, name="by_category"),
path("year/<int:year>/month/<int:month>/", views.archive, name="archive"),
path("uuid/<uuid:token>/", views.by_token, name="by_token"),
]デフォルトで提供される converter:
| タイプ | マッチ |
|---|---|
str | スラッシュ以外のすべての文字列 (デフォルト) |
int | 正の整数 |
slug | [-a-zA-Z0-9_]+ (URL フレンドリーな文字列) |
uuid | UUID 形式 |
path | スラッシュを含むすべての文字列 |
これらのパラメータが view 関数の キーワード引数 として渡されます。
def post_detail(request, post_id):
...
def archive(request, year, month):
...名前が正確に一致する必要があります — <int:post_id> なら関数の引数も post_id。
関数ベースビュー (FBV) #
Django の view は リクエストを受け取りレスポンスを返す関数 です。もっとも単純な形:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, blog!")ルールは 2 つ — 第一引数が request、戻り値が HttpResponse (またはそのサブクラス)。
request オブジェクト
#
request (正確には HttpRequest インスタンス) が持っているもの:
def example(request):
request.method # "GET", "POST", "PUT", "DELETE", ...
request.GET # ?key=value のクエリパラメータ (QueryDict)
request.POST # POST 本文 (フォームデータ)
request.FILES # アップロードされたファイル
request.COOKIES # クッキー dict
request.session # セッション dict (#7)
request.user # ログイン済みユーザー (#7)
request.headers # リクエストヘッダ
request.path # "/blog/1/" のようなパス
...HttpResponse — もっとも基本のレスポンス #
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello!", content_type="text/plain")
def html(request):
return HttpResponse("<h1>こんにちは</h1>") # デフォルトは text/html
def with_status(request):
return HttpResponse("Forbidden", status=403)ステータスコード / ヘッダなどを直接扱うとき。
JsonResponse — JSON レスポンス #
from django.http import JsonResponse
def post_list_json(request):
posts = [
{"id": 1, "title": "最初の記事"},
{"id": 2, "title": "2番目の記事"},
]
return JsonResponse({"items": posts})dict を自動で JSON に変換してくれます。safe=False を渡せば list も直接レスポンス可能。ただし本格的な JSON API なら DRF が正解です。
render — テンプレート + コンテキストで HTML #
もっともよく使うレスポンスヘルパー。
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.filter(is_published=True).order_by("-created_at")
return render(request, "blog/post_list.html", {"posts": posts})3 つの引数:
request- テンプレートのパス (アプリの
templates/ディレクトリ基準) - コンテキスト dict (テンプレートで
{{ posts }}としてアクセス)
テンプレートファイルを作るのは #5 で詳しく。今回は view の形に集中します。
get_object_or_404 — なければ 404 #
Post.objects.get(pk=1) はオブジェクトがなければ Post.DoesNotExist 例外を投げます。これを毎回 try/except で囲まずに get_object_or_404 を使ってください。
from django.shortcuts import get_object_or_404, render
from .models import Post
def post_detail(request, post_id):
post = get_object_or_404(Post, pk=post_id, is_published=True)
return render(request, "blog/post_detail.html", {"post": post})オブジェクトがなければ自動で HTTP 404 レスポンスを作ってくれます。追加条件 (is_published=True のような) も一緒に渡せます。
QuerySet 用には同じく get_list_or_404 があります — 空のリストなら 404。
request.method 分岐 — GET / POST #
フォームのある view のよくあるパターン。
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from .models import Post
def post_new(request):
if request.method == "POST":
title = request.POST.get("title", "").strip()
content = request.POST.get("content", "").strip()
if not title:
return render(request, "blog/post_form.html", {"error": "タイトルを入力してください"})
post = Post.objects.create(title=title, content=content, author=request.user)
return HttpResponseRedirect(reverse("blog:post_detail", args=[post.id]))
# GET — 空のフォーム
return render(request, "blog/post_form.html")このパターンは毎回手で書くと面倒です — Django の Form がこれを抽象化します (#6)。
Named URL — name=... がある理由
#
URL をハードコードするのは悪い習慣です。/blog/1/ のようなパスが変わるとコード / テンプレート両方をいちいち直さなければなりません。名前 で参照してください。
from django.urls import reverse
reverse("blog:post_list") # "/blog/"
reverse("blog:post_detail", args=[42]) # "/blog/42/"
reverse("blog:post_detail", kwargs={"post_id": 42}) # "/blog/42/"<app_name>:<url_name> 形式。urls.py の app_name = "blog" + path(..., name="post_detail") が "blog:post_detail" を作ります。
テンプレートでは {% url %}
#
<a href="{% url 'blog:post_detail' post.id %}">{{ post.title }}</a>redirect — reverse の短縮版 #
from django.shortcuts import redirect
def post_new(request):
if request.method == "POST":
post = Post.objects.create(...)
return redirect("blog:post_detail", post_id=post.id)
return render(request, "blog/post_form.html")redirect は内部的に reverse + HttpResponseRedirect を一度にしてくれます。URL 名、パス文字列、モデルインスタンス (モデルに get_absolute_url を定義した場合) いずれも受け取ります。
get_absolute_url — モデル自身の URL
#
モデルによく追加されるメソッド。
from django.db import models
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=200)
# ...
def get_absolute_url(self) -> str:
return reverse("blog:post_detail", args=[self.pk])この 1 行があれば:
redirect(post)が自動で動作- Admin の「サイトで見る」リンクが生きる
- テンプレートで
<a href="{{ post.get_absolute_url }}">として使える
POST と CSRF #
Django はすべての POST リクエストに CSRF トークン を要求します (セキュリティのデフォルト値)。フォームテンプレートの中に {% csrf_token %} を入れる必要があります — 詳しくは #6。
API のように CSRF が必要ない場合はデコレータで除外:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook(request):
...csrf_exempt は 外部 webhook のように本当に必要な箇所だけに。ユーザーフォームには使ってはいけません。
HTTP メソッド制限 — require_http_methods
#
view が受け付けるメソッドを明示的に制限。
from django.views.decorators.http import require_GET, require_POST, require_http_methods
@require_GET
def post_list(request):
...
@require_POST
def post_delete(request, post_id):
...
@require_http_methods(["GET", "POST"])
def post_new(request):
...許可されていないメソッドであれば 405 Method Not Allowed を自動レスポンス。
小さな総合例 #
ブログ view 4 つを一カ所に:
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_http_methods
from .models import Post
def post_list(request):
qs = Post.objects.filter(is_published=True).order_by("-created_at")
q = request.GET.get("q")
if q:
qs = qs.filter(title__icontains=q)
return render(request, "blog/post_list.html", {"posts": qs, "q": q or ""})
def post_detail(request, post_id):
post = get_object_or_404(Post, pk=post_id, is_published=True)
return render(request, "blog/post_detail.html", {"post": post})
@login_required
@require_http_methods(["GET", "POST"])
def post_new(request):
if request.method == "POST":
title = request.POST.get("title", "").strip()
content = request.POST.get("content", "").strip()
if title and content:
post = Post.objects.create(
title=title,
content=content,
author=request.user,
)
return redirect("blog:post_detail", post_id=post.id)
return render(request, "blog/post_form.html")
@login_required
@require_POST
def post_delete(request, post_id):
post = get_object_or_404(Post, pk=post_id, author=request.user)
post.delete()
return redirect("blog:post_list")@login_required はログインしていないユーザーをログインページへリダイレクトします — #7 で。
FBV vs CBV — プレビュー #
Django は クラスベースビュー (CBV) も提供します。
from django.views.generic import ListView, DetailView
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html"
context_object_name = "posts"
class PostDetailView(DetailView):
model = PostCBV は 繰り返しパターンをクラスで縮めるツール ですが、最初は FBV のほうが直感的です。小さなプロジェクトなら FBV だけで十分です。CBV の深さは 中級 #1 で別途扱います。
まとめ #
今回つかんだもの:
- URLconf —
path()、include()、app_nameでモジュール化 - URL パラメータ —
<int:>、<slug:>、<uuid:>、<path:> - FBV —
def view(request, ...) -> HttpResponse request.method、request.GET、request.POST、request.userHttpResponse、JsonResponse、renderget_object_or_404— なければ自動で 404- Named URL —
app:name+reverse/{% url %}/redirect get_absolute_urlのコンベンション- CSRF はデフォルト、フォームに
{% csrf_token %}必須 require_GET / POST / http_methodsでメソッド制限- CBV は 中級 #1 で
次回(#5 Templates と静的ファイル)では view が返した render(...) が実際の HTML になるところ — テンプレート構文、継承、静的ファイル (CSS、JS、画像) まで扱います。