Django如何实现用户仅编辑自有模型(遵循DRY原则)
这问题我太熟了!用Mixin确实是最符合DRY原则的解决方案——它本质就是一个可复用的类,让你的视图类继承它之后,就能自动获得封装好的权限校验逻辑,再也不用在20多个视图里重复写相同的if判断了。咱们一步步来实现:
核心思路:封装OwnerRequiredMixin
先写一个通用的Mixin类,把所有权限校验的逻辑都放在这里,后续视图只需要继承它并做简单配置就行。
基础版Mixin(适配大多数场景)
这个Mixin会处理两种常见情况:
- 模型直接有
owner字段关联当前用户 - 模型关联其他模型(比如Space关联House),通过关联路径找到所有者
from django.core.exceptions import PermissionDenied from django.views.generic.base import View class OwnerRequiredMixin(View): # 可选:指定模型中关联用户的字段名,默认是'owner' owner_field = 'owner' # 可选:如果是关联模型,指定所有者的关联路径,比如'Space'关联'House',就写'house__owner' owner_path = None def get_object(self, queryset=None): # 先获取视图要操作的对象 obj = super().get_object(queryset) # 校验权限 self._check_owner_permission(obj) return obj def _check_owner_permission(self, obj): # 根据配置获取所有者对象 if self.owner_path: # 解析关联路径,比如'house__owner'拆成['house', 'owner'] path_parts = self.owner_path.split('__') owner = obj for part in path_parts: owner = getattr(owner, part) else: # 直接取模型的owner字段 owner = getattr(obj, self.owner_field) # 校验当前用户是否是所有者 if owner != self.request.user: raise PermissionDenied def form_valid(self, form): # 针对CreateView:自动把当前用户设为模型的创建者 if not self.owner_path: form.instance.owner = self.request.user return super().form_valid(form)
步骤2:在视图中快速复用Mixin
现在你只需要让你的视图类继承这个Mixin,再根据场景配置少量参数即可:
场景1:模型直接属于当前用户(比如House模型)
from django.views.generic import DetailView, UpdateView, DeleteView, CreateView from .models import House # 查看House详情:只有所有者能访问 class HouseDetailView(OwnerRequiredMixin, DetailView): model = House # 如果你的模型中所有者字段不是'owner',比如叫'creator',就改这里: # owner_field = 'creator' # 更新House:只有所有者能编辑 class HouseUpdateView(OwnerRequiredMixin, UpdateView): model = House fields = ['name', 'address'] # 删除House:只有所有者能删除 class HouseDeleteView(OwnerRequiredMixin, DeleteView): model = House success_url = '/houses/' # 创建House:自动把当前用户设为所有者 class HouseCreateView(OwnerRequiredMixin, CreateView): model = House fields = ['name', 'address']
场景2:关联模型(比如Space关联House,House的owner是当前用户)
from django.views.generic import DetailView, UpdateView, CreateView from .models import Space # 查看Space详情:校验关联的House是否属于当前用户 class SpaceDetailView(OwnerRequiredMixin, DetailView): model = Space # 指定关联路径:Space -> House -> owner owner_path = 'house__owner' # 更新Space:同样校验关联House的所有者 class SpaceUpdateView(OwnerRequiredMixin, UpdateView): model = Space fields = ['name', 'description'] owner_path = 'house__owner' # 创建Space:确保选中的House属于当前用户 class SpaceCreateView(OwnerRequiredMixin, CreateView): model = Space fields = ['name', 'house'] def get_queryset(self): # 限制表单中可选的House只能是当前用户的 return super().get_queryset().filter(house__owner=self.request.user) def form_valid(self, form): # 额外校验:确保用户选中的House确实属于自己 if form.instance.house.owner != self.request.user: raise PermissionDenied return super().form_valid(form)
进阶优化:让Mixin更智能
如果想让Mixin自动处理创建关联模型的权限,可以扩展一下,把创建时的校验也封装进去:
class OwnerRequiredMixin(View): owner_field = 'owner' owner_path = None # 针对关联模型的CreateView,指定关联的父模型字段(比如Space的'house'字段) parent_model_field = None def get_object(self, queryset=None): obj = super().get_object(queryset) self._check_owner_permission(obj) return obj def _check_owner_permission(self, obj): if self.owner_path: path_parts = self.owner_path.split('__') owner = obj for part in path_parts: owner = getattr(owner, part) else: owner = getattr(obj, self.owner_field) if owner != self.request.user: raise PermissionDenied def form_valid(self, form): if self.parent_model_field: # 校验父模型的所有者 parent_obj = getattr(form.instance, self.parent_model_field) if getattr(parent_obj, self.owner_field) != self.request.user: raise PermissionDenied else: # 普通模型自动设置所有者 form.instance.owner = self.request.user return super().form_valid(form) def get_queryset(self): # 自动过滤出当前用户有权限的对象(适配Update/Delete视图) queryset = super().get_queryset() filter_param = self.owner_path if self.owner_path else self.owner_field return queryset.filter(**{filter_param: self.request.user})
这样使用SpaceCreateView时就更简洁了:
class SpaceCreateView(OwnerRequiredMixin, CreateView): model = Space fields = ['name', 'house'] parent_model_field = 'house'
总结
通过这个Mixin,你只需要写一次权限校验逻辑,所有需要该权限的视图只需要继承它并配置几个参数即可,完美解决了重复代码的问题,完全符合DRY原则。
内容的提问来源于stack exchange,提问作者Tony
相关产品推荐
相关产品推荐

