﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
22543	Add pre_update and post_update signal for querysets	Ben Davis	nobody	"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)
}}}"	New feature	closed	Database layer (models, ORM)	dev	Normal	wontfix	queryset update signals		Unreviewed	0	0	0	0	0	0
