Вызов метода .update() вложенного сериализатора

У меня есть JSONField в моей модели, в которой хранятся некоторые данные конфигурации. Я хочу получить доступ к этому полю (как для чтения, так и для записи) с возможностью частичного обновления внутренних полей и их значений.

Для примера пусть модель называется MyModel, а JSONField называется config:

class MyModel(models.Model):
    config = JSONField(default=dict())
    ...

Я создал отдельный ViewSet для доступа к информации, хранящейся в поле config. Предположим, что модель user имеет отношение ForeignKey к MyModel. Упрощенная версия этого ViewSet:

class ConfigurationFieldViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):

serializer_class = MyModelConfigurationSerializer

def get_object(self):
    return self.request.user.my_model

Данные, хранящиеся в config, имеют определенную структуру с несколькими возможными внутренними объектами:

{
    "C1": {"counter": 42, "active": false},
    "C2": {"counter": 13, "active": true}
}

Чтобы получить доступ и правильно сериализовать экземпляр MyModel на всех уровнях вложенности, я создал сериализаторы для каждого уровня поля. Чтобы получить доступ к полю config в самом MyModel, я использую этот сериализатор:

class MyModelConfigurationSerializer(serializers.ModelSerializer):
    configuration = ConfigurationFieldSerializer(required=True)

    class Meta:
        model = MyModel
        fields = ('configuration',)

Для доступа и сериализации первого уровня поля configuration есть второй сериализатор:

class ConfigurationFieldSerializer(serializers.Serializer):
    C1 = BaseConfigurationSerializer(required=True)
    C2 = BaseConfigurationSerializer(required=True)

Наконец, для доступа к внутренней структуре каждого поля C1 и C2 есть третий сериализатор:

class BaseConfigurationSerializer(serializers.Serializer):

    counter = serializers.IntegerField(
        required=False,
        help_text=_('Some integer field help text')
    )
    active = serializers.BooleanField(
        required=False,
        help_text=_('Some boolean field description')
    )

Приведенный выше код отлично работает для чтения данных, хранящихся в поле config, и правильно сериализует его внутренние объекты. Проблема возникает, когда я пытаюсь выполнить PUT в этом поле.

Если я переопределяю метод update на уровне MyModelConfigurationSerializer, то сериализаторы проверяют данные, которые я отправляю, но в виде фрагмента, и я могу сохранить их все сразу. Если я пытаюсь отправить какое-то внутреннее поле, я все равно правильно получаю ошибки проверки внутренними сериализаторами.

    def update(self, instance, validated_data):
        instance.configuration = validated_data.get(
            'configuration', instance.configuration
        )
        instance.save()
        return instance

Чего я не могу сделать, так это вызвать методы update внутренних сериализаторов (в данном случае ConfigurationFieldSerializer и BaseConfigurationSerializer): если я реализую их методы update, они просто не будут вызываться.

Согласно документации DRF вложенные представления с возможностью записи возможны, и соответствующие методы update или create должны вызываться всякий раз, когда update вызывается в сериализаторе верхнего уровня.


person Stan Redoute    schedule 16.04.2019    source источник
comment
Я считаю, что вам нужно вызвать частичное обновление, используя метод PATCH вместо PUT   -  person ivissani    schedule 16.04.2019
comment
@ivissani Тот же эффект, только сериализатор 1 уровня вызывается update   -  person Stan Redoute    schedule 16.04.2019
comment
Конечно, DRF не обрабатывает создание или обновление вложенных сериализаторов сам по себе, вам нужно написать собственные методы create() и update(), если вы хотите это сделать. Я комментировал проверку.   -  person ivissani    schedule 16.04.2019
comment
@ivissani, но даже если я пишу эти методы, они не вызываются   -  person Stan Redoute    schedule 16.04.2019
comment
Вам нужно написать собственный метод update() в сериализаторе верхнего уровня, который обрабатывает обновление вложенных объектов.   -  person ivissani    schedule 16.04.2019
comment
@ivissani это именно то, что я описал в вопросе и что я смог придумать до сих пор   -  person Stan Redoute    schedule 16.04.2019
comment
Давайте продолжим обсуждение в чате.   -  person ivissani    schedule 16.04.2019


Ответы (1)


Недавно у меня тоже была эта проблема, и похоже, что то, как вы это делаете, - единственный способ, когда дело доходит до вложенных сериализаторов с возможностью записи.

Из тех же документов DRF вы, вероятно, уже видели:

Поскольку поведение вложенных операций создания и обновления может быть неоднозначным и может потребовать сложных зависимостей между связанными моделями, платформа REST 3 требует, чтобы вы всегда писали эти методы явно. Методы ModelSerializer по умолчанию .create() и .update() не включают поддержку вложенных представлений с возможностью записи.

Однако доступны сторонние пакеты, такие как DRF Writable Nested, которые поддерживают автоматические вложенные представления с возможностью записи.

В основном это означает, что когда у вас есть вложенность, он даже не будет пытаться вызвать какой-либо из методов хранения вложенного сериализатора.

Это может показаться немного болезненным, но в ретроспективе это, вероятно, лучше для дизайна вашего приложения. Ваш пример довольно прост, но в других ситуациях порядок сохранения может быть важен. Если бы update каждого вложенного сериализатора запускался автоматически, DRF должен был бы каким-то образом знать, когда сохранять каждую вещь.

Например, если ваш пример был о create, а не update, это означало бы, что вам нужно сначала сохранить свою модель MyModel, прежде чем сохранять конфигурацию поверх нее. Однако DRF не может этого знать.

Также легко могло случиться так, что конфигурация была на самом деле другой связанной моделью, которую нужно было сохранить сначала, прежде чем вы сможете сохранить отношение к ней из MyModel. Таким образом, DRF предлагает вам сделать это самостоятельно в корневом сериализаторе.

По моему собственному опыту, это также полезно, чтобы вы могли позже настроить производительность (например, в вашем случае вы можете избежать повторного сохранения MyModel).

Наконец, если вы хотите сделать свой код более модульным, вы все равно можете это сделать (отправить сегменты проверенных данных в разные обработчики, например, в новую функцию update_configurations()), просто это не будет сделано автоматически с помощью вложенных сериализаторов.

person Geekfish    schedule 16.04.2019
comment
Спасибо, это все полезные заметки. Я немного разочарован тем, что это невозможно сделать красиво и просто, потому что мой API генерирует автоматическую документацию по чванству для каждого URI, и я надеялся, что, делая это с вложенными представлениями, я смогу предоставить разработчикам макет чванства. работа на стороне клиента приложения - person Stan Redoute; 16.04.2019
comment
В использовании вложенных сериализаторов по-прежнему есть смысл, просто определение и проверка схемы — это уже выигрыш. Мы также используем swagger и имеем множество вложенных сериализаторов, которые не обрабатывают собственное хранилище. В долгосрочной перспективе это не кажется менее простым/элегантным. - person Geekfish; 16.04.2019