Django Rest Framework - como adicionar um campo personalizado no ModelSerializer


88

Criei um ModelSerializere desejo adicionar um campo personalizado que não faz parte do meu modelo.

Encontrei uma descrição para adicionar campos extras aqui e tentei o seguinte:

customField = CharField(source='my_field')

Quando adiciono este campo e chamo minha validate()função, então este campo não faz parte do attrdicionário. attrcontém todos os campos do modelo especificados, exceto os campos extras. Portanto, não posso acessar este campo na minha validação de sobrescrita, posso?

Quando adiciono este campo à lista de campos assim:

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

então recebo um erro porque customFieldnão faz parte do meu modelo - o que é correto porque eu quero adicioná-lo apenas para este serializador.

Existe alguma maneira de adicionar um campo personalizado?


Você poderia expandir em "Mas quando meu campo não está na lista de campos do modelo especificada no serializador, ele não faz parte do validate () attr-dictionary.", Não tenho certeza se isso está muito claro.
Tom Christie

Também "reclama - corretamente - que não tenho um campo customField em meu modelo.", Poderia ser explícito sobre a exceção que está vendo - obrigado! :)
Tom Christie

Eu atualizei minha postagem e espero que esteja mais claro agora. Só quero saber como posso adicionar um campo que não faz parte do meu modelo ...
Ron


Respostas:


62

Você está fazendo a coisa certa, exceto que CharField(e os outros campos digitados) são para campos graváveis.

Nesse caso, você deseja apenas um campo somente leitura simples, então, em vez disso, use:

customField = Field(source='get_absolute_url')

4
obrigado, mas eu quero um campo gravável. Eu passo um certo token de usuário que identifica meu usuário. mas no meu modelo tenho o usuário e não o token. então, eu quero passar o token e "convertê-lo" em um objeto de usuário por meio de uma consulta.
Ron

a próxima coisa é que a origem precisa ter como alvo um atributo de modelo, certo? no meu caso, não tenho um atributo para apontar.
Ron

Não entendo a parte do usuário / token do comentário. Mas se você deseja incluir um campo no serializador que é removido antes de você restaurar em uma instância de modelo, você pode usar o .validate()método para remover o atributo. Veja: django-rest-framework.org/api-guide/serializers.html#validation Isso faria o que você precisa, embora eu realmente não entenda o caso de uso, ou porque você quer um campo gravável que está vinculado a um propriedade somente leituraget_absolute_url ?
Tom Christie

esqueça o get_absolute_urlque acabei de copiar e colar dos documentos. Eu só quero um campo gravável normal que posso acessar no validate(). Acabei de adivinhar que precisaria do sourceatributo ...
Ron

Isso faz mais sentido. :) O valor deve ser passado para validação, então eu verificaria como você está instanciando o serializador e se esse valor realmente está sendo fornecido.
Tom Christie

82

Na verdade, existe uma solução sem tocar em todo o modelo. Você pode usar o SerializerMethodFieldqual permite conectar qualquer método ao serializador.

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

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk

4
Como o OP mencionou neste comentário , eles querem um campo gravável, o que SerializerMethodFieldnão é
esmail

14

... para maior clareza, se você tiver um Método de Modelo definido da seguinte maneira:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Você pode adicionar o resultado da chamada do referido método ao serializador da seguinte maneira:

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

ps Uma vez que o campo personalizado não é realmente um campo em seu modelo, você geralmente deseja torná-lo somente leitura, assim:

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

2
E se eu quiser que seja gravável?
Csaba Toth de

1
@Csaba: Você só precisa escrever para guardar personalizada e ganchos de eliminação para o conteúdo adicional: Veja "Salvar e ganchos de exclusão" em "Métodos" ( Aqui ) Você vai precisar de costume escrita perform_create(self, serializer), perform_update(self, serializer), perform_destroy(self, instance).
Lindauson

13

aqui responda para sua pergunta. você deve adicionar à sua conta modelo:

@property
def my_field(self):
    return None

agora você pode usar:

customField = CharField(source='my_field')

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


6
Usei essa abordagem quando fez sentido, mas não é ótimo adicionar código extra a modelos para coisas que são realmente usadas apenas para chamadas de API específicas.
Andy Baker,

1
Você pode escrever um modelo de proxy para o
ashwoods

9

Para mostrar self.author.full_name, recebi um erro com Field. Funcionou com ReadOnlyField:

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    author_name = ReadOnlyField(source="author.full_name")
    class Meta:
        model = Comment
        fields = ('url', 'content', 'author_name', 'author')

6

Com a última versão do Django Rest Framework, você precisa criar um método em seu modelo com o nome do campo que deseja adicionar.

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

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

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

3

Eu estava procurando uma solução para adicionar um campo personalizado gravável a um serializador de modelo. Encontrei este, que não foi abordado nas respostas a esta pergunta.

Parece que você realmente precisa escrever seu próprio serializador simples.

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

Agora você pode usar este Serializer para adicionar campos personalizados a um 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

Isso também funciona, se o modelo MyModelrealmente tiver uma propriedade chamada, my_custom_fieldmas você deseja ignorar seus validadores.


0

Depois de ler todas as respostas aqui, minha conclusão é que é impossível fazer isso de forma limpa. Você tem que jogar sujo e fazer algo horrível, como criar um campo write_only e, em seguida, substituir os métodos validatee to_representation. Isto é o que funcionou para mim:

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.