# Django 最佳实践 适用于 Django 5.x 和 Django REST Framework 的生产级指南。涵盖 8 个类别共 40 余条规则。 ## 核心原则(7 条) ``` 1. ✅ 在首次迁移前配置自定义用户模型(之后无法更改) 2. ✅ 每个领域概念对应一个 Django 应用(用户、订单、支付) 3. ✅ 胖模型、瘦视图——业务逻辑放在模型/管理器里,而不是视图里 4. ✅ 始终使用 select_related/prefetch_related(避免 N+1) 5. ✅ 按环境拆分配置(base + dev + prod) 6. ✅ 使用 pytest-django + factory_boy 进行测试(而不是 fixtures) 7. ✅ 生产环境绝不使用 runserver(使用 Gunicorn + Nginx) ``` --- ## 1. 项目结构(关键) ### 按领域划分应用 ``` myproject/ ├── config/ # 项目配置 │ ├── __init__.py │ ├── settings/ │ │ ├── base.py # 共享配置 │ │ ├── dev.py # DEBUG=True,可接受 SQLite │ │ └── prod.py # DEBUG=False,Postgres,HTTPS │ ├── urls.py │ ├── wsgi.py │ └── asgi.py ├── apps/ │ ├── users/ # 自定义用户模型 │ │ ├── models.py │ │ ├── serializers.py │ │ ├── views.py │ │ ├── urls.py │ │ ├── admin.py │ │ ├── services.py # 业务逻辑 │ │ ├── selectors.py # 复杂查询 │ │ └── tests/ │ │ ├── test_models.py │ │ ├── test_views.py │ │ └── factories.py │ ├── orders/ │ └── payments/ ├── manage.py ├── requirements/ │ ├── base.txt │ ├── dev.txt │ └── prod.txt └── docker-compose.yml ``` ### 规则 ``` ✅ 一个应用 = 一个限界上下文(用户、订单、支付) ✅ 业务逻辑放在 services.py / selectors.py 中,而不是视图里 ✅ 每个应用拥有自己的 urls.py、admin.py、tests/ ❌ 绝不要把一切塞进同一个应用 ❌ 绝不在模型层面跨应用边界导入(使用 ID) ❌ 绝不要把业务逻辑放在视图或序列化器里 ``` --- ## 2. 模型与迁移(关键) ### 自定义用户模型(第一天就要做!) ```python # apps/users/models.py from django.contrib.auth.models import AbstractUser from django.db import models import uuid class User(AbstractUser): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) email = models.EmailField(unique=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] class Meta: db_table = 'users' # config/settings/base.py AUTH_USER_MODEL = 'users.User' ``` **这必须在 `migrate` 之前完成。之后无法更改。** ### 模型最佳实践 ```python class TimeStampedModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Order(TimeStampedModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders') status = models.CharField(max_length=20, choices=OrderStatus.choices, default=OrderStatus.PENDING, db_index=True) total = models.DecimalField(max_digits=10, decimal_places=2) class Meta: db_table = 'orders' ordering = ['-created_at'] indexes = [ models.Index(fields=['user', 'status']), ] def can_cancel(self) -> bool: return self.status in [OrderStatus.PENDING, OrderStatus.CONFIRMED] def cancel(self): if not self.can_cancel(): raise ValueError(f"Cannot cancel order in {self.status} status") self.status = OrderStatus.CANCELLED self.save(update_fields=['status', 'updated_at']) ``` ### 迁移规则 ``` ✅ 审查迁移 SQL:python manage.py sqlmigrate app_name 0001 ✅ 为迁移起描述性名称:--name add_status_index_to_orders ✅ 将数据迁移与 schema 迁移分开 ✅ 先非破坏性操作:添加列 → 回填数据 → 移除旧列 ❌ 绝不编辑或删除已应用的迁移 ❌ 绝不使用不带 reverse 函数的 RunPython ``` --- ## 3. 视图与序列化器——DRF(高优先级) ### 服务层模式 ```python # apps/orders/services.py from django.db import transaction class OrderService: @staticmethod @transaction.atomic def create_order(user, items_data: list[dict]) -> Order: total = sum(item['price'] * item['quantity'] for item in items_data) order = Order.objects.create(user=user, total=total) OrderItem.objects.bulk_create([ OrderItem(order=order, **item) for item in items_data ]) return order @staticmethod def cancel_order(order_id: str, user) -> Order: order = Order.objects.select_for_update().get(id=order_id, user=user) order.cancel() return order ``` ### 序列化器 ```python class OrderSerializer(serializers.ModelSerializer): items = OrderItemSerializer(many=True, read_only=True) class Meta: model = Order fields = ['id', 'status', 'total', 'items', 'created_at'] read_only_fields = ['id', 'total', 'created_at'] class CreateOrderSerializer(serializers.Serializer): """仅用于输入的序列化器——与输出序列化器分开。""" items = serializers.ListField( child=serializers.DictField(), min_length=1, max_length=50, ) def validate_items(self, items): for item in items: if item.get('quantity', 0) < 1: raise serializers.ValidationError("Quantity must be at least 1") return items ``` ### 视图(保持轻薄!) ```python @api_view(['POST']) @permission_classes([IsAuthenticated]) def create_order(request): serializer = CreateOrderSerializer(data=request.data) serializer.is_valid(raise_exception=True) order = OrderService.create_order(request.user, serializer.validated_data['items']) return Response({'data': OrderSerializer(order).data}, status=status.HTTP_201_CREATED) ``` ### 规则 ``` ✅ 将输入序列化器与输出序列化器分开 ✅ 视图只做:校验 → 调用服务 → 序列化 → 响应 ✅ 多模型写入使用 @transaction.atomic ❌ 绝不把业务逻辑放在视图或序列化器里 ❌ 绝不使用 ModelSerializer 进行写操作(过于隐式) ``` --- ## 4. 认证(高优先级) | 方法 | 适用场景 | 前端 | |--------|------|----------| | Session | 同域、SSR、Django 模板 | Django 模板 / htmx | | JWT | 不同域、SPA、移动端 | React、Vue、移动应用 | | OAuth2 | 第三方登录、API 消费者 | 任意 | ### JWT 配置(djangorestframework-simplejwt) ```python SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, } ``` --- ## 5. 性能优化(高优先级) ### 预防 N+1 查询 ```python # ❌ N+1:1 次查询获取订单 + N 次查询获取用户 orders = Order.objects.all() for o in orders: print(o.user.email) # 每次循环都访问数据库 # ✅ select_related(外键/一对一——JOIN) orders = Order.objects.select_related('user').all() # ✅ prefetch_related(多对多/反向外键——2 次查询) orders = Order.objects.prefetch_related('items').all() # ✅ 组合使用 orders = Order.objects.select_related('user').prefetch_related('items').all() ``` ### 查询优化工具集 ```python # 只获取需要的列 User.objects.values('id', 'email') User.objects.values_list('email', flat=True) # 使用注解代替 Python 循环 from django.db.models import Count, Sum Order.objects.annotate(item_count=Count('items'), revenue=Sum('items__price')) # 批量操作 OrderItem.objects.bulk_create([...]) Order.objects.filter(status='pending').update(status='cancelled') # 数据库索引 class Meta: indexes = [ models.Index(fields=['user', 'status']), models.Index(fields=['-created_at']), models.Index(fields=['email'], condition=Q(is_active=True)), ] # 分页 from rest_framework.pagination import CursorPagination class OrderPagination(CursorPagination): page_size = 20 ordering = '-created_at' ``` ### 缓存 ```python from django.core.cache import cache def get_product(product_id: str): cache_key = f'product:{product_id}' product = cache.get(cache_key) if product is None: product = Product.objects.get(id=product_id) cache.set(cache_key, product, timeout=300) return product ``` --- ## 6. 测试(中高优先级) ### pytest-django + factory_boy ```python # conftest.py @pytest.fixture def api_client(): return APIClient() @pytest.fixture def authenticated_client(api_client, user_factory): user = user_factory() api_client.force_authenticate(user=user) return api_client ``` ```python # factories.py class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User email = factory.Sequence(lambda n: f'user{n}@example.com') username = factory.Sequence(lambda n: f'user{n}') class OrderFactory(factory.django.DjangoModelFactory): class Meta: model = 'orders.Order' user = factory.SubFactory(UserFactory) total = factory.Faker('pydecimal', left_digits=3, right_digits=2, positive=True) ``` ```python # test_views.py @pytest.mark.django_db class TestListOrders: def test_returns_user_orders(self, authenticated_client): OrderFactory.create_batch(3, user=authenticated_client.handler._force_user) response = authenticated_client.get('/api/orders/') assert response.status_code == 200 assert len(response.data['data']) == 3 def test_requires_authentication(self, api_client): response = api_client.get('/api/orders/') assert response.status_code == 401 ``` --- ## 7. 管理后台定制(中等优先级) ```python class OrderItemInline(admin.TabularInline): model = OrderItem extra = 0 readonly_fields = ['price'] @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ['id', 'user', 'status', 'total', 'created_at'] list_filter = ['status', 'created_at'] search_fields = ['user__email', 'id'] readonly_fields = ['id', 'created_at', 'updated_at'] inlines = [OrderItemInline] date_hierarchy = 'created_at' def get_queryset(self, request): return super().get_queryset(request).select_related('user') ``` --- ## 8. 生产部署(中等优先级) ### 安全配置 ```python # settings/prod.py DEBUG = False ALLOWED_HOSTS = ['example.com', 'www.example.com'] CSRF_TRUSTED_ORIGINS = ['https://example.com'] SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 ``` ### 部署架构 ``` Nginx → Gunicorn → Django ↕ PostgreSQL + Redis (cache) ↕ Celery (background tasks) ``` ```bash gunicorn config.wsgi:application \ --bind 0.0.0.0:8000 \ --workers 4 \ --timeout 120 \ --access-logfile - ``` ### 静态文件——WhiteNoise ```python MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # 紧跟在 SecurityMiddleware 之后 ... ] STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' ``` ### 规则 ``` ✅ Gunicorn + Nginx(或 Cloud Run / Railway) ✅ PostgreSQL(不要用 SQLite) ✅ python manage.py check --deploy ✅ 使用 Sentry 进行错误追踪 ❌ 生产环境绝不使用 runserver ❌ 生产环境绝不使用 DEBUG=True ❌ 生产环境绝不使用 SQLite ``` --- ## 反模式 | # | ❌ 不要这样做 | ✅ 应该这样做 | |---|---------|--------------| | 1 | 在视图中写业务逻辑 | 服务层(`services.py`) | | 2 | 一个巨型应用 | 按领域划分应用 | | 3 | 使用默认 User 模型 | 首次迁移前配置自定义 User 模型 | | 4 | 不使用 `select_related` | 始终预加载关联对象 | | 5 | 使用 Django fixtures 做测试 | 使用 `factory_boy` 工厂 | | 6 | 单一 `settings.py` 文件 | 拆分:base + dev + prod | | 7 | 生产环境使用 `runserver` | Gunicorn + Nginx | | 8 | 生产环境使用 SQLite | PostgreSQL | | 9 | 使用 `ModelSerializer` 进行写操作 | 显式的输入序列化器 | | 10 | 在视图中写原生 SQL | ORM querysets + `selectors.py` | --- ## 常见问题 ### 问题 1:「首次迁移后无法更改 User 模型」 **解决方案:** 如果从零开始:删除所有迁移文件 + 数据库,设置自定义 User 模型,重新迁移。如果已有数据:需进行复杂迁移(使用 `django-allauth` 或增量字段迁移)。 ### 问题 2:「序列化器在处理大型查询集时速度太慢」 **解决方案:** 缺少 `select_related` / `prefetch_related` → 导致 N+1 查询。 ```python queryset = Order.objects.select_related('user').prefetch_related('items') ``` ### 问题 3:「应用之间存在循环导入」 **解决方案:** 使用字符串引用:`models.ForeignKey('orders.Order', ...)` 代替导入模型类。对于服务层,在函数内部进行导入。