#22543: Add pre_update and post_update signal for querysets
----------------------------------------------+----------------------------
Reporter: bendavis78 | Owner: nobody
Type: New feature | Status: new
Component: Database layer (models, ORM) | Version: master
Severity: Normal | Keywords: queryset
Triage Stage: Unreviewed | update signals
Easy pickings: 0 | Has patch: 0
| UI/UX: 0
----------------------------------------------+----------------------------
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:
{{{
#!python
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:
{{{
#!python
# 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:
{{{
#!python
@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)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/22543>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/053.f75b440195931432e2c1d8b773d94c17%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.