问题
I wrote some smart generic counters and managers for my models (to avoid select count
queries etc.). Therefore I got some heavy logic going on for post_save.
I would like to prevent handling the signal when there's no need to. I guess the perfect interface would be:
instance.save(dispatch_signal=False)
How can I accomplish this?
Update
More information about what I'm doing, if anyone's interested:
- Generic counters are stored in a separate table
- Every time Django paginates an object list, it calls overriden count() method of my custom manager, which basically retrieves the static counter value for appropriate object class.
- Signals trigger the logic of counters update, which is a bit complicated since it checks many aspects of related models (i.e. it has to generate a visibility property based on a nested category tree). I can't put this logic in Model.save() because one counter depends on many different models. I'd like to have that logic in one piece, instead of fragments spread around.
- I am denormalizing some of my models, so I rewrite (duplicate) certain values across tables.
- For testing purposes I run my little command-extension -- Dilla, to populate random data around.
- I've noticed unwanted signals triggering, therefore I'd like them to run conditionally.
Hope it's clear enough. Excuse my language mistakes.
回答1:
A quick and dirty solution would be:
from django.db.models.signals import post_save
from somewhere_in_my_app import my_post_save_handler
post_save.disconnect(my_post_save_handler)
instance.save()
post_save.connect(my_post_save_handler)
But otherwise i strongly recommend moving your logic into the save()
method of your model.
回答2:
You can disconnect and reconnect the signal. Try using a with:
statement with this utility class:
class SignalBlocker(object):
def __init__(self, signal, receiver, **kwargs):
self.signal = signal
self.receiver = receiver
self.kwargs = kwargs
def __enter__(self, *args, **kwargs):
self.signal.disconnect(self.receiver)
def __exit__(self, *args, **kwargs):
self.signal.connect(self.receiver, **self.kwargs)
You can now use:
with SignalBlocker(post_save, my_post_save_handler):
instance.save()
回答3:
I found simple and easy solution:
MyModel.objects.filter(pk=instance.id).update(**data)
It is due to (https://docs.djangoproject.com/en/1.5/ref/models/querysets/#update):
Finally, realize that update() does an update at the SQL level and, thus, does not call any save() methods on your models, nor does it emit the pre_save or post_save signals (which are a consequence of calling Model.save()).
回答4:
You can also call instance.save_base(raw=True)
and check for the raw
argument in your pre_save
or post_save
signal handler:
def my_post_save_handler(instance, raw, **kwargs):
if not raw:
heavy_logic()
You can add some sugar and get your perfect interface:
class MyModel:
def save(self, dispatch_signal=True, **kwargs):
self.save_base(raw=not dispatch_signal, **kwargs)
Note that save_base()
is not part of the public API of Django, so it might change in a future version.
来源:https://stackoverflow.com/questions/577376/django-how-do-i-not-dispatch-a-signal