Consider the following piece of code:

@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    instance = kwargs['instance']
    if instance.verified:
        do_something(instance)
    else:
        do_else(instance)



Its good, because it keeps `MyModel` decoupled from `do_something`

But there is one flaw. If we do:

MyModel.objects.filter(verified=False).update(verified=True)

we are screwed, `do_something` is not executed, and our models get 
out-of-sync.

If we try to get smart and manually fire the pre_save signal for each 
instance, we are gonna have a hard time.
Its gonna be slow.
And its gonna be memory inefficient.

We already experienced it in our app.

So our new approach is like this:

    pre_bulk_update = Signal(providing_args=["queryset", "update_kwargs"])
    post_bulk_update = Signal(providing_args=["update_kwargs",])

    pre_bulk_create = Signal(providing_args=["objs", "batch_size"])
    post_bulk_create = Signal(providing_args=["objs", "batch_size"])


    class MyModelQuerySet(models.QuerySet):
        def update(self, **kwargs):
            pre_bulk_update.send(sender=self.model, queryset=self, 
update_kwargs=kwargs)
            res = super(MyModelQuerySet, self).update(**kwargs)
            # The queryset will be altered after the update call
            # so no reason to send it.
            post_bulk_update.send(sender=self.model, update_kwargs=kwargs)
            return res

        def bulk_create(self, objs, batch_size=None):
            pre_bulk_create.send(sender=self.model, objs=objs, batch_size=
batch_size)
            res = super(MyModelQuerySet, self).bulk_create(objs, batch_size)
            post_bulk_create.send(sender=self.model, objs=objs, batch_size=
batch_size)
            return res


    class MyModel(models.Model):
        #...
        objects = MyModelQuerySet.as_manager()



This gives us a nice interface to handle all kind of changes regarding 
`MyModel`
Our example usage looks like this:


@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    instance = kwargs['instance']
    if instance.verified:
        do_something(instance)
    else:
        do_else(instance)


@receiver(pre_bulk_update, sender=MyModel)
def my_bulk_update_handler(sender, **kwargs):
    update_kwargs = kwargs['update_kwargs']
    if 'verified' not in update_kwargs:
        # no change im interested in
        # no need to take any action
        return

    queryset = kwargs['queryset']
    pks_to_be_updated = queryset.values_list('pk', flat=True)
    if update_kwargs['verified']:
        do_something_bulk_update_implementation(pks_to_be_updated)
    else:
        bulk_update_do_else_implementation(pks_to_be_updated)


@receiver(pre_bulk_create, sender=MyModel)
def my_bulk_create_handler(sender, **kwargs):
    objs = kwargs['objs']
    group_1 = []
    group_2 = []
    for obj in objs:
        if obj.verified:
            group_1.append(obj)
        else:
            group_2.append(obj)

    if group_1:
        do_something_bulk_create_implementation(group_1)
    if group_2:
        bulk_create_do_else_implementation(group_2)



I think this turns out to be a very clean approach.
It help us use the most optimal strategy to handle the change.
So I'm sharing this with the community to check your feedback.
I believe if this gets into the Django Internals, it can be a very powerful 
tool.
It will lose power as a 3rd party app.

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/94257dee-e88c-41ae-ab39-c853aaaa5d8e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to