#21461: Add pre_update and post_update signals
-------------------------------------+-------------------------------------
Reporter: loic84 | Owner: loic84
Type: New feature | Status: assigned
Component: Database layer | Version: master
(models, ORM) | Resolution:
Severity: Normal | Triage Stage: Accepted
Keywords: | Needs documentation: 0
Has patch: 1 | Patch needs improvement: 0
Needs tests: 0 | UI/UX: 0
Easy pickings: 0 |
-------------------------------------+-------------------------------------
Comment (by bendavis78):
I would love to have an update signal for the purpose of cache
invalidation. Currently I use save/delete signals to detect when something
has changed to update an expensive calculation. While `pk_set` idea solves
some problems, it's not ideal for large result sets and has unpredictable
consequences.
For example, let's say I don't care about the primary keys of objects that
were updated, but rather the objects related to those that were updated. I
could run a much less expensive query by doing, for example,
`queryset.distinct().values_list('related_id')`.
Personally I feel it's better to allow the user be explicit in what is
passed around. I would propose that `pre_update` accept the queryset, and
users have the option to return a dict of extra data that will be
available to `post_update` receivers. For example:
{{{
#!python
@receiver(pre_update, sender=MyModel)
def on_pre_update(sender, update_fields, queryset, **kwargs):
return {
'related_pks': queryset.distinct().values_list('relatedmodel_id')
}
@receiver(post_update, sender=MyModel)
def on_post_update(sender, update_feilds, extra_data, **kwargs):
related_pks = extra_data.get('related_pks', [])
for obj in RelatedModel.objects.filter(pk__in=related_pks):
obj.do_something()
}}}
The implementation if `QuerySet.update()` would look like this:
{{{
#!python
def update(self, **kwargs):
"""
Updates all elements in the current QuerySet, setting all the given
fields to the appropriate values.
"""
assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken."
meta = self.model._meta
self._for_write = True
query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs)
extra_data = {}
if not meta.auto_created:
responses = signals.pre_update.send(
sender=self.model, update_fields=kwargs, queryset=self,
using=self.db)
for rcvr, response in responses:
extra_data.update(response)
with transaction.atomic(using=self.db, savepoint=False):
rows = query.get_compiler(self.db).execute_sql(CURSOR)
self._result_cache = None
if not meta.auto_created:
signals.post_update.send(
sender=self.model, update_fields=kwargs,
extra_data=extra_data, using=self.db)
return rows
}}}
It's a bit cleaner and addresses any performance concerns, as well as the
issue with updated pks. Regarding comment:10, I think there use cases for
both `pre_update` and `post_update`, either together or alone.
--
Ticket URL: <https://code.djangoproject.com/ticket/21461#comment:11>
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/064.349ea9f5b669f8c550f2cec0a2264892%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.