You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django如何实现用户仅编辑自有模型(遵循DRY原则)

这问题我太熟了!用Mixin确实是最符合DRY原则的解决方案——它本质就是一个可复用的类,让你的视图类继承它之后,就能自动获得封装好的权限校验逻辑,再也不用在20多个视图里重复写相同的if判断了。咱们一步步来实现:

核心思路:封装OwnerRequiredMixin

先写一个通用的Mixin类,把所有权限校验的逻辑都放在这里,后续视图只需要继承它并做简单配置就行。

基础版Mixin(适配大多数场景)

这个Mixin会处理两种常见情况:

  1. 模型直接有owner字段关联当前用户
  2. 模型关联其他模型(比如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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:08:12