#22543 closed New feature (wontfix)
Add pre_update and post_update signal for querysets
Reported by: | Ben Davis | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | queryset update signals |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
One use case for this would be cache coherence. For example, let's say we're caching an expensive operation based on a model pk, and we want to invalidate that cache when a row is changed in any way. Currently we can only do this with save/delete signals. Consider the following example for calculating points awarded to an account:
def get_points_earned(account): key = 'acct-{0}-points'.format(account.id) if not cache.get(key): point_awards = PointAward.objects.filter(account=account) points = point_awards.aggregate(total=Sum('points'))['total'] cache.set(key, points) return cache.get(key) # Invalidate points earned if a PointAward is added/changed/deleted @receiver([post_save, post_delete, post_update], sender=PointAward) def on_point_award_changed(sender, **kwargs): instance = kwargs.get('instance') if instance: key = 'acct-{0}-points'.format(instance.id) cache.delete(key)
With this coherence strategy, a call to PointAward.objects.update()
will cause the cache to become incoherent, and will cause errors in the calculation.
This can easily be addressed by providing a signal allowing a developer to decide how best to update the cache when
update()
is called:
# django/db/models/signals.py pre_update = ModelSignal(providing_args=["queryset", "values", "using"], use_caching=True) post_update = ModelSignal(providing_args=["queryset", "values", "using"], use_caching=True)
We can now update our receiver to also respond to updates as well as changes and deletions:
@receiver([post_save, post_delete], sender=PointAward) def on_point_award_changed(sender, **kwargs): point_award = kwargs.get('instance') if instance and instance.pk: # this is a save/delete, invalidate the related account's points account_id = point_award.account_id key = 'acct-{0}-points'.format(instance.id) cache.delete(key) elif kwargs.get('queryset'): # this is an update, invalidate all matching accounts qs = kwargs['queryset'] for acct_id in qs.objects.distinct().values_list('account_id', flat=True) key = 'acct-{0}-points'.format(acct_id) cache.delete(key)
Change History (2)
comment:1 by , 11 years ago
Resolution: | → duplicate |
---|---|
Status: | new → closed |
comment:2 by , 11 years ago
Resolution: | duplicate → wontfix |
---|
Seems this is similar to #17824 which was won't fixed. Needs discussion on the django-developers mailing list in order to be accepted.
Hi @bendavis78, this feature is discussed at #21461.