2

I have a case when user needs to update one instance together with adding/editing the m2m related objects on this instance.

Here is my solution:

# models.py

class AdditionalAction(SoftDeletionModel):
    ADDITIONAL_CHOICES = (
        ('to_bring', 'To bring'),
        ('to_prepare', 'To prepare'),
    )
    title = models.CharField(max_length=50)
    type = models.CharField(choices=ADDITIONAL_CHOICES, max_length=30)

class Event(models.Model):
     title= models.CharField(max_length=255)
     actions = models.ManyToManyField(AdditionalAction, blank=True)



# serializers.py
class MySerializer(serializers.ModelSerializer):
    def update(self, instance, validated_data):
        actions_data = validated_data.pop('actions')

        # Use atomic block to rollback if anything raised Exception 
        with transaction.atomic():

            # update main object
            updated_instance = super().update(instance, validated_data)

            actions = []
            # Loop over m2m relation data and
            # create/update each action instance based on id present
            for action_data in actions_data:
                action_kwargs = {
                    'data': action_data
                }
                id = action_data.get('id', False)
                if id:
                    action_kwargs['instance'] = AdditionalAction.objects.get(id=id)
                actions_ser = ActionSerializerWrite(**action_kwargs)
                actions_ser.is_valid(raise_exception=True)
                actions.append(actions_ser.save())

            updated_instance.actions.set(actions)

        return updated_instance

Can anyone suggest better solution?

P.S. actions can be created or updated in this case, so i can't just use many=True on serializer cause it also needs instance to update.

Feanor
  • 3,568
  • 5
  • 29
  • 49

1 Answers1

1

Using for loop with save here will be a killer if you have a long list or actions triggered on save, etc. I'd try to avoid it. You may be better off using ORMS update with where clause: https://docs.djangoproject.com/en/2.0/topics/db/queries/#updating-multiple-objects-at-once and even reading the updated objects from the database after the write.

For creating new actions you could use bulk_create:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#bulk-create

There is also this one: https://github.com/aykut/django-bulk-update (disclaimer: I am not a contributor or author of the package).

You have to be aware of cons of this method - if you use any post/pre_ save signals those will not be triggered by the update.

In general, running multiple saves will kill the database, and you might end up with hard to diagnose deadlocks. In one of the projects I worked on moving from save() in the loop into update() decreased response time from 30 something seconds to < 10 where the longest operations left where sending emails.

jacoor
  • 760
  • 5
  • 9
  • This doesn't relate to my question in terms of how to deal with DRF serializers. – Feanor Mar 02 '18 at 12:42
  • You asked for a better solution, not how to handle serializers :P. Anyway, jokes aside - I understand your point (you can use serializer.is_valid() but save() is a bad idea). I think I described how I'd handled this case in the previous post, and why. I am sorry if it doesn't suit your needs. – jacoor Mar 03 '18 at 14:26