Django Styleguide 中文翻译 - 实用技巧和资源
💡 本章导读
如果说前面的章节是在构建项目的“骨架”,那么本章则是赋予项目“灵魂”的实战百宝箱。这里汇集了我们在处理数十个大中型 Django 项目后,沉淀出的最具生产力的开发技巧。
这些技巧并非凭空构思,而是为了解决真实开发中的 确定性(Certainty) 、可维护性(Maintainability) 和 开发效率(DX) 问题:
实战 Cookbook (通用更新服务):
意义:解决传统
instance.save()带来的全表字段刷新问题,实现“改哪里、写哪里”的精准控制。场景:在高并发或涉及复杂副作用(如修改姓名后自动触发邮件)的业务场景中,它是确保系统行为可预测的关键。
强类型化转型 (Mypy & 类型注解):
意义:将 Bug 拦截在运行之前。在 Python 的动态性与团队协作的稳定性之间寻找平衡。
场景:当项目规模超过万行,或需要频繁重构核心服务层时,类型注解是开发者唯一的“引路灯”。
业界实战案例:
意义:通过 Facturedo 等真实公司的背书,验证本套风格指南在商业环境下的抗压能力。
学习与替代资源:
意义:不盲目迷信单一标准,提供更广阔的视野,让你根据项目实际阶段灵活选型。
总之,这一章旨在帮助你从一个“会写 Django 的程序员”进化为“能掌控复杂工程的架构师”。
Cookbook(实用代码片段)
🎯 目的: 一些通用可复用代码的实现存储在这里。
通用更新服务
问题背景
在 Django 项目中,模型实例的更新(Update)操作几乎无处不在。然而,如果不使用统一的模式,简单的更新逻辑往往会演变成工程灾难。
1. 陷入“赋值泥潭” (Boilerplate Hell)
当你需要更新一个包含 10 几个字段的 Profile 时,代码往往长这样:
# ❌ 糟糕:机械、重复且容易手抖写错变量名
profile.bio = data.get('bio', profile.bio)
profile.location = data.get('location', profile.location)
profile.website = data.get('website', profile.website)
profile.phone = data.get('phone', profile.phone)
# ... 重复 10 次 ...
profile.save()
2. 极其隐蔽的 save() 遗漏
在复杂的业务逻辑分支中,很容易在某个 if 路径下修改了属性却忘了存库。
# ❌ 风险:逻辑复杂时极易漏掉保存操作
def update_user_status(user, data):
if data.get('is_active'):
user.is_active = True
# 这里忘了写 user.save(),导致 Bug 极难排查
...
3. 盲目全量更新的性能与安全风险
默认的 instance.save() 会将所有字段重新写入数据库,这会带来:
性能损耗:哪怕只改了一个字段,也要更新整行数据。
竞态条件(Race Condition):如果两个进程同时操作同一个对象,全量更新会不小心覆盖掉另一个进程刚刚修改的其他字段。
4. 难以追踪“究竟改了没?”
在业务中,我们经常需要:“如果用户修改了邮箱,就发一封验证邮件”。
# ❌ 痛苦:为了判断变化,你得手动记录旧值,代码极其啰嗦
old_email = user.email
user.email = data.get('email', user.email)
user.save()
if old_email != user.email:
send_verification_email(user)
解决方案:model_update 服务
这个服务的核心逻辑其实非常直观,它扮演了两个关键角色:
自动化机械劳动:由于大多数基础字段(如姓名、简介)的更新逻辑实在太简单、太重复,我们不希望在 Service 中手动写几十个
if分支。业务触发的“侦测器”:这是它最大的价值。我们编写一个通用的函数来实时校测哪些字段更新了。一旦发现初始字段发生实质性改变,它会立刻亮起信号灯,让我们能够针对性地管理后续的副作用——比如更新数据库里的派生字段(如重新生成用户名)、发送验证邮件,或者推送异步任务。
这种模式建立了一个 “业务哨兵” ,确保了源头数据变化与后续动作之间的一致性。
💡 模式的核心思想
model_update 的设计精髓可以概括为:“将对象的同步操作转化为声明式的数据映射,并对状态变更保持高度感知。”
它是如何化解前文痛点的?
自动化 (Automation):用循环映射代替繁琐的手动
instance.field = value。防御性 (Defensiveness):将
save()锁死在函数内部,只要发生了变动就一定会存库。极致性能 (Performance):默认开启
update_fields,通过“脏检查(Dirty Check)”确保只写变动的列。语义化副作用 (Semantic Side Effects):将“字段变了没”这个判断从业务代码中抽离,通过一个布尔值
has_updated赋予业务逻辑极其简洁的表达力。
🚀 进化之路:从“机械劳动”到“通用规范”
我们可以利用 getattr 和 setattr 这两个编程函数,来解决手动复制的问题,你可以把它们理解为get和set的升级版。
instance.name = 'Tom':是硬编码,你必须提前知道字段叫name。setattr(instance, 'name', 'Tom'):是变量化。这意味着你可以把'name'存在变量里,从而写出支持任意字段的代码。
这是实现“通用(Generic)”的必经之路。让我们看看演进过程:
Level 1:封装循环逻辑 (解决“手酸”问题)
不再手动一行行写赋值,而是通过一个字符串列表告诉函数:“帮我把这些字段对号入座”。
# 初级思路:批量自动赋值
def basic_update(instance, fields, data):
for field in fields:
if field in data:
# 这里的魔法:把字符串 field 变成实实在在的属性操作
setattr(instance, field, data[field])
# ❌ 这里的缺点:即使一个字都没改,也会触发一次全表的数据库写入操作。
instance.save()
Level 2:脏检查与精准保存 (解决“盲目写入”)
与其每个字段都改,不如先 “对比一下” 。如果新旧值一样,我们根本不需要动它。
# 进阶:引入脏检查 (Dirty Check)
def efficient_update(instance, fields, data):
has_updated = False
for field in fields:
if field in data:
new_value = data[field]
current_value = getattr(instance, field) # 先“看一眼”旧值
# 只有真的变了,才动手
if current_value != new_value:
setattr(instance, field, new_value)
has_updated = True
if has_updated:
# ✅ 精准写入:只更新 fields 中指定的列,不干扰其他字段。
# 这也解决了 Race Condition(多个进程同时改不同字段导致互相覆盖)的问题。
instance.save(update_fields=fields)
return has_updated
model_update 的底层实现
这是我们根据上述思路打造出的“通用武器”。建议将其放在项目的 common/services.py 中。
# common/services.py
from typing import Tuple, Iterable
from django.db import models
def model_update(
*,
instance: models.Model,
fields: Iterable[str],
data: dict
) -> Tuple[models.Model, bool]:
"""
通用模型更新服务
只更新指定的字段,并且只在值实际改变时才保存。
Args:
instance: 要更新的模型实例
fields: 允许更新的字段列表
data: 包含新值的字典
Returns:
(更新后的实例, 是否有字段被修改)
"""
has_updated = False
# 遍历允许更新的字段
for field in fields:
# 检查 data 中是否包含该字段
if field not in data:
continue
# 获取新值
new_value = data[field]
# 获取当前值
current_value = getattr(instance, field)
# 只在值确实改变时才更新
if current_value != new_value:
has_updated = True
setattr(instance, field, new_value)
# 只在有更新时才保存
if has_updated:
# 使用 update_fields 优化 SQL 查询,确保原子性
instance.save(update_fields=fields)
return instance, has_updated
📝 实现详解
核心逻辑解析:
分治策略:只处理
fields白名单中定义的字段,彻底杜绝了前端传多余字段导致的非法修改。原子性保护:通过
instance.save(update_fields=fields),我们告诉 Django 只生成UPDATE ... SET col1=val1, col2=val2的 SQL,而不是刷新整行,这在多进程环境下是“救命”的。零浪费原则:如果没有字段发生变化,
has_updated为False,不仅省下了数据库写入开销,也防止了可能触发的 Model Signals(信号)。
Level 3:最终形态 - 业务感知的更新服务
这里的核心逻辑是:我们将底层繁琐的“数据清洗”和“数据库写入”逻辑(即 Level 1 和 Level 2)封装进了一个名为 model_update 的工具函数中。
而我们在业务服务层(如 user_update)中调用它。它们之间的联系如下:
交给专业的人做专业的事:
user_update只需要声明哪些字段可以改,具体的赋值、对比、保存都交给内置了 Level 2 逻辑的model_update。接收反馈:
model_update执行完后会返回一个 bool 值。服务层根据这个反馈,决定是否触发后续的业务“副作用”(比如发送欢迎邮件)。
这就是关注点分离:model_update 负责数据一致性,user_update 负责业务流程。
完整示例:user_update 服务
# users/services.py
from styleguide_example.common.services import model_update
from styleguide_example.users.models import User
def user_update(*, user: User, data) -> User:
"""
用户更新服务
Args:
user: 要更新的用户实例
data: 包含更新字段的字典
Returns:
更新后的用户实例
"""
# 定义没有副作用的字段
non_side_effect_fields = ['first_name', 'last_name']
# 使用通用 model_update 服务更新这些字段
user, has_updated = model_update(
instance=user,
fields=non_side_effect_fields,
data=data
)
# 在这里更新有副作用的字段
# (例如,username 是根据 first_name 和 last_name 生成的)
if has_updated:
user.username = generate_username(
first_name=user.first_name,
last_name=user.last_name
)
user.save(update_fields=['username'])
# ... 执行一些与用户相关的额外任务 ...
# 例如:发送通知
if has_updated:
from users.tasks import user_profile_updated_task
transaction.on_commit(
lambda: user_profile_updated_task.delay(user.id)
)
return user
📝 代码详解
1. non_side_effect_fields(白名单分拣):
non_side_effect_fields = ['first_name', 'last_name']
💡 这两个字段的目的是什么?
你可以直接把这种写法理解为 “数据分拣” 与 “因果联动”。
定义“信号源”: 名单里的
first_name和last_name本质上就是 业务信号发射器。因为在数据库中username是由它们决定的,所以把它们放在一起作为“诱因”。状态变更触发器: 只要名单里的字段变了(即使只改了一个字母),底层工具就会瞬间亮起
has_updated信号灯。拿到信号后,你才去干重活(如重新计算username或更新订单总价)。这种 “因变 -> 果随” 的逻辑在所有数据库物理字段同步的场景中(如订单、档案、统计报表)都是通用的。自动化降噪: 把这些“体力活字段”收纳进白名单,是为了让 Service 主体只剩下 核心业务意图 。通过这一行代码,你就能一眼看清:“因为名字动了,所以我去同步更新了账户名。”
2. 调用 model_update:
user, has_updated = model_update(
instance=user,
fields=non_side_effect_fields,
data=data
)
返回值:
user- 更新后的实例has_updated- 布尔值,指示是否有字段被修改
3. 副作用字段的处理:
if has_updated:
user.username = generate_username(...)
user.save(update_fields=['username'])
只在有更新时才处理副作用
使用
update_fields优化数据库查询
💡 这种模式的好处
| 好处 | 说明 |
| ✅ 减少重复代码 | 字段赋值逻辑集中在一处 |
| ✅ 性能优化 | 使用 update_fields 只更新需要的字段 |
| ✅ 避免不必要的写入 | 值未改变时不触发数据库更新 |
| ✅ 清晰的关注点分离 | 无副作用字段 vs 有副作用字段 |
| ✅ 易于测试 | 可以单独测试通用服务 |
| ✅ 追踪更新 | has_updated 让你知道是否有实际变化 |
使用场景示例
示例 1:简单更新
def profile_update(*, profile: Profile, data: dict) -> Profile:
"""更新用户个人资料"""
simple_fields = ['bio', 'location', 'website']
profile, has_updated = model_update(
instance=profile,
fields=simple_fields,
data=data
)
return profile
示例 2:带验证的更新
def product_update(*, product: Product, data: dict) -> Product:
"""更新产品信息"""
# 验证
if 'price' in data and data['price'] < 0:
raise ValidationError("Price cannot be negative")
# 更新无副作用字段
basic_fields = ['name', 'description', 'price', 'stock']
product, has_updated = model_update(
instance=product,
fields=basic_fields,
data=data
)
# 处理副作用
if has_updated and 'price' in data:
# 价格变化时,记录历史
PriceHistory.objects.create(
product=product,
old_price=product.price,
new_price=data['price']
)
return product
示例 3:带权限检查的更新
def order_update(*, order: Order, data: dict, user: User) -> Order:
"""更新订单(带权限检查)"""
# 根据用户角色决定可更新的字段
if user.is_staff:
allowed_fields = ['status', 'notes', 'shipping_address']
else:
allowed_fields = ['notes']
order, has_updated = model_update(
instance=order,
fields=allowed_fields,
data=data
)
# 状态变化时发送通知
if has_updated and 'status' in data:
from orders.tasks import order_status_changed_task
transaction.on_commit(
lambda: order_status_changed_task.delay(order.id)
)
return order
完整实现参考
完整的实现可以在我们的示例项目中找到:
⚠️ 重要提示:
如果你要在项目中包含 model_update,请确保:
将测试也包含在你的项目中
理解边界情况和限制
测试示例
# common/tests/services/test_model_update.py
from django.test import TestCase
from users.tests.factories import UserFactory
from common.services import model_update
class ModelUpdateTests(TestCase):
def test_updates_only_changed_fields(self):
"""测试只更新改变的字段"""
user = UserFactory(first_name='John', last_name='Doe')
data = {
'first_name': 'Jane', # 会改变
'last_name': 'Doe', # 不会改变
}
user, has_updated = model_update(
instance=user,
fields=['first_name', 'last_name'],
data=data
)
self.assertTrue(has_updated)
self.assertEqual(user.first_name, 'Jane')
self.assertEqual(user.last_name, 'Doe')
def test_returns_false_when_no_changes(self):
"""测试没有变化时返回 False"""
user = UserFactory(first_name='John')
data = {'first_name': 'John'} # 相同的值
user, has_updated = model_update(
instance=user,
fields=['first_name'],
data=data
)
self.assertFalse(has_updated)
def test_ignores_fields_not_in_data(self):
"""测试忽略不在 data 中的字段"""
user = UserFactory(first_name='John', last_name='Doe')
data = {'first_name': 'Jane'} # 只包含 first_name
user, has_updated = model_update(
instance=user,
fields=['first_name', 'last_name'], # 但允许两个字段
data=data
)
self.assertTrue(has_updated)
self.assertEqual(user.first_name, 'Jane')
self.assertEqual(user.last_name, 'Doe') # 保持不变
def test_ignores_fields_not_in_allowed_list(self):
"""测试忽略不在允许列表中的字段"""
user = UserFactory(first_name='John', is_staff=False)
data = {
'first_name': 'Jane',
'is_staff': True, # 不在允许列表中
}
user, has_updated = model_update(
instance=user,
fields=['first_name'], # 只允许 first_name
data=data
)
self.assertTrue(has_updated)
self.assertEqual(user.first_name, 'Jane')
self.assertFalse(user.is_staff) # 未被更新
DX(开发者体验)
🎯 目标: 所谓 DX,就是让开发者在写代码时感到“丝滑”且“确定”。在 Django 开发中,这通常意味着更少的黑盒猜测,更多的 IDE 智能提示。
mypy / 类型注解:在灵活与严谨间寻找平衡
🧩 我们的核心理念
当涉及到 mypy 时,我们拒绝“一刀切”。我们的最高准则是:
类型注解不是为了取悦机器,而是为了在开发者之间建立清晰的“契约”,并赋予我们重构代码的底气。
🏗️ 实战策略:根据项目生命周期选型
在我们的工程实践中,我们会根据项目的规模、寿命和团队规模,在以下两种策略间灵活切换。我们坚信:上下文(Context)是决定技术选型的唯一关键。
1. 🛡️ 严格模式 (Strict Mode) —— “大型战舰的稳压器”
如果你的项目预计会活过 1 年,且团队成员超过 3 人,请毫不犹豫地选择此模式。
核心实践:
✅ 全量覆盖:所有
services.py和selectors.py必须有 100% 的类型注解。✅ CI 强管控:将
mypy校验挂载到 Git Hook 或 CI 流程中,报错即禁止合并。✅ 禁止
Any:在配置中开启disallow_any_generics = True。
带来的 DX 价值:
重构信心:当你修改一个核心 Service 的参数类型时,Mypy 会瞬间告诉你全站还有哪 20 个地方需要跟着改。
活文档:函数签名就是最好的说明书。不看代码实现,你就知道输入什么,产出什么。
2. ⚡ 宽松模式 (Loose Mode) —— “灵活快艇的加速器”
如果你的项目处于极其早期的 MVP(最小可行性开发)阶段,或者是一个几个周内就会结束的一次性项目。
核心实践:
⚠️ 局部注解:只在最复杂的业务逻辑处写注解。
⚠️ 关闭严格校验:可能完全不运行
mypy,只依靠 Python 运行时的动态特性。
带来的 DX 价值:
极速迭代:省去了定义复杂
TypedDict和解决django-stubs类型冲突的时间。降低门槛:让对类型系统不熟悉的开发者也能快速上手贡献代码。
💡 核心原则:上下文(Context)决定一切
不要因为追求“政治正确”而强行引入 Mypy。
如果你在开快艇(小型项目),背着沉重的装甲(严格类型)会让你沉没。
如果你在开航母(千人协作的大型 Django 应用),没有雷达和损管系统(强类型检查),任何一个小疏忽都能让系统瞬间瘫痪。
配置示例
在 Django-Styleguide-Example 中,我们配置了 mypy,使用:
你可以将其作为示例参考。
mypy.ini 配置示例
[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
plugins =
mypy_django_plugin.main,
mypy_drf_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = config.django.base
[mypy-*.migrations.*]
ignore_errors = True
[mypy-*.tests.*]
ignore_errors = True
[mypy-factory.*]
ignore_errors = True
类型注解示例
服务层:
from typing import Optional
from django.db import transaction
from users.models import User
@transaction.atomic
def user_create(
*,
email: str,
first_name: str,
last_name: str,
password: Optional[str] = None
) -> User:
"""
创建用户服务
Args:
email: 用户邮箱
first_name: 名字
last_name: 姓氏
password: 密码(可选)
Returns:
创建的用户实例
"""
user = User.objects.create_user(
email=email,
first_name=first_name,
last_name=last_name,
)
if password is not None:
user.set_password(password)
user.save(update_fields=['password'])
return user
选择器层:
from typing import Optional, Iterable
from django.db.models import QuerySet
from users.models import User
def user_list(
*,
filters: Optional[dict] = None
) -> QuerySet[User]:
"""
用户列表选择器
Args:
filters: 过滤条件字典
Returns:
用户查询集
"""
filters = filters or {}
qs = User.objects.all()
return qs.filter(**filters)
def user_get(*, user_id: int) -> User:
"""
获取单个用户
Args:
user_id: 用户 ID
Returns:
用户实例
Raises:
User.DoesNotExist: 用户不存在时
"""
return User.objects.get(id=user_id)
API 层:
from typing import Any
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import serializers, status
class UserCreateApi(APIView):
class InputSerializer(serializers.Serializer):
email = serializers.EmailField()
first_name = serializers.CharField()
last_name = serializers.CharField()
password = serializers.CharField()
def post(self, request: Request) -> Response:
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
from users.services import user_create
user = user_create(**serializer.validated_data)
return Response(status=status.HTTP_201_CREATED)
额外资源
另外,这个特定的项目也有 mypy 配置:
💡 决策指南
何时使用严格的类型检查?
✅ 推荐使用的场景:
大型项目(超过 10,000 行代码)
多人协作的项目
长期维护的项目
复杂的业务逻辑
公共 API 或库
重构遗留代码时
⚠️ 可以不用的场景:
小型项目或原型
快速迭代的早期阶段
团队不熟悉类型注解
短期项目
最佳实践
1. 渐进式采用:
# 从关键部分开始
# services.py - 严格类型检查
def user_create(...) -> User:
...
# 逐步扩展到其他部分
# models.py, apis.py 等
2. 忽略生成的代码:
[mypy-*.migrations.*]
ignore_errors = True
3. 为第三方库添加存根:
pip install django-stubs
pip install djangorestframework-stubs
pip install celery-stubs
4. 在 CI/CD 中运行:
# .github/workflows/ci.yml
- name: Run mypy
run: mypy .
弄清楚什么对你最有效。
额外资源和替代方案
📚 学习更多: 我们发现有用的额外资源和其他替代方案,可以为风格指南增加价值。
推荐视频
1. Scaling Django to 500 apps
主题:大型 Django 项目的扩展
适合:大型团队和企业应用
2. Django structure for scale and longevity
主题:可扩展和长期维护的 Django 结构
适合:所有 Django 开发者
3. Quality Assurance in Django
主题:Django 项目的质量保证和测试
适合:关注代码质量的团队
替代方案和相关项目
1. Django API Domains
另一种组织 Django 项目的方法
强调 API 优先和领域驱动设计
特点:
按领域(domain)组织代码
API 优先的方法
清晰的架构边界
对比 Django Styleguide:
Django Styleguide 更注重服务层和选择器
Django API Domains 更强调领域边界
两者可以互补使用
2. Django Design Patterns
多种 Django 设计模式的集合
适合学习不同的架构方法
3. Two Scoops of Django
经典的 Django 最佳实践书籍
涵盖广泛的主题
社区讨论
Hacker News 讨论:
你可能会在这里找到额外的有用信息
包含不同观点和实践经验
主要讨论点:
服务层 vs Fat Models
何时使用哪种模式
不同规模项目的选择
团队采用的挑战
🧠 核心设计哲学与灵感来源
💡 致敬与溯源: 本风格指南并非凭空创造,而是站在巨人的肩膀上,融合了软件工程领域的经典思想与现代 Web 开发的最佳实践。
1. 关注点分离 (Separation of Concerns)
这是整个指南的基石。在传统的 Django 开发中(通常被称为 "Fat Models" 或 "View-Heavy" 模式),业务逻辑往往混杂在 Model 的 save 方法或 Controller(View)中。这种耦合导致了代码难以测试、难以复用。
本指南的解法: 我们将系统拆解为三个职责分明的层级,每一层只关注自己的“一亩三分地”:
Model 层:回归数据定义的本质,只负责“数据结构”和“数据库关系”。
Service 层:接管“业务逻辑”。它是系统的心脏,负责编排数据变更、触发副作用(如发邮件、调用第三方 API)。
API/View 层:负责“HTTP 交互”。它只关心如何解析请求、验证输入(Input Serializer)以及格式化响应(Output Serializer)。
2. 系统边界 (Inspired by "Boundaries" by Gary Bernhardt)
Gary Bernhardt 在其经典演讲《Boundaries》中提出了 “Shell(壳)与 Core(核)” 的概念:核心应由纯逻辑组成,而副作用应被推向外层的壳。
本指南的映射:
Core (逻辑内核):我们的 Selectors(选择器) 和 Services(服务)。
Selectors 往往设计为只读的、纯粹的查询逻辑。
Services 虽然包含副作用,但我们将“写数据库”和“发消息”等副作用显式地封装在其中,避免它们泄露到 View 或 Model 中。
Shell (交互外壳):我们的 APIs 和 Celery Tasks。
它们作为系统的“边界”,负责与外界(用户、定时器)交互,然后迅速将控制权移交给内部的 Service。
通过建立清晰的边界,我们使得核心业务逻辑(Service/Selector)变得可测试(易于 Mock)且可移植(可以被 API 调用,也可以被命令行或 Celery 调用)。
3. 服务对象模式 (Rails Service Objects)
Ruby on Rails 社区为了解决 "Fat Models" 问题,广泛流行 Service Objects 模式。Django 社区虽然原生没有这个概念,但我们将其引入并本地化。
关键借镜:
单一职责:一个 Service 函数通常只做一件事(如
user_create,order_ship)。这使得代码更加原子化。显式调用:我们摒弃了 Django Signals(信号)这种隐式的触发机制,转而在 Service 中显式调用
send_email()。好处:当你阅读
user_create代码时,你确切地知道发了邮件,而不是去猜“哪里藏了个 Signal 处理器”。
4. 认知负荷管理 (Cognitive Load Management)
我们认为:代码是写给人看的,顺便给机器运行。 复杂的架构虽然看起来“高级”,但如果增加了开发者的理解成本,那就是失败的设计。
如何降低认知负荷?
一致性 (Consistency):无论你打开
users应用还是billing应用,你都会看到完全相同的结构(services.py,selectors.py,apis.py)。这种可预测性让开发者能瞬间定位代码。显式优于隐式 (Explicit > Implicit):
我们喜欢:
user_update(user, data)-> 清晰的函数调用。我们不喜欢:重写
Model.save()并隐藏大量副作用。
扁平结构:我们推荐函数式编程(Functional approach)用于 Service 和 Selector,因为函数比类(Class)更简单,没有隐藏的状态(State),易于推理。
🌟 综合价值观
如果用四个词总结本指南的灵魂,那就是:
简单性 (Simplicity):
拒绝过度设计。我们不引入复杂的 Repository 模式或并没有必要的 DDD 战术模式。我们只在 Django 原生基础上做最小限度的分层扩展。
一致性 (Consistency):
给团队一个统一的“方言”。当所有人都用同一种方式写代码时,Code Review 变得容易,新成员入职也能迅速上手。
可维护性 (Maintainability):
通过分层和解耦,我们确保了项目在扩展到 50+ 个 App、几十万行代码时,依然井井有条,不会坍缩成“大泥球”。
实用主义 (Pragmatism):
这是一套**“经过实战检验”**的指南,而非象牙塔里的理论。所有的规则(如
model_update的脏检查、Mypy 的宽松策略)都是为了解决真实工程痛点而生。
🔗 相关资源总览
官方文档
工具和库
学习资源
视频
🎉 结语
感谢你阅读完整个 Django Styleguide 中文翻译!
希望这份指南能帮助你:
✅ 构建更好的 Django 项目架构
✅ 编写更清晰、更可维护的代码
✅ 提升团队的开发效率
✅ 减少技术债务
记住核心原则:
🎯 关注点分离
🎯 保持简单
🎯 一致性
🎯 实用主义
继续学习:
参考示例项目
尝试在你的项目中应用
根据你的需求调整
与社区分享经验
祝你的 Django 项目开发顺利! 🚀