资讯动态

Django 后端命令与代码组织:从 manage.py 到部署实战

发布时间:2026/10/20 13:47:49 来源:尧图企业网站定制
接手一个维护三年的Django项目时我习惯把第一条命令敲成python manage.py check --deploy。这个命令不会帮你写业务代码但能在项目启动前把配置里的安全隐患、错误设置全部摆到桌面上比盲目写一百行业务还踏实。也正是从那次之后我开始有意识地把 Django 后端命令和代码组织方式总结成笔记方便自己和团队复用。很多人入门时只记住startproject和runserver实际上手维护或重构时会发现Django 的绝大多数能力都藏在manage.py和框架约束的代码结构里。命令只是入口真正值钱的是命令背后帮你管理的那层状态。这篇笔记的读者画像大概是两类一类是刚把 Django 官方教程跑完想拿到真实项目里开发的同学另一类是写过一阵子 Django但总在迁移、静态文件、部署命令上反复踩坑的人。我会尽量把命令和对应的代码组织方式放一起讲因为命令是骨架代码才是血肉。下面直接进入正题。1. 项目骨架命令从 startproject 到 manage.py 的日常组合拳1.1 django-admin 和 manage.py 到底有什么区别Django 安装后会提供两个入口django-admin和项目内的manage.py。很多教程直接让你用django-admin startproject创建项目但之后就切换到manage.py不少人没搞明白为什么要这么折腾。django-admin是一个全局命令它不依赖任何项目环境主要用来创建项目、创建应用也可以查看当前 Django 版本信息。而manage.py是生成在项目根目录下的一个 Python 脚本它在django-admin的基础上多做了两件事把项目根目录加入 Python 模块搜索路径以及通过DJANGO_SETTINGS_MODULE环境变量指定当前使用的配置模块。也就是说当你运行python manage.py runserver时Django 知道该加载哪套 settings也知道项目里有哪些 app。实际操作中我的习惯是创建项目、创建 app 用django-admin或manage.py都可以但如果是项目已经存在一定用manage.py startapp避免老项目里出现奇怪的应用路径问题。项目运行期间的所有维护动作包括启动开发服务器、执行迁移、进入交互式 shell、创建超级用户、检查项目配置等全部走manage.py。这两者选错的直接后果往往不是报错而是模块导入混乱。比如你在一台服务器上同时部署了多个 Django 项目全局django-admin无法自动感知当前目录属于哪个项目如果手动设错了DJANGO_SETTINGS_MODULE很可能把 A 项目的配置加载到 B 项目上数据写错库这种事故就是这么来的。1.2 从零开始建项目时我会敲的一组命令假设我们要在一个全新目录里搭建一个名为blog的项目最稳妥的顺序是# 建议先用虚拟环境隔离依赖 python -m venv .venv source .venv/bin/activate # Windows 下为 .venv\Scripts\activate # 安装 Django pip install django # 创建项目注意最后有一个点 django-admin startproject blog . # 创建一个用户模块相关的 app python manage.py startapp accounts # 把当前依赖导出到 requirements.txt pip freeze requirements.txt注意startproject blog .最后那个点表示在当前目录生成项目文件而不是再套一层blog/blog。很多初学者漏掉点结果项目根目录变成两层路径变长不说部署时还容易找不到 wsgi 模块。创建完accountsapp 之后有一个经常被忽略的步骤把accounts注册到项目的INSTALLED_APPS里。否则你写了模型、跑了迁移Django 默认不会为这个 app 建表数据会莫名其妙不知道写到哪里。接下来的常用命令可以整理成一张表命令作用使用频率python manage.py runserver启动开发服务器日常python manage.py startapp app新建子应用每个模块一次python manage.py makemigrations app根据模型生成迁移文件每次改模型后python manage.py migrate将迁移应用到数据库每次同步表结构python manage.py createsuperuser创建管理员账号项目初始化时python manage.py shell进入带项目环境的 Python REPL写脚本/排查数据python manage.py check检查项目配置是否有错误改配置后python manage.py collectstatic收集静态文件到指定目录部署时python manage.py runserver 0.0.0.0:8000让本机局域网也能访问联调时我自己比较推荐把shell当成日常排错的瑞士军刀。它和普通python交互式的区别是启动时已经加载了 Django settings可以像写脚本一样直接导入项目里的模型类来做查询。遇到复杂数据问题我基本不直接改数据库而是在shell里写一段脚本验证后再执行。1.3 项目目录结构与 settings 拆分新建的 Django 项目默认结构是manage.py blog/ __init__.py settings.py urls.py asgi.py wsgi.py但真实项目不会只用一个settings.py。开发环境和生产环境的数据库、调试开关、静态文件路径完全不同一份配置要么导致开发时小心翼翼要么导致生产环境暴露调试信息。我常用的做法是把settings.py改成包拆成base.py、dev.py、prod.pyblog/settings/ __init__.py base.py # 公共配置 dev.py # 开发环境 prod.py # 生产环境然后在项目根目录放一个.env文件把数据库密码、密钥、调试开关这类环境相关的内容放到环境变量里而不是写死在代码里。manage.py本身默认读的是blog.settings如果你拆包了需要在使用命令时指定模块python manage.py runserver --settingsblog.settings.dev更好的做法是直接在.env里设置DJANGO_SETTINGS_MODULEblog.settings.dev然后配合类似python-decouple或django-environ的库读取。这样每次敲命令不用额外带参数部署时只需切换环境变量。我在一次维护经历里吃过不拆分 settings 的亏某项目直接把 DEBUG 开在生产环境导致线上异常页面对所有人可见用户访问某些错误 URL 时能看到完整的堆栈信息、甚至服务器文件路径。用check --deploy结果第一项就报了 SECRET_KEY 暴露的问题。从那以后新项目第一步就是拆分配置这不是什么高深技巧就是减少风险的习惯。2. 数据模型迁移makemigrations、migrate、sqlmigrate 背后的工作流2.1 迁移文件的本质模型状态快照Django 的迁移系统是少有的、让多人协作项目不至于被表结构变更拖垮的设计。它的本质是对模型字段状态做增量记录。当你执行makemigrations时Django 会对比当前模型定义和历史迁移文件找出差异生成一个新的迁移文件migrate则把这些迁移按依赖关系依次应用到数据库。迁移文件存放在每个 app 的migrations目录下核心就是一个类继承自django.db.migrations.Migrationfrom django.db import migrations, models class Migration(migrations.Migration): dependencies [ (accounts, 0001_initial), ] operations [ migrations.AddField( model_nameuserprofile, namenickname, fieldmodels.CharField(blankTrue, max_length32), ), ]dependencies表示当前迁移依赖哪些之前的迁移Django 正是靠这个链来保证迁移顺序。所以在多个 app 之间如果存在模型外键引用跨 app 迁移的依赖关系也会自动生成。日常开发中我建议把迁移文件当作代码一样纳入版本管理。很多人图省事直接在数据库里手动改表结构然后项目代码和线上库就慢慢错开了。等到哪天重新拉一份环境初始化数据迁移文件缺失导致建表失败悔不当初。2.2 改字段、加约束、删字段的推荐工作流当我需要修改一个模型时流程基本是固定的编辑models.py改完字段运行python manage.py makemigrations app_label只针对改动的 app 生成迁移运行python manage.py sqlmigrate app_label migration_name预览即将执行的 SQL确认 SQL 没问题后运行python manage.py migrate提交迁移文件和模型文件到版本库。sqlmigrate是我特别喜欢的一个命令它不会真的执行 SQL只是把迁移对应的 SQL 打出来。这个命令在高风险操作前几乎是安全网。比如要把一个字段从CharField改成整型Django 生成的迁移可能涉及ALTER COLUMN在数据量大的表上执行会锁表。先看 SQL可以预判风险再决定是直接迁移还是分步骤凌晨执行。这里有一个很多人踩过的坑Django 对choices参数的修改并不会生成迁移文件。choices只影响表单验证和 Admin 的下拉选项数据库层面没有约束。如果你以为改个choices就算改完模型、跑一遍migrate就完事那么非重启 Django 进程不可生效。正确做法是改完choices后重启服务不需要迁移。2.3 数据迁移当普通字段不够用时有些模型变更不只是改字段类型还需要同时转换历史数据。比如某次业务重构需要把avatar_url一个文本字段换成一张独立的附件表。直接删字段会丢失信息光加字段又没法把已有 URL 搬过去。这时候就要用到数据迁移。数据迁移的写法是from django.db import migrations def forwards(apps, schema_editor): Profile apps.get_model(accounts, Profile) Attachment apps.get_model(accounts, Attachment) for profile in Profile.objects.all(): if profile.avatar_url: Attachment.objects.create(ownerprofile, urlprofile.avatar_url) def backwards(apps, schema_editor): # 反向操作一般用于回滚 pass class Migration(migrations.Migration): dependencies [ (accounts, 0012_add_attachment_model), ] operations [ migrations.RunPython(forwards, backwards), ]注意数据迁移中不能用from accounts.models import Profile因为模型类可能已经在后续迁移中发生过变化而迁移必须基于历史状态执行。这里必须用apps.get_model()取到的是“当前迁移时间点”的模型字段集合和历史保持一致。这也是很多新手最容易写错的位置。2.4 迁移冲突与合成迁移多人协作时极容易出现两个人分别在各自分支里给同一个 app 的最终状态生成了不同的迁移文件。合并代码时migrate就会提示检测到迁移冲突。解决方式python manage.py makemigrations --merge--merge会生成一个新的迁移把两条冲突支线合并到一个依赖链上。合并时会有交互提示选择让新迁移依赖哪些旧迁移即可。要注意合并后先运行migrate确认数据库能正常同步再继续后续开发。我自己的习惯是提交代码前先makemigrations --check --dry-run检查是否还有未生成的模型变更。如果这个命令有输出说明模型和迁移不同步代码提交上去迟早出问题。3. 视图与 URL让请求真正“动”起来的代码组织方式3.1 函数视图还是类视图怎么选写视图时很多人纠结到底用 FBV函数视图还是 CBV类视图。我的判断标准很简单如果功能就是标准的展示列表、创建对象、更新删除这类 CRUD直接用 CBV 能省下大量重复代码如果业务里有复杂的分支逻辑、多个表单联动、前置校验则用 FBV 更直白。比如一个简单的列表页用类视图from django.views.generic import ListView from .models import Post class PostListView(ListView): model Post template_name blog/post_list.html context_object_name posts paginate_by 10 ordering -created_at这一个类就解决了查询、分页、模板变量命名三件事。视图代码只需要重写get_queryset来做过滤class PostListView(ListView): ... def get_queryset(self): qs super().get_queryset() keyword self.request.GET.get(q) if keyword: qs qs.filter(title__icontainskeyword) return qs而复杂的表单提交场景我倾向于 FBVfrom django.shortcuts import redirect, render from django.contrib import messages def register(request): if request.method POST: form RegisterForm(request.POST) if form.is_valid(): form.save() messages.success(request, 注册成功) return redirect(accounts:login) else: form RegisterForm() return render(request, accounts/register.html, {form: form})核心原则是不要为了“纯 CBV”而强行把逻辑塞进类里。代码是给人读的哪个形态读起来更顺畅就用哪个。3.2 URL 路由的解析顺序与命名空间URL 配置经常被低估。Django 从上到下匹配urlpatterns一旦命中第一个就不再往下执行所以顺序很重要。path用转换器自带的类型匹配比如int:pk、slug:slug比正则的re_path更安全推荐首选。跨 app 的 URL 我会用include加命名空间# 项目 urls.py from django.urls import include, path urlpatterns [ path(accounts/, include(accounts.urls, namespaceaccounts)), ]# accounts/urls.py from django.urls import path from . import views app_name accounts urlpatterns [ path(login/, views.login_view, namelogin), ]定义好命名空间以后在视图或模板里用reverse(accounts:login)或{% url accounts:login %}来反向解析 URL这样以后路径调整时不用满项目改链接。关于reverse还有一个容易迷惑的地方在模块导入阶段调用reverse可能因为 URLConf 还没加载而报错。如果确实要在模块顶层拼接 URL建议用reverse_lazy它就是为延迟解析设计的。3.3 一次请求经历哪些层调试时往哪看Django 请求生命周期大致是Web 服务器把请求交给 WSGI/ASGI 应用经过中间件栈URL 解析器找到视图视图处理完返回响应响应再经过中间件出口返回给客户端。调试时最有效的手段不是打印而是分层次看。第一层看中间件是否拦截请求比如登录校验在LoginRequiredMixin或自定义中间件里做了请求可能还没进视图就重定向了。第二层看 URL 是否能匹配如果看到 404优先检查app_name和name是否写对。第三层看视图内部逻辑此时可借助日志输出或调试库的 SQL 面板查看实际执行的查询。我常用的一个调试习惯是在settings/dev.py里配置django-debug-toolbar它能在开发页面右侧展示当前请求执行的 SQL、缓存、模板渲染过程。线上环境不会用它但开发期它几乎能把“数据没显示出来”的问题直接锁定到 SQL 层减少大量猜代码的时间。3.4 高频视图代码模式分页、搜索、表单处理分页是列表页最常遇到的问题。CBV 里paginate_by已经做了但如果你用 FBV可以这样写from django.core.paginator import Paginator from django.shortcuts import render def post_list(request): post_qs Post.objects.prefetch_related(tags).order_by(-created_at) paginator Paginator(post_qs, 10) page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, blog/post_list.html, {page_obj: page_obj})搜索场景里我建议将搜索条件封装成可复用的filterset或者直接在视图里写filter(**conditions)。关键点在于所有来自用户输入的字段务必用字段名白名单过滤避免用户通过order_by之类参数传入非法字段名导致数据库抛出异常。Django ORM 虽然做了参数化但传入非法字段名会暴露表结构信息很不安全。表单处理模式上最稳妥的是 GET 请求显示空白表单POST 请求校验并保存成功就重定向失败就携带原有数据返回模板。这个“PRG 模式”Post-Redirect-Get能有效防刷新重复提交。用 CBV 的FormView也自带这个语义。4. 模板、静态与媒体前端资源在 Django 里的正确管理姿势4.1 模板继承比重复 include 更好用模板渲染是 Django 很擅长的部分但模板用不好会变成维护负担。核心是设计好base.html把页头、页脚、导航栏这些公共部分放在基模板里用block留出口{% load static %} !DOCTYPE html html head meta charsetUTF-8 title{% block title %}默认标题{% endblock %}/title link relstylesheet href{% static css/base.css %} /head body header.../header main {% block content %}{% endblock %} /main footer.../footer {% block extra_js %}{% endblock %} /body /html子模板只需要覆盖content等 block保持整体结构一致。很多初学者习惯把导航栏拷贝到每个页面一旦需要改链接每个模板都要改一遍这个账早晚要还。include适合复用“小片段”比如一个文章的卡片组件、一个分页条它和block的定位不同。我在一个项目里见过把整段include当作 block 使用的做法结果页面之间上下文变量传得很隐晦出了问题很难查。我的建议是共享整体布局用block共享局部片段用include或自定义标签。4.2 collectstatic 命令和静态文件路径的约定开发环境里Django 自带的runserver会自动帮我们服务STATIC_URL指向的静态文件所以我们很少关心静态文件收集问题。到了生产环境通常由 Web 服务器处理静态文件而不是让 Django 进程占用资源。于是就需要collectstatic。这个命令做的事情很简单把所有 app 的static目录、以及STATICFILES_DIRS里配置的公共静态目录都复制到STATIC_ROOT指定的目录下。生产环境里的 Web 服务器配置为直接服务这个目录即可。我在部署时常用的命令是python manage.py collectstatic --noinput如果静态文件有过大规模更新我会加--clear清空旧文件后重新收集避免残留旧版本。但要注意--clear会先删除STATIC_ROOT下所有文件如果该目录同时被其他项目共用会造成事故。所以部署前要先确认STATIC_ROOT是独立目录。还有个高频问题为什么collectstatic后页面样式还是旧版多半是浏览器缓存或 Web 服务器缓存了静态文件。常见的缓解手段是给静态 URL 加版本参数link relstylesheet href{% static css/app.css %}?v202506014.3 媒体文件上传之后的文件去哪里了用户上传的文件不是静态资源Django 称之为 media。MEDIA_ROOT是文件的物理存放路径MEDIA_URL是访问路径的前缀。开发阶段如果你需要在页面预览上传图片需要在 urls 里临时挂载from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这段代码只能写在DEBUGTrue时执行否则生产环境会通过 Django 进程提供媒体文件性能极差。生产部署后媒体文件应该交给 Web 服务器直接服务Django 只负责响应业务请求。上传文件的处理上我最常忽略的坑是文件名。不同用户可能上传同名的1.jpg如果不上重命名文件会被覆盖。所以推荐在上传处理时用uuid或时间戳重新生成文件名import os import uuid from django.utils import timezone def upload_to_func(instance, filename): ext os.path.splitext(filename)[-1].lower() return fuploads/{timezone.now():%Y%m%d}/{uuid.uuid4().hex}{ext}模型字段里这样引用class Attachment(models.Model): file models.FileField(upload_toupload_to_func)4.4 过滤器与自定义模板标签让模板少写逻辑Django 模板语言刻意限制了逻辑能力比如不能直接调用带参数的方法也不推荐在模板里做大段判断。遇到需要格式化时间、根据状态显示颜色这类需求优先用内置过滤器{{ post.created_at|date:Y-m-d H:i }} {{ text|truncatechars:50 }}如果内置过滤器不够就写自定义模板标签。我在项目里经常写一个time_ago标签把时间显示成“3 分钟前”这种相对时间# app/templatetags/time_tags.py from django import template from django.utils import timezone register template.Library() register.filter def time_ago(value): delta timezone.now() - value if delta.days 0: return f{delta.days} 天前 if delta.seconds 3600: return f{delta.seconds // 3600} 小时前 if delta.seconds 60: return f{delta.seconds // 60} 分钟前 return 刚刚不过要注意templatetags 目录必须存在__init__.py文件否则 Django 找不到标签库模板加载时会直接抛异常。这是我见过的高频低级错误之一。5. Django Admin 的后台定制与命令联动5.1 用 list_display 和 search_fields 把后台变工作台Django Admin 是很多内部系统“零开发”的利器。注册模型后后台列表页默认只显示对象的__str__结果真要拿来日常运营还差得远。我一般会给实际业务模型配一套后台选项from django.contrib import admin from .models import Order admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display (order_no, user, status, amount, created_at) list_filter (status, created_at) search_fields (order_no, user__username) ordering (-created_at,) readonly_fields (order_no,)search_fields用user__username这种跨表写法后后台搜索框不仅能搜关联对象的字段还会自动生成带 JOIN 的查询比较方便。但如果关联表很大性能可能拖慢需要评估。list_display里也可以放自定义方法让后台显示自定义状态比如格式化金额admin.display(description金额) def amount_display(self, obj): return f¥ {obj.amount / 100:.2f}5.2 admin action给批量操作挂一个“后端按钮”后台列表页支持批量操作默认只有删除动作。如果要批量把订单标记为已发货可以自定义一个admin actionfrom django.contrib import admin from django.contrib.admin import action from .models import Order action(description标记为已发货) def mark_shipped(modeladmin, request, queryset): updated queryset.update(statusshipped) modeladmin.message_user(request, f已更新 {updated} 条订单)然后在OrderAdmin.actions列表中加入该方法。这个模式下运营同学不需要碰代码、数据库就能完成常见数据变更。而且queryset.update()是数据库层面的批量更新比循环单条save()高效得多。要注意的是update()不会触发模型的save()方法也不会更新auto_now字段所以涉及这些字段时需要额外处理。5.3 数据修复用 manage.py shell而不是直接改库生产环境的数据修复我强烈建议先用manage.py shell验证再落为脚本而不是直接登录数据库敲 SQL。理由是 SQL 绕过 Django 的信号、校验逻辑容易破坏数据一致性。比如要批量修正一个字段的内容可以这样python manage.py shell然后from orders.models import Order qa_set Order.objects.filter(statuspending) for o in qa_set: o.amount round(o.amount, 2) o.save()但更高效的是from orders.models import Order from django.db.models import F Order.objects.filter(statuspending).update(amount...)当逻辑复杂时比如要遍历并调用多个模型的方法用shell写一段脚本再一次性执行是安全的。执行前最好先开启一个事务出错后回滚避免数据改到一半。我自己的一个习惯是生产环境执行批量变更前先做逻辑导出的备份比如把相关主键和旧值导出到一个 CSV 文件再执行变更。这样即使出现不可预料的问题也能根据备份恢复。5.4 写一个自定义管理命令让定时任务有“入口”manage.py支持自定义命令这是把代码变成运维能力的关键。比如我们要每天清理过期的验证码可以新建一个 app 下的management/commands/clean_expired_secrets.py目录结构为accounts/ management/ __init__.py commands/ __init__.py clean_expired_secrets.py里面写from django.core.management.base import BaseCommand from django.utils import timezone from accounts.models import SecretCode class Command(BaseCommand): help 清理过期的验证码 def handle(self, *args, **options): deleted, _ SecretCode.objects.filter( expires_at__lttimezone.now() ).delete() self.stdout.write(self.style.SUCCESS(f清理完成共删除 {deleted} 条))定义好后就可以执行python manage.py clean_expired_secrets还可以用add_arguments加入参数比如--days指定保留天数def add_arguments(self, parser): parser.add_argument(--days, typeint, default30, help保留天数)编写自定义命令时容易踩的坑BaseCommand.handle方法里不要写业务校验以外的复杂逻辑尽量调用 service 层函数这样命令和视图代码可以复用同一套逻辑避免“脚本只有一份、逻辑写两遍”的坏味道。6. API 开发场景下的命令与代码总结DRF 篇6.1 配置 DRF 和 Serializer 的常见姿势现在的 Web 项目很少不做 API。Django REST Framework 是 Django 生态里最主流的 API 框架。安装以后要记得把rest_framework注册到INSTALLED_APPS并且经常需要设置默认的渲染类和认证类。一个模型对应的序列化器一般这样写from rest_framework import serializers from .models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model Post fields (id, title, content, created_at) read_only_fields (id, created_at)再用ViewSet和Router组合就能快速提供一套接口from rest_framework import viewsets from .models import Post from .serializers import PostSerializer class PostViewSet(viewsets.ModelViewSet): queryset Post.objects.all() serializer_class PostSerializer在 urls 里from rest_framework.routers import DefaultRouter from .views import PostViewSet router DefaultRouter() router.register(posts, PostViewSet) urlpatterns router.urlsDefaultRouter会自动生成列表、详情、以及可浏览 API 的根路径省掉手写GET/POST/DELETE路由的麻烦。6.2 权限、限流、过滤排序的配置思路实际项目中API 不能裸奔。DRF 的权限系统通过permission_classes控制。比如只允许登录用户创建from rest_framework.permissions import IsAuthenticatedOrReadOnly class PostViewSet(viewsets.ModelViewSet): permission_classes [IsAuthenticatedOrReadOnly]限流可以写在 settings 里REST_FRAMEWORK { DEFAULT_THROTTLE_RATES: { anon: 100/day, user: 1000/day, }, }对于列表接口过滤、排序几乎是刚需。我会配合django-filter来配置from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import SearchFilter, OrderingFilter class PostViewSet(viewsets.ModelViewSet): filter_backends [DjangoFilterBackend, SearchFilter, OrderingFilter] filterset_fields [status] search_fields [title, content] ordering_fields [created_at, updated_at]这样前端就可以通过?statuspublishedsearchdjangoordering-created_at参数组合查询Django 会把这些参数解析成 ORM 查询条件免去了手写大量if request.GET.get(...)的重复代码。6.3 用管理命令导出接口 schema团队协作时前端和后端往往需要一份接口文档。DRF 早期可以用coreapi生成 schema现在更主流的是drf-spectacular。安装配置后可以用命令生成 openapi 文件python manage.py spectacular --file schema.yml这个文件可以导入到文档工具里也方便前后端对字段契约。我在联调阶段常对后端同事说的一句话是“先跑一遍这个命令确认字段有没有变化再跟前端对。”减少了很多口头沟通误差。6.4 测试 API 时的命令行习惯测试接口不一定非要用 POSTMAN我用命令行较多# 获取列表 curl -H Authorization: Token token http://127.0.0.1:8000/api/posts/ # 创建对象 curl -X POST -H Authorization: Token token \ -H Content-Type: application/json \ -d {title: 新文章, content: 正文} \ http://127.0.0.1:8000/api/posts/如果项目用了 token 认证需要在 Django Admin 或代码里给用户生成 token。也可以在 shell 里快速创建python manage.py shellfrom rest_framework.authtoken.models import Token from django.contrib.auth import get_user_model User get_user_model() user User.objects.get(usernamebob) token, _ Token.objects.get_or_create(useruser) print(token.key)Token 只是其中一种方式对于前后端分离项目也可以考虑 JWT。但无论哪种密钥和 token 都不要写进仓库。7. 部署与运维迁移、收集静态、重启进程的一套命令流7.1 部署前先跑一遍 check --deploy上线不是把runserver换成gunicorn就完了。我强烈建议每次部署前执行python manage.py check --deploy这个命令会在不启动服务的情况下检查安全配置输出一堆警告比如 DEBUG 开启、SECRET_KEY 是否硬编码、ALLOWED_HOSTS 是否为空、是否缺少安全响应头等。我见过很多项目第一次跑这个命令时输出几十条警告但不用太慌按照警告一条条改。比如DEBUG FalseALLOWED_HOSTS配成域名列表SECRET_KEY从环境变量读取设置CSRF_COOKIE_SECURE True、SESSION_COOKIE_SECURE True配置SECURE_SSL_REDIRECT等安全头这些调整不一定每个都强制但至少要先理解每条风险点再决定是否忽略。7.2 发布流程一条命令一条命令地走我在一个小团队里总结过一套标准的发布顺序基本适用于大多数云服务器部署cd /srv/www/blog git pull origin main source .venv/bin/activate pip install -r requirements.txt python manage.py check --deploy python manage.py migrate python manage.py collectstatic --noinput sudo systemctl restart gunicorn顺序有讲究先更新代码再装依赖然后检查配置接着迁移数据库收集静态文件最后重启进程。迁移放收集静态前面是因为新版代码可能需要新的表结构静态文件收集失败不至于影响数据但迁移失败的话最好不要重启先排查原因。重启进程之后要立刻看日志确认 worker 成功启动端口正常监听再部署下一台机器或切换流量。7.3 gunicorn 与 manage.py 的“隐式关系”生产环境一般不会用runserver而是用gunicorn这类 WSGI 服务器。启动命令通常是gunicorn blog.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 4 \ --timeout 60 \ --access-logfile /var/log/blog/access.log \ --error-logfile /var/log/blog/error.log这里有个容易被忽略的点gunicorn进程并不知道你的 Django settings 模块在哪。它通过DJANGO_SETTINGS_MODULE环境变量来定位。所以使用 systemd 或 supervisor 管理 gunicorn 时需要在启动脚本里设置export DJANGO_SETTINGS_MODULEblog.settings.prod export SECRET_KEY... export DATABASE_URL...否则 gunicorn 只会加载默认的blog.settings如果默认不是 prod后果就是线上服务误用开发配置。7.4 线上日志排查grep 与 shell 的组合线上问题排查我一般先看日志。gunicorn 的 error log 会记录 Python traceback、worker 退出原因access log 会记录请求状态码。找到一个异常请求后再去数据库或缓存层面查原因。需要进一步查数据时我会在一台能连接数据库的管理机上执行python manage.py shell --settingsblog.settings.prod要注意直接在线上项目目录执行 shell一定要确保环境变量指向 prod否则可能连到开发库。更稳妥的做法是用单独配置的管理机而不是直接在运行服务的机器上乱敲。线上数据变更后一般不建议直接改代码里的 mock 数据而是写成自定义管理命令或一次性脚本放到版本库里执行。这样别人看到提交记录也能知道数据为什么变了。7.5 定时任务把管理命令交给 cron自定义管理命令写好后用系统的 cron 或 systemd timer 定时执行比在代码里自己写死线程更可靠。比如每天凌晨清理过期 session0 3 * * * cd /srv/www/blog .venv/bin/python manage.py clean_expired_secrets --settingsblog.settings.prod写进 crontab 的时候注意三点一要用绝对路径二要把输出重定向到日志文件三要确认manage.py的执行用户是否有项目目录和数据库的权限。定时任务执行失败时最怕的不是脚本报错而是日志被系统邮件吞掉没人看。所以我会在命令里用self.stdout.write输出结果并由 cron 把标准输出追加到固定日志0 3 * * * cd /srv/www/blog .venv/bin/python manage.py clean_expired_secrets --settingsblog.settings.prod /var/log/blog/clean.log 21这套命令和代码的组合我在不同规模的项目里反复用过。从只有一两张表的小工具到十几个 app 的多人协作系统Django 的约束结构一直很稳。最深的体会是命令本身不难记难的是理解每条命令背后维护的那套状态。数据库迁移别靠手动改库、上线之前跑一遍 check、改数据先在 shell 里试只要守住这几条习惯Django 后端的开发风险会小很多。如果你刚开始接触项目也不要怕敲命令敲错manage.py --help和每个子命令的--help就是最好的说明书遇到不确定的先查它再动手。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑