User
User

Reputation: 2148

Django REST Framework: save/update/delete object with related in one transaction. How to?

I want to ask you, how to deal with adding/editing/deleting objects with many inlines objects (like in Django Admin + FormSet) using DRF. For example:

Publication:
  - title
  - description
  + Notofications (1 and more)
    - first_name
    - email
  + Images (1 and more)
    - title
    - url
  + Attributes (1 and more)
    - name
    - value

JSON input:

{
     "title": "..", 
     "description": "..", 
     "notifications": [{"first_name": "", "email": ""}, ...]
     "images": [{"title": "", "url": ""}, ...]
     "attributes": [{"name": "", "value": ""}, ...]
}

So I think that "adding" such structure is quite simple, but how about "updating" (or "patching") and "deleting" (eg. one of the images)? The whole request should be done in transaction, eg: if we do some edit of publication's title and image's url, and the url has wrong format, we should not save neither publication object nor the image object.

Is there a good pattern in REST API?

Thank you.

Upvotes: 1

Views: 1762

Answers (1)

zymud
zymud

Reputation: 2249

I would recommend to do it in with nested serializers:

class NestedNotificationSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Notification
        fields = ('id', 'first_name', 'email')

    def to_internal_value(self, data):
        """ Return exist notification as internal value if id is provided """
        if 'id' in data:
            try:
                return models.Notification.objects.get(id=data['id'])
            except models.Notification.DoesNotExists:
                raise serializers.ValidationError('Notification with id %s does not exist' % data['id'])
        else:
            internal_data = super(NestedNotificationSerializer, self).to_internal_value(data)
            return models.Notification(**internal_data)


class PublicationSerializer(serializers.ModelSerializer):

    notifications = NestedNotificationSerializer(many=True)
    ...

    def create(self, validated_data):
        notifications = validated_data.pop('notifications', [])
        # create publication and its notifications in one transaction
        with transaction.atomic():
            publication = super(PublicationSerializer, self).create(validated_data)
            for notification in notifications:
                publication.notifications.add(notification)

        return publication

    def update(self, instance, validated_data):
        notifications = validated_data.pop('notifications', [])
        new_notifications = [notification for notification in notifications if notification.id is None]
        existed_notifications = set([notification for notification in notifications if notification.id is not None])

        # update publication and its notifications in one transaction:
        with transaction.atomic():
            publication = super(PublicationSerializer, self).update(instance, validated_data)
            old_notifications = set(publication.notifications.all())

            removed_notifications = old_notifications - existed_notifications
            for notification in removed_notifications:
                notification.delete()

            for notification in new_notifications:
                publication.notifications.add(notification)

        return publication

Upvotes: 5

Related Questions