第六站练习答案
python
from django.core.exceptions import ValidationError
try:
ticket.full_clean()
except ValidationError as exc:
return JsonResponse(
{"error": {"code": "validation_error", "fields": exc.message_dict}},
status=400,
)python
from django.test import TestCase
from django.urls import reverse
class TicketApiTests(TestCase):
def test_response_uses_public_field_whitelist(self):
self.client.force_login(self.user)
response = self.client.get(reverse("tickets:api-collection"))
item = response.json()["results"][0]
self.assertEqual(
set(item), {"id", "title", "status", "priority", "updated_at"}
)运行 python manage.py test tickets.tests.TicketApiTests。精确字段集合会在意外暴露 assignee 邮箱、内部标记或其他新增字段时失败。
专题二核对
python
from django.core import signing
cursor = signing.dumps({"v": 1, "created": row.created_at.isoformat(), "id": row.pk}, salt="ticket-page")
payload = signing.loads(cursor, salt="ticket-page", max_age=3600)篡改、过期或字段不完整统一映射为稳定 400。409 响应可用 {"error":{"code":"version_conflict","retryable":true}};v2 增字段时保留单独的 v1 序列化器和字段集合测试,不让查询模型直接决定公开合同。
深水边界
展示性示例直接调用 full_clean(),但高并发下仍以数据库约束和事务结果为准。校验通过到保存之间存在竞争窗口。
开放练习(不提供答案)
设计 Idempotency-Key 的存储、请求摘要、响应重放和过期策略;说明两个并发相同键请求如何只创建一个工单。
加码题解:API 契约、分页与版本
python
def error(code, message, details=None):
return {'error': {
'code': code,
'message': message,
'details': details or {},
}}关键推理
错误结构稳定,客户端可以按 code 分支,而不是解析人类文案。
可替代方案
对长列表使用 cursor/seek 分页,对一次性导出使用异步任务并返回 job id。
典型错误
offset 深页没有稳定排序,或把数据库异常堆栈直接返回客户端。
复盘时请把状态码/事务/权限/资源清理中的至少一项写成自动化测试,而不是只截图成功页面。