URL 라우팅과 View 기초
장고에서는 각 HTTP 요청을 처리하는 함수/클래스를 View라고 부릅니다. 각 View가 호출되기 위해서는 호출될 URL 패턴을 지정해줘야 하는 데요. 이를 위해 URLconf라는 별도의 설정 파일을 사용합니다.
1. Django URL 라우팅의 기본 개념
FastAPI vs Django 라우팅
FastAPI:
/posts/123 주소와 /posts/hello-slug 주소가 모두 매칭되지만, post_id: int 타입으로 인해 /posts/hello-slug 요청에서는 상태코드 422 오류가 발생합니다.
Django:
from django.urls import path
from . import views
urlpatterns = [
# URL Pattern에 정규표현식으로 매칭할 패턴을 명시
path('posts/<int:post_id>/', views.post_detail, name='post_detail'),
]
# views.py
def post_(request, post_id):
return HttpResponse(f"Post ID: {post_id}")
2. URLconf 구조
프로젝트 레벨 URLs (mysite/urls.py)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')), # blog 앱의 URL 포함
path('', include('main.urls')), # 메인 페이지
]
앱 레벨 URLs (blog/urls.py)
from django.urls import path
from . import views
app_name = 'blog' # 네임스페이스 설정
urlpatterns = [
path('', views.post_list, name='post_list'),
path('<int:pk>/', views.post_detail, name='post_detail'),
path('new/', views.post_create, name='post_create'),
path('<int:pk>/edit/', views.post_edit, name='post_edit'),
path('<int:pk>/delete/', views.post_delete, name='post_delete'),
]
3. URL 패턴 문법
장고의 URLconf에서는 정규표현식을 통한 패턴 매칭으로 호출할 View를 찾습니다.
Path Converters
자주 사용되는 정규표현식 패턴에 대해서는 Path Converters를 통해 alias를 통해 간결하게 패턴을 지정하실 수 있습니다.
<int:pk>:int타입으로서\d+패턴에 매칭하며, 매칭 시에pk이름으로 View 호출 시에 인자로 전달합니다.
from django.urls import path
path('posts/<int:pk>/', views.post_detail), # 정수
path('posts/<str:slug>/', views.post_by_slug), # 문자열
path('posts/<slug:slug>/', views.post_by_slug), # 슬러그 (문자, 숫자, -, _)
path('posts/<path:path>/', views.post_by_path), # 경로 (/ 포함)
path('posts/<uuid:uuid>/', views.post_by_uuid), # UUID
이를 직접 정규표현식으로 URL 패턴을 지정한다면 아래와 같이 구현하실 수 있습니다.
from django.urls import re_path
re_path(r'^posts/(?P<pk>[0-9]+)/$', views.post_detail),
re_path(r'^posts/(?P<slug>[^/]+)/$', views.post_by_slug),
re_path(r'^posts/(?P<slug>[-a-zA-Z0-9_]+)/$', views.post_by_slug),
re_path(r'^posts/(?P<path>.+)/$', views.post_by_path),
re_path(r'^posts/(?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/$', views.post_by_uuid),
정규표현식 패턴 (고급)
기본 Path Converters를 통해 지원되지 않는 패턴에 대해 커스텀 Path Converter를 구현하실 수 있고, 혹은 직접 정규표현식으로 패턴을 지정하실 수도 있습니다.
from django.urls import re_path
urlpatterns = [
re_path(r'^posts/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^posts/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
]
4. View 함수 작성
기본 View 함수
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
def index(request):
"""단순 텍스트 응답"""
return HttpResponse("Hello Django!")
def json_response(request):
"""JSON 응답 (FastAPI와 유사)"""
data = {'message': 'Hello', 'status': 'success'}
return JsonResponse(data)
def template_view(request):
"""템플릿 렌더링"""
context = {'title': '블로그', 'posts': Post.objects.all()}
return render(request, 'blog/index.html', context)
Request 객체 활용
def detailed_view(request):
"""request 객체의 다양한 속성"""
# HTTP 메서드
if request.method == 'GET':
# GET 파라미터
search = request.GET.get('search', '')
page = request.GET.get('page', 1)
elif request.method == 'POST':
# POST 데이터
title = request.POST.get('title')
content = request.POST.get('content')
# 요청 정보
user_agent = request.META.get('HTTP_USER_AGENT', '')
ip_address = request.META.get('REMOTE_ADDR')
# 사용자 정보
user = request.user # 인증된 사용자
return HttpResponse(f"Method: {request.method}")
5. URL 네임스페이스와 reverse
URL 이름 지정
# urls.py
urlpatterns = [
path('posts/', views.post_list, name='post_list'),
path('posts/<int:pk>/', views.post_detail, name='post_detail'),
]
# views.py
from django.urls import reverse
from django.shortcuts import redirect
def create_post(request):
# 포스트 생성 후...
return redirect('post_detail', pk=1)
# 또는
return redirect(reverse('post_detail', kwargs={'pk': 1}))
템플릿에서 URL 사용
<!-- FastAPI는 프론트엔드에서 직접 URL 작성 -->
<a href="/posts/1">Post 1</a>
<!-- Django는 URL 이름 사용 -->
<a href="{% url 'post_detail' pk=1 %}">Post 1</a>
6. 실습: 블로그 URL과 View 구현
1단계: blog/urls.py 생성
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:pk>/', views.post_detail, name='post_detail'),
path('post/new/', views.post_create, name='post_create'),
path('api/posts/', views.api_post_list, name='api_post_list'),
]
2단계: blog/views.py 구현
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from .models import Post
def post_list(request):
"""포스트 목록 (템플릿 렌더링)"""
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, pk):
"""포스트 상세 (템플릿 렌더링)"""
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})
def post_create(request):
"""포스트 생성"""
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
post = Post.objects.create(
title=title,
content=content,
author=request.user
)
return redirect('blog:post_detail', pk=post.pk)
return render(request, 'blog/post_form.html')
def api_post_list(request):
"""API 스타일 응답 (FastAPI와 유사)"""
posts = Post.objects.values('id', 'title', 'created_at')
return JsonResponse(list(posts), safe=False)
3단계: 프로젝트 URLs에 연결
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
]
7. HTTP 메서드별 처리
단일 View에서 여러 메서드 처리
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'GET':
return render(request, 'blog/post_detail.html', {'post': post})
elif request.method == 'POST':
# 댓글 작성 등
comment = request.POST.get('comment')
# ... 처리 로직
return redirect('blog:post_detail', pk=pk)
elif request.method == 'DELETE':
post.delete()
return JsonResponse({'status': 'deleted'})
메서드별 분리 (권장)
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET"])
def post_list(request):
# GET만 허용
pass
@require_http_methods(["GET", "POST"])
def post_create(request):
# GET과 POST만 허용
pass
8. 에러 처리
404 에러
from django.http import Http404
def post_detail(request, pk):
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
raise Http404("Post does not exist")
# 또는 간단히
post = get_object_or_404(Post, pk=pk)
커스텀 에러 페이지
# views.py
def custom_404(request, exception):
return render(request, '404.html', status=404)
# urls.py
handler404 = 'myapp.views.custom_404'
9. FastAPI 스타일의 Django View
Django에서도 FastAPI처럼 JSON API를 만들 수 있습니다:
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def api_posts(request):
if request.method == 'GET':
posts = list(Post.objects.values('id', 'title', 'content'))
return JsonResponse({'posts': posts})
elif request.method == 'POST':
data = json.loads(request.body)
post = Post.objects.create(
title=data['title'],
content=data['content'],
author=request.user
)
return JsonResponse({
'id': post.id,
'title': post.title,
'content': post.content
}, status=201)
10. URL 패턴 팁
1. URL 일관성
# 좋은 예
path('posts/', views.post_list),
path('posts/<int:pk>/', views.post_detail),
path('posts/<int:pk>/edit/', views.post_edit),
# 나쁜 예
path('posts/', views.post_list),
path('post/<int:pk>/', views.post_detail), # posts vs post
path('edit-post/<int:pk>/', views.post_edit), # 일관성 없음
2. RESTful URL 설계
urlpatterns = [
path('posts/', views.post_list), # GET: 목록, POST: 생성
path('posts/<int:pk>/', views.post_detail), # GET: 조회, PUT: 수정, DELETE: 삭제
]
정리
Django의 URL 라우팅과 View: - URLconf: URL 패턴을 별도 파일로 관리 - Path Converters: 타입 안전한 URL 파라미터 - 네임스페이스: URL 이름으로 관리 - HttpRequest: 풍부한 요청 정보 제공 - 다양한 응답: HTML, JSON, 파일 등
FastAPI와 비교: - Django는 URL과 View를 분리 - 템플릿 렌더링이 기본 - URL 이름을 통한 리버스 라우팅 - 더 많은 내장 기능과 미들웨어 지원
다음 장에서는 Django의 강력한 템플릿 시스템을 알아보겠습니다!