Django Rest Framework: cómo agregar un campo personalizado en ModelSerializer

89

Creé ModelSerializery quiero agregar un campo personalizado que no es parte de mi modelo.

Encontré una descripción para agregar campos adicionales aquí e intenté lo siguiente:

customField = CharField(source='my_field')

Cuando agrego este campo y llamo a mi validate()función, este campo no es parte del attrdict. attrcontiene todos los campos del modelo especificados excepto los campos adicionales. Entonces, no puedo acceder a este campo en mi validación sobrescrita, ¿verdad?

Cuando agrego este campo a la lista de campos de esta manera:

class Meta:
    model = Account
    fields = ('myfield1', 'myfield2', 'customField')

luego customFieldaparece un error porque no es parte de mi modelo, lo que es correcto porque quiero agregarlo solo para este serializador.

¿Hay alguna forma de agregar un campo personalizado?

Ron
fuente
¿Podría ampliar "Pero cuando mi campo no está en la lista de campos del modelo especificada en el serializador, no es parte del diccionario de atributos validate ()", no estoy seguro de que sea muy claro.
Tom Christie
Además, "se queja, correctamente, de que no tengo un campo customField en mi modelo", ¿podría ser explícito sobre la excepción que está viendo? ¡Gracias! :)
Tom Christie
Actualicé mi publicación y espero que esté más claro ahora. Solo quiero saber cómo puedo agregar un campo que no es parte de mi modelo ...
Ron

Respuestas:

63

Está haciendo lo correcto, excepto que CharField(y los otros campos escritos) son para campos de escritura.

En este caso, solo desea un campo simple de solo lectura, así que en su lugar use:

customField = Field(source='get_absolute_url')
Tom Christie
fuente
4
gracias, pero quiero un campo en el que se pueda escribir. Paso un determinado token de usuario que identifica a mi usuario. pero en mi modelo tengo el usuario y no el token. así que quiero pasar el token y "convertirlo" en un objeto de usuario a través de una consulta.
Ron
lo siguiente es que la fuente debe apuntar a un atributo de modelo, ¿verdad? en mi caso, no tengo ningún atributo al que señalar.
Ron
No entiendo la parte de usuario / token del comentario. Pero si desea incluir un campo en el serializador que se elimina antes de restaurar en una instancia de modelo, puede usar el .validate()método para eliminar el atributo. Ver: django-rest-framework.org/api-guide/serializers.html#validation Eso haría lo que necesita, aunque realmente no entiendo el caso de uso, o por qué desea un campo de escritura que esté vinculado a un propiedad de solo lecturaget_absolute_url ?
Tom Christie
olvidarse de la get_absolute_urlque acabo de copiar y paste'd que a partir de los documentos. Solo quiero un campo de escritura normal al que pueda acceder en el validate(). Supuse que necesitaría el sourceatributo ...
Ron
Eso tiene más sentido. :) El valor debería pasar para validar, así que comprobaría cómo está creando una instancia del serializador y si ese valor realmente se está proporcionando.
Tom Christie
82

De hecho existe una solución sin tocar en absoluto el modelo. Puede utilizar los SerializerMethodFieldque le permiten conectar cualquier método a su serializador.

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

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk
Idaho
fuente
6
Como OP mencionó en este comentario , quieren un campo de escritura, que SerializerMethodFieldno lo son
esmail
14

... para mayor claridad, si tiene un método de 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 así:

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á que sea de solo lectura, así:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
fuente
3
¿Qué pasa si quiero que se pueda escribir?
Csaba Toth
1
@Csaba: Usted sólo tendrá que escribir de encargo Guardar y ganchos de eliminación para el contenido adicional: Ver "Guardar y ganchos de eliminación" en "Métodos" ( Aquí ) Tendrá que escritura personalizado perform_create(self, serializer), perform_update(self, serializer), perform_destroy(self, instance).
Lindauson
13

aquí responde a tu pregunta. debe agregar a su cuenta modelo:

@property
def my_field(self):
    return None

ahora puedes usar:

customField = CharField(source='my_field')

fuente: https://stackoverflow.com/a/18396622/3220916

va-dev
fuente
6
He usado este enfoque cuando tiene sentido, pero no es bueno agregar código adicional a los modelos para cosas que solo se usan realmente para llamadas API específicas.
Andy Baker
1
Puede escribir un modelo proxy para el
ashwoods
10

Para mostrar self.author.full_name, recibí un error con Field. Funcionó con ReadOnlyField:

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    author_name = ReadOnlyField(source="author.full_name")
    class Meta:
        model = Comment
        fields = ('url', 'content', 'author_name', 'author')
François Constant
fuente
6

Con la última versión de Django Rest Framework, necesita crear un método en su modelo con el nombre del campo que desea agregar.

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

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

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
fuente
4

Estaba buscando una solución para agregar un campo personalizado de escritura a un serializador de modelo. Encontré este, que no se ha cubierto en las respuestas a esta pregunta.

Parece que realmente necesita escribir su propio serializador simple.

class PassThroughSerializer(serializers.Field):
    def to_representation(self, instance):
        # This function is for the direction: Instance -> Dict
        # If you only need this, use a ReadOnlyField, or SerializerField
        return None

    def to_internal_value(self, data):
        # This function is for the direction: Dict -> Instance
        # Here you can manipulate the data if you need to.
        return data

Ahora puede usar este serializador para agregar campos personalizados a un ModelSerializer

class MyModelSerializer(serializers.ModelSerializer)
    my_custom_field = PassThroughSerializer()

    def create(self, validated_data):
        # now the key 'my_custom_field' is available in validated_data
        ...
        return instance

Esto también funciona si el modelo MyModelrealmente tiene una propiedad llamada my_custom_fieldpero desea ignorar sus validadores.

David Schumann
fuente
por lo que no funciona si my_custom_field no es una propiedad de MyModel, ¿verdad? Recibí el error El campo del serializador puede tener un nombre incorrecto y no coincide con ningún atributo o clave de la MyModelinstancia.
Sandeep Balagopal
2

Después de leer todas las respuestas aquí, mi conclusión es que es imposible hacer esto limpiamente. Tienes que jugar sucio y hacer algo como crear un campo write_only y luego anular los métodos validatey to_representation. Esto es lo que funcionó para mí:

class FooSerializer(ModelSerializer):

    foo = CharField(write_only=True)

    class Meta:
        model = Foo
        fields = ["foo", ...]

    def validate(self, data):
        foo = data.pop("foo", None)
        # Do what you want with your value
        return super().validate(data)

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data["foo"] = whatever_you_want
        return data
Ariel
fuente