第五站练习答案
python
# TicketAdmin 中加入
def get_queryset(self, request):
queryset = super().get_queryset(request).select_related("assignee")
if request.user.is_superuser:
return queryset
return queryset.filter(assignee=request.user)
def has_delete_permission(self, request, obj=None):
if obj is not None and obj.status == Ticket.Status.CLOSED:
return request.user.is_superuser
return super().has_delete_permission(request, obj)python
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
class TicketPermissionTests(TestCase):
def test_user_cannot_edit_another_users_ticket(self):
User = get_user_model()
owner = User.objects.create_user("owner", password="secret-test-pass")
intruder = User.objects.create_user("intruder", password="secret-test-pass")
ticket = Ticket.objects.create(title="Private", status="claimed", assignee=owner)
self.client.force_login(intruder)
response = self.client.post(
reverse("tickets:edit", args=[ticket.pk]), {"title": "stolen", "priority": 3}
)
self.assertEqual(response.status_code, 404)专题二核对
- 关闭服务在同一事务写入
closed_by、closed_at与状态;审计字段由服务端身份生成,不接受表单覆盖。 - 每次高价值请求重新检查当前权限;撤权若要求立即生效,则让会话携带权限版本并在版本变化时强制重新认证。
- Admin action 逐项调用同一授权服务,累计成功、无权与冲突数量,再通过
message_user()汇总;不要用一次裸queryset.update()绕开规则。
深水边界
Admin 的 UI 限制不是最终授权。模型保存、服务层和数据库约束仍要守住不变量,因为脚本、API 和任务可以绕开 Admin。
开放练习(不提供答案)
为“关闭工单”建立独立 permission,并比较 403 与隐藏资源时返回 404 的审计和用户体验取舍。
加码题解:表单、身份与对象权限
python
def can_edit(ticket, user):
return user.is_authenticated and (
user.is_staff or ticket.owner_id == user.id
)关键推理
身份认证和对象授权分开表达,Admin、API、任务都可以复用同一规则。
可替代方案
高风险操作可再加独立 permission、审计事件和数据库约束。
典型错误
只依赖表单字段或前端按钮控制权限;批量 action 绕过逐项授权会产生越权。
复盘时请把状态码/事务/权限/资源清理中的至少一项写成自动化测试,而不是只截图成功页面。