如何为Django基于类的通用视图装饰器添加参数?

1 投票
3 回答
1217 浏览
提问于 2025-04-17 03:19

我写了一个装饰器,用来在对象创建时显示成功消息:

from django.contrib import messages

def success_message(klass):
    def form_valid(self, form):
        response = super(klass, self).form_valid(form)
        messages.success(self.request, 'Object added successfully')
        return response

    klass.form_valid = form_valid
    return klass

然后我用这个装饰器来装饰基于类的通用视图:

@success_message
class BandCreateView(CreateView):
    model = Band

现在我想让这个装饰器可以接收参数,这样就可以做到:

@success_message('Band created successfully.')
class BandCreateView(CreateView):
    model = Band

我该怎么做呢?我试着给success_message加一个message参数,但编译器说参数数量不匹配,所以我想可能还有其他方法。

3 个回答

0

简要说明

没有参数的装饰器是以类 -> 类的方式定义的。
而带参数的装饰器则是以更高阶的方式定义,形式为参数 -> (类 -> 类)

它接受参数,并返回一个可以接受类并返回类的函数(也就是“简单”的装饰器)。

详细解释

在一个相关的讨论串中,来自t.dubrownik的回答真的帮了我大忙。

总之,带参数的装饰器的语法稍微有点不同——带参数的装饰器应该返回一个可以接受函数并返回另一个函数的函数。所以它实际上应该返回一个普通的装饰器。

虽然他在讲函数装饰器,但这个概念同样适用于类装饰器。
这是我得到的理解:

from django.contrib import messages

def success_message(message):
    def actual_decorator(klass):
        def form_valid(self, form):
            response = super(klass, self).form_valid(form)
            messages.success(self.request, message)
            return response

        klass.form_valid = form_valid
        return klass

    return actual_decorator

注意到actual_decorator本身重复了没有参数的success_message版本,并且success_message现在是一个更高阶的函数,因为它的返回值本身就是一个函数。

1

我之前是用混合类来做这种事情,但用装饰器更合适(而且加到类里也更简单)。你可能想要获取表单实例的详细名称,可以像下面这样做,这样就不需要把消息传给装饰器了:

from django.utils.translation import ugettext_lazy as _

def ucfirst(value):
    return value[0].upper() + value[1:]

def success_message(klass):
    __orig_form_valid = klass.form_valid
    def form_valid(self, form):
        response = __orig_form_valid(self, form)
        messages.success(self.request, _("%(object)s \"%(object_name)s\" was saved successfully.") %
                         {'object': ucfirst(form.instance._meta.verbose_name), 'object_name': unicode(form.instance)})
        return response

    klass.form_valid = form_valid
    return klass

这样会生成一个成功的消息,大概是这样的:

客户 "ACME Inc." 已成功保存。

3

看起来你需要用到闭包:

def decorator(arg):
    def wrap(klass): ...
    return wrap

因为你的调用会被计算成

class BandCreateView(CreateView): ...
BandCreateView = @success_message('Band created successfully.')(BandCreateView)

注意这里有两次调用

撰写回答