Django 视图与路由基础:从URL映射到视图函数

在 Django 开发中,"URL路由"和"视图"是处理用户请求的核心环节 ------ 路由负责将用户输入的 URL 映射到对应的处理逻辑,视图则负责执行逻辑、交互数据并返回响应。它们共同构成了 "请求 - 处理 - 响应" 流程的骨架,也就是连接前端页面与后端模型的关键桥梁。本文将从基础入手,带你掌握 URL 路由的配置技巧和视图函数的核心写法,搭建起 Django 处理请求的基础能力

一、URL 路由配置

URL路由的本质是"URL 路径" 与 "视图函数" 的映射关系。当用户发送 HTTP 请求(如访问http://127.0.0.1:8000/article/1/)时,Django 会通过路由配置找到对应的视图函数,执行后返回响应

1、路由基础

Django 的路由配置写在 urls.py 文件中,核心是 urlpatterns 列表------每个path()函数定义一条路由规则,包含URL路径、"视图函数"、"路由名称"三个关键要素

示例:基础路由配置

python 复制代码
# 项目主 urls.py(或应用的 urls.py)
from django.contrib import admin
from django.urls import path
from myapp import views  # 导入应用的视图函数

urlpatterns = [
    # 管理员后台路由(Django自带)
    path('admin/', admin.site.urls),
    # 首页路由:访问 / 时,调用 views.index 视图,命名为 'home'
    path('', views.index, name='home'),
    # 文章详情路由:访问 /article/1/ 时,调用 views.article_detail,命名为 'article_detail'
    path('article/<int:pk>/', views.article_detail, name='article_detail'),
    # 分类文章列表路由:访问 /category/tech/ 时,调用 views.category_articles,命名为 'category_articles'
    path('category/<slug:slug>/', views.category_articles, name='category_articles'),
]

注解:path(router,view,name=None)

router: URL路径规则,支持参数的捕获 **view:**对应的视图函数,请求会传递给改函数

name: 路由的唯一名称,用于"反向解析"

2、Django 处理请求的完整流程

当用户发送一个HTTP请求(如http://127.0.0.1:8000/article/1/),Django会按一下步骤处理:

①接收请求:服务器接收用户的 HTTP 请求,提取 URL 路径(如 /article/1/ )

②解析URL:忽略域名(127.0.0.1:8000)、查询参数和锚点,仅保留路径部分

③匹配路由:遍历 urlpatterns 列表,找到与路径匹配的 path 规则(如<int:pk>匹配1)

④调用视图:将请求对象(request)和捕获的参数(如pk=1)传递给对应的视图函数

⑤返回响应:视图函数处理完成后,返回 HttpResponse 对象,Django 将其转换为 HTTP响应发送给用户

3、路径转换器:捕获 URL 中的参数

当需要从 URL 中提取参数(如文章 ID、分类别名)时,Django 提供了路径转换器,无需手动解析字符串。常用转换器如下:

|------|----------------------------|----------------|----------------------------------------------------------|
| 转换器 | 描述 | 适用场景 | 示例 |
| str | 匹配除斜杠(/)外的任意字符 | 字符串参数(如标题、别名) | path('user/<str:username>/', views.user_profile) |
| int | 匹配非负整数 | 主键 ID、页码 | path('article/<int:pk>/', views.article_detail) |
| slug | 匹配字母、数字、下划线、连字符(URL 友好字符串) | 分类别名、文章短标题 | path('category/<slug:slug>/', views.category_articles) |
| uuid | 匹配 UUID 字符串(唯一标识) | 安全的唯一 ID(如订单号) | path('order/<uuid:order_id>/', views.order_detail) |
| path | 匹配包含斜杠的完整路径 | 文件路径 | path('file/<path:file_path>/', views.file_view) |

示例:用路径转换器实现文章详情与分类列表

python 复制代码
# myapp/urls.py(应用的路由配置)
from django.urls import path
from . import views

urlpatterns = [
    # 文章详情:捕获整数类型的pk(主键)
    path('article/<int:pk>/', views.article_detail, name='article_detail'),
    # 分类列表:捕获slug类型的分类别名
    path('category/<slug:slug>/', views.category_articles, name='category_articles'),
]

# myapp/views.py(对应的视图函数)
from django.shortcuts import render, get_object_or_404
from .models import Article, Category

def article_detail(request, pk):
    """根据pk获取文章详情,不存在则返回404"""
    # get_object_or_404:找不到对象时自动抛出404错误
    article = get_object_or_404(Article, pk=pk)
    return render(request, 'article_detail.html', {'article': article})

def category_articles(request, slug):
    """根据slug获取分类下的文章列表"""
    category = get_object_or_404(Category, slug=slug)
    # 关联查询:获取该分类下的所有文章
    articles = Article.objects.filter(category=category)
    return render(request, 'category_articles.html', {'category': category, 'articles': articles})

模版渲染示例(article_detail.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{{ article.title }}</title>
</head>
<body>
    <h1>{{ article.title }}</h1>
    <p>{{ article.content }}</p>
    <p>发布时间:{{ article.created_at|date:"Y-m-d" }}</p>
</body>
</html>

4、正则表达式:处理复杂 URL 模式

当路径规则需要更灵活的匹配(如 "按年月归档文章"/article/2025/09/),路径转换器无法满足时,可使用正则表达式 配置路由,需用re_path()函数

示例:用正则实现文章年月归档

python 复制代码
# myapp/urls.py
from django.urls import path, re_path  # 导入re_path
from . import views

urlpatterns = [
    # 匹配格式:/article/2025/09/(4位年份+2位月份)
    # (?P<year>[0-9]{4}):命名分组,捕获4位数字作为year参数
    # (?P<month>[0-9]{2}):捕获2位数字作为month参数
    re_path(r'^article/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', 
            views.article_archive, 
            name='article_archive'),
]

# myapp/views.py
def article_archive(request, year, month):
    """获取指定年月的文章列表"""
    # 过滤条件:created_at的年份=year,月份=month
    articles = Article.objects.filter(
        created_at__year=year,
        created_at__month=month
    )
    return render(request, 'article_archive.html', {
        'year': year,
        'month': month,
        'articles': articles
    })

模版渲染示例

html 复制代码
<h1>{{ year }}年{{ month }}月文章归档</h1>
{% if articles %}
    <ul>
        {% for article in articles %}
            <li>
                <a href="{% url 'article_detail' article.pk %}">{{ article.title }}</a>
                <span>发布于:{{ article.created_at|date:"Y-m-d H:i" }}</span>
            </li>
        {% endfor %}
    </ul>
{% else %}
    <p>该月份暂无文章</p>
{% endif %}

5、路由进阶技巧

(1)URLconf 的查找规则

Django 的URLconf 仅匹配URL的路径部分,忽略一下内容

①域名 ②查询参数:如/article/1/?page=2中的?page=2不参与匹配

③锚点:如/article/1/#comment中的#comment不参与匹配

此外,RLconf 不区分 HTTP 方法 ------ 同一 URL 的 GET、POST 请求会映射到同一个视图函数,需在视图中通过 request.method 判断

(2)指定视图参数的默认值

可在路由中为视图参数设置默认值,避免重复配置路由。例如 "文章列表分页",默认显示第 1 页:

python 复制代码
# myapp/urls.py
urlpatterns = [
    # 默认分页:/articles/page/ → 对应page=1
    path('articles/page/', views.article_list, name='article_list_default'),
    # 指定分页:/articles/page/2/ → 对应page=2
    path('articles/page/<int:page>/', views.article_list, name='article_list_paginated'),
]

# myapp/views.py
def article_list(request, page=1):  # page默认值为1
    """文章列表分页视图"""
    # 分页逻辑(后续会讲,此处简化)
    return render(request, 'article_list.html', {'page': page})

(3)包含其他 URLconf:拆分大型项目路由

大型项目中,多个应用(如blog、api、user)的路由混在一起会难以维护。可将每个应用的路由拆到各自的urls.py,再通过include()导入主路由

示例:

python 复制代码
# 项目主 urls.py
from django.contrib import admin
from django.urls import path, include  # 导入include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 将 /blog/ 开头的URL交给 blog 应用的 urls.py 处理
    path('blog/', include('blog.urls', namespace='blog')),
    # 将 /api/ 开头的URL交给 api 应用的 urls.py 处理
    path('api/', include('api.urls', namespace='api')),
]

# blog/urls.py(blog应用的路由)
from django.urls import path
from . import views

app_name = 'blog'  # 应用命名空间(配合namespace使用)
urlpatterns = [
    path('', views.blog_home, name='blog_home'),  # 匹配 /blog/
    path('article/<int:pk>/', views.article_detail, name='article_detail'),  # 匹配 /blog/article/1/
]

(4)反向解析:避免硬编码 URL

硬编码 URL(如<a href="/blog/article/1/">)会导致后续修改路径时需全局替换,效率低下。通过 "路由名称" 反向生成 URL(即 "反向解析"),可解决此问题

反向解析的两种场景:①在视图中使用 :通过**reverse()**函数,传入路由名称和参数;

在模板中使用 :通过**{% url '路由名称' 参数 %}**模板标签

示例:

python 复制代码
# 1. 视图中反向解析(myapp/views.py)
from django.urls import reverse
from django.shortcuts import redirect

def redirect_to_article(request, pk):
    """重定向到文章详情页"""
    # 反向生成URL:根据名称'article_detail'和pk=pk生成 /blog/article/pk/
    article_url = reverse('blog:article_detail', args=[pk])  # args传递位置参数
    return redirect(article_url)  # 重定向到生成的URL

# 2. 模板中反向解析(article_list.html)
{% for article in articles %}
    <!-- 生成 /blog/article/文章pk/ 的链接 -->
    <a href="{% url 'blog:article_detail' article.pk %}">{{ article.title }}</a>
{% endfor %}

(5)命名空间:解决多应用同名路由冲突

当多个应用存在同名路由是(如 blog 和 user 都有 name='index' 的路由),反向解析会无法区分,此时需要通过"命名空间"(namespace)隔离

配置步骤:

主路由设置 namespace:include() 时指定 namespace

应用路由设置 app_name:在应用的 urls.py 中定义 app_name

示例:

python 复制代码
# 项目主 urls.py
urlpatterns = [
    # blog应用路由:namespace='blog'
    path('blog/', include('blog.urls', namespace='blog')),
    # user应用路由:namespace='user'
    path('user/', include('user.urls', namespace='user')),
]

# blog/urls.py
app_name = 'blog'  # 应用命名空间
urlpatterns = [
    path('', views.index, name='index'),  # 路由名'index'
]

# user/urls.py
app_name = 'user'  # 应用命名空间
urlpatterns = [
    path('', views.index, name='index'),  # 同名路由'index'
]

# 反向解析时指定命名空间
def my_view(request):
    # 生成blog应用的index URL:/blog/
    blog_index_url = reverse('blog:index')
    # 生成user应用的index URL:/user/
    user_index_url = reverse('user:index')
    return ...

# 模板中使用
<a href="{% url 'blog:index' %}">博客首页</a>
<a href="{% url 'user:index' %}">用户首页</a>

二、视图函数

视图函数是 Django 处理 Http 请求的核心逻辑载体 ------ 它接收**request** 对象(包含请求所有信息),与模型交互获取数据,最终返回**HttpResponse**对象(或其子类)作为响应

1、什么是视图函数

(1)本质 :一个python函数,第一个参数必须是request(Django 封装的 HTTP 请求对象)

(2)返回值 :必须是HttpResponse对象或其子类(如JsonResponse

(3)存放位置 :约定放在应用的views.py文件中

(4)核心职责:①接收请求参数(URL 参数、GET/POST 数据、Cookie 等)

②与模型交互(查询 / 修改数据)

③渲染模板(将数据传递给 HTML)或返回 JSON 等数据

④返回响应给用户

2、简单视图函数:Hello Django

最基础的视图函数仅返回一段文本,无需模板和模型:

python 复制代码
# myapp/views.py
from django.http import HttpResponse

def hello(request):
    """简单视图:返回Hello Django"""
    # HttpResponse(content, status=200):content是响应内容,status是HTTP状态码
    return HttpResponse('Hello, Django!', status=200)

# 配置路由(myapp/urls.py)
urlpatterns = [
    path('hello/', views.hello, name='hello'),
]

在浏览器中访问 http://127.0.0.1:8000/hello/,页面会显示 Hello Django!

3、错误视图:处理异常情况

当请求无法正常处理时(如页面不存在、权限不足),需要返回对应的 HTTP 错误响应。Django 提供了多种错误视图的实现方式

(1)直接返回错误状态码

通过HttpResponsestatus参数指定错误码,或使用 Django 封装的错误响应类(如HttpResponseNotFound):

python 复制代码
from django.http import HttpResponseNotFound, HttpResponseForbidden

# 1. 返回404(页面未找到)
def page_not_found(request):
    return HttpResponseNotFound('页面不存在!(404)')

# 2. 返回403(禁止访问)
def permission_denied(request):
    return HttpResponseForbidden('您没有权限访问此页面!(403)')

# 3. 直接用HttpResponse指定状态码
def bad_request(request):
    return HttpResponse('请求参数错误!(400)', status=400)

(2)主动抛出404异常

当需要根据业务逻辑判断是否返回 404(如 "文章不存在"),可使用get_object_or_404()快捷函数(后续会讲)或主动抛出Http404异常:

python 复制代码
from django.http import Http404
from django.shortcuts import render
from .models import Article

def article_detail(request, pk):
    """文章详情:不存在则抛出404"""
    try:
        article = Article.objects.get(pk=pk)  # 查询文章
    except Article.DoesNotExist:
        # 主动抛出404异常,Django会返回默认404页面
        raise Http404('该文章不存在或已被删除!')
    return render(request, 'article_detail.html', {'article': article})

(3)自定义错误页面

Django 默认的错误页面(如 404、500)样式简陋,实际项目中需自定义。配置步骤如下:

步骤一:修改项目设置(settings.py

生产环境下需关闭DEBUG模式,并指定允许的域名(否则无法显示自定义错误页面):

python 复制代码
# settings.py
DEBUG = False  # 关闭DEBUG(生产环境必需)
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']  # 允许访问的域名
步骤 2:配置错误处理器(主urls.py

在项目主urls.py中配置handler400handler403handler404handler500,指定自定义错误视图:

python 复制代码
# 项目主 urls.py
from django.contrib import admin
from django.urls import path
from myapp import views  # 导入自定义错误视图

# 配置错误处理器(必须在主urls.py中配置,子应用无效)
handler400 = 'myapp.views.bad_request'  # 400:请求错误
handler403 = 'myapp.views.permission_denied'  # 403:禁止访问
handler404 = 'myapp.views.page_not_found'  # 404:页面未找到
handler500 = 'myapp.views.server_error'  # 500:服务器内部错误

urlpatterns = [
    path('admin/', admin.site.urls),
    # 其他路由...
]
步骤 3:编写错误视图(myapp/views.py
python 复制代码
# myapp/views.py
from django.shortcuts import render
from django.views.decorators.csrf import requires_csrf_token

@requires_csrf_token  # 确保CSRF令牌可用
def bad_request(request, exception):
    """处理400错误:请求参数错误"""
    return render(request, '400.html', status=400)

@requires_csrf_token
def permission_denied(request, exception):
    """处理403错误:权限不足"""
    return render(request, '403.html', status=403)

@requires_csrf_token
def page_not_found(request, exception):
    """处理404错误:页面不存在"""
    return render(request, '404.html', status=404)

@requires_csrf_token
def server_error(request):
    """处理500错误:服务器内部错误(无需exception参数)"""
    return render(request, '500.html', status=500)
步骤 4:编写错误模板

templates目录下创建400.html403.html404.html500.html,示例404.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>页面未找到 - 404</title>
    <style>
        .error-container { text-align: center; margin-top: 50px; }
        h1 { font-size: 48px; color: #dc3545; }
        p { font-size: 18px; margin: 20px 0; }
        a { color: #007bff; text-decoration: none; }
    </style>
</head>
<body>
    <div class="error-container">
        <h1>404 - 页面不见了</h1>
        <p>您访问的页面不存在或已被删除</p>
        <a href="{% url 'home' %}">返回首页</a>
    </div>
</body>
</html>

4、异步视图:处理耗时操作

Django 3.1+ 支持异步视图函数,可处理耗时操作(如调用外部 API、读取大文件)而不阻塞其他请求。只需用async def定义视图,内部通过await执行异步操作

示例:异步视图模拟耗时任务

python 复制代码
# myapp/views.py
import asyncio
from django.http import HttpResponse

async def async_view(request):
    """异步视图:模拟1秒耗时操作"""
    # 异步操作(如调用异步API、异步读取文件),需用await关键字
    await asyncio.sleep(1)  # 模拟1秒耗时(不阻塞其他请求)
    return HttpResponse('Hello from async view!(耗时1秒)')

# 配置路由
urlpatterns = [
    path('async/', views.async_view, name='async_view'),
]

注意:

①异步视图仅在支持异步的服务器(如daphne)上生效,runserver(开发服务器)也支持但不建议用于生产

②若视图中调用的是同步函数(如 Django ORM 的同步操作),需用sync_to_async包装,否则会阻塞事件循环

总结

本文聚焦 Django 视图与路由的基础能力,核心知识点包括:

1、URL 路由配置:从基础映射到进阶技巧(路径转换器、正则、反向解析、命名空间),解决了 "请求如何找到对应视图" 的问题

2、视图函数:从简单文本响应到错误处理、自定义错误页面、异步视图,覆盖了 "如何处理请求并返回响应" 的核心逻辑

相关推荐
Leinwin2 小时前
Codex CLI 配置 Azure OpenAI GPT-5-codex 指南
后端·python·flask
会跑的葫芦怪2 小时前
Go test 命令完整指南:从基础到高级用法
开发语言·后端·golang
之歆3 小时前
LangGraph构建多智能体
人工智能·python·llama
闲人编程3 小时前
告别Print: Python调试入门,用PDB高效找Bug
开发语言·python·bug·调试·pdb·断点设置
AI量化投资实验室3 小时前
年化422%,回撤7%,夏普比5.4| Deap因子挖掘新增qlib因子库,附python代码
开发语言·python
站大爷IP3 小时前
Python爬取微博热搜并实时发送到邮箱:零基础实现指南
python
Cache技术分享3 小时前
203. Java 异常 - Throwable 类及其子类
前端·后端
用户4099322502123 小时前
PostgreSQL索引这么玩,才能让你的查询真的“飞”起来?
后端·ai编程·trae
道可到3 小时前
字节面试 Java 面试通关笔记 03| java 如何实现的动态加载(面试可复述版)
java·后端·面试