Django URLs 路由設計與配置完全指南 | Django 教學

2026/06/03 2026/05/24
Django URLs 路由設計與配置完全指南 | Django 教學

當使用者在瀏覽器輸入一個網址並按下 Enter,Django 如何知道該呼叫哪個 View 來處理這個請求?答案就是 URL 路由系統(URLconf)。URLconf 是 Django 請求處理流程的關鍵環節,它負責將傳入的 URL 對應到正確的 View 函數或類別。本篇文章將從 urlpatterns 基礎、path()re_path() 函數、路徑轉換器(Path Converters)include() 模組化路由、URL 命名reverse() 反向解析(Reverse Resolution)Namespace 命名空間RESTful 風格 的 URL 設計最佳實踐,帶你全面掌握 Django 路由系統。

URL 配置基礎(URLconf)

Django 的路由核心是一個名為 urlpatterns 的 Python 列表,定義在專案的 urls.py 檔案中。當 HTTP 請求進來時,Django 從 settings.pyROOT_URLCONF 設定所指定的模組載入 urlpatterns,然後由上而下依序嘗試匹配每一條路由規則。第一個匹配成功的規則會生效,後續規則不再比對。

# myproject/urls.py(ROOT_URLCONF 指向的模組)
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', views.hello_world),
]

這段程式碼做了兩件事:

  1. /admin/ 開頭的 URL 交給 Django 內建的管理後台處理
  2. /hello/ 這個 URL 對應到 views.hello_world 這個 View 函數

Django 如何匹配 URL?

Django 匹配 URL 時有幾個重要規則:

  • 不匹配域名和 GET 參數:對 https://example.com/articles/?page=2 這個請求,Django 只會拿 articles/ 來比對
  • 由上而下依序比對:匹配到第一個符合的規則就停止,因此路由的順序很重要
  • 開頭不含斜線urlpatterns 中的路由路徑不需要加上前導斜線,例如寫 articles/ 而非 /articles/

path() 函數詳解

path() 是 Django 路由系統最核心的函數,它接受四個參數:

path(route, view, kwargs=None, name=None)
參數說明是否必填
routeURL 匹配模式,例如 'articles/<int:pk>/'必填
view匹配成功後呼叫的 View callable必填
kwargs額外傳遞給 View 的關鍵字參數(dict)選填
nameURL 的命名,用於反向解析選填
from django.urls import path
from . import views

urlpatterns = [
    # 基本用法:route + view
    path('articles/', views.article_list),

    # 帶路徑參數:<int:pk> 會自動轉換為 int 型別
    path('articles/<int:pk>/', views.article_detail),

    # 帶額外參數:透過 kwargs 傳遞固定值給 View
    path('blog/', views.article_list, kwargs={'blog_mode': True}),

    # 帶命名:方便反向解析
    path('articles/<int:pk>/', views.article_detail, name='article-detail'),
]

路徑轉換器(Path Converters)

path() 函數的一大優勢是內建的路徑轉換器,能自動將 URL 字串轉換為對應的 Python 型別。Django 提供了五種內建轉換器:

轉換器匹配規則Python 型別範例
str/ 的任何非空字串(預設)str<str:name>
int零或正整數int<int:pk>
slugASCII 字母、數字、連字號、底線str<slug:slug>
uuidUUID 格式字串uuid.UUID<uuid:uid>
path包含 / 的任意非空字串str<path:file_path>
from django.urls import path
from . import views

urlpatterns = [
    # int:匹配整數,自動轉為 Python int
    path('users/<int:user_id>/', views.user_detail),

    # slug:匹配 slug 格式的字串(如 my-first-post)
    path('posts/<slug:post_slug>/', views.post_detail),

    # uuid:匹配 UUID 格式(如 075194d3-6885-417e-a8a8-6c931e272f00)
    path('items/<uuid:item_uuid>/', views.item_detail),

    # path:匹配含有斜線的完整路徑
    path('files/<path:file_path>/', views.serve_file),
]

自訂路徑轉換器

如果內建的轉換器無法滿足需求,你可以自訂。一個轉換器需要定義 regex 屬性、to_python() 方法和 to_url() 方法:

# converters.py
class FourDigitYearConverter:
    regex = '[0-9]{4}'  # 匹配規則(正規表達式)

    def to_python(self, value):
        return int(value)  # URL 字串 → Python 型別

    def to_url(self, value):
        return '%04d' % value  # Python 型別 → URL 字串
# urls.py
from django.urls import path, register_converter
from . import converters, views

# 註冊自訂轉換器,命名為 'yyyy'
register_converter(converters.FourDigitYearConverter, 'yyyy')

urlpatterns = [
    # 使用自訂轉換器
    path('articles/<yyyy:year>/', views.year_archive, name='year-archive'),
]

re_path() 正則表達式路由

對於 path() 無法處理的複雜匹配邏輯,Django 提供了 re_path(),它使用正規表達式(Regular Expression)來定義匹配規則:

from django.urls import re_path
from . import views

urlpatterns = [
    # 匹配兩位數的月份(01-12)
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/$',
            views.month_archive),

    # 匹配特定格式的產品編號(如 PRD-001)
    re_path(r'^products/(?P<product_code>[A-Z]{3}-[0-9]{3})/$',
            views.product_detail),
]

需要注意的是,re_path() 擷取到的參數一律為字串,不會自動做型別轉換,你需要在 View 中自行處理型別轉換。

原則:優先使用 path(),只在需要複雜匹配邏輯時才使用 re_path()

include() 模組化路由

隨著專案規模成長,把所有路由擠在一個 urls.py 會變得難以維護。include() 函數讓你將路由拆分到各個 App 的 urls.py 中,實現模組化管理:

# 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')),       # 將 /blog/ 開頭的 URL 交給 blog App 處理
    path('accounts/', include('accounts.urls')),  # 將 /accounts/ 開頭的 URL 交給 accounts App 處理
    path('api/', include('api.urls')),         # 將 /api/ 開頭的 URL 交給 api App 處理
]
# blog/urls.py(App 級別)
from django.urls import path
from . import views

app_name = 'blog'  # 設定 App 命名空間

urlpatterns = [
    path('', views.post_list, name='list'),              # /blog/
    path('<int:pk>/', views.post_detail, name='detail'),  # /blog/1/
    path('create/', views.post_create, name='create'),    # /blog/create/
]

使用 include() 時,Django 會將已匹配的 URL 前綴去掉,把剩餘的部分傳給被引入的 URLconf 繼續匹配。例如對 /blog/1/ 這個請求,Django 先在專案級別匹配到 blog/,然後將 1/ 傳給 blog/urls.py 去比對。

URL 命名(name 參數)

為 URL 設定名稱是 Django 路由設計的重要實踐。有了名稱,你就不需要在程式碼中硬編碼 URL 字串,當路由路徑改變時,只需修改 urls.py 中的定義即可:

# 定義命名 URL
path('articles/<int:pk>/', views.article_detail, name='article-detail'),

命名的好處在下面的反向解析段落中會更加明顯。

reverse() 與 reverse_lazy()

反向解析(Reverse Resolution) 是指透過 URL 的名稱來取得實際的 URL 路徑,而不是硬編碼。Django 提供了 reverse() 函數來實現這個功能:

from django.urls import reverse
from django.shortcuts import redirect

def my_view(request):
    # 基本用法
    url = reverse('article-detail', kwargs={'pk': 1})
    # 結果:'/articles/1/'

    # 使用位置參數
    url = reverse('article-detail', args=[1])
    # 結果:'/articles/1/'

    # 帶命名空間的反向解析
    url = reverse('blog:detail', kwargs={'pk': 1})
    # 結果:'/blog/1/'

    # 常見用途:重導向到另一個頁面
    return redirect(reverse('blog:list'))

reverse_lazy()reverse() 的延遲求值(Lazy Evaluation)版本,它不會在匯入時就執行,而是在實際存取值時才進行解析。這在類別屬性中特別有用,因為類別定義時 URLconf 可能尚未載入完成:

from django.urls import reverse_lazy
from django.views.generic import CreateView

class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'content']
    # 使用 reverse_lazy() 而非 reverse()
    # 因為類別屬性在模組載入時就會被求值
    success_url = reverse_lazy('blog:list')

模板中的 {% url %} 標籤

在 Django 模板(Template)中,使用 {% url %} 標籤來做反向解析,效果等同於 Python 中的 reverse()

<!-- 基本用法 -->
<a href="{% url 'blog:list' %}">文章列表</a>

<!-- 帶關鍵字參數 -->
<a href="{% url 'blog:detail' pk=article.pk %}">{{ article.title }}</a>

<!-- 帶位置參數 -->
<a href="{% url 'blog:detail' article.pk %}">{{ article.title }}</a>

<!-- 搭配表單 action -->
<form method="post" action="{% url 'blog:create' %}">
    {% csrf_token %}
    <!-- 表單欄位 -->
</form>

使用 {% url %} 標籤而非硬編碼 URL,是 Django 開發的基本功,能確保模板中的連結在路由變更後仍然正確。

Namespace 命名空間(app_name)

當專案包含多個 App 時,不同 App 中可能出現相同名稱的 URL(例如 blogshop 都有 detail)。命名空間(Namespace) 透過前綴來解決這個衝突問題。

Django 的命名空間分為兩種:

  • App Namespace(應用命名空間):在 App 的 urls.py 中用 app_name 變數宣告,是 App 的固定識別符
  • Instance Namespace(實例命名空間):在 include() 時透過 namespace 參數指定,用於同一個 App 被 include 多次的場景
# blog/urls.py
from django.urls import path
from . import views

app_name = 'blog'  # App Namespace

urlpatterns = [
    path('', views.post_list, name='list'),
    path('<int:pk>/', views.post_detail, name='detail'),
]
# shop/urls.py
from django.urls import path
from . import views

app_name = 'shop'  # App Namespace

urlpatterns = [
    path('', views.product_list, name='list'),
    path('<int:pk>/', views.product_detail, name='detail'),
]
# myproject/urls.py
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls')),   # 反向解析:blog:list、blog:detail
    path('shop/', include('shop.urls')),   # 反向解析:shop:list、shop:detail
]

現在即使兩個 App 都有名為 listdetail 的 URL,透過命名空間也能明確區分。

URL 設計最佳實踐(RESTful 風格)

良好的 URL 設計能讓 API 和頁面路徑直觀、易讀、可預測。以下是遵循 RESTful 風格的設計原則:

  1. 使用名詞而非動詞/articles/ 而非 /get-articles/
  2. 使用複數名詞/articles/ 而非 /article/
  3. 利用 HTTP 方法表達操作:GET 取得、POST 建立、PUT/PATCH 更新、DELETE 刪除
  4. 使用階層結構表達資源關係/articles/1/comments/ 表示文章 1 的所有留言
  5. 結尾統一加斜線:Django 預設行為 APPEND_SLASH = True
  6. 使用連字號而非底線/my-article/ 而非 /my_article/

URL 路由效能考量

urlpatterns 列表由上而下匹配,有幾個效能相關的建議:

  • 高頻路由放在前面:將最常被存取的路由放在列表開頭,減少不必要的比對
  • 使用 include() 分組:可以提前排除不匹配的路由群組,減少逐一比對的數量
  • 避免頂層使用 re_path():正規表達式的比對效能低於 path() 的路徑轉換器

不過不需要過度擔心 — Django 會在首次請求時編譯所有 URLconf 並快取結果,後續請求不會重複編譯。

完整的 myblog URL 配置範例

讓我們用一個完整的部落格專案 myblog 來整合以上所有知識:

# myblog/urls.py(專案級別 ROOT_URLCONF)
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # 首頁
    path('', include('pages.urls')),

    # 部落格文章(最頻繁存取,放在前面)
    path('posts/', include('posts.urls')),

    # 使用者帳號相關
    path('accounts/', include('accounts.urls')),

    # 管理後台(最少使用,放在最後)
    path('admin/', admin.site.urls),
]

# 開發環境下提供媒體檔案服務
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# posts/urls.py(App 級別)
from django.urls import path
from . import views

app_name = 'posts'

urlpatterns = [
    # 文章列表 — GET /posts/
    path('', views.PostListView.as_view(), name='list'),

    # 建立文章 — GET /posts/create/(表單)、POST /posts/create/(送出)
    path('create/', views.PostCreateView.as_view(), name='create'),

    # 文章詳情 — GET /posts/1/
    path('<int:pk>/', views.PostDetailView.as_view(), name='detail'),

    # 更新文章 — GET /posts/1/edit/(表單)、POST /posts/1/edit/(送出)
    path('<int:pk>/edit/', views.PostUpdateView.as_view(), name='edit'),

    # 刪除文章 — POST /posts/1/delete/
    path('<int:pk>/delete/', views.PostDeleteView.as_view(), name='delete'),

    # 依 slug 存取文章 — GET /posts/my-first-post/
    path('<slug:slug>/', views.PostBySlugView.as_view(), name='detail-by-slug'),

    # 依年份篩選文章 — GET /posts/archive/2026/
    path('archive/<int:year>/', views.PostArchiveView.as_view(), name='archive-year'),

    # 文章留言(巢狀路由)— /posts/1/comments/
    path('<int:post_pk>/comments/', include('comments.urls')),
]
# comments/urls.py
from django.urls import path
from . import views

app_name = 'comments'

urlpatterns = [
    # 留言列表 — GET /posts/1/comments/
    path('', views.CommentListView.as_view(), name='list'),

    # 新增留言 — POST /posts/1/comments/create/
    path('create/', views.CommentCreateView.as_view(), name='create'),

    # 刪除留言 — POST /posts/1/comments/5/delete/
    path('<int:pk>/delete/', views.CommentDeleteView.as_view(), name='delete'),
]
# accounts/urls.py
from django.urls import path
from . import views

app_name = 'accounts'

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),
    path('register/', views.RegisterView.as_view(), name='register'),
    path('profile/<int:pk>/', views.ProfileView.as_view(), name='profile'),
]

在模板中使用這些命名路由:

<!-- 導航列 -->
<nav>
    <a href="{% url 'posts:list' %}">所有文章</a>
    <a href="{% url 'posts:create' %}">撰寫新文章</a>
    <a href="{% url 'accounts:profile' pk=request.user.pk %}">個人檔案</a>
    <a href="{% url 'accounts:logout' %}">登出</a>
</nav>

<!-- 文章列表中的連結 -->
{% for post in posts %}
    <article>
        <h2><a href="{% url 'posts:detail' pk=post.pk %}">{{ post.title }}</a></h2>
        <a href="{% url 'posts:edit' pk=post.pk %}">編輯</a>
    </article>
{% endfor %}

在 View 中使用反向解析做重導向:

from django.urls import reverse
from django.shortcuts import redirect

def create_post(request):
    if request.method == 'POST':
        # 處理表單資料並儲存文章...
        post = form.save()
        # 重導向到新建立的文章頁面
        return redirect(reverse('posts:detail', kwargs={'pk': post.pk}))
    # ...

總結

Django 的 URL 路由系統(URLconf)是整個請求處理流程的核心環節。本篇文章涵蓋了路由設計的方方面面:urlpatterns 列表的匹配機制、path() 函數的四個參數、五種內建路徑轉換器與自訂轉換器、re_path() 正則表達式路由、include() 模組化拆分、URL 命名與 reverse() / reverse_lazy() 反向解析、模板中的 {% url %} 標籤、以及 Namespace 命名空間的應用。

掌握這些知識後,你應該能夠為任何規模的 Django 專案設計出清晰、可維護、符合 RESTful 風格的 URL 架構。記住幾個核心原則:優先使用 path() 而非 re_path()、永遠為 URL 命名並使用反向解析、善用 include() 和 Namespace 做模組化管理,以及遵循 RESTful 設計風格讓 URL 直觀可預測。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。