Как добавить параметры в декораторы общего вида на основе класса 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

Как это сделать? Я попытался добавить параметр message к success_message, но компилятор пожаловался на несоответствие количества параметров, поэтому я полагаю, что должен быть другой способ.


person Dan Abramov    schedule 28.09.2011    source источник
comment
возможный дубликат аргументов декоратора класса Python   -  person agf    schedule 29.09.2011


Ответы (3)


Похоже, вам нужно использовать закрытие:

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

потому что ваш звонок оценивается как

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

обратите внимание на двойной вызов

person Guard    schedule 29.09.2011
comment
Да, только что понял.. Спасибо. - person Dan Abramov; 29.09.2011

Раньше я делал такие вещи как миксин, но декоратор имеет больше смысла (и его добавление к классам немного проще). Возможно, вы захотите получить подробное имя экземпляра формы, подобное следующему, чтобы исключить необходимость передачи сообщения декоратору:

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.» был успешно сохранен.

person Daniel Swarbrick    schedule 28.02.2012

TL;DR

Декоратор без параметров объявлен как функция class -> class.
Декоратор с параметрами объявлен как функция args -> (class -> class) более высокого порядка.

Он принимает параметры и возвращает функцию, которая принимает класс и возвращает класс (то есть «простой» декоратор).

Объяснение

Ответ из связанного потока от 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 теперь является функцией более высокого порядка, потому что ее возвращаемое значение является самой функцией.

person Dan Abramov    schedule 29.09.2011