#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.

Reply via email to