Django REST Framework: добавление дополнительного поля в ModelSerializer

149

Я хочу сериализовать модель, но хочу включить дополнительное поле, которое требует выполнения некоторых операций поиска в базе данных для экземпляра модели, который необходимо сериализовать:

class FooSerializer(serializers.ModelSerializer):
  my_field = ... # result of some database queries on the input Foo object
  class Meta:
        model = Foo
        fields = ('id', 'name', 'myfield')

Как правильно это сделать? Я вижу, что вы можете передать в дополнительный «контекст» сериализатору, является ли правильным ответом передать дополнительное поле в контекстном словаре? При таком подходе логика получения нужного мне поля не будет автономной с определением сериализатора, что является идеальным, так как потребуется каждому сериализованному экземпляру my_field. В другом месте в документации по сериализаторам DRF говорится, что «дополнительные поля могут соответствовать любому свойству или вызываться в модели». О дополнительных полях я говорю? Должен ли я определить функцию в Fooопределении модели, которая возвращает my_fieldзначение, и в сериализаторе я подключаю my_field к этому вызываемому объекту? На что это похоже?

Заранее спасибо, с удовольствием уточню вопрос при необходимости.

Нил
источник

Ответы:

226

Я думаю, что SerializerMethodField это то, что вы ищете:

class FooSerializer(serializers.ModelSerializer):
  my_field = serializers.SerializerMethodField('is_named_bar')

  def is_named_bar(self, foo):
      return foo.name == "bar" 

  class Meta:
    model = Foo
    fields = ('id', 'name', 'my_field')

http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

JP
источник
19
Можно ли добавить проверку в такие поля? у меня вопрос: как принять пользовательские значения POST, которые можно проверить и обработать в обработчике post_save ()?
Alp
20
Обратите внимание, что SerializerMethodField доступен только для чтения, поэтому он не будет работать для входящих POST / PUT / PATCH.
Скотт
24
В DRF 3 он изменен на field_name = serializers.SerializerMethodField()иdef get_field_name(self, obj):
Химический программист
1
что fooкогда определяется SerializerMethodField? Когда используется CreateAPIView, хранится ли foo, можно ли использовать метод is_named_bar ()?
244-го
Итак, основываясь на этом ответе, если вы хотите передать, скажем, 12 дополнительных полей вашему сериализатору, вам нужно определить 12 конкретных методов для каждого поля, которое просто возвращает foo.field_custom?
AlxVallejo
41

Вы можете изменить метод модели на свойство и использовать его в сериализаторе с этим подходом.

class Foo(models.Model):
    . . .
    @property
    def my_field(self):
        return stuff
    . . .

class FooSerializer(ModelSerializer):
    my_field = serializers.ReadOnlyField(source='my_field')

    class Meta:
        model = Foo
        fields = ('my_field',)

Изменить: В последних версиях остальные рамки (я попробовал 3.3.3), вам не нужно менять свойство. Модельный метод будет просто отлично работать.

Василь Сергейчик
источник
Спасибо @ Васил! Я не знаком с использованием свойств в моделях Django и не могу найти хорошее объяснение того, что это значит. Вы можете объяснить? Какой смысл в @property decorator здесь?
Нил
2
это означает, что вы можете вызывать этот метод как свойство: т.е. variable = model_instance.my_fieldдает тот же результат, что и variable = model_instance.my_field()без декоратора. также: stackoverflow.com/a/6618176/2198571
Васил Сергейчик
1
Это не работает, по крайней мере, в Django 1.5.1 / djangorestframework == 2.3.10. ModelSerializer не получает свойства, даже если явно указано в метаатрибуте "fields".
Игнео
9
вам нужно добавить поле в сериализатор, потому что это не настоящее поле модели: my_field = serializers.Field (source = 'my_field')
Marius
2
source='my_field' больше не требуется и выдвигает исключение
Гийом Винсент
13

В последней версии Django Rest Framework вам необходимо создать метод в вашей модели с именем поля, которое вы хотите добавить. Нет необходимости @propertyи source='field'поднять ошибку.

class Foo(models.Model):
    . . .
    def foo(self):
        return 'stuff'
    . . .

class FooSerializer(ModelSerializer):
    foo = serializers.ReadOnlyField()

    class Meta:
        model = Foo
        fields = ('foo',)
Гийом Винсент
источник
Что делать, если я хочу, чтобы requestвнутри def foo (self) был объект, который мог бы изменить значение foo? (пример поиска на основе request.user)
saran3h
1
Что если значение foo исходит из запроса?
saran3h
11

Мой ответ на подобный вопрос ( здесь ) может быть полезным.

Если у вас есть метод модели, определенный следующим образом:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Вы можете добавить результат вызова указанного метода в ваш сериализатор следующим образом:

class MyModelSerializer(serializers.ModelSerializer):
    model_method_field = serializers.CharField(source='model_method')

ps Так как пользовательское поле на самом деле не является полем в вашей модели, вы обычно захотите сделать его доступным только для чтения, например, так:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
источник
10

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

class ExtraFieldSerializer(serializers.Serializer):
    def to_representation(self, instance): 
        # this would have the same as body as in a SerializerMethodField
        return 'my logic here'

    def to_internal_value(self, data):
        # This must return a dictionary that will be used to
        # update the caller's validation data, i.e. if the result
        # produced should just be set back into the field that this
        # serializer is set to, return the following:
        return {
          self.field_name: 'Any python object made with data: %s' % data
        }

class MyModelSerializer(serializers.ModelSerializer):
    my_extra_field = ExtraFieldSerializer(source='*')

    class Meta:
        model = MyModel
        fields = ['id', 'my_extra_field']

я использую это в связанных вложенных полях с некоторой пользовательской логикой

Марко Сильва
источник
2

Это сработало для меня . Если мы хотим просто добавить дополнительное полеModelSerializer, мы можем сделать это, как показано ниже, а также поле может быть присвоено некоторое значение val после некоторых вычислений поиска. Или в некоторых случаях, если мы хотим отправить параметры в ответ API.

В model.py

class Foo(models.Model):
    """Model Foo"""
    name = models.CharField(max_length=30, help_text="Customer Name")

В serializer.py

class FooSerializer(serializers.ModelSerializer):
    retrieved_time = serializers.SerializerMethodField()
    
    @classmethod
    def get_retrieved_time(self, object):
        """getter method to add field retrieved_time"""
        return None

  class Meta:
        model = Foo
        fields = ('id', 'name', 'retrieved_time ')

Надеюсь, это может кому-то помочь.

Винай Кумар
источник