尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Django后端开发实战:从项目初始化到ORM与部署避坑指南
做后端开发这些年Django 一直是我最顺手的 Web 框架之一。框架本身不难上手但真正的痛点却在日常细节里环境怎么搭、迁移命令执行顺序是什么、settings.py 里哪个配置不写会导致上线直接 400、ORM 查询怎么避免 N1……这些问题单看文档都能找到答案可它们分散在各处等你踩坑后才想起来去查。这篇 Django 后端命令与代码总结就是我把实际项目中反复用到的东西整理出来的一份“直接抄作业”手册覆盖初始化、配置、模型迁移、视图路由、模板静态文件、自定义命令和问题排查。刚接触 Django 的人可以照着走一遍流程中途接手老项目的开发者也能用来查漏补缺哪怕只是面试前想把知识体系过一遍这份笔记同样合适。1. 环境准备与项目初始化1.1 虚拟环境与依赖管理我强烈建议所有 Django 项目都用虚拟环境。你可能会觉得“我电脑上就装一个 Django 不行吗”真不行。我见过太多次因为全局环境里 Django 版本不一致导致项目在 A 电脑能跑、到 B 电脑直接语法报错的场景。Python 的依赖冲突本来就很折磨人虚拟环境就是把每个项目的依赖隔离到独立目录里互不干扰。创建虚拟环境的命令很简单python -m venv venvmacOS 和 Linux 下激活source venv/bin/activateWindows 下激活venv\Scripts\activate激活成功后命令行前面会多一个(venv)提示符这时候你再用pip装东西就只会进到当前项目的虚拟环境里。我第一次用的时候经常忘了激活直接在全局环境里pip install结果项目里怎么都 import 不到排查半天才发现根本没激活环境。这个细节看起来蠢但极其常见。装 Django 也一样pip install django如果你希望版本可控建议直接指定大版本例如pip install Django5.0.*。当前 Django 5.x 已经很稳定LTS 版本 4.2 也仍在维护期内具体用哪个取决于项目需求和老代码兼容性。我在老项目里一般保留 4.2新项目会直接用 5.0 以上版本避免一开始就背历史包袱。依赖管理方面项目根目录下维护requirements.txt是基本功pip freeze requirements.txt但要注意pip freeze会把所有间接依赖也列进去时间久了文件里会有一堆你根本不知道干嘛的包。更优雅的方案是用pip-compile维护requirements.in再生成锁定版本的requirements.txt。不过对大多数中小项目来说一个干净的requirements.txt就够了关键是团队协作时大家用同一个文件装依赖避免“你本地能跑我拉下来跑不起来”的尴尬。1.2 创建项目与应用Django 里的“项目”和“应用”是两个概念新手经常混为一谈。项目是整个站点的配置和入口应用则是实现具体业务功能的模块。一个项目可以包含多个应用一个应用理论上也可以被多个项目复用。创建项目的标准命令是django-admin startproject config .这里的.非常重要它表示在当前目录生成manage.py和config配置包而不是再套一层目录。很多教程会让你直接写django-admin startproject config结果生成的是config/config这种嵌套结构后续所有路径都别扭。我习惯把配置包命名为config而不是项目同名这样以后项目改名也不会影响内部引用。创建应用python manage.py startapp users python manage.py startapp orders有人问为什么不直接用django-admin startapp因为通过manage.py创建应用会自动关联当前项目的配置环境避免某些路径问题。创建完应用后第一件事就是去settings.py的INSTALLED_APPS里注册否则 Django 根本不会加载这个应用的模型和迁移文件。这里有个提升体验的小习惯在应用的apps.py里自定义AppConfig把应用显示名称改成中文后台管理界面看起来就舒服很多# apps/users/apps.py from django.apps import AppConfig class UsersConfig(AppConfig): default_auto_field django.db.models.BigAutoField name users verbose_name 用户管理然后在INSTALLED_APPS里注册users.apps.UsersConfig而不是简单的users。这个配置不会影响功能但管理后台更清晰属于“做了不亏、不做也能跑”的润色项。1.3 启动开发服务器初始化完成后跑起来看效果的命令python manage.py runserver默认监听127.0.0.1:8000只能本机访问。如果你想用手机在同一个局域网里调试或者项目跑在远端开发机上就要指定地址python manage.py runserver 0.0.0.0:8000这样开发服务器会监听所有网卡你可以用http://开发机IP:8000访问。但我要强调这个runserver是开发服务器性能和安全性都不适合生产环境。我见过有人图省事直接把runserver挂到公网当正式服务用结果并发一高就卡死这是大忌。runserver自带自动重载你改代码后它会自动重启。但有个注意点新增文件、修改INSTALLED_APPS这种操作有时候不会触发自动重载遇到“改了半天没生效”的情况手动重启一下最省时间。启动后如果提示有未应用的迁移控制台会直接显示警告。这时候我一般顺手执行python manage.py migrate把 Django 内置的表建好。刚创建的项目还没建任何业务表数据库里只要完成初始迁移就能正常进后台。2. settings.py 配置后端开发的第一个大坑2.1 基础配置项DEBUG、SECRET_KEY、ALLOWED_HOSTS每个 Django 项目的配置差异最后都落在 settings.py 里。这个文件里的每一项几乎都能讲出一堆故事但最重要的三个基础项是 DEBUG、SECRET_KEY 和 ALLOWED_HOSTS。DEBUG 表示调试模式。调试模式下出错页面会显示完整堆栈信息方便你本地排查问题但这个特性绝对不能在生产环境开启。想象一下你的线上站点突然报错用户直接看到代码路径、数据库连接参数、甚至环境变量那和把后台密码贴在门口没区别。所以上线前必须把 DEBUG 设为 False并且配置好 ALLOWED_HOSTS。ALLOWED_HOSTS 是 Django 的安全校验机制只有在这个列表里的域名或 IP 才能访问。DEBUGTrue 时默认允许 localhost但 DEBUGFalse 且 ALLOWED_HOSTS 为空时任何访问都会返回 400 Bad Request。很多人第一次部署时忘了配置怎么访问都是 400其实就是这里的问题。配置示例ALLOWED_HOSTS [example.com, www.example.com, localhost, 127.0.0.1]如果服务器有内网 IP 或临时域名也可以先加上测试但不要为了省事直接设成[*]除非你在完全隔离的内网环境。SECRET_KEY 是 Django 做加密签名的核心比如 session、cookie、密码重置令牌都依赖它。这个值一旦泄露攻击者就能伪造签名数据严重性不亚于数据库密码泄露。开发环境里 settings.py 里写死还好生产环境我强烈建议用环境变量读取import os SECRET_KEY os.environ.get(DJANGO_SECRET_KEY)这样密钥就不会出现在代码仓库里。如果你用 git 管理项目一定要确保 settings.py 里的真实密钥没提交上去或者提交的是一个占位值。这类事故我见过不止一次。2.2 数据库连接配置Django 默认用的是 SQLite配置简单文件型数据库适合本地开发和演示。但真实业务项目一般都会换成 MySQL 或 PostgreSQL。以 MySQL 为例基本配置长这样DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: myapp, USER: root, PASSWORD: os.environ.get(DB_PASSWORD), HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }这里的ENGINE是数据库驱动MySQL 需要额外安装mysqlclient。在 Windows 上装mysqlclient可能遇到编译问题一个临时替代方案是用pymysql并在项目__init__.py里调用import pymysql pymysql.install_as_MySQLdb()但这个方案我一般只在本地开发用生产环境还是尽量用 mysqlclient性能和稳定性更好。CONN_MAX_AGE是数据库长连接时间默认 0 表示每个请求都重新建连。改成 60 表示连接可以在 60 秒内复用能明显减少握手开销。要注意的是MySQL 服务端默认wait_timeout是 8 小时如果CONN_MAX_AGE设置得比服务端超时时间还长就可能出现“连接已被关闭”的报错到时候重启服务或加个连接池清理逻辑即可。还有一个高频坑就是中文乱码。Django 连接 MySQL 时数据库表、连接的字符集都要统一成 utf8mb4否则 emoji 或者生僻字在存储时会出问题。建库时直接用CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;然后在上面OPTIONS里指定charset两边对上了才稳。2.3 时区与静态资源配置时区配置看起来不起眼但在做数据统计、定时任务、日志分析时时间相差八小时会让你崩溃。Django 默认配置LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_I18N True USE_TZ True这里最重要也最容易忽略的是USE_TZ True。开启后Django 在数据库里存储时区时间都使用 UTC显示时才按TIME_ZONE转成本地时间。这样做的优点是不同地区用户时间都能正确转换。但在编写代码时千万不要用datetime.datetime.now()获取当前时间因为拿到的可能是不带时区的 naive 时间与数据库里的 UTC 时间比较会出问题。正确写法是from django.utils import timezone now timezone.now()静态文件配置也在这附近。开发环境最简单的套路是用 Django 自带的静态文件服务STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediaSTATIC_URL是浏览器访问静态资源的 URL 前缀STATICFILES_DIRS是开发时你自己放置静态文件的目录列表MEDIA_ROOT则是用户上传文件的磁盘目录。我习惯把所有上传文件统一放media目录和代码目录分开管理这样备份数据时只要额外备份media就行。生产环境下这些目录交给 Nginx 托管后面说到模板与静态文件时再展开。3. 模型层与数据库迁移ORM 命令全解3.1 模型字段与关联关系Django 的模型Model就是数据库表结构的 Python 描述形式。写模型时不需要先建表而是靠迁移命令把模型变成真实表。基础模型示例from django.db import models class Category(models.Model): name models.CharField(max_length50, uniqueTrue) class Meta: db_table category verbose_name 分类 def __str__(self): return self.name class Article(models.Model): title models.CharField(max_length200, verbose_name标题) content models.TextField(verbose_name内容) category models.ForeignKey(Category, on_deletemodels.CASCADE, related_namearticles) views models.IntegerField(default0, verbose_name浏览量) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) updated_at models.DateTimeField(auto_nowTrue, verbose_name更新时间) class Meta: db_table article ordering [-created_at] indexes [ models.Index(fields[created_at]), ]几个字段设计的核心细节nullTrue表示数据库该字段允许为空blankTrue表示表单校验时允许不填。很多人分不清这两个参数一句话总结null 是数据库层面的blank 是校验层面的。字符串字段多用blankTrue加默认空值不要轻易设nullTrue否则查询会出现None和空字符串两种“空”的形态给业务逻辑添乱。on_delete参数在外键关系里必填它决定了关联对象被删除时当前对象怎么处理。CASCADE是最常用的级联删除但要注意删除一条分类会把所有文章也删掉如果这不是期望行为可以用PROTECT阻止删除或者SET_NULL把外键置空前提是该字段设了nullTrue。我一般在需要保护历史数据的场景用PROTECT避免手一抖把关联数据全删了。auto_now_add和auto_now有区别前者是第一次插入时自动设置时间且之后不再变化后者是每次修改记录时自动更新。这两个参数不要去手动赋值由 Django 维护。如果你需要自定义保存时间就别用这两个参数改成在save()方法里手动写。verbose_name会影响后台管理界面和错误提示建议所有字段都写上。它和中文说明没有必然关系但配合LANGUAGE_CODE zh-hans后后台界面看起来会友好很多。3.2 迁移命令的执行流程模型定义好后并不会立刻影响数据库必须要执行迁移命令。这套流程我记成三步改模型、生成迁移、应用迁移。改完模型后先执行python manage.py makemigrations这个命令会对比当前模型和历史迁移记录生成新的迁移文件到应用的migrations目录。如果你有多个应用可以指定应用名python manage.py makemigrations articles接着执行python manage.py migrate这个命令才真正把迁移文件里的变化同步到数据库。注意makemigrations只是生成“计划”migrate才是执行两个命令经常被搞混。新手最常见的错误是只执行了makemigrations然后查数据库发现表没变以为出 bug 了。迁移之后我习惯用下面几条命令确认状态python manage.py showmigrations python manage.py showmigrations articlesshowmigrations会列出所有迁移文件前面有[X]标记的表示已应用没有的就是还没执行。这个命令是排查“表为什么没建出来”的第一利器。还有一条很实用的命令python manage.py sqlmigrate articles 0001_initial它会打印出某个迁移文件对应的真实 SQL而不是直接执行。我在排查索引、外键约束这类问题时都会先看 SQL 确认是否符合预期。比如你担心某次迁移会不会把表 DROP 掉先用sqlmigrate看一遍心里就有底了。在项目上线后迁移文件就是数据库的“版本管理工具”。团队协作时不要手动去数据库里改表结构任何改表操作都要通过新增迁移文件完成。否则就会出现“你本地模型是新结构线上数据库还是旧结构”的分裂状态迁移校验一报错整条链路都堵住。当一个字段被删除但迁移历史里还引用着它时可能会出现迁移冲突。常见处理办法是找出冲突的迁移文件按依赖关系重新排序或者创建新的空迁移来“吸收”差异。不建议直接编辑已经应用过的迁移文件尤其是线上环境。除了结构迁移Django 还支持数据迁移。比如给新字段填充默认值或者把旧数据从一个表搬到另一个表都可以用RunPython实现# 在迁移文件里 from django.db import migrations def fill_default_category(apps, schema_editor): Category apps.get_model(articles, Category) for item in Category.objects.all(): item.level 1 item.save() class Migration(migrations.Migration): dependencies [] operations [ migrations.RunPython(fill_default_category), ]要注意数据迁移里建议用apps.get_model获取模型而不是直接 import model 文件因为迁移执行时模型结构可能还没到最新状态apps.get_model能拿到的是迁移历史节点对应的模型版本。3.3 常用 ORM 查询与性能优化ORM 查询是后端编码里频率最高的操作之一。日常写业务时最常用的查询套路有这些# 获取所有 Article.objects.all() # 过滤 Article.objects.filter(category_id1, title__icontainsDjango) # 排除 Article.objects.exclude(statusdraft) # 取单条不存在会报 DoesNotExist Article.objects.get(id1) # 拿第一条/最后一条 Article.objects.first() Article.objects.last() # 只取某些字段返回字典列表 Article.objects.values(id, title) # 只取某些字段返回元组列表 Article.objects.values_list(id, title) # 统计 Article.objects.filter(category_id1).count()filter里支持很多字段查询条件比如__contains是模糊匹配__icontains是不区分大小写模糊匹配__in是范围查询__range是区间查询__isnull判断是否为空。这些符号刚开始容易记混其实规律很统一双下划线后面跟的是查询类型。再复杂一点的动态查询常用Q对象和F表达式from django.db.models import Q, F # 多条件 OR Article.objects.filter(Q(title__icontainspython) | Q(content__icontainspython)) # 字段间比较比如浏览量大于点赞数 Article.objects.filter(views__gtF(likes))F表达式的作用是在数据库层面直接引用某字段的值而不是先把数据取到 Python 里再比较既快又避免并发下读到旧值。比如文章浏览量 1写成Article.objects.filter(id1).update(viewsF(views) 1)是原子操作不会因为两个人同时点击把增量覆盖掉。性能优化里最经典的问题是 N1 查询。下面这段代码很容易写出articles Article.objects.all() for article in articles: print(article.category.name)每次取article.category都会发一条 SQL100 篇文章就是 101 条查询。解决方式是用select_related它在查询时直接通过 JOIN 把外键关联对象一起取出来articles Article.objects.select_related(category).all()对于反向关联、多对多这类无法用 JOIN 简单拿下的场景用prefetch_relatedcategories Category.objects.prefetch_related(articles).all()判断该用哪个有一个简单的经验select_related适合“我通过当前对象马上拿单个外键对象”的情况prefetch_related适合“我要拿到某个对象的多个关联对象”的情况。prefetch_related本质上是先查主表再根据主表 id 批量查关联表然后在内存里完成映射避免一条条查询。3.4 数据导入导出命令Django 提供了内置的序列化工具可以把数据导出成 JSON 或 YAML再用命令导回来。这在环境迁移、数据备份、测试数据准备时很有用。# 导出指定应用的全部数据 python manage.py dumpdata articles articles.json # 只导出部分数据 python manage.py dumpdata articles.Article --pks 1,2,3 articles_123.json # 导入数据 python manage.py loaddata articles.jsondumpdata导出的是模型对应的序列化数据默认包含所有字段。loaddata导入时会按主键去重如果主键冲突可以选择--ignorenonexistent忽略不存在的字段。我在迁移环境时最常用的套路是先把旧环境dumpdata导出再到新环境loaddata然后清理掉 session、log 这类没用的数据。要注意如果两家数据库的字符集、表结构不一致数据导出导入后可能对不上所以导出前先确认好模型版本一致。4. 视图与路由业务逻辑的出口4.1 URL 路由配置Django 的 URL 路由本质是一张映射表把用户请求的 URL 匹配到对应的视图函数。新版用path和re_path老版本里常见的url已经不太推荐了。我在新项目里基本只用path除非遇到特别复杂的正则需求。根路由文件是项目配置包下的urls.py一般长这样from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(api/, include(articles.urls)), ]应用内再建一个urls.pyfrom django.urls import path from . import views urlpatterns [ path(, views.article_list, namearticle_list), path(int:pk/, views.article_detail, namearticle_detail), ]int:pk这种写法是路径转换器Django 会自动把这一段 URL 参数解析成整数然后作为pk关键字参数传给视图。如果只想捕获任意字符串用str:name如果你想捕获路径分隔符用path:url_path。这些转换器既让路由清晰又省了在视图里手动正则匹配的麻烦。命名 URL 是个好习惯我给每个路由都加name参数然后在视图或模板里通过reverse反向解析from django.urls import reverse url reverse(article_detail, args[1])这样做的好处是URL 结构改动时只要路由里的 name 不变代码里的引用就不用跟着改。我在项目后期重构 URL 时经常靠这个节省大量时间。路由顺序也有讲究。Django 按照urlpatterns里从上到下的顺序匹配第一个命中就停止。所以具体路由要放在模糊路由前面。比如path(int:pk/, ...)如果在path(article/new/, ...)前面article/new会被前一个规则匹配成 pk 为字符串的请求直接 404。4.2 函数视图与装饰器函数视图是最直观的写法用一个函数接收request返回HttpResponse或JsonResponse。基础例子from django.shortcuts import render, get_object_or_404 from django.http import JsonResponse from django.views.decorators.http import require_http_methods from django.contrib.auth.decorators import login_required login_required require_http_methods([GET, POST]) def article_detail(request, pk): if request.method POST: # 处理提交 return JsonResponse({status: ok}) article get_object_or_404(Article, pkpk) return render(request, articles/detail.html, {article: article})这里有几个常用装饰器每个都解决一个具体问题login_required要求用户必须登录未登录时会跳转到登录页或返回 302。这在内部管理系统里几乎是必备的。require_http_methods([GET, POST])限制请求方法比如某个接口只允许 POST其他请求直接返回 405 Method Not Allowed比你在视图里手动判断request.method更规范。使用render时Django 会自动去 templates 目录下找模板文件。get_object_or_404等价于get加DoesNotExist异常处理查不到就返回 404比手动 try-except 简洁得多。开发 API 时我习惯统一返回JsonResponse。它默认会把字典转成 JSON 字符串并且设置正确的 Content-Type。如果你要返回列表JsonResponse(data, safeFalse)才能传非字典对象。这个safeFalse是新手的常见报错点。CSRF 验证是 Django 给表单类 POST 请求提供的防护机制。普通后端页面里的表单需要带{% csrf_token %}模板标签否则提交会报 403。如果你写的接口设计为纯 API又不想麻烦每个客户端都处理 CSRF Token可以在视图上加csrf_exempt。但我强烈建议仅对确实需要的外部回调接口使用内部接口尽量保留 CSRF 防护多一层安全就多一分安心。4.3 类视图Django 的函数视图能解决大部分问题但 CRUD 场景的代码会写得很重复列表查询、分页、详情查询、表单校验、重定向……几乎都是复制粘贴。类视图Class-Based ViewCBV把常见的模式抽象出来写出来的代码更少。最常用的是ListView和DetailViewfrom django.views.generic import ListView, DetailView from .models import Article class ArticleListView(ListView): model Article template_name articles/list.html context_object_name articles paginate_by 10 ordering [-created_at] class ArticleDetailView(DetailView): model Article template_name articles/detail.html context_object_name articleListView默认会把当前模型的所有对象传给模板。如果你不想用默认的object_list变量名就设置context_object_name模板里直接用你想用的名字这样更符合阅读直觉。paginate_by开启分页后模板里会自动获得page_obj通过page_obj.has_next、page_obj.has_previous可以渲染上一页下一页。CreateView和UpdateView则用来处理表单提交from django.views.generic.edit import CreateView, UpdateView from django.urls import reverse_lazy from .models import Article class ArticleCreateView(CreateView): model Article fields [title, content, category] template_name articles/form.html success_url reverse_lazy(article_list)我强调一点使用 CBV 时success_url最好用reverse_lazy而不是reverse。因为reverse在模块加载时就会执行路由解析如果此时 URL 配置还没加载完可能抛出异常reverse_lazy会延迟到真正需要时才解析更稳妥。CBV 和 FBV 怎么选我的经验是简单接口、逻辑复杂的接口用 FBV 更灵活调试也直观标准的列表、详情、创建、更新场景用 CBV 能省一半代码。项目里两种方式混用完全没问题别为了统一而把简单的接口硬写成 CBV。4.4 请求生命周期与中间件理解 Django 的请求生命周期能帮你快速定位很多奇怪的报错。一次请求大致经过这几个阶段客户端发请求 → Web 服务器Nginx 或 Django 自带服务器接收 → 经过中间件链 → URL 路由匹配 → 视图函数处理 → 返回响应 → 再经过中间件链返回给客户端。中间件是 Django 非常强大的机制它相当于在请求前后插入的钩子。settings.py里的MIDDLEWARE列表就是中间件链顺序不能随便调。比如CommonMiddleware负责 URL 规范化CsrfViewMiddleware负责 CSRF 校验AuthenticationMiddleware负责把用户信息绑定到 request 上。调试时如果某个中间件抛了异常后续的中间件就不会执行。我看到很多 500 报错其实是中间件里自定义逻辑写得有问题而不是视图的问题。排查方法是看堆栈显示异常发生在哪个中间件类一般在process_view或process_request方法里。如果你需要统计每个接口的耗时自己写一个中间件也很简单import time class RequestTimingMiddleware: def __init__(self, get_response): self.get_response get_response def __call__(self, request): start time.time() response self.get_response(request) duration time.time() - start response[X-Request-Time] f{duration:.3f}s return response注册到MIDDLEWARE后每个响应头都会带上耗时排查慢接口非常有用。5. 模板与静态文件5.1 模板语法与模板继承Django 的模板系统语法简单上手很快。核心语法有{% %}是逻辑标签{{ }}是变量输出{# #}是注释。变量输出示例h1{{ article.title }}/h1 p分类{{ article.category.name }}/p p浏览量{{ article.views }}/p模板里的变量自动支持属性访问article.category.name会先尝试取属性再尝试字典取值这个查找顺序和 Python 不太一样新手可能觉得灵活但也会掩盖数据结构的类型问题。模板循环和判断{% for article in articles %} {% if article.views 1000 %} span classhot热门/span {% else %} span普通/span {% endif %} {% empty %} p列表为空/p {% endfor %}模板继承是后台系统里最有价值的功能。我做一个管理后台时把公共的侧边栏、顶栏、底部脚本放在base.html!DOCTYPE html html head meta charsetutf-8 title{% block title %}默认标题{% endblock %}/title /head body header公共顶栏/header main {% block content %}{% endblock %} /main footer公共底部/footer /body /html子模板只需要写{% extends base.html %} {% block title %}文章列表{% endblock %} {% block content %} ul {% for article in articles %} li{{ article.title }}/li {% endfor %} /ul {% endblock %}这样每个页面只维护自己差异化的部分整体结构永远保持一致。多人协作时公共部分改一处全站生效比复制粘贴强壮太多。模板过滤器常用|date、|length、|default、|safe。比如p发布时间{{ article.created_at|date:Y-m-d H:i }}/p p标题长度{{ article.title|length }}/p p摘要{{ article.summary|default:暂无摘要 }}/p|safe会告诉 Django 这个变量是可信的 HTML不做转义直接输出。使用它时要格外小心如果变量里包含用户输入的内容相当于给 XSS 攻击开了门。比如用户提交了一段script代码你用|safe输出浏览器就会执行。我的原则是除非明确知道内容是安全的否则永远不要给用户输入的数据加|safe。5.2 静态文件收集与部署开发环境里浏览器访问STATIC_URL下的文件时Django 会自动去STATICFILES_DIRS目录找。但生产环境通常不会让 Django 直接服务静态文件而是用 Nginx 或 CDN 处理。部署时有一个必做命令python manage.py collectstatic这个命令会把项目里所有应用下的 static 目录、STATICFILES_DIRS里的文件全部复制到STATIC_ROOT指定的目录。Nginx 只需要把/static/请求映射到这个目录就行了。如果项目不想单独配 Nginx或者机器上不方便部署独立静态服务可以用whitenoise中间件。它让 Django 在生产模式也能直接服务静态文件简单项目部署很省事。配置方式是在MIDDLEWARE里加一行再把STATICFILES_STORAGE改成它的存储类。但并发量较高的项目我还是建议静态文件走 Nginx 或 CDN让 Django 专注处理动态业务。上传文件相关的MEDIA_ROOT在部署时也要配套处理。用户上传的图片存在项目目录外的media文件夹Nginx 单独配置/media/的 alias 指向它。我踩过的坑是本地开发能上传部署后上传失败一查发现是MEDIA_ROOT的路径权限不对进程没有写权限。解决方式就是确保运行项目的用户对该目录有读写权限或者把目录挂载到专门的存储上。6. 自定义命令与调试测试6.1 自定义 management commandDjango 项目跑久了会发现很多重复工作其实可以交给命令完成清理过期数据、给用户发提醒邮件、同步第三方平台数据、定时生成报表……这些任务如果靠手工执行不仅累还容易漏。Django 的自定义命令机制可以让你通过manage.py 命令名直接调用自己的脚本。创建自定义命令的第一步是在应用目录下建好固定结构your_app/ management/ __init__.py commands/ __init__.py clean_expired.py注意management和commands目录下都要有__init__.py文件否则 Django 识别不到。命令文件名就是命令名这里对应的是python manage.py clean_expired。命令文件的核心代码from django.core.management.base import BaseCommand, CommandError from django.utils import timezone from myapp.models import Token class Command(BaseCommand): help 清理过期 token def add_arguments(self, parser): parser.add_argument(--days, typeint, default7, help保留天数) def handle(self, *args, **options): days options[days] cutoff timezone.now() - timezone.timedelta(daysdays) deleted_count, _ Token.objects.filter(created_at__ltcutoff).delete() self.stdout.write(self.style.SUCCESS(f清理完成共删除 {deleted_count} 条记录))add_arguments用 argparse 的方式定义参数handle是命令真正执行的入口。self.stdout.write和self.style.SUCCESS是标准输出封装支持颜色提示比动辄print更像命令行工具。自定义命令还可以和系统定时任务结合。比如 Linux 上用 crontab 每天凌晨跑一次清理0 3 * * * cd /path/to/project source venv/bin/activate python manage.py clean_expired --days 1这样重复工作就不需要人肉惦记了。调试命令时我经常在handle里先打印参数确认传参是否符合预期再执行实际逻辑。6.2 调试工具与日志Django 开发时的调试利器是django-debug-toolbar。安装后在settings.py里添加配置即可在页面右侧显示一个调试面板能看到当前请求执行了多少条 SQL、每一条耗时多久、模板渲染时间、缓存命中情况等。配置要点INSTALLED_APPS [ # ... debug_toolbar, ] MIDDLEWARE [ debug_toolbar.middleware.DebugToolbarMiddleware, # ... ] INTERNAL_IPS [127.0.0.1]注意INTERNAL_IPS要包含你本机访问的 IP否则调试面板不显示。如果用了远程开发环境可以把开发机 IP 加进去或者临时设置SHOW_TOOLBAR_CALLBACK为恒真。但生产环境绝对要关掉调试工具栏会暴露接口性能和 SQL 语句不该出现在公网。日志配置是后端排查问题的另一只眼睛。settings.py里可以这样配一个基础的日志输出import os LOGGING { version: 1, disable_existing_loggers: False, handlers: { console: { class: logging.StreamHandler, }, file: { class: logging.FileHandler, filename: BASE_DIR / logs/app.log, encoding: utf-8, }, }, root: { handlers: [console, file], level: INFO, }, loggers: { django.db.backends: { handlers: [console], level: DEBUG, }, }, }把django.db.backends的日志级别设为 DEBUG就会在控制台打印每条 SQL 语句。这个开关在排查慢查询时非常有用能直接看到是哪条 SQL 耗时最长。日志文件要注意按天切割不然跑几个月文件会奇大无比我一般用logging.handlers.TimedRotatingFileHandler当 handler。6.3 自动化测试基础后端项目如果完全没有测试改一个字段都可能让另外十个接口悄悄挂掉。Django 自带的测试框架基于unittest不需要额外安装库。最基础的测试长这样from django.test import TestCase from django.urls import reverse from myapp.models import Article class ArticleViewTest(TestCase): def setUp(self): self.article Article.objects.create(title测试文章, content内容) def test_article_list_status(self): resp self.client.get(reverse(article_list)) self.assertEqual(resp.status_code, 200) self.assertContains(resp, 测试文章)self.client是 Django 提供的测试客户端可以模拟 GET、POST 请求。跑测试的命令python manage.py test只跑某个应用的测试python manage.py test myapp写测试时不用覆盖所有场景优先覆盖核心业务逻辑和容易回归的边界情况比如用户没有权限访问某个接口时返回 403、创建文章时没有传必填字段时提示错误等。测试的目的不是为了追求覆盖率数字而是让你在改代码时敢继续往下写跑一遍测试就知道有没有破坏旧行为。7. 常见问题排查速查表7.1 高频报错对照表我在项目维护过程中整理了一些出现频率最高的报错下面这张表基本能覆盖大部分日常问题。报错现象常见原因解决思路400 Bad RequestALLOWED_HOSTS 没配置或配错检查 DEBUGFalse 后是否填了域名/IP403 ForbiddenCSRF 校验失败表单加上{% csrf_token %}或对纯 API 接口谨慎使用csrf_exempt404 Not Found路由写错或视图不存在用resolve或手动访问确认 URL检查路由匹配顺序405 Method Not Allowedrequire_http_methods限制之外的方法请求确认接口支持的请求方法500 Internal Server Error视图代码异常、模型字段不存在看服务日志和堆栈重点区分调试模式和生产模式Table xxx doesnt exist迁移未执行执行migrate用showmigrations确认状态Column xxx doesnt exist模型新加字段但未迁移执行makemigrations和migrateField id expected a numberURL 参数被解析成字符串或类型不匹配确认路由转换器用的是int:pkConnection refused数据库服务没启动或 HOST/PORT 配错检查数据库连接配置用客户端工具先连通试试中文乱码MySQL 字符集与连接字符集不一致统一建库和连接为 utf8mb4静态文件 404collectstatic 未执行或 Nginx 路径没配对执行collectstatic检查STATIC_ROOT和 Nginx alias如果报错是后台管理的OperationalError: no such table: django_session基本就是初始迁移没跑python manage.py migrate一下就好。这个错我每次新建环境都会见到属于“新手的第一课”。7.2 避坑经验清单除了对照表还有一些我自己踩过多次的坑值得单独列出来。第一不要在数据库层面手动改表。很多人图快直接用 SQL 改了线上表结构然后 Django 迁移一执行就报“字段已存在”或“字段不存在”的冲突。正确做法是永远通过makemigrations和migrate来改表数据库结构以迁移历史为准。第二User模型要早做扩展。Django 自带用户模型能满足基础登录但如果业务需要手机号登录、昵称、头像等字段项目早期就应该通过AbstractUser自定义用户模型配置到AUTH_USER_MODEL。一旦项目上线再换用户模型迁移会非常痛苦涉及大量外键引用变动。我吃过一次亏之后每个新项目第一件事就是扩展用户模型。第三上传文件路径不要写死。用户上传的文件名如果直接用用户输入既容易产生路径冲突也可能埋下安全问题。建议用工具库生成随机文件名并把文件按日期分目录存储。具体到 Django 后端upload_to参数可以传一个函数自己控制路径def upload_path(instance, filename): ext filename.split(.)[-1] return fuploads/{timezone.now().strftime(%Y/%m)}/{uuid4().hex}.{ext}第四多个环境之间requirements.txt要定期对齐。我在维护旧项目时经常遇到本地装的依赖版本很新线上还是老版本导致某些新语法在线上报错。建议部署时用虚拟环境全量重装而不是增量升级。第五runserver不是万能调试器。有时修改了settings.py里的配置但服务没有冷重启新配置没生效。比如改了MEDIA_ROOT后上传位置还是老目录就是因为开发服务器没有完全重启。遇到诡异问题时先重启服务再排查这是成本最低的一步。写在最后的一点体会这份 Django 后端命令和代码总结最初只是我自己存储在项目仓库里的一个维护文档每次排查完一个奇怪问题就往里补充一条后来发现它比大部分现成教程都好用因为里面的每一条记录都对应一次真实的踩坑过程。尤其是迁移命令、CSRF、静态文件 404、数据库长连接这几类问题几乎是每个项目都会遇到的“老朋友”。我个人最大的体会是Django 的上手门槛并不高但能让你真正高效起来的是你对自己项目沉淀下来的那份“命令记忆”。遇到报错别急着改代码先想想是配置问题还是迁移问题用showmigrations、sqlmigrate、日志面板把现场摸清楚再动手往往几分钟就能定位。希望这份整理能帮你少走一些弯路把时间花在真正的业务逻辑上。
RELATED

相关推荐

零侵入修复时间乱序:乱序窗口与水位线方案详解

零侵入修复时间乱序:乱序窗口与水位线方案详解

前阵子我排查一个线上问题,现象很典型:写入链路的吞吐完全正常,Redis 和数据库的压力也不大,但下游时间窗口报表的数据就是不断“穿越”——曲线不是平滑往前走,而是每隔一阵就跳回几秒前,然后再跳回来。查…

📅 2026/10/18 20:49:29
vmtk血管建模原理与工程实践:从医学影像到计算几何

vmtk血管建模原理与工程实践:从医学影像到计算几何

简介:vmtk(血管建模工具包)是一套面向医学图像处理领域的开源C/Python混合架构工具集,专为临床医生、生物医学研究人员及医学影像算法开发者设计,用于解决CT/MR影像中血管结构的3D重建、几何分析、表面数据处理与网格生…

📅 2026/10/18 20:49:29
5G SA信令分析全流程实战:从开机入网到切换完成

5G SA信令分析全流程实战:从开机入网到切换完成

简介:面向5G网络优化与信令分析人员,这份指导书以端到端信令流程为主线,系统梳理终端从开机入网到会话建立、切换及NAS信令的完整过程,可帮助读者快速建立5G信令分析框架,解决网优现场常见流程定位问题。资源为单个doc…

📅 2026/10/18 20:49:29
MORE NEWS

更多资讯

📰

Effective TypeScript:从类型系统到工程实践的取舍之道

简介:《Effective TypeScript精要》面向具备一定基础、希望进阶的中高级前端与全栈开发者,聚焦类型系统、类型设计、类型推断与类型级编程等核心主题,通过83条具体建议帮助读者写出更安全、可维护的TypeScript代码。内容结合真实工程场景&…

📰

PySide6 + LibreOffice 实现 Word 批量转 PDF 桌面工具

简介:这份资源面向需要批量处理文档格式转换的办公人员与Python初学者,提供一套基于PySide6与docx2pdf库实现的Word转PDF桌面工具。它通过图形化界面替代命令行操作,用户只需选择待转换的Word文件并点击触发,即可在指定位置生成PD…

📰

ArcGIS Pro AI助手插件2.1接入DeepSeek:用大模型自动生成arcpy代码

简介:ArcGISProAI助手插件2.1是一款面向GIS与地质数据处理从业者的效率增强工具,通过接入Deepseek技术提升空间数据分析的速度与准确性,适用于自然资源管理、城市规划、石油天然气勘探等场景。插件仅兼容ArcGIS Pro 3.3与GeoScene Pro 5.1两个…

📰

开源在线预览服务部署集成实战:文件转换原理与避坑指南

简介:kkFileView 4.4.0-beta 压缩包是知名开源在线文件预览工具的测试版本,适合需要自建文档在线预览、将办公文档与 PDF 在浏览器中直接展示的开发者或运维人员。压缩包内共收录 2000 个文件,打包体积约 588.76MB,文件类型集中在…

📰

CefSharp 125 x64 H.264 发布包替换与验证实战

简介:本资源为 CEF 125.0 系列 x64-H264 版本发布包,基于 Chromium 6422 分支,面向使用 CefSharp 125 系列进行 WPF 或 C# 桌面客户端开发的工程师。它解决了在 .NET 4.6.2 及以上、Win10/Win11 环境中集成 Chromium 内核并支持 H264 视频播放…

📰

从参数化建模到七彩渐变:3D打印鹦鹉螺魔方制作实战指南

先说结论:“七彩鹦鹉螺魔方”这个项目,我前后做了两版才最终满意。它不是一个能拧的竞速魔方,而是一只以鹦鹉螺天然螺旋结构为骨架、用魔方模块化语言来做分块表达的多彩立体装置。你可以把它当成桌面摆件、把玩件,也可以做成带磁…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬