[Django] URL 디스패치: path, re_path, include
정의
Django는 URL 패턴 리스트로 요청을 view에 라우팅한다. 1.0~1.x는 url()(regex만), 2.0+ path()(간결한 converter 문법)와 re_path()(regex) 두 방식.
기본
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
path("api/", include("api.urls")),
path("", include("home.urls")),
]
# blog/urls.py
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="post-list"),
path("<slug:slug>/", views.post_detail, name="post-detail"),
path("create/", views.post_create, name="post-create"),
]
include()로 앱별 URL 분리. app_name으로 namespace.
Path Converter
<type:name> 형식. 빌트인 converter:
| Converter | 매치 | 예시 |
|---|---|---|
str | 빈 문자열 X, / 제외 모든 문자 | <str:name> |
int | 양의 정수 | <int:year> |
slug | ASCII 영문/숫자/-/_ | <slug:slug> |
uuid | UUID | <uuid:id> |
path | / 포함 모든 문자 | <path:filepath> |
path("posts/<int:year>/<int:month>/<slug:slug>/", views.post_detail),
path("docs/<path:filepath>/", views.docs), # 'a/b/c/d' 같은 경로
커스텀 Converter
# converters.py
class FourDigitYearConverter:
regex = r"\d{4}"
def to_python(self, value):
return int(value)
def to_url(self, value):
return f"{value:04d}"
# urls.py
from django.urls import path, register_converter
from . import converters
register_converter(converters.FourDigitYearConverter, "yyyy")
urlpatterns = [
path("archive/<yyyy:year>/", views.archive),
]
re_path (정규식)
복잡한 패턴.
from django.urls import re_path
urlpatterns = [
re_path(r"^posts/(?P<slug>[a-z0-9-]+)/$", views.post_detail),
re_path(r"^archive/(?P<year>\d{4})/(?P<month>\d{1,2})/$", views.archive),
]
(?P<name>pattern)으로 named group. view에 키워드 인자로 전달.
path()가 가독성 좋으므로 가능하면 path. 정말 복잡한 패턴만 re_path.
reverse: URL 생성
하드코딩 대신 name으로 URL 생성.
from django.urls import reverse, reverse_lazy
reverse("blog:post-detail", kwargs={"slug": "hello"})
# '/blog/hello/'
reverse("blog:post-detail", args=["hello"])
# '/blog/hello/' (위치 인자)
# CBV의 success_url은 reverse_lazy
class MyView(CreateView):
success_url = reverse_lazy("post-list") # 클래스 정의 시점엔 URL conf 미준비
템플릿:
<a href="{% url 'blog:post-detail' post.slug %}">{{ post.title }}</a>
<a href="{% url 'blog:post-detail' slug=post.slug %}">link</a>
모델의 get_absolute_url:
class Post(models.Model):
slug = models.SlugField()
def get_absolute_url(self):
return reverse("blog:post-detail", kwargs={"slug": self.slug})
관리자 “사이트에서 보기”, redirect(post) 등에 자동 사용.
Namespace
URL 이름 충돌 방지.
# blog/urls.py
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="list"),
]
# news/urls.py
app_name = "news"
urlpatterns = [
path("", views.news_list, name="list"),
]
reverse("blog:list")
reverse("news:list")
include()에서 namespace 명시도 가능:
path("blog/", include("blog.urls", namespace="blog")),
app_name과 namespace가 같을 때 단순. 다중 인스턴스 라우팅에선 다를 수 있음.
URL 파라미터 vs 쿼리스트링
path("posts/<int:id>/", ...) # /posts/42/ (URL의 일부)
path("search/", ...) # /search/?q=django (쿼리)
URL 파라미터:
- view에 자동 전달
- SEO 친화적
쿼리스트링:
- view 인자 X,
request.GET["q"]로 접근 - 선택적 필터에 적합
슬래시 정책
Django 기본은 trailing slash. APPEND_SLASH = True(기본)면 슬래시 없는 URL을 자동 리다이렉트.
GET /posts → 301 /posts/
API는 trailing slash 없는 게 표준이라 명시적 결정 필요.
# 명시적으로 슬래시 없는 패턴
path("api/posts", views.api_posts)
다국어 (i18n)
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path("posts/", include("blog.urls")),
prefix_default_language=False,
)
# /posts/ (기본 언어)
# /ko/posts/ /en/posts/
함정
1. /admin과 /admin/
/admin/이 표준. trailing slash 일관성 유지.
2. include의 위치
길이가 긴 패턴이 먼저. 첫 매치에서 종료.
urlpatterns = [
path("posts/feed/", views.feed), # 정확 매치 먼저
path("posts/<slug:slug>/", views.detail),
]
순서 바꾸면 posts/feed/도 detail로.
3. handler404 등 커스텀
# myproject/urls.py
handler404 = "myapp.views.custom_404"
handler500 = "myapp.views.custom_500"
handler403 = "myapp.views.custom_403"
handler400 = "myapp.views.custom_400"
# views.py
def custom_404(request, exception):
return render(request, "404.html", status=404)
DEBUG=False 일 때만 작동.
4. URL 이름 변경 시 reverse 깨짐
기존 URL을 옮길 때 redirect 패턴:
from django.views.generic import RedirectView
urlpatterns = [
path("old-posts/<slug:slug>/", RedirectView.as_view(pattern_name="blog:post-detail", permanent=True)),
]
자주 보는 패턴
Health check
from django.http import JsonResponse
def health(request):
return JsonResponse({"status": "ok"})
urlpatterns = [
path("health/", health),
]
sitemap.xml
from django.contrib.sitemaps.views import sitemap
from .sitemaps import PostSitemap
sitemaps = {"posts": PostSitemap}
urlpatterns = [
path("sitemap.xml", sitemap, {"sitemaps": sitemaps}),
]
정적/미디어 (개발만)
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [...]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
프로덕션은 nginx 또는 CDN.
💬 댓글