Передача аргументов сигналов django - post_save/pre_save

Я работаю над приложением уведомлений в Django 1.6 и хочу передать дополнительные аргументы сигналам Django, таким как post_save, Я пытался использовать частичное из functools, но не повезло.

from functools import partial
post_save.connect(
    receiver=partial(notify,
        fragment_name="categories_index"),
            sender=nt.get_model(),
            dispatch_uid=nt.sender
    )

notify функция имеет ключевое слово аргумент fragment_name который я хочу передать по умолчанию в моих сигналах.

Какие-либо предложения?

4 ответа

Решение

Ваша попытка с частичным не работает, потому что по умолчанию эти приемники подключены с использованием слабой ссылки.

Согласно документации Django:

Django хранит обработчики сигналов как слабые ссылки по умолчанию, поэтому, если ваш обработчик является локальной функцией, он может быть сборщиком мусора. Чтобы предотвратить это, передайте слабый = Ложный, когда вы вызываете сигнал connect().

from functools import partial
post_save.connect(
    receiver=partial(notify,
        fragment_name="categories_index"),
            sender=nt.get_model(),
            dispatch_uid=nt.sender,
            weak=False
    )

Включить слабый = Ложь, и эта часть не будет собирать мусор.

Мой оригинальный ответ ниже и использует подход, который не использует частичное.

Вы можете украсить свою функцию сохранения поста до подключения ее к получателю post_save.

from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save, post_delete

def extra_args(fragment_name, *args, **kwargs):
    def inner1(f, *args, **kwargs):
        def inner2(sender, instance, **kwargs):
            f(sender, instance, fragment_name=fragment_name, **kwargs)
        return inner2
    return inner1

@receiver(post_save, sender=ExampleModel)
@extra_args(fragment_name="categories_index")
def my_post_save(sender, instance, fragment_name, **kwargs):
    print "fragment_name : ", fragment_name
    #rest of post save...

Дополнительный внутренний элемент в extra_args предназначен для декораторов, которые принимают параметры.

Если вы хотите сделать это программно, это работает так же, но обратите внимание, что вам нужно включить weak=False чтобы обернутая функция не была сборкой мусора.

receiver(post_save, sender=aSenderClass, weak=False)(extra_args(fragment_name="meep")(my_post_save))

Или без переноса, но вызов post_save.connect как ваша первоначальная попытка с частичным

post_save.connect(extra_args(fragment_name="meepConnect")(my_post_save), sender=Author, weak=False)

Вы можете определить дополнительные аргументы в пользовательском методе сохранения модели следующим образом:

class MyModel(models.Model):
    ....

    def save(self, *args, **kwargs):
        super(MyModel, self).save(*args, **kwargs)
        self.my_extra_param = 'hello world'

И получить доступ к этому дополнительному аргументу через экземпляр в приемнике сигнала post_save:

@receiver(post_save, sender=MyModel)
def process_my_param(sender, instance, *args, **kwargs):
    my_extra_param = instance.my_extra_param

Я попробовал ответить Евгения Солдатова, но он заставил меня понять, что это может быть намного проще:

У вас может быть что-то вроде:

obj = MyModel.objects.first()
obj.my_extra_param = "hello world"
obj.save() # this will trigger the signal call

а затем возьмите трубку, как в ответе Юджина, и она все равно будет работать.

@receiver(post_save, sender=MyModel)
def process_my_param(sender, instance, *args, **kwargs):
    my_extra_param = instance.my_extra_param

Нет необходимости создавать собственный метод сохранения, подверженный ошибкам.

Вот как это сейчас работает в Django 3.0. Предыдущие версии не пробовал.

Если предопределенные сигналы не подходят, вы всегда можете определить свой собственный.

import django.dispatch

custom_post_save = django.dispatch.Signal(providing_args=[
    "sender", "instance", "created", "raw", "using", "update_fields", "fragment_name"
])

Тогда в вашей модели вы просто должны переопределить save() метод:

from django.db import router

class YourModel(Model):

    # Your fields and methods

    def save(self, force_insert=False, force_update=False, using=None,
         update_fields=None):
         custom_signal_kwargs = {
             "sender": self.__class__,
             "instance": self,
             "created": self.pk is None,
             "raw": False, # As docs say, it's True only for fixture loading
             "using": using or router.db_for_write(self.__class__, instance=self),
             "update_fields": update_fields,
             "fragment_name": "categories_index" # The thing you want
         }
         super(YourModel, self).save(force_insert=False, force_update=False, using=None,
             update_fields=None)
         custom_post_save.send(**custom_signal_kwargs) # Send custom signal

Теперь вам просто нужно подключить этот пользовательский сигнал к вашему notify(...) приемник и он получит fragment_name в кваргах.

Код в Django, ответственном за Сигналы, определен здесь https://github.com/django/django/blob/master/django/dispatch/dispatcher.py. Видите, как это проверяет приемник? Я подозреваю, что ваши проблемы лежат там. Возможно, вам нужна функция-обертка, которая учитывает аргументы, которые должен иметь сигнал, но также устанавливает значение фрагмента_имя.

def fragment_receiver(sender, **kwargs)
    return notify(sender, fragment_name="categories_index", **kwargs)
Другие вопросы по тегам