
简介这是一份面向Python初学者与Django入门者的实战型学习资源聚焦Web开发全流程实践帮助学习者打通从前端HTML/CSS界面搭建到后端Django逻辑处理的完整链路。资源包含186个文件总大小154.5MB涵盖45个核心Python源码.py与50个已编译字节码.pyc支撑Django项目运行10个HTML页面与10个CSS样式文件如home.css、login.css、comment.css等构成清晰的前端结构另有16个LRC歌词、11个OGG音频及多种图片资源表明项目具备多媒体内容管理功能。已有2364人下载学习适合作为课设参考、自学练手或面试项目储备——不仅提供可直接运行的完整工程更通过模块化目录与典型业务场景如用户注册登录、内容展示、搜索评论等展现Django MTV架构落地细节便于理解路由配置、视图逻辑、模板渲染与静态资源组织方式。 写这篇博文的起因是我把一个做了快一年的 Django 实战项目源码整理归档时发现很多刚接触 Python 和 Django 的朋友要么卡在环境搭建要么拿到源码不知道从哪儿看起要么一部署到服务器上就各种报错。其实这个框架本身已经足够成熟真正拉开差距的是对项目结构、数据模型设计、ORM 查询细节和部署方案的理解。这篇博文我就拿这套实战项目源码当例子把从选型、拆解到跑通、部署的完整链路捋一遍包括我踩过的一些坑。这套源码不是那种 hello world 级别的 Demo而是一个面向真实业务场景的内容管理后端包含用户认证、角色权限、内容发布、数据统计、操作日志等模块前端页面用 Bootstrap 5 少量原生 JS 实现服务端完全基于 Python 3.10 Django 4.2数据库默认使用 MySQL同时也兼容 SQLite 快速启动。它能解决的痛点很直接很多团队在开发后台管理系统时会把大量精力花在重复造轮子上而基于 Django 这套源码你可以直接把核心模块抽出来复用无论是做企业内部工具、个人博客还是小型 CMS都能节省大量的开发时间。适合看这篇内容的人有三类刚学完 Python 语法想找一个完整项目练手的朋友工作中需要快速交付后台系统的开发人员以及正在纠结“Django 到底怎么在真实环境里跑起来”的读者。下面我直接从整个项目的设计思路开始讲。1. 技术选型与项目设计思路1.1 为什么在这个项目里选 Django 而不是 Flask 或 FastAPI这个项目最开始我也考虑过 Flask 和 FastAPI毕竟它们更轻量写起来很灵活。但后来还是选了 Django原因是这个项目的定位是“后台管理系统”而不是单纯的 API 服务。Django 自带 Admin 后台、ORM、认证授权、表单处理、中间件机制这些功能如果全部用 Flask 自己拼装工程量会大很多而且团队协作时每个人引入的第三方库版本参差不齐维护成本很高。还有一个很现实的因素是人才储备。在国内Django 虽然不像 Spring Boot 那样在企业级市场占绝对主导但在 Python 后台开发领域Django 依然是生态最完整、最稳定的选择之一。招聘方如果要求“Python 后端”大概率会问 Django 相关经验而求职者如果简历里写“熟悉 Django”也意味着他理解 MTV 架构、ORM、中间件这些核心概念。这个项目选 Django本质上就是在选一条学习曲线平滑、社区资料多、坑容易被搜到解决方案的技术路线。FastAPI 的优势在异步接口和高性能场景但这个项目里并没有超高并发需求Django 4.x 也支持了异步视图足够覆盖当前业务。Flask 的灵活确实诱人但灵活意味着需要自己做更多决策在项目要快速落地、多人协作的情况下Django 这种“约定优于配置”的框架反而能帮团队省下大量沟通成本。1.2 项目模块划分与 MTV 架构落地Django 的 MTVModel-Template-View架构是这个项目的骨架。很多初学者会把 MTV 和 MVC 搞混简单理解就是Model 管数据Template 管展示View 管业务逻辑URL 分发器负责把请求路由到对应的 View。在写这个项目的时候我没有把所有业务都塞进一个 app 里而是按功能域拆分了多个 app这样的好处是每个模块的边界清晰后期维护或复用某个模块时不会牵一发动全身。实际项目里我划分了这么几个 appusers负责用户注册、登录、个人信息修改rbac负责角色和权限管理content负责文章/内容的增删改查analytics负责访问量、点赞量等统计数据的展示operation_log负责记录用户的关键操作。每个 app 都有自己的 models.py、views.py、urls.py、admin.py这种结构在 Django 里非常标准也很容易被新接手的人看懂。划分模块的时候有个原则一个 app 只做一类事不要做成万能工具箱。我在一开始写项目时也犯过把所有 model 都放在一个 app 里的错误后来发现当 model 数量超过 20 个时文件就会变得极其臃肿迁移文件也会互相纠缠很难追踪哪次改动影响到了哪张表。后来痛定思痛把模块拆开每个 app 内聚自己的数据模型和业务逻辑整个项目瞬间清爽了很多。1.3 数据模型设计的核心思路数据模型设计是整个项目里最值得仔细琢磨的部分。我在设计阶段先画出了实体关系图再转成 Django 的 model 代码。这个项目涉及的核心实体包括用户、角色、权限、内容、内容分类、操作日志。它们之间的关系不复杂但有几处设计细节值得分享。第一处是用户表的设计。Django 自带的User模型已经包含用户名、密码、邮箱、是否超级用户等字段但如果直接用它后期想加手机号、头像、部门等业务字段就会很别扭。我采用的方式是继承AbstractUser在users/models.py里扩展自己的UserProfile字段同时设置AUTH_USER_MODEL users.UserProfile。注意这个配置必须在第一次迁移之前完成否则中途更换用户模型会导致迁移链路异常我就在这个坑上浪费过两个小时。第二处是角色和权限的关系。Django 自带Group和Permission模型可以直接用但在真实业务里往往需要把权限细粒度到某个按钮或某个接口。我借鉴了 RBAC基于角色的访问控制模型设计了三张表用户表、角色表、用户-角色关联表。这样在判断“当前用户能不能删除某篇文章”时只需要查他的角色列表里是否包含该操作对应的权限标识。具体代码可以这样写# users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class UserProfile(AbstractUser): phone models.CharField(max_length11, blankTrue, nullTrue, verbose_name手机号) avatar models.URLField(blankTrue, nullTrue, verbose_name头像链接) department models.CharField(max_length64, blankTrue, nullTrue, verbose_name部门) def has_perm_by_code(self, perm_code): return self.roles.filter(permissions__codeperm_code).exists() class Meta: db_table users verbose_name 用户第三处是级联删除的谨慎处理。Django 的 ForeignKey 默认是on_deletemodels.CASCADE意思是删除主表记录时关联的外键记录会自动一并删除。听起来很方便但在真实业务里这是双刃剑。比如删除一个用户时如果他的所有文章、评论都被静默删掉可能造成数据灾难。所以我更推荐在核心业务表上使用on_deletemodels.PROTECT保护模式有关联数据时禁止删除或on_deletemodels.SET_NULL删除后置空同时在逻辑层面提供软删除字段。这一点后面在“删除对象”专题里还会细讲。2. 环境搭建与项目快速跑通2.1 Python 和 Django 的版本选择这个项目使用的 Python 版本是 3.10Django 版本是 4.2 LTS。版本选择这件事很关键直接决定了后面会不会遇到兼容性问题。Python 3.10 在类型提示、模式匹配等语法特性上都有改进而且对 Django 4.2 的支持非常稳定如果你的系统装了 Python 3.8 或更老的版本建议先升级否则一些代码写法跑不起来。Django 4.2 是 LTSLong Term Support版本官方承诺会提供较长时间的安全更新和维护。如果你不想频繁追新版本选 LTS 版本永远是稳妥的选择。另外一个很重要的点是 Django 和数据库驱动的版本匹配如果项目用 MySQL需要安装mysqlclient或pymysql而mysqlclient在 Windows 上经常需要预编译的 wheel 包安装流程要稍微花点心思。这里给一套比较顺手的安装命令假设你用的是 Ubuntu 22.04麒麟系统的兼容性我会在后面单独讲# 更新系统包 sudo apt update sudo apt upgrade -y # 安装 Python 3.10 和 pip sudo apt install python3.10 python3.10-venv python3-pip -y # 创建项目虚拟环境 python3.10 -m venv venv source venv/bin/activate # 安装 Django 与项目依赖 pip install -r requirements.txt2.2 创建 Django 项目和应用的具体步骤进入虚拟环境后我用django-admin startproject config .创建项目主目录注意这里最后的点代表在当前目录生成manage.py和config包而不是嵌套一层目录。用点这个参数可以避免后期部署时路径多套一层的问题算是一个小技巧。接下来是创建各个业务 apppython manage.py startapp users python manage.py startapp rbac python manage.py startapp content python manage.py startapp analytics python manage.py startapp operation_log创建完成后记得在config/settings.py的INSTALLED_APPS列表里注册这些 app这是新手最容易漏掉的一步。注册完之后修改settings.py里的DATABASES配置把默认的 SQLite 改为你自己的 MySQL 连接。比如# config/settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: django_project, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }用utf8mb4字符集是因为它支持完整的 Unicode包括 emoji 和生僻字。别用utf8或utf8mb3等你真正往数据库里写中文加特殊符号时就知道这个配置有多重要。第一次迁移时我先执行python manage.py makemigrations生成迁移文件再执行python manage.py migrate同步数据库。顺序不能反得先告诉 Django“我要改哪些模型”Django 才会生成对应的 SQL 迁移脚本。如果你是第一次跑项目还没建过库可以先用 SQLite 初始化一遍把整个项目跑通了再切到 MySQL这样排障的时候变量更少。2.3 静态文件与媒体文件的配置细节Django 在开发阶段会自动处理静态文件但部署到生产环境时静态文件的处理逻辑完全不同。这个项目的静态资源包括 Bootstrap 的 CSS/JS、自定义 CSS、图片、上传的图片等。我建议在 settings.py 里维护一套规范的配置# config/settings.py STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, ] STATIC_ROOT BASE_DIR / staticfiles MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediaSTATICFILES_DIRS指向开发阶段存放静态文件的目录STATIC_ROOT是执行collectstatic命令时把所有静态文件汇总的目录。部署时 Nginx 通常会直接把/static/和/media/两个路径指到对应目录Django 只负责动态请求。如果你在开发阶段发现 CSS 样式不生效可以先把DEBUG True打开Django 会通过django.contrib.staticfiles帮你自动处理。但生产环境里必须把DEBUG False并且配合 Nginx 来处理静态文件否则 Django 处理静态文件的性能会非常差而且有安全风险。我在第一次部署到服务器时就是因为没执行collectstatic导致页面一片惨白看起来像是业务逻辑写错了实际只是静态文件路径不对。3. 核心模块源码解析与实操要点3.1 用户认证与权限控制的实现思路用户认证这部分的代码是整个项目里最常被复用的一段。我在users/views.py里实现了注册、登录、登出三个接口全部基于 Django 内置的authenticate、login、logout方法没有自己造轮子。注册接口的代码逻辑大概是这样的先对前端传过来的用户名、密码、确认密码做基础校验然后调用UserProfile.objects.create_user()创建用户。create_user是 Django 提供的辅助方法会自动做密码哈希千万别图省事用objects.create()去创建用户那样密码会以明文存储一旦数据库泄露就是重大安全事故。登录接口则使用authenticate(username..., password...)验证用户名和密码验证通过后调用login(request, user)写入 session。Django 默认使用的 session 引擎是数据库版也就是说 session 数据会存到django_session表里。如果并发量很大可以把 session 引擎换成 Redis代码里只需要改一行配置比如SESSION_ENGINE django.contrib.sessions.backends.cache SESSION_CACHE_ALIAS default权限控制方面我在rbac里实现了基于装饰器的权限校验。具体做法是在用户登录成功后把他拥有的全部权限代码放进 session然后在需要权限控制的视图函数上用自定义装饰器做校验# rbac/decorators.py from django.http import JsonResponse from functools import wraps def require_permission(perm_code): def decorator(view_func): wraps(view_func) def _wrapped_view(request, *args, **kwargs): if not request.user.is_authenticated: return JsonResponse({code: 401, msg: 未登录}, status401) if not request.user.has_perm_by_code(perm_code): return JsonResponse({code: 403, msg: 无权限}, status403) return view_func(request, *args, **kwargs) return _wrapped_view return decorator这样的装饰器用起来非常直观比如要在删除文章接口上加权限控制只需要在视图函数前一行写require_permission(content:delete)。当然Session 里的权限数据在权限变更后不会立即生效需要等用户重新登录。如果业务要求实时生效可以把权限查询改成每次请求都查数据库或者用 Redis 缓存并设置极短的过期时间。这两种方案各有取舍我在项目里为了简单采用的是写进 session 的方式。3.2 ORM 查询优化避免 N1 查询Django ORM 是自动化程度很高的数据库访问层但正因为太自动化很多人在查询时忽略了它生成的 SQL 是否高效。这个项目里踩过最典型的一个坑就是 N1 查询。所谓 N1 查询比如你要展示文章列表每篇文章都有关联的作者信息。最直观的写法是articles Article.objects.all() for article in articles: print(article.author.username)这看起来没什么问题但实际执行时Django 会先执行一条 SQL 查出所有文章再对每篇文章执行一条 SQL 查出作者信息。如果列表有 100 篇文章那就需要执行 101 条 SQL性能可想而知。解决方案是使用select_related或prefetch_relatedarticles Article.objects.select_related(author).all() for article in articles: print(article.author.username)select_related适用于 ForeignKey 和 OneToOneField它在查询时通过 SQL 的 JOIN 一次性把关联数据取出来只执行一条 SQL。prefetch_related适用于 ManyToManyField 和反向外键它会把关联查询拆成两条 SQL再在 Python 层完成拼接。两者各有适用场景核心原则是能用一条 SQL 解决的问题不要用 N1 条。还有一个小技巧是only和defer。如果某张表的字段特别多但某个页面只需要其中两三个字段可以用only(title, created_at)只查这两个字段能有效减少数据库传输量。但注意之后如果访问了only之外的字段Django 会再发一条 SQL 去补齐数据所以only只用在确定页面不会访问其他字段的场景。3.3 ORM 删除对象时要注意的那些坑删除操作在 Django ORM 里有两个层级Model 实例的delete()方法和 QuerySet 的delete()方法。这两者都会触发数据库的 DELETE 语句但行为有明显差异。Model 实例的delete()只删除当前这一条记录而 QuerySet 的delete()会批量删除所有匹配的记录。很多人在这里忽略了“级联删除”的连锁反应。前面提到ForeignKey 默认on_deletemodels.CASCADE如果你删除一篇文章那么所有外键指向这篇文章的记录也会被自动删除。这在某些场景下是想要的行为但在很多场景下是灾难。比如在内容管理系统里一篇文章可能会被多个用户收藏。如果不小心给收藏表的外键设置了 CASCADE那么删除文章时所有用户的收藏记录都会一夜之间消失。我在这个项目里把这类外键改成了on_deletemodels.SET_NULL同时允许该字段为空这样删除文章后收藏记录还在只是里面的 article 字段变成 NULL。另外Django 的delete()方法的返回值值得关注它返回一个元组(deleted_count, detail_dict)deleted_count表示总共删除的对象数量detail_dict表示每个模型各删了多少条。这在查看“到底删了什么”时非常有用特别是在级联删除生效的情况下你可以通过日志把删除详情记录下来方便事后追溯。还有一个非常容易被忽略的坑QuerySet 的delete()方法会立刻在数据库层面执行不支持事务回滚吗确切的说在 Django 默认的自动提交模式下一条 DELETE 语句执行完后就不可逆了。如果你希望“删除错了还可以恢复”有两个思路一是使用软删除给模型加一个is_deleted字段删除操作只是更新这个字段为 True二是把删除操作包在事务里配合transaction.atomic()在确认无误后提交。我建议核心业务表都优先考虑软删除因为在真实环境里数据恢复的成本永远比删除的成本高得多。4. 模板渲染与前端交互4.1 Django 模板语法的高效使用Django 模板系统本身不复杂但实际项目中如何组织模板结构、如何避免逻辑冗余是有讲究的。这个项目里的页面大多属于后台管理系统风格所以我用三层继承结构来组织模板base.html定义页面骨架导航栏、侧边栏、内容区base_content.html在 base 基础上定义内容页的通用布局具体的页面模板再继承base_content.html。模板继承的核心是{% block %}标签。比如 base.html 里定义一个{% block content %}{% endblock %}子模板里重写这个 block 就可以了。在写大量表单页面的时候模板继承能帮你省下大量重复的 HTML。另外Django 模板自带的{% url %}标签非常实用它通过视图函数的 name 属性反向解析 URL这样即使你调整了 urlpatterns 里的路径模板里的链接也不用逐个修改而是会自动跟着变。强烈建议所有页面里的链接都使用{% url %}而不是硬编码路径。模板里的自定义过滤器、自定义标签也值得了解。比如项目里要把时间戳格式化成“刚刚、5分钟前、昨天”这种相对时间直接在模板里写过滤器或标签会比在视图里预处理要优雅得多。实现方式是在某个 app 下建templatetags包然后在模板顶部{% load my_tags %}引入即可。4.2 Django 与前端数据交互的几种方式这个项目早期是纯服务端渲染页面跳转、表单提交都靠 Django 的视图函数处理。后来为了提升交互体验我把部分页面改成了 AJAX JSON 接口的方式。两种方式各有利弊服务端渲染对 SEO 友好而且实现简单但页面局部刷新需要重新加载整个 HTMLJSON 接口方式适合单页应用或局部刷新但前端代码复杂度上升。在这个项目里我采用的是混合模式主要页面仍然用 Django 模板渲染表单提交和部分动态交互走 JSON 接口。比如文章列表页的“批量删除”按钮点击后通过 fetch 发起一个 POST 请求视图函数返回 JSON 格式的{code: 0, msg: 删除成功}前端根据返回值动态刷新页面局部内容。一个很常见的问题是 POST 请求的 CSRF 校验。Django 默认开启 CSRF 中间件表单方式需要在模板中加{% csrf_token %}前端 fetch 方式则需要从 cookie 中读取csrftoken然后在请求头里带上X-CSRFToken。这个细节不处理好前端请求就会一直报 403 Forbidden。项目里的做法是在公共 JS 文件里写了一个统一的请求封装每次发送 POST 请求前自动带上 CSRF token这样业务代码里就不用每次重复处理了。// static/js/request.js function getCookie(name) { let cookieValue null; if (document.cookie document.cookie ! ) { const cookies document.cookie.split(;); for (let i 0; i cookies.length; i) { const cookie cookies[i].trim(); if (cookie.substring(0, name.length 1) (name )) { cookieValue decodeURIComponent(cookie.substring(name.length 1)); break; } } } return cookieValue; } function sendPost(url, data) { return fetch(url, { method: POST, headers: { Content-Type: application/json, X-CSRFToken: getCookie(csrftoken), }, body: JSON.stringify(data), }); }4.3 Django Admin 后台的二次开发Django Admin 是很多人选择 Django 的重要原因之一它零代码就能生成一套可用的数据管理后台。但直接拿默认 Admin 上生产环境是不现实的它默认的界面风格、权限粒度都不够精细。这个项目里我对 Admin 做了三个层面的定制。第一注册模型并设置显示的字段。在admin.py里用admin.register(Article)注册然后通过list_display指定列表页显示哪些字段search_fields指定可搜索字段list_filter指定右侧过滤条件。这样管理后台的可用性会大大提升。第二重写save_model方法注入当前操作人。当用户在 Admin 后台新增或修改文章时自动把operator字段设为当前登录用户不需要前端传这个字段从源头上避免了伪造操作人的风险。第三给管理后台加自定义操作按钮。比如把一篇文章从“草稿”状态一键改成“已发布”。在 Admin 里可以通过自定义actions实现给actions列表加一个函数这个函数会在下拉菜单里出现一个“批量发布”的选项。选中若干篇文章点击执行后Django 会循环调用这个函数完成批量状态更新。如果业务场景复杂到 Admin 的默认表单已经无法满足需求还可以通过定义ModelForm来定制表单字段的展示和校验甚至在change_form_template中指定自定义模板。但我的原则是Admin 只是辅助工具不是面向用户的核心界面。核心业务界面尽量自己写模板不要把 Admin 直接暴露给非管理员用户避免操作界面过于复杂导致误操作。5. 部署上线与跨平台迁移实战5.1 从 Windows 开发环境迁移到国产化系统麒麟的完整记录这个项目有一个很特殊的部署经历最早的开发和测试环境都在 Windows 上后来因为业务需要要把整套系统迁移到麒麟操作系统上。这个迁移过程踩了不少坑我觉得比想象中的“直接复制代码就能跑”要复杂得多拿出来单独讲一下非常有价值。先说结论Django 的代码本身是跨平台的真正的坑在依赖编译和系统库上。在 Windows 上mysqlclient安装很简单直接pip install mysqlclient就能装上预编译的 wheel 包。但在麒麟系统上这个包没有预编译版本需要从源码编译而编译它需要系统里先装好gcc、python3-dev、default-libmysqlclient-dev这几个依赖。如果你装依赖的顺序不对编译就会报错。在干净环境里我建议先执行sudo yum install -y gcc python3-devel mysql-devel pip install mysqlclient如果你是更精瘦的环境没有 yum 只有 dnf那命令换成sudo dnf install -y gcc python3-devel mysql-devel即可。还有一个容易踩坑的细节是python3命令的指向。在麒麟系统上python3可能默认指向 Python 3.6而你项目里用的是 Python 3.10 的语法特性比如str | None这种类型注解写法老版本 Python 根本不认识。我在迁移时就是用python3 --version先确认版本发现不对后直接安装新版本 Python然后用python3.10 -m venv venv创建虚拟环境。这个过程里不要用系统自带的pip而是用虚拟环境里的pip不然很容易把包装到系统 Python 的环境里导致版本冲突。静态文件的处理方式也需要调整。在 Windows 上开发时Django 的runserver会自动处理静态文件不需要额外配置但上了麒麟系统、使用 Nginx 部署时必须执行python manage.py collectstatic把静态文件收集到STATIC_ROOT再用 Nginx 把/static/路径指过去。如果这一步漏了页面样式会全部丢失而且浏览器控制台会报一堆 404 错误。5.2 使用 Nginx uWSGI 或 Gunicorn 部署的两种方案这个项目生产环境采用的是 Nginx uWSGI 的部署组合。为什么选 uWSGI 而不是 Gunicorn主要考虑到 uWSGI 的配置更灵活对性能调优的参数控制更细而且在企业内网环境里我们更熟悉这套方案。如果你只是一个小型项目Gunicorn 会更轻量配置也更简单。两者都是成熟的方案没有绝对的优劣我这里分别给出配置思路。Gunicorn 的方式比较简单安装gunicorn后直接执行gunicorn config.wsgi:application -w 4 -b 0.0.0.0:8000-w 4表示启用 4 个 worker 进程-b 0.0.0.0:8000表示监听所有网卡的 8000 端口。如果你的服务器是多核 CPUworker 数量可以按“CPU 核心数 * 2 1”的公式预估。config.wsgi:application是 Django 项目自带的 WSGI 入口不需要额外修改。uWSGI 的配置则需要一个 ini 文件。我在项目根目录维护了一个uwsgi.ini内容大概是[uwsgi] chdir /opt/django_project module config.wsgi:application master true processes 4 threads 2 socket 127.0.0.1:8001 vacuum true buffer-size 32768这里唯一容易混淆的是socket参数。如果 uWSGI 直接对外提供服务用http 0.0.0.0:8000如果前面还有一层 Nginx则用socket 127.0.0.1:8001让 Nginx 通过内部端口把请求转发给 uWSGI。这个内部端口的 IP 一定要写127.0.0.1不要写0.0.0.0避免其他机器直接绕过 Nginx 访问应用端口这是个安全隐患。Nginx 的配置核心是反向代理和静态文件处理server { listen 80; server_name example.com; location /static/ { alias /opt/django_project/staticfiles/; } location /media/ { alias /opt/django_project/media/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8001; } }这样配置后外部请求先到 NginxNginx 根据路径判断是静态文件还是动态请求。动态请求转发给 uWSGI由 Django 处理静态文件直接由 Nginx 从磁盘读取返回性能比让 Django 处理不知道高了多少倍。5.3 在 NAS 上搭建 Django 网站的探索这里顺便聊聊热搜里提到的“飞牛 NAS 搭建 Django 网站”。NAS 的好处是功耗低、7x24 小时在线适合跑一些个人项目。但 NAS 的 CPU 性能普遍一般内存也有限所以部署 Django 时要格外注意资源占用。我在一台飞牛 NAS 上做过实验跑起来这套项目没问题但有一些限制。首先SQLite 在这种设备上比 MySQL 更合适因为 NAS 上的 MySQL 可能没有专门优化反而更吃内存。如果你坚持用 MySQL建议把max_connections调低避免内存不够时进程被杀掉。其次静态文件的处理要特别留意我在 NAS 上直接使用 Gunicorn 再加一层 Caddy 做反向代理Caddy 配置简单自动申请 HTTPS 证书对小项目非常友好。NAS 上部署还有一个好处是可以和现有文件协议结合。比如 Django 的媒体文件目录可以直接指向 NAS 上的共享文件夹这样用户上传的图片会自动同步到 NAS 的目录里不需要额外处理。但要注意权限配置确保 Django 进程有权限读写这个目录否则上传接口会一直报权限错误。6. 常见问题排查与避坑指南6.1 Django 版本与 Python 版本不匹配我见过很多人都栽在这个问题上Python 3.13 发布后网上很多教程还在讲 Django 3.2结果安装时一堆兼容性错误。Django 对 Python 版本的要求是有限制的比如 Django 4.2 支持 Python 3.8 到 3.12Django 5.0 支持 Python 3.10 到 3.12。如果你想用 Python 3.13就必须使用 Django 5.1 或更高版本。遇到这种问题最快的排查方式是在虚拟环境里执行python manage.py check它会直接告诉你 Django 版本和 Python 版本是否兼容。千万不要在报兼容性错误时强行忽略因为即使能启动后续运行时也容易出现莫名其妙的问题。6.2 数据库连接问题与超时处理MySQL 服务在长时间运行后可能会出现MySQL server has gone away的错误。这通常是因为 MySQL 的wait_timeout默认值比较短而 Django 的长连接在空闲一段时间后被数据库服务端断开了。Django 的连接池虽然会复用连接但不会自动识别连接是否已失效。解决方式有两种一是修改 MySQL 的wait_timeout配置调大到一个合理的值比如 28800 秒但这治标不治本更好的方式是在 Django 的数据库配置里加一个参数让 Django 在连接前先测试连接是否有效DATABASES { default: { # ... CONN_MAX_AGE: 60, OPTIONS: { charset: utf8mb4, }, TEST: { CHARSET: utf8mb4, }, } }CONN_MAX_AGE表示连接最长保持 60 秒超过这个时间后 Django 会关闭并重建连接这样可以在一定程度上避免“gone away”问题。如果这种错误还是频繁出现可以在项目里加一个自定义的重连机制捕获异常后重试一次。但这个方案要谨慎实现避免在事务中间重连导致数据不一致。6.3 CSRF 验证失败与跨域问题CSRF 验证失败的表现通常是表单提交或 AJAX POST 请求返回 403 Forbidden。排查思路分三个方向第一检查 Django 的MIDDLEWARE里有没有django.middleware.csrf.CsrfViewMiddleware默认是有但如果你改过中间件列表可能丢掉了第二检查模板文件里有没有{% csrf_token %}没有的话表单就没有携带 CSRF 字段第三如果是 AJAX 请求检查请求头里有没有带X-CSRFToken。这里特别提醒一个容易误导人的点如果你的网站同时配置了多个域名而某个域名的 CSRF 校验失败很可能是CSRF_TRUSTED_ORIGINS配置的问题。Django 4.x 之后跨域 POST 请求除了验证 CSRF token还会验证 Origin 请求头如果你确实需要允许某个域名跨域请求需要在设置里显式添加CSRF_TRUSTED_ORIGINS [ https://example.com, https://admin.example.com, ]但这个配置本身会增加安全风险非必需情况尽量不开放。6.4 时区设置不当导致的时间错乱Django 默认的TIME_ZONE是UTC如果你直接用它来显示“发布时间”国内用户看到的就会比北京时间慢 8 个小时。这个项目里设置的是LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ TrueUSE_TZ True表示 Django 在数据库里存储的是 UTC 时间但在渲染到模板时会自动转换到TIME_ZONE指定的时区。这个设计的好处是数据库时间不受时区影响方便多时区扩展。如果你把USE_TZ FalseDjango 就会直接用本地时间存储短时间看没什么问题但如果将来服务器迁移到其他时区时间就会全部错乱。6.5 性能排查用 Django Debug Toolbar 定位慢 SQL项目上线之后发现某个页面的响应时间居然超过了 2 秒用浏览器 F12 看网络请求发现请求本身并不大问题基本就出在数据库查询上。这时候我用 Django Debug Toolbar 排查它在页面侧边展示所有 SQL 的执行时间能非常直观地看到哪条 SQL 最慢、有没有 N1 查询、有没有重复查询。安装方式很简单pip install django-debug-toolbar然后把它注册到INSTALLED_APPS和MIDDLEWARE再加一个INTERNAL_IPS配置。注意 Debug Toolbar 只有在DEBUGTrue时才生效生产环境千万别开它会把敏感信息暴露给所有看到页面的人。我通过 Debug Toolbar 发现一个典型的慢查询文章列表页一次性查出了所有文章的正文内容而这些字段在列表页根本不会展示。解决方式就是用之前提到的only(title, summary, created_at)只查必要字段再加上select_related(author)查询时间从几百毫秒降到了几十毫秒。优化 SQL 的效果立竿见影这也是我觉得 Django 项目性能优化里性价比最高的一步。7. 项目的可扩展性与源码的复用建议这套项目源码的另一个价值在于它的可扩展性。很多人拿到源码后会问我想加一个公告模块该怎么做我想把数据库从 MySQL 换成 PostgreSQL 怎么改我想给用户增加一个积分功能怎么实现这几个问题其实都指向同一件事如何在现有结构上低成本地扩展功能。以新增“公告模块”为例正确路径是先创建一个新 apppython manage.py startapp announcement然后在 models.py 里定义公告模型在 views.py 里写公告列表和公告详情视图在 urls.py 里配置路由在 templates 里新建公告模板在 admin.py 里注册模型。整个过程不会动到现有模块的代码这就是模块化拆分的好处。至于把数据库从 MySQL 换成 PostgreSQL只需要修改settings.py里的DATABASES配置Django ORM 会尽量屏蔽底层的 SQL 方言差异大部分代码不需要改动但要注意字段类型的细微差别比如 MySQL 的TextField和 PostgreSQL 的对应类型在个别场景下索引行为不太一样。如果你想把这个项目作为脚手架去开发一个全新的业务系统我的建议是保留users、rbac、operation_log这三个与业务无关的基础模块把content、analytics替换成自己的业务模块。如果业务有大量审批流、工作流需求目前这套源码不包含复杂的流程引擎但可以基于 Django 的事件机制和中间件自行扩展。我也看到过一些企业级脚手架比如若依框架、芋道源码它们用 Java 实现了非常丰富的低代码能力但如果你要求团队必须使用 Python 技术栈那么这套 Django 源码完全可以作为替代方案的起步基础。我在实际使用中发现Django 项目源码复用的最大障碍不在代码本身而在文档和注释的完整性。所以归档这套源码时我给每个核心函数都写了 docstring关键业务逻辑加了中文注释还维护了一份 README把本地开发、部署上线、以及常见报错的解决方案全部记录下来。对于一个团队项目来说这份文档的维护成本并不高但它能把“开发者一个人看得懂”变成“整个团队都能快速接手”这是项目能否持续迭代的分水岭。最后再分享一个小技巧把项目里的错误页面、公共工具函数、基础模板当成一个独立的“公共模块”来维护不要散落在各个业务 app 里。这套源码里的common目录就专门存放分页器、Excel 导入导出、验证码生成等跨模块复用的功能。新手往往忽视这个抽象层的价值但当你经历过一次因为工具函数重复实现导致两种行为不一致的 bug 之后就会明白公共模块的重要性。如果你打算拿这份源码作为自己项目的起点从第一天就建立公共模块和其他模块的边界意识将来的你会感谢现在的这个决定。本文还有配套的精品资源点击获取