장고 기초 #4 URL과 Views (FBV)
#3 Models와 ORM 기초에서 Post 모델과 데이터를 갖췄습니다. 이번 글은 그 데이터를 URL로 노출 하고 view 함수가 어떻게 응답을 만드는지 봅니다. **함수 기반 뷰 (FBV)**부터 다룹니다. 클래스 기반 뷰 (CBV)는 중급 #1에서 다루겠습니다.
URLconf — URL과 view의 매핑 #
Django의 URL 라우팅은 **urls.py**가 담당합니다. 한 file의 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"),
]세 가지 핵심 함수:
path(route, view, name=...)— URL 패턴 한 줄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!")규칙은 두 개 — 첫 인자가 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": "두 번째 글"},
]
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})세 인자:
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을 hardcode 하는 건 나쁜 습관입니다. /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])이 한 줄이 있으면:
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가 받을 HTTP 메소드를 명시적으로 제한합니다.
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, 이미지) 까지 다룹니다.