Disconnect signals for models and reconnect in django

后端 未结 5 1010
心在旅途
心在旅途 2020-12-08 14:11

I need make a save with a model but i need disconnect some receivers of the signals before save it.

I mean,

I have a model:

class MyModel(mo         


        
相关标签:
5条回答
  • 2020-12-08 14:32

    I haven't tested the following code, but it should work:

    from django.db.models.signals import pre_save
    
    
    def save_without_the_signals(instance, *args, **kwargs):
        receivers = pre_save.receivers
        pre_save.receivers = []
        new_instance = instance.save(*args, **kwargs)
        pre_save.receivers = receivers
        return new_instance
    

    It will silence signals from all sender's though not just instance.__class__.


    This version disables only the given model's signals:

    from django.db.models.signals import pre_save
    from django.dispatch.dispatcher import _make_id
    
    
    def save_without_the_signals(instance, *args, **kwargs):
        receivers = []
        sender_id = _make_id(instance.__class__)
        for index in xrange(len(self.receivers)):
            if pre_save.receivers[index][0][1] == sender_id:
                receivers.append(pre_save.receivers.pop(index))
        new_instance = instance.save(*args, **kwargs)
        pre_save.receivers.extend(receivers)
        return new_instance
    
    0 讨论(0)
  • 2020-12-08 14:34

    If you only want disconnect and reconnect one custom signal, you may use this code:

    def disconnect_signal(signal, receiver, sender):
        disconnect = getattr(signal, 'disconnect')
        disconnect(receiver, sender)
    
    def reconnect_signal(signal, receiver, sender):
        connect = getattr(signal, 'connect')
        connect(receiver, sender=sender)
    

    In this way you can make this:

    disconnect_signal(pre_save, pre_save_model, MyModel)
    a.save()
    reconnect_signal(pre_save, pre_save_model, MyModel)
    
    0 讨论(0)
  • 2020-12-08 14:35

    I needed to prevent certain signals from firing during unittests so I made a decorator based on qris's response:

    from django.db.models import signals
    
    def prevent_signal(signal_name, signal_fn, sender):
        def wrap(fn):
            def wrapped_fn(*args, **kwargs):
                signal = getattr(signals, signal_name)
                signal.disconnect(signal_fn, sender)
                fn(*args, **kwargs)
                signal.connect(signal_fn, sender)
            return wrapped_fn
        return wrap
    

    Using it is simple:

    @prevent_signal('post_save', my_signal, SenderClass)
    def test_something_without_signal(self):
        # the signal will not fire inside this test
    
    0 讨论(0)
  • 2020-12-08 14:49

    You can connect and disconnect signals as Haystack does in RealTimeSearchIndex, which seems more standard:

    from django.db.models import signals
    signals.pre_save.disconnect(pre_save_model, sender=MyModel)
    a.save()
    signals.pre_save.connect(pre_save_model, sender=MyModel)
    
    0 讨论(0)
  • 2020-12-08 14:53

    For a clean and reusable solution, you can use a context manager:

    class temp_disconnect_signal():
        """ Temporarily disconnect a model from a signal """
        def __init__(self, signal, receiver, sender, dispatch_uid=None):
            self.signal = signal
            self.receiver = receiver
            self.sender = sender
            self.dispatch_uid = dispatch_uid
    
        def __enter__(self):
            self.signal.disconnect(
                receiver=self.receiver,
                sender=self.sender,
                dispatch_uid=self.dispatch_uid,
                weak=False
            )
    
        def __exit__(self, type, value, traceback):
            self.signal.connect(
                receiver=self.receiver,
                sender=self.sender,
                dispatch_uid=self.dispatch_uid,
                weak=False
            )
    

    Now, you can do something like the following:

    from django.db.models import signals
    
    from your_app.signals import some_receiver_func
    from your_app.models import SomeModel
    
    ...
    kwargs = {
        'signal': signals.post_save,
        'receiver': some_receiver_func,
        'sender': SomeModel, 
        'dispatch_uid': "optional_uid"
    }
    with temp_disconnect_signal(**kwargs):
        SomeModel.objects.create(
            name='Woohoo',
            slug='look_mom_no_signals',
        )
    

    Note: If your signal handler uses a dispatch_uid, you MUST use the dispatch_uid arg.

    0 讨论(0)
提交回复
热议问题