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

Reply via email to