Skip to content

URL 라우팅과 View 기초

장고에서는 각 HTTP 요청을 처리하는 함수/클래스를 View라고 부릅니다. 각 View가 호출되기 위해서는 호출될 URL 패턴을 지정해줘야 하는 데요. 이를 위해 URLconf라는 별도의 설정 파일을 사용합니다.

1. Django URL 라우팅의 기본 개념

FastAPI vs Django 라우팅

FastAPI:

@app.get("/posts/{post_id}")
async def post_detail(post_id: int):
    return {"post_id": post_id}

/posts/123 주소와 /posts/hello-slug 주소가 모두 매칭되지만, post_id: int 타입으로 인해 /posts/hello-slug 요청에서는 상태코드 422 오류가 발생합니다.

Django:

blog/urls.py
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 호출 시에 인자로 전달합니다.
앱/urls.py
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 패턴을 지정한다면 아래와 같이 구현하실 수 있습니다.

앱/urls.py
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의 강력한 템플릿 시스템을 알아보겠습니다!

Comments