如何为Django基于类的通用视图装饰器添加参数?
我写了一个装饰器,用来在对象创建时显示成功消息:
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)
注意这里有两次调用