Django REST Framework: ajout d'un champ supplémentaire à ModelSerializer

149

Je souhaite sérialiser un modèle, mais je souhaite inclure un champ supplémentaire qui nécessite d'effectuer des recherches dans la base de données sur l'instance de modèle à sérialiser:

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

Quel est le bon moyen de le faire? Je vois que vous pouvez passer en "contexte" supplémentaire au sérialiseur, est-ce que la bonne réponse est à passer dans le champ supplémentaire dans un dictionnaire de contexte? Avec cette approche, la logique d'obtention du champ dont j'ai besoin ne serait pas autonome avec la définition du sérialiseur, ce qui est idéal puisque chaque instance sérialisée en aura besoin my_field. Ailleurs dans la documentation des sérialiseurs DRF, il est dit que "des champs supplémentaires peuvent correspondre à n'importe quelle propriété ou être appelés sur le modèle". Est-ce que je parle de champs supplémentaires? Dois-je définir une fonction dans Foola définition de modèle de qui renvoie une my_fieldvaleur, et dans le sérialiseur, je connecte my_field à cet appelable? A quoi cela ressemble-t-il?

Merci d'avance, heureux de clarifier la question si nécessaire.

Neil
la source

Réponses:

226

Je pense que SerializerMethodField est ce que vous recherchez:

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
la source
19
est-il possible d'ajouter une validation à ces champs? ma question est: comment accepter les valeurs POST personnalisées qui peuvent être validées et les processus dans le gestionnaire post_save ()?
Alp
20
Notez que SerializerMethodField est en lecture seule, donc cela ne fonctionnera pas pour le POST / PUT / PATCH entrant.
Scott A
24
Dans DRF 3, il est changé en field_name = serializers.SerializerMethodField()etdef get_field_name(self, obj):
Programmeur chimique
1
quel est le fooquand def un SerializerMethodField? lorsque vous utilisez CreateAPIView, le toto a-t-il été stocké puis peut-il utiliser la méthode is_named_bar ()?
244boy
Donc, sur la base de cette réponse, si vous voulez passer par exemple 12 champs supplémentaires à votre sérialiseur, vous devez définir 12 méthodes spécifiques pour chaque champ qui renvoie juste foo.field_custom?
AlxVallejo
41

Vous pouvez changer votre méthode de modèle en propriété et l'utiliser dans le sérialiseur avec cette approche.

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

Edit: Avec les versions récentes du cadre de repos (j'ai essayé 3.3.3), vous n'avez pas besoin de changer de propriété. La méthode du modèle fonctionnera très bien.

Wasil Sergejczyk
la source
Merci @Wasil! Je ne suis pas familier avec l'utilisation des propriétés dans les modèles Django, et je ne peux pas trouver une bonne explication de ce que cela signifie. Peux-tu expliquer? Quel est l'intérêt du décorateur @property ici?
Neil
2
cela signifie que vous pouvez appeler cette méthode comme une propriété: ie variable = model_instance.my_fielddonne le même résultat que variable = model_instance.my_field()sans le décorateur. aussi: stackoverflow.com/a/6618176/2198571
Wasil Sergejczyk
1
Cela ne fonctionne pas, du moins dans Django 1.5.1 / djangorestframework == 2.3.10. Le ModelSerializer n'obtient pas la propriété même s'il est explicitement référencé dans l'attribut Meta "champs".
ygneo
9
vous devez ajouter le champ au sérialiseur car ce n'est pas un vrai champ de modèle: my_field = serializers.Field (source = 'my_field')
Marius
2
source='my_field' n'est plus nécessaire et soulève une exception
Guillaume Vincent
13

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. Pas besoin @propertyet source='field'signaler une erreur.

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
Et si je veux avoir un requestobjet à l'intérieur de def foo (self) qui pourrait modifier la valeur de foo? (exemple une recherche basée sur request.user)
saran3h
1
Que faire si la valeur de foo provient d'une requête?
saran3h
11

Ma réponse à une question similaire ( ici ) pourrait être utile.

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
10

si vous voulez lire et écrire sur votre champ supplémentaire, vous pouvez utiliser un nouveau sérialiseur personnalisé, qui étend les sérialiseurs.Serializer, et l'utiliser comme ceci

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

J'utilise ceci dans les champs imbriqués associés avec une logique personnalisée

Marco Silva
la source
2

Cela a fonctionné pour moi . Si nous voulons simplement ajouter un champ supplémentaireModelSerializer, nous pouvons le faire comme ci-dessous, et le champ peut également se voir attribuer une valeur après quelques calculs de recherche. Ou dans certains cas, si nous voulons envoyer les paramètres dans la réponse API.

Dans model.py

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

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

J'espère que cela pourrait aider quelqu'un.

Vinay Kumar
la source
0

Comme l'a dit le programmeur chimique dans ce commentaire , dans le dernier DRF, vous pouvez le faire comme ceci:

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

Source de la documentation DRF

trici0pa
la source