路由篇:请求是怎么找到视图的?(含跨域 + 媒体文件 + 原生 Django 案例))
大家好这是我 Django 学习日记的第二篇。上一篇我们把 request 请求讲明白了知道前端会把数据打包发给后端。那紧接着一个最关键的问题一个请求进来Django 怎么精准找到对应的视图去处理答案就是路由urls.py。本文将从「项目配置」讲到「DRF 自动化路由」再到「媒体文件配置」「跨域配置」重点细化 DRF 自动路由的生成逻辑、反向解析的具体用法。开发中遇到路由问题直接翻这篇就能解决。一、路由是什么一句话总结路由就是 Django 的 “导航地图”本质是「URL 路径」与「视图函数 / 类」的映射关系。核心作用接收前端请求的 URL 路径匹配到对应的视图函数 / 类分发请求让不同的 URL 对应不同的业务逻辑如查询员工、激活账号、上传文件规范化 URL 格式符合 RESTful 规范让接口更易维护、易调用请求完整流程text前端发起请求如http://127.0.0.1:8000/staff/active ↓ Django 启动后先读取 settings.py 中的 ROOT_URLCONF找到项目主路由oaback/urls.py ↓ 主路由通过 include() 引入子应用路由apps/staff/urls.py ↓ 子应用路由匹配 URL 路径/active找到对应的视图ActiveStaffView ↓ 视图处理请求返回响应给前端没有路由哪怕你的视图写得再完美前端也无法访问到接口——路由是前后端交互的「必经之路」。二、路由的入口配置settings.py很多新手不知道 Django 从哪里开始找路由这是在settings.py里明确指定的。这是路由的「总入口」不能出错。核心配置代码python# settings.py # 路由总入口指定项目的主路由文件 ROOT_URLCONF oaback.urls # oaback 是项目名称urls 是主路由文件名补充说明备查重点若修改了主路由文件名比如改成main_urls.py必须同步修改ROOT_URLCONF否则 Django 会报错「找不到路由配置」。这是整个项目的路由「总调度」所有请求都会先进入这个指定的主路由文件再分发到各个子应用。企业级项目中主路由只负责「分发」不写具体的业务路由避免主路由过于臃肿。三、项目主路由总 urls.py真实项目中绝对不能把所有路由都写在主路由里必须用include()拆分到各个子应用这是企业级开发的标准写法也是后续项目维护的关键。实战代码我们 OA 系统的真实主路由python# oaback/urls.py项目主路由 from django.contrib import admin from django.urls import path, include # 导入媒体文件相关配置 from django.conf import settings from django.conf.urls.static import static urlpatterns [ # 后台管理路由Django 自带 path(admin/, admin.site.urls), # 员工管理模块引入子应用 staff 的路由 path(staff/, include(apps.staff.urls)), # 前缀 /staff 所有 staff 子应用的路由都要加这个前缀 # 若有其他子应用如用户模块、权限模块继续用 include 引入 # path(user/, include(apps.user.urls)), ] # 开发环境提供媒体文件访问后续媒体文件配置会详细讲 if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)关键细节干货重点include(apps.staff.urls)的作用把apps/staff/urls.py这个子应用的所有路由「挂载」到主路由的/staff前缀下。比如子应用中有path(active, ...)实际访问路径就是http://127.0.0.1:8000/staff/active。include()的注意事项必须传入「子应用路由模块的路径」格式应用名.urls子应用必须在settings.py的INSTALLED_APPS中注册否则include会报错主路由的职责只做「路由分发」不写具体的视图映射比如不直接写path(active, views.ActiveStaffView.as_view())。四、子应用路由与DRF 自动路由这是本篇最核心的部分重点讲「DRF 自动路由的生成逻辑」——为什么一行router.register()就能生成 5 个接口生成的接口具体是什么样子结合我们 OA 系统的真实路由一步步拆解。1. 我们 OA 系统的子应用路由python# apps/staff/urls.py子应用路由 from django.urls import path from . import views from rest_framework.routers import DefaultRouter # 1. 命名空间反向解析必备后面会讲 app_name staff # 2. DRF 自动路由重点讲解 # 初始化 DefaultRoutertrailing_slashFalse 表示 URL 末尾不带 /避免 DRF 自动添加斜杠带来的重定向问题 router DefaultRouter(trailing_slashFalse) # 注册路由参数路由前缀、视图集、路由别名 router.register(prefixstaff, viewsetviews.StaffViewSet, basenamestaff) # 3. 普通路由手动编写对应单独的视图 urlpatterns [ # 部门列表接口 path(departments, views.DepartmentListView.as_view(), namedepartments), # 员工激活接口 path(active, views.ActiveStaffView.as_view(), nameactive_staff), # 员工下载接口 path(download, views.StaffDownloadView.as_view(), namedownload_staff), # 员工上传接口 path(upload, views.StaffUploadView.as_view(), nameupload_staff), # Celery 测试接口 path(test/celery, views.TestCeleryView.as_view(), nametest_celery) ] router.urls # 合并 DRF 自动生成的路由2. DRF DefaultRouter 自动路由的生成逻辑很多新手只知道router.register()能自动生成接口但不知道它是「怎么生成的」「生成了什么」这里彻底讲透1自动路由的核心前提必须使用 DRF 提供的「视图集ViewSet」比如我们用的StaffViewSet继承了viewsets.GenericViewSetmixins扩展类。视图集必须实现对应的mixins方法或自己重写比如ListModelMixin对应列表接口、CreateModelMixin对应新增接口。我们的StaffViewSet继承了mixins.ListModelMixin列表、mixins.CreateModelMixin新增、mixins.UpdateModelMixin修改所以能生成对应的接口。class StaffViewSet( viewsets.GenericViewSet, mixins.CreateModelMixin, mixins.ListModelMixin, mixins.UpdateModelMixin ):2自动路由的生成规则router.register(prefixstaff, viewsetviews.StaffViewSet, basenamestaff)这一行代码会根据「视图集的 mixins 方法」自动生成以下 5 个 RESTful 标准接口请求方式生成的 URL 路径对应视图集方法接口功能实际访问示例GET/stafflist()查询员工列表http://127.0.0.1:8000/staff/staffPOST/staffcreate()新增员工http://127.0.0.1:8000/staff/staffGET/staff/{pk}retrieve()查询单个员工详情http://127.0.0.1:8000/staff/staff/1PUT/staff/{pk}update()完整修改员工信息http://127.0.0.1:8000/staff/staff/1DELETE/staff/{pk}destroy()删除员工http://127.0.0.1:8000/staff/staff/13关键细节补充避免踩坑prefixstaff指定自动生成的路由前缀所以所有自动路由的 URL 都以/staff开头结合主路由的/staff前缀最终路径是/staff/staff。basenamestaff路由别名的前缀用于反向解析后面会详细讲。注意如果视图集中没有定义queryset属性则注册时必须显式提供basename否则 Django 会抛出异常即便定义了queryset也建议显式指定basename以提高可读性。trailing_slashFalse禁用 URL 末尾的/比如访问/staff/staff有效访问/staff/staff/无效避免新手踩「路径带 / 导致 301 重定向」的坑。若视图集没有实现某个mixins方法比如没继承mixins.DestroyModelMixin则不会生成对应的 DELETE 接口例如我们的StaffViewSet没写删除就不会生成DELETE /staff/{pk}。4自动路由 vs 手动路由对比理解自动路由适合「标准的增删改查接口」一行代码生成 5 个接口效率极高DRF 核心优势。手动路由适合「非标准接口」比如员工激活/active、文件下载/download这些接口不符合「增删改查」规范必须手动编写path。五、为什么类视图要加 .as_view()不管是 DRF 的类视图APIView、ListAPIView还是原生 Django 的类视图View后面都要加.as_view()这是新手最容易懵的点核心原因Django 原生的路由系统只能识别「函数视图」无法直接识别「类视图」。我们写的DepartmentListViewListAPIView、ActiveStaffViewView都是「类」不是「函数」。class DepartmentListView(ListAPIView).as_view()是类视图的一个「类方法」作用是把类视图转换成 Django 路由系统能识别的函数视图。通俗理解可以把.as_view()理解成一个「转换器」输入类视图比如ActiveStaffView输出一个函数Django 能识别能接收 request、处理请求、返回响应代码对比一看就懂python# 1. 原生 Django 函数视图不用加 .as_view() def demo_view(request): return HttpResponse(函数视图) urlpatterns [ path(demo, demo_view), # 直接写函数名不用加 .as_view() ] # 2. 类视图必须加 .as_view() class DemoView(APIView): def get(self, request): return Response(类视图) urlpatterns [ path(demo, DemoView.as_view()), # 必须加 .as_view()否则报错 ]补充注意事项避免踩坑只有「类视图」需要加.as_view()「函数视图」直接写函数名即可。不管是 DRF 的APIView还是原生 Django 的View只要是类视图都必须加.as_view()。.as_view()后面不能加括号比如写成.as_view否则会报错「必须调用 as_view() 方法」。六、路由命名 反向解析路由命名和反向解析是 Django 路由的「高级用法」也是实战中必用的知识点——避免硬编码 URL后续修改路由路径时不用修改所有相关代码大大提升维护效率。结合我们 OA 系统的实战场景详细讲解1. 路由命名基础步骤路由命名的核心是给path()加上name参数格式path(路径, 视图, name路由名)。实战示例我们 OA 系统的路由命名python# apps/staff/urls.py urlpatterns [ # 给路由命名namedepartments path(departments, views.DepartmentListView.as_view(), namedepartments), # 命名nameactive_staff员工激活路由 path(active, views.ActiveStaffView.as_view(), nameactive_staff), ] # DRF 自动路由的命名由 basename 固定后缀组成 # 比如 router.register(prefixstaff, basenamestaff)自动生成的路由命名 # staff-list列表接口、staff-detail详情接口、staff-create新增接口等命名规范备查路由名要「见名知意」比如active_staff表示「员工激活路由」departments表示「部门列表路由」。若有多个子应用路由名可以加「应用前缀」避免重名比如staff_active、user_login。不能用中文、特殊字符建议用小写字母 下划线snake_case。2. 反向解析反向解析的核心是「通过路由名反向获取对应的 URL 路径」不用硬写 URL 字符串核心函数是django.urls.reverse()。反向解析的 3 种场景全部实战可用场景 1子应用内反向解析最常用前提子应用路由必须设置app_name staff命名空间否则无法精准匹配路由。pythonfrom django.urls import reverse # 格式reverse(命名空间:路由名) url reverse(staff:active_staff) # 输出结果/staff/active结合主路由前缀 /staff 和子应用路由 /active场景 2DRF 自动路由的反向解析自动路由的路由名是「basename 固定后缀」后缀对应不同的接口比如我们的basenamestaff自动生成的接口路由命名反向解析代码输出 URL 路径列表接口staff-listreverse(staff:staff-list)/staff/staff详情接口staff-detailreverse(staff:staff-detail, args[1])/staff/staff/1新增接口staff-createreverse(staff:staff-create)/staff/staff注意详情接口需要传递pk参数比如员工 ID用args[pk]传入。场景 3跨应用反向解析若需要从其他子应用比如 user 应用反向解析 staff 应用的路由同样需要指定命名空间python# 从 user 应用反向解析 staff 应用的激活路由 url reverse(staff:active_staff)3. 实战应用我们 OA 系统的真实用法我们 OA 系统中「发送激活邮件」时需要生成激活链接就是用反向解析实现的避免硬编码 URLpython# StaffViewSet 中的 send_active_email 方法 def send_active_email(self, email): token aes.encrypt(email) # 反向解析激活路由生成 /staff/active active_path reverse(staff:active_staff) ?token token # 生成完整的激活 URL比如 http://127.0.0.1:8000/staff/active?tokenxxx active_url self.request.build_absolute_uri(active_path) # 发送邮件省略邮件发送代码4. 反向解析的优势避免硬编码 URL比如后续把/active改成/staff/active只要路由名不变反向解析会自动生成新的 URL不用修改所有代码。减少错误手动写 URL 容易写错路径比如多写/、少写前缀反向解析不会出错。便于维护项目越大路由越多反向解析能大大提升维护效率。七、媒体文件配置用户上传头像 / 文件前后端分离项目中用户上传的头像、文件需要配置媒体文件路由才能通过 URL 访问到比如用户上传的头像需要通过http://127.0.0.1:8000/media/avatar/xxx.png访问。完整配置步骤步骤 1settings.py 配置核心pythonimport os from pathlib import Path # 项目根目录Django 3.2 默认配置 BASE_DIR Path(__file__).resolve().parent.parent # 媒体文件配置用户上传的文件会存放在这里 MEDIA_URL /media/ # 媒体文件的 URL 前缀访问时需要加这个前缀 MEDIA_ROOT os.path.join(BASE_DIR, media) # 媒体文件的本地存储路径项目根目录下的 media 文件夹步骤 2主路由添加媒体文件访问路由python# oaback/urls.py主路由 from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(staff/, include(apps.staff.urls)), ] # 关键开发环境下提供媒体文件的访问路由生产环境不建议这么配置用 Nginx 代理 if settings.DEBUG: # static(媒体文件URL前缀本地存储路径) urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)步骤 3实战测试验证配置是否生效在项目根目录下手动创建media文件夹Django 不会自动创建。上传一个图片文件比如avatar.png存放在media/avatar/文件夹下。浏览器访问http://127.0.0.1:8000/media/avatar/avatar.png能正常显示图片说明配置生效。补充细节MEDIA_ROOT必须是绝对路径用os.path.join(BASE_DIR, media)确保跨系统兼容Windows、Linux 都能用。开发环境用static()配置即可生产环境需要用 Nginx 代理媒体文件否则会有安全隐患、性能问题。若上传文件报错「权限不足」给media文件夹添加写入权限Windows 不用管Linux 执行chmod 777 media。八、跨域配置前后端分离必装前后端分离项目中前端Vue、小程序、React访问 Django 接口时会出现「跨域问题」浏览器安全策略限制不同域名/端口的请求被拦截必须配置django-cors-headers解决。完整步骤步骤 1安装依赖包bashpip install django-cors-headers步骤 2settings.py 配置关键不能漏python# settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 1. 注册 corsheaders 应用必须放在其他应用前面尤其是 rest_framework 前面 corsheaders, # DRF 应用 rest_framework, # 自己的子应用 apps.staff, ] MIDDLEWARE [ # 2. 添加 cors 中间件必须放在最前面尤其是 CommonMiddleware 前面 corsheaders.middleware.CorsMiddleware, django.middleware.security.SecurityMiddleware, django.contrib.sessions.middleware.SessionMiddleware, django.middleware.common.CommonMiddleware, # 其他中间件... ] # 3. 跨域核心配置开发环境配置简单高效 # 允许所有域名跨域开发环境可用生产环境建议指定具体域名 CORS_ORIGIN_ALLOW_ALL True # 可选配置解决复杂跨域场景 # 允许跨域请求携带 cookie若前端需要传递 cookie必须配置 CORS_ALLOW_CREDENTIALS True # 允许的请求方式默认允许所有可省略 CORS_ALLOW_METHODS [GET, POST, PUT, DELETE, OPTIONS] # 允许的请求头默认允许所有可省略 CORS_ALLOW_HEADERS [*]步骤 3验证跨域配置是否生效启动 Django 项目python manage.py runserver。用前端或 Postman 模拟前端访问接口比如http://127.0.0.1:8000/staff/departments。若能正常获取响应没有出现「跨域错误」比如控制台报错Access to XMLHttpRequest at ... from origin ... has been blocked by CORS policy说明配置生效。补充细节生产环境注意事项开发环境用CORS_ORIGIN_ALLOW_ALL True很方便但生产环境不安全建议指定具体的前端域名例如python# 生产环境配置只允许指定域名跨域 CORS_ORIGIN_ALLOW_ALL False CORS_ALLOWED_ORIGINS [ # 新版本使用 CORS_ALLOWED_ORIGINS http://localhost:8080, # 前端 Vue 项目地址 http://192.168.1.100:8080, # 本地前端地址 ]九、原生 Django 非前后端分离项目的路由案例完整示例前面讲的都是「前后端分离 DRF」的路由很多新手也会接触「原生 Django 非前后端分离」项目比如用 Django 模板渲染页面这里给一个完整的案例对比理解1. 子应用路由apps/blog/urls.pypythonfrom django.urls import path from . import views app_name blog urlpatterns [ # 首页路由函数视图 path(, views.index, nameindex), # 文章详情路由带参数 pk传递文章 ID path(article/int:pk/, views.article_detail, namearticle_detail), # 新增文章路由类视图 path(article/add/, views.ArticleAddView.as_view(), namearticle_add), ]2. 视图apps/blog/views.pypythonfrom django.shortcuts import render, get_object_or_404, redirect from django.views import View from django.urls import reverse from .models import Article # 假设有一个 Article 模型 # 1. 函数视图非前后端分离渲染模板 def index(request): articles Article.objects.all() return render(request, blog/index.html, {articles: articles}) # 2. 函数视图带参数文章详情 def article_detail(request, pk): article get_object_or_404(Article, pkpk) return render(request, blog/article_detail.html, {article: article}) # 3. 类视图新增文章 class ArticleAddView(View): def get(self, request): return render(request, blog/article_add.html) def post(self, request): title request.POST.get(title) content request.POST.get(content) Article.objects.create(titletitle, contentcontent) return redirect(reverse(blog:index))3. 模板templates/blog/index.html简单示例html!DOCTYPE html html langzh-CN head meta charsetUTF-8 title博客首页/title /head body h1我的博客/h1 a href{% url blog:article_add %}新增文章/a ul {% for article in articles %} li a href{% url blog:article_detail article.pk %}{{ article.title }}/a /li {% endfor %} /ul /body /html4. 核心区别非前后端分离 vs 前后端分离对比项非前后端分离原生 Django前后端分离Django DRF路由用途映射到「模板渲染视图」映射到「接口视图」视图返回值render()渲染模板Response()返回 JSON 数据前端交互模板渲染 表单提交前端框架Vue调用接口路由写法以函数视图为主类视图为辅以 DRF 视图集 自动路由为主反向解析用法模板中用{% url 命名空间:路由名 %}代码中用reverse()函数十、本篇总结路由本质URL 与视图的映射关系是请求的「导航地图」。路由入口settings.py中的ROOT_URLCONF指定主路由文件。主路由核心用include()拆分子应用路由模块化管理不写具体业务路由。子应用路由手动路由path(路径, 视图.as_view(), name路由名)。DRF 自动路由DefaultRouterrouter.register()根据视图集的mixins方法自动生成 5 个标准 RESTful 接口。类视图必须加.as_view()把类视图转换成 Django 能识别的函数视图。路由命名与反向解析命名path(..., name路由名)子应用需设置app_name命名空间。反向解析reverse(命名空间:路由名)避免硬编码 URL便于维护。媒体文件配置MEDIA_URLMEDIA_ROOT主路由加static()配置实现上传文件的访问。跨域配置安装django-cors-headers注册应用、添加中间件开发环境用CORS_ORIGIN_ALLOW_ALL True。非前后端分离 vs 前后端分离前者渲染模板后者返回 JSON路由写法和视图返回值不同。 预告下一篇我会带大家进入DRF 视图篇结合我们的 OA 员工管理系统彻底讲透APIView、GenericAPIView、ViewSet的区别与用法mixins扩展类的作用ListModelMixin、CreateModelMixin等权限、分页、序列化器如何与视图结合我们 OA 系统的视图代码逐行拆解让你看懂每一行的作用敬请期待