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?

Ron
fonte
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')
Tom Christie
fonte
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
Idaho
fonte
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',
        )
Lindauson
fonte
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

va-dev
fonte
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')
François Constant
fonte
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',)
Guillaume Vincent
fonte
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.

David Schumann
fonte
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
Ariel
fonte