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路径、“视图函数”、“路由名称”三个关键要素
示例:基础路由配置
# 项目主 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) |
示例:用路径转换器实现文章详情与分类列表
# myapp/urls.py(应用的路由配置)
from django.urls import path
from . import viewsurlpatterns = [# 文章详情:捕获整数类型的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, Categorydef 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
)
<!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()
函数
示例:用正则实现文章年月归档
# myapp/urls.py
from django.urls import path, re_path # 导入re_path
from . import viewsurlpatterns = [# 匹配格式:/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,月份=montharticles = Article.objects.filter(created_at__year=year,created_at__month=month)return render(request, 'article_archive.html', {'year': year,'month': month,'articles': articles})
模版渲染示例
<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 页:
# myapp/urls.py
urlpatterns = [# 默认分页:/articles/page/ → 对应page=1path('articles/page/', views.article_list, name='article_list_default'),# 指定分页:/articles/page/2/ → 对应page=2path('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()导入主路由
示例:
# 项目主 urls.py
from django.contrib import admin
from django.urls import path, include # 导入includeurlpatterns = [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 viewsapp_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 '路由名称' 参数 %}
模板标签
示例:
# 1. 视图中反向解析(myapp/views.py)
from django.urls import reverse
from django.shortcuts import redirectdef 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
示例:
# 项目主 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
最基础的视图函数仅返回一段文本,无需模板和模型:
# myapp/views.py
from django.http import HttpResponsedef 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)直接返回错误状态码
通过HttpResponse
的status
参数指定错误码,或使用 Django 封装的错误响应类(如HttpResponseNotFound):
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
异常:
from django.http import Http404
from django.shortcuts import render
from .models import Articledef 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
模式,并指定允许的域名(否则无法显示自定义错误页面):
# settings.py
DEBUG = False # 关闭DEBUG(生产环境必需)
ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] # 允许访问的域名
步骤 2:配置错误处理器(主urls.py
)
在项目主urls.py
中配置handler400
、handler403
、handler404
、handler500
,指定自定义错误视图:
# 项目主 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
)
# 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.html
、403.html
、404.html
、500.html
,示例404.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
执行异步操作
示例:异步视图模拟耗时任务
# myapp/views.py
import asyncio
from django.http import HttpResponseasync 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、视图函数:从简单文本响应到错误处理、自定义错误页面、异步视图,覆盖了 “如何处理请求并返回响应” 的核心逻辑