Django Rest Framework - Comment ajouter un champ personnalisé dans ModelSerializer

88

J'ai créé un ModelSerializeret je souhaite ajouter un champ personnalisé qui ne fait pas partie de mon modèle.

J'ai trouvé une description pour ajouter des champs supplémentaires ici et j'ai essayé ce qui suit:

customField = CharField(source='my_field')

Lorsque j'ajoute ce champ et que j'appelle ma validate()fonction, ce champ ne fait pas partie du attrdict. attrcontient tous les champs de modèle spécifiés à l'exception des champs supplémentaires. Je ne peux donc pas accéder à ce champ dans ma validation écrasée, n'est-ce pas?

Lorsque j'ajoute ce champ à la liste des champs comme ceci:

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

alors j'obtiens une erreur car customFieldne fait pas partie de mon modèle - ce qui est correct parce que je veux l'ajouter juste pour ce sérialiseur.

Existe-t-il un moyen d'ajouter un champ personnalisé?

Ron
la source
Pourriez-vous développer sur "Mais lorsque mon champ n'est pas dans la liste de champs de modèle spécifiée dans le sérialiseur, il ne fait pas partie du dictionnaire attr-validate ().", Je ne suis pas sûr que ce soit très clair.
Tom Christie
Aussi "il se plaint - correctement - que je n'ai pas de champ customField dans mon modèle.", Pourriez-vous être explicite sur l'exception que vous voyez - merci! :)
Tom Christie
J'ai mis à jour mon message et j'espère que c'est plus clair maintenant. Je veux juste savoir comment je peux ajouter un champ qui ne fait pas partie de mon modèle ...
Ron
1
Copie
Guillaume Vincent

Réponses:

62

Vous faites la bonne chose, sauf que CharField(et les autres champs tapés) sont pour les champs inscriptibles.

Dans ce cas, vous voulez juste un champ en lecture seule simple, alors utilisez simplement:

customField = Field(source='get_absolute_url')
Tom Christie
la source
4
merci, mais je veux un champ inscriptible. Je passe un certain jeton d'utilisateur qui identifie mon utilisateur. mais dans mon modèle, j'ai l'utilisateur et non le jeton. je veux donc passer le jeton et le «convertir» en objet utilisateur via une requête.
Ron
la prochaine chose est que la source doit cibler un attribut de modèle, non? dans mon cas, je n'ai pas d'attribut à pointer.
Ron
Je ne comprends pas le bit utilisateur / jeton du commentaire. Mais si vous souhaitez inclure un champ dans le sérialiseur qui est supprimé avant la restauration dans une instance de modèle, vous pouvez utiliser la .validate()méthode pour supprimer l'attribut. Voir: django-rest-framework.org/api-guide/serializers.html#validation Cela ferait ce dont vous avez besoin, même si je ne comprends pas vraiment le cas d'utilisation, ou pourquoi vous voulez un champ inscriptible lié à un propriété en lecture seuleget_absolute_url ?
Tom Christie
oubliez le get_absolute_urlje viens de le copier et coller à partir des documents. Je veux juste un champ inscriptible normal auquel je peux accéder dans le fichier validate(). Je viens de deviner que j'aurais besoin de l' sourceattribut ...
Ron
Cela a plus de sens. :) La valeur devrait être transmise pour valider, donc je vérifierais comment vous instanciez le sérialiseur, et si cette valeur est vraiment fournie.
Tom Christie
82

En fait, il existe une solution sans toucher du tout au modèle. Vous pouvez utiliser SerializerMethodFieldce qui vous permet de connecter n'importe quelle méthode à votre sérialiseur.

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

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk
Idaho
la source
5
Comme OP l'a mentionné dans ce commentaire , ils veulent un champ inscriptible, ce qui SerializerMethodFieldn'est pas le cas
mail du
14

... pour plus de clarté, si vous avez une méthode modèle définie de la manière suivante:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Vous pouvez ajouter le résultat de l'appel de ladite méthode à votre sérialiseur comme ceci:

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

ps Puisque le champ personnalisé n'est pas vraiment un champ dans votre modèle, vous voudrez généralement le rendre en lecture seule, comme ceci:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
la source
3
Et si je veux qu'il soit accessible en écriture?
Csaba Toth
1
@Csaba: Vous aurez juste besoin d'écrire et d' enregistrement personnalisé crochets de suppression du contenu supplémentaire: Voir « Enregistrer et crochets de suppression » sous la rubrique « Méthodes » ( ici ) Vous avez besoin d'écrire sur mesure perform_create(self, serializer), perform_update(self, serializer), perform_destroy(self, instance).
Lindauson
13

voici la réponse à votre question. vous devez ajouter à votre compte modèle:

@property
def my_field(self):
    return None

maintenant vous pouvez utiliser:

customField = CharField(source='my_field')

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

va-dev
la source
6
J'ai utilisé cette approche quand cela a du sens, mais ce n'est pas génial d'ajouter du code supplémentaire aux modèles pour des choses qui ne sont vraiment utilisées que pour des appels d'API spécifiques.
Andy Baker
1
Vous pouvez écrire un modèle proxy pour le
ashwoods
9

Pour montrer self.author.full_name, j'ai eu une erreur avec Field. Cela a fonctionné avec 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
la source
6

Avec la dernière version de Django Rest Framework, vous devez créer une méthode dans votre modèle avec le nom du champ que vous souhaitez ajouter.

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

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

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
la source
3

Je cherchais une solution pour ajouter un champ personnalisé inscriptible à un sérialiseur de modèle. J'ai trouvé celui-ci, qui n'a pas été couvert dans les réponses à cette question.

Il semble que vous ayez effectivement besoin d'écrire votre propre sérialiseur 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

Vous pouvez maintenant utiliser ce sérialiseur pour ajouter des champs personnalisés à 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

Cela fonctionne également si le modèle a MyModelen fait une propriété appelée my_custom_fieldmais que vous souhaitez ignorer ses validateurs.

David Schumann
la source
1

Après avoir lu toutes les réponses ici, ma conclusion est qu'il est impossible de faire cela proprement. Vous devez jouer sale et faire quelque chose de hadk, comme créer un champ write_only, puis remplacer les méthodes validateet to_representation. C'est ce qui a fonctionné pour moi:

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
la source