Compose 装饰器应用于基于 class 的视图

Compose decorators applied to class-based views

我有权限检查和视图的层次结构,需要由它们来保护。例如,考虑这两项检查。

def has_profile(user):
    return user.profile is not None

def is_a_sorcerer(user):
    return user.profile.category == 'sorcerer'

请注意,只有在用户有个人资料时调用 is_a_sorcerer 才有意义。

有些观点应该可供任何拥有个人资料的人访问,有些观点进一步仅限于巫师。

class ProfileView(View):
    @method_decorator(user_passes_test(has_profile))
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

class SorcererView(ProfileView):
    @method_decorator(user_passes_test(is_a_sorcerer))
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

但是,请注意,由于继承,is_a_sorcerer 将在 之前被调用 has_profile 并出错。

可以在 is_a_sorcerer 中检查 has_profile:

def is_a_sorcerer(user):
    return has_profile(user) and user.profile.category == 'sorcerer'

虽然这修复了错误,但会导致检查 has_profile 两次,并且检查超过两层时,开销会迅速累积。

如何在不重复代码的情况下组合这些装饰器?我仍然希望将权限检查保留为函数,以便它们可以应用于基于函数的视图。

实际上,我会简单地检查 is_a_sorcerer 方法中的 has_profile。检查 has_profile 两次的开销可以忽略不计。

def is_a_sorcerer(user):
    return has_profile(user) and user.profile.category == 'sorcerer'

可以避免 运行 检查两次,但我认为您无法使用装饰器。相反,您可以更改 ProfileView 以便它检查测试列表。然后覆盖子类中的测试列表。

class ProfileView(View):
    user_tests = [has_profile]

    def dispatch(self, *args, **kwargs):
        for test_func in self.user_tests:
            if not test_func(self.request.user):
                return redirect('login')
        return super().dispatch(*args, **kwargs)


class SorcererView(ProfileView):
    user_tests = ProfileView.user_tests + [is_a_sorcerer]

请注意,这不太传统,而且可能更 fragile/error 容易发生。在子类中覆盖 user_tests 时很容易忘记包含 ProfileView.user_tests