본문으로 건너뛰기
김신건의 로그

[Django] URL 디스패치: path, re_path, include

· 수정 · 📖 약 1분 · 454자/단어 #django #urls #routing
django urls, URL routing, path converter, reverse, url namespace

정의

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>
slugASCII 영문/숫자/-/_<slug:slug>
uuidUUID<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_namenamespace가 같을 때 단순. 다중 인스턴스 라우팅에선 다를 수 있음.

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.

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기