news 2026/10/20 13:47:28

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django 后端命令与代码组织:从 manage.py 到部署实战

接手一个维护三年的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.py:

blog/settings/ __init__.py base.py # 公共配置 dev.py # 开发环境 prod.py # 生产环境

然后在项目根目录放一个.env文件,把数据库密码、密钥、调试开关这类环境相关的内容放到环境变量里,而不是写死在代码里。manage.py本身默认读的是blog.settings,如果你拆包了,需要在使用命令时指定模块:

python manage.py runserver --settings=blog.settings.dev

更好的做法是直接在.env里设置DJANGO_SETTINGS_MODULE=blog.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.Migration:

from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('accounts', '0001_initial'), ] operations = [ migrations.AddField( model_name='userprofile', name='nickname', field=models.CharField(blank=True, max_length=32), ), ]

dependencies表示当前迁移依赖哪些之前的迁移,Django 正是靠这个链来保证迁移顺序。所以在多个 app 之间如果存在模型外键引用,跨 app 迁移的依赖关系也会自动生成。

日常开发中,我建议把迁移文件当作代码一样纳入版本管理。很多人图省事,直接在数据库里手动改表结构,然后项目代码和线上库就慢慢错开了。等到哪天重新拉一份环境初始化数据,迁移文件缺失导致建表失败,悔不当初。

2.2 改字段、加约束、删字段的推荐工作流

当我需要修改一个模型时,流程基本是固定的:

  1. 编辑models.py,改完字段;
  2. 运行python manage.py makemigrations <app_label>,只针对改动的 app 生成迁移;
  3. 运行python manage.py sqlmigrate <app_label> <migration_name>预览即将执行的 SQL;
  4. 确认 SQL 没问题后运行python manage.py migrate;
  5. 提交迁移文件和模型文件到版本库。

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(owner=profile, url=profile.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__icontains=keyword) return qs

而复杂的表单提交场景,我倾向于 FBV:

from 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', namespace='accounts')), ]
# accounts/urls.py from django.urls import path from . import views app_name = 'accounts' urlpatterns = [ path('login/', views.login_view, name='login'), ]

定义好命名空间以后,在视图或模板里用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 charset="UTF-8"> <title>{% block title %}默认标题{% endblock %}</title> <link rel="stylesheet" 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 rel="stylesheet" href="{% static 'css/app.css' %}?v=20250601">

4.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_root=settings.MEDIA_ROOT)

这段代码只能写在DEBUG=True时执行,否则生产环境会通过 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 f'uploads/{timezone.now():%Y%m%d}/{uuid.uuid4().hex}{ext}'

模型字段里这样引用:

class Attachment(models.Model): file = models.FileField(upload_to=upload_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 action:

from 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(status='shipped') 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(status='pending') 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(status='pending').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__lt=timezone.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', type=int, default=30, 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.urls

DefaultRouter会自动生成列表、详情、以及可浏览 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']

这样前端就可以通过?status=published&search=django&ordering=-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 shell
from rest_framework.authtoken.models import Token from django.contrib.auth import get_user_model User = get_user_model() user = User.objects.get(username='bob') token, _ = Token.objects.get_or_create(user=user) print(token.key)

Token 只是其中一种方式,对于前后端分离项目,也可以考虑 JWT。但无论哪种,密钥和 token 都不要写进仓库。

7. 部署与运维:迁移、收集静态、重启进程的一套命令流

7.1 部署前先跑一遍 check --deploy

上线不是把runserver换成gunicorn就完了。我强烈建议每次部署前执行:

python manage.py check --deploy

这个命令会在不启动服务的情况下检查安全配置,输出一堆警告,比如 DEBUG 开启、SECRET_KEY 是否硬编码、ALLOWED_HOSTS 是否为空、是否缺少安全响应头等。我见过很多项目第一次跑这个命令时输出几十条警告,但不用太慌,按照警告一条条改。

比如:

  • DEBUG = False
  • ALLOWED_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_MODULE=blog.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 --settings=blog.settings.prod

要注意,直接在线上项目目录执行 shell,一定要确保环境变量指向 prod,否则可能连到开发库。更稳妥的做法是用单独配置的管理机,而不是直接在运行服务的机器上乱敲。

线上数据变更后,一般不建议直接改代码里的 mock 数据,而是写成自定义管理命令或一次性脚本,放到版本库里执行。这样别人看到提交记录也能知道数据为什么变了。

7.5 定时任务:把管理命令交给 cron

自定义管理命令写好后,用系统的 cron 或 systemd timer 定时执行,比在代码里自己写死线程更可靠。比如每天凌晨清理过期 session:

0 3 * * * cd /srv/www/blog && .venv/bin/python manage.py clean_expired_secrets --settings=blog.settings.prod

写进 crontab 的时候注意三点:一要用绝对路径;二要把输出重定向到日志文件;三要确认manage.py的执行用户是否有项目目录和数据库的权限。定时任务执行失败时,最怕的不是脚本报错,而是日志被系统邮件吞掉没人看。所以我会在命令里用self.stdout.write输出结果,并由 cron 把标准输出追加到固定日志:

0 3 * * * cd /srv/www/blog && .venv/bin/python manage.py clean_expired_secrets --settings=blog.settings.prod >> /var/log/blog/clean.log 2>&1

这套命令和代码的组合,我在不同规模的项目里反复用过。从只有一两张表的小工具,到十几个 app 的多人协作系统,Django 的约束结构一直很稳。最深的体会是:命令本身不难记,难的是理解每条命令背后维护的那套状态。数据库迁移别靠手动改库、上线之前跑一遍 check、改数据先在 shell 里试,只要守住这几条习惯,Django 后端的开发风险会小很多。如果你刚开始接触项目,也不要怕敲命令敲错,manage.py --help和每个子命令的--help就是最好的说明书,遇到不确定的,先查它再动手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 13:46:11

零基础入门网络安全:52周系统学习路线全解析

1. 为什么你不需要"第100份学习清单"&#xff0c;而是需要一条有时间刻度的路线这两年我陆陆续续被问过无数次同一个问题&#xff1a;"我想学网络安全&#xff0c;该从哪里开始&#xff1f;"问的人里有大学刚毕业的、有工作两三年想转行的、也有完全零基础…

作者头像 李华
网站建设 2026/10/20 13:45:40

Flink SQL自研连接器实战:从UDP日志源到动态表连接器全解析

看到“自定义数据源”这几个字&#xff0c;不少人的第一反应是“官方连接器还不够用吗”。这个反应我特别理解——Kafka、JDBC、Hive、ClickHouse、ES&#xff0c;该有的几乎都有了&#xff0c;日常要接的数据源十有八九都能从官方清单里找到。但真正把Flink SQL推到业务深处之…

作者头像 李华
网站建设 2026/10/20 13:44:03

深度残差收缩网络(DRSN):残差+注意力+软阈值化的抗噪分类实战

深度残差收缩网络&#xff08;Deep Residual Shrinkage Network&#xff0c;DRSN&#xff09;这名字听着唬人&#xff0c;实际拆开就是三件事&#xff1a;残差结构、注意力机制和软阈值化。最近我把它的完整 Python 程序从头到尾复现了一遍&#xff0c;专门测试了它在带噪声图像…

作者头像 李华
网站建设 2026/10/20 13:43:31

人类溢价:AI时代内容可信度的新型价值标尺

1. “人类溢价”不是修辞&#xff0c;而是正在发生的市场定价机制最近在整理一批内容平台的后台数据时&#xff0c;我注意到一个反直觉但反复出现的现象&#xff1a;某知识类社区里&#xff0c;同一主题的两篇教程——一篇由AI生成、结构工整、术语准确&#xff1b;另一篇由从业…

作者头像 李华
网站建设 2026/10/20 13:43:01

M5 Ultra本地AI工作流:Hermes Agent实战搭建与优化

1. 项目概述&#xff1a;这不是“跑个模型”那么简单&#xff0c;而是一次本地AI工作流的重构实验最近在科技圈里&#xff0c;“Hermes Agent”这个词突然密集出现&#xff0c;和它绑定最紧的&#xff0c;就是那台刚发布不久、被称作“工作站级怪兽”的M5 Ultra Mac Studio。标…

作者头像 李华
网站建设 2026/10/20 13:41:01

STM32驱动ESP8266:可重入AT指令状态机与DMA透传实现

简介&#xff1a;本资源是一套面向嵌入式初学者与物联网开发者的STM32与ESP8266模块串口通信驱动程序&#xff0c;聚焦Wi-Fi联网功能实现&#xff0c;解决STM32主控如何稳定、可配置地通过AT指令控制ESP8266完成AP/STA双模切换、Wi-Fi连接、TCP/UDP数据收发等核心问题。压缩包为…

作者头像 李华