Django REST Framework: agregar campo adicional a ModelSerializer

149

Quiero serializar un modelo, pero quiero incluir un campo adicional que requiera realizar algunas búsquedas en la base de datos en la instancia del modelo para serializar:

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

¿Cuál es la forma correcta de hacer esto? Veo que puede pasar un "contexto" adicional al serializador, ¿es la respuesta correcta pasar en el campo adicional en un diccionario de contexto? Con ese enfoque, la lógica de obtener el campo que necesito no estaría contenida en la definición del serializador, lo cual es ideal ya que cada instancia serializada necesitará my_field. En otra parte de la documentación de los serializadores DRF dice "los campos adicionales pueden corresponder a cualquier propiedad o invocarse en el modelo". ¿De qué campos extra estoy hablando? ¿Debo definir una función en Foola definición del modelo que devuelva my_fieldvalor, y en el serializador conecto my_field a ese invocable? ¿Cómo se ve eso?

Gracias de antemano, feliz de aclarar la pregunta si es necesario.

Neil
fuente

Respuestas:

226

Creo que SerializerMethodField es lo que estás buscando:

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
fuente
19
¿Es posible agregar validación a tales campos? mi pregunta es: ¿cómo aceptar valores POST personalizados que se puedan validar y procesar en el controlador post_save ()?
Alp
20
Tenga en cuenta que SerializerMethodField es de solo lectura, por lo que esto no funcionará para POST / PUT / PATCH entrante.
Scott A
24
En DRF 3, se cambia a field_name = serializers.SerializerMethodField()ydef get_field_name(self, obj):
Programador químico
1
¿Cuál es el foomomento en que def un SerializerMethodField? cuando usa CreateAPIView, ¿el foo ha almacenado y luego puede usar el método is_named_bar ()?
244boy
Entonces, según esta respuesta, si desea pasar, digamos, 12 campos adicionales a su serializador, debe definir 12 métodos específicos para cada campo que solo devuelve foo.field_custom?
AlxVallejo
41

Puede cambiar su método de modelo a propiedad y usarlo en serializador con este enfoque.

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',)

Editar: con versiones recientes de rest framework (probé 3.3.3), no necesita cambiar a propiedad. El método modelo funcionará bien.

Wasil Sergejczyk
fuente
Gracias @Wasil! No estoy familiarizado con el uso de propiedades en los modelos de Django, y no puedo encontrar una buena explicación de lo que significa. ¿Puedes explicar? ¿Cuál es el punto del decorador @property aquí?
Neil
2
significa que puede llamar a este método como una propiedad: es decir, variable = model_instance.my_fieldda el mismo resultado que variable = model_instance.my_field()sin el decorador. también: stackoverflow.com/a/6618176/2198571
Wasil Sergejczyk
1
Esto no funciona, al menos en Django 1.5.1 / djangorestframework == 2.3.10. ModelSerializer no obtiene la propiedad incluso si se menciona explícitamente en el atributo Meta "campos".
Ygneo
9
debe agregar el campo al serializador porque no es un campo de modo real : my_field = serializers.Field (source = 'my_field')
Marius
2
source='my_field' No se requieren más y lanzar una excepción
Guillaume Vicente
13

Con la última versión de Django Rest Framework, debe crear un método en su modelo con el nombre del campo que desea agregar. No es necesario @propertyy source='field'genera un error.

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

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

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
fuente
¿Qué pasa si quiero tener un requestobjeto dentro de def foo (self) que pueda modificar el valor de foo? (ejemplo, una búsqueda basada en request.user)
saran3h
1
¿Qué pasa si el valor de foo proviene de la solicitud?
saran3h
11

Mi respuesta a una pregunta similar ( aquí ) podría ser útil.

Si tiene un Método modelo definido de la siguiente manera:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Puede agregar el resultado de llamar a dicho método a su serializador de la siguiente manera:

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

ps Dado que el campo personalizado no es realmente un campo en su modelo, generalmente querrá hacerlo de solo lectura, de esta manera:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
fuente
10

si desea leer y escribir en su campo adicional, puede usar un nuevo serializador personalizado, que amplía los serializadores. Serializador, y úselo de esta manera

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']

Lo uso en campos anidados relacionados con alguna lógica personalizada

Marco Silva
fuente
2

Esto funcionó para mí . Si solo queremos agregar un campo adicionalModelSerializer, podemos hacerlo como a continuación, y también se le puede asignar un valor al campo después de algunos cálculos de búsqueda. O en algunos casos, si queremos enviar los parámetros en respuesta API.

En model.py

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

En 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 ')

Espero que esto pueda ayudar a alguien.

Vinay Kumar
fuente
0

Como dijo Chemical Programer en este comentario , en el último DRF puedes hacerlo así:

class FooSerializer(serializers.ModelSerializer):
    extra_field = serializers.SerializerMethodField()

    def get_extra_field(self, foo_instance):
        return foo_instance.a + foo_instance.b

    class Meta:
        model = Foo
        fields = ('extra_field', ...)

Fuente de documentos DRF

trici0pa
fuente