Django - 对象级权限与基于类的通用视图

2 投票
2 回答
1450 浏览
提问于 2025-04-17 04:44

这是模型:

class Car(models.Model):
    user = models.ForeignKey(User, related_name='cars')
    name = models.CharField(max_length=64)

网址模式大致是这样的:

url(r'^car/(?P<pk>\d+)/$', login_required(CarDetails.as_view()), name='car_details)

还有视图:

class CarDetail(DetailView):
    context_object_name = 'car'
    template_name = 'my_app/car_details.html'
    model = models.Car

    def get_object(self, *args, **kwargs):
        car = super(CarDetail, self).get_object(*args, **kwargs)
        if car.user != self.request.user:
            raise PermissionDenied()
        else:
            return car

这样做是没问题的,但在每个类里我都得重写一下 get_object 方法,以防止用户去修改别人的对象。这包括我所有模型的编辑和删除,这样做严重违反了DRY原则(不要重复自己)。

有没有更好的方法来解决这个问题?比如说像 login_required 装饰器那样?

2 个回答

0

这个解决方案基本上很简单,就像DrTyrsa在他的回答中提到的那样,只是有一点小不同。我创建了一个基础类CurUserOnly,它继承自object,而不是DetailView(因为我也想把这个类用在DeleteViewUpdateView上),现在CarDetail类继承了CurUserOnlyDetailViewCarDelete类继承了CurUserOnlyDeleteView,依此类推……

有趣的是,我之前也试过这样做,但没有成功,因为我忘记了Python的MRO(方法解析顺序),导致DetailView在继承列表中排在前面,而CurUserOnly应该在前面!

最后,这就是CurUserOnly类的代码:

class CurUserOnly(object):
    def get_object(self, *args, **kwargs):
        obj = super(CurUserOnly, self).get_object(*args, **kwargs)
        user_attribute = getattr(self, 'user_attribute', 'user')
        user = obj
        for part in user_attribute.split('.'):
            user = getattr(user, part, None)
        if user != self.request.user:
            raise PermissionDenied()
        else:
            return obj

如果我有一个模型与用户没有直接联系,我只需要添加一个user_attribute字段。例如,如果我有一个模型Tyre,它有一个指向Car的外键,那么它的DeleteView看起来会是这样的:

class TyreDelete(CurUserOnly, DeleteView):
    model = models.Tyre
    user_attribute = 'car.user'

这个回答作为对问题的编辑发布,问题是Django - 对象级权限和基于类的通用视图,由提问者del-boy发布,采用CC BY-SA 3.0协议。

6

关于定义基类(或者混入类)并使用继承的事情,你怎么看?

class CurUserOnlyDetailView(DetailView):
    def get_object(self, *args, **kwargs):
        obj = super(CurUserOnlyDetailView, self).get_object(*args, **kwargs)
        if obj.user != self.request.user:
            raise PermissionDenied()
        else:
            return obj

class CarDetail(CurUserOnlyDetailView):
    context_object_name = 'car'
    template_name = 'my_app/car_details.html'
    model = models.Car

# another view, no DRY violation
class BikeDetail(CurUserOnlyDetailView):
    context_object_name = 'bike'
    template_name = 'my_app/bike_details.html'
    model = models.Bike

撰写回答