web-dev-qa-db-fra.com

Django REST Framework - Sérialisation des champs optionnels

J'ai un objet qui a des champs optionnels. J'ai défini mon sérialiseur de la manière suivante:

class ProductSerializer(serializers.Serializer):
    code = serializers.Field(source="Code")
    classification = serializers.CharField(source="Classification", required=False)

Je pensaisrequired=False ferait le travail de contourner le champ s'il n'existe pas. Cependant, il est mentionné dans la documentation que cela affecte la désérialisation plutôt que la sérialisation.

Je reçois l'erreur suivante:

'Product' object has no attribute 'Classification'

Ce qui se passe lorsque j'essaie d'accéder à .data de l'instance sérialisée. (Cela ne signifie-t-il pas que c'est la désérialisation qui soulève cette question?)

Cela se produit pour les instances qui n'ont pas Classification. Si j'omets Classification de la classe serializer, cela fonctionne très bien.

Comment est-ce que je fais correctement ceci? Sérialiser un objet avec des champs optionnels, c'est-à-dire.

24
Aziz Alfoudari

Les sérialiseurs sont délibérément conçus pour utiliser un ensemble fixe de champs afin que vous ne puissiez pas facilement ne pas laisser éventuellement l'une des clés.

Vous pouvez utiliser SerializerMethodField pour renvoyer la valeur du champ ou None si le champ n'existe pas, ou vous ne pouvez pas utiliser de sérialiseurs et simplement écrire une vue qui renvoie directement la réponse.

Update pour REST framework 3.0serializer.fields peut être modifié sur un sérialiseur instancié. Lorsque des classes de sérialiseur dynamique sont requises, je suggérerais probablement de modifier les champs dans une méthode Serializer.__init__() personnalisée.

10
Tom Christie

Django REST Framework 3.0+
Champs dynamiques maintenant supportés, voir http://www.Django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields - cette approche définit tous les champs de la sérialiseur, puis vous permet de supprimer sélectivement ceux que vous ne voulez pas.

Ou vous pouvez également faire quelque chose comme ceci pour un sérialiseur de modèle, où vous bousillez avec Meta.fields dans le sérialiseur init:

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ('code',)

    def __init__(self, *args, **kwargs):
        if SHOW_CLASSIFICATION: # add logic here for optional viewing
            self.Meta.fields = list(self.Meta.fields)
            self.Meta.fields.append('classification')
        super(ProductSerializer, self).__init__(*args, **kwargs)

Vous devrez toutefois demander à Tom si c'est la "bonne façon", car cela risque de ne pas correspondre au plan à long terme.

Django REST Framework <3.0
Essayez quelque chose comme ça:

class ProductSerializer(serializers.Serializer):
    ...
    classification = serializers.SerializerMethodField('get_classification')

    def get_classification(self, obj):
        return getattr(obj, 'classification', None)

Sérialiseurs multiples

Une autre approche consiste à créer plusieurs sérialiseurs avec différents ensembles de champs. Un sérialiseur hérite d'un autre et ajoute des champs supplémentaires. Ensuite, vous pouvez choisir le sérialiseur approprié dans la vue avec la méthode get_serializer_class. Voici un exemple concret de la façon dont j'utilise cette approche pour appeler différents sérialiseurs afin de présenter différentes données utilisateur si l'objet utilisateur est identique à l'utilisateur de la demande.

def get_serializer_class(self):
    """ An authenticated user looking at their own user object gets more data """
    if self.get_object() == self.request.user:
        return SelfUserSerializer
    return UserSerializer

Suppression des champs de la représentation

Une autre approche que j'ai utilisée dans les contextes de sécurité consiste à supprimer les champs de la méthode to_representation. Définir une méthode comme

def remove_fields_from_representation(self, representation, remove_fields):
    """ Removes fields from representation of instance.  Call from
    .to_representation() to apply field-level security.
    * remove_fields: a list of fields to remove
    """
    for remove_field in remove_fields:
        try:
            representation.pop(remove_field)
        except KeyError:
            # Ignore missing key -- a child serializer could inherit a "to_representation" method
            # from its parent serializer that applies security to a field not present on
            # the child serializer.
            pass

puis dans votre sérialiseur, appelez cette méthode comme

def to_representation(self, instance):
    """ Apply field level security by removing fields for unauthorized users"""
    representation = super(ProductSerializer, self).to_representation(instance)
    if not permission_granted: # REPLACE WITH PERMISSION LOGIC
        remove_fields = ('classification', ) 
        self.remove_fields_from_representation(representation, remove_fields)
    return representation

Cette approche est simple et flexible, mais elle entraîne une sérialisation des champs qui ne sont parfois pas affichés. Mais ça va probablement.

19
Mark Chackerian

La méthode décrite ci-dessous a fait le travail pour moi .

Version DRF utilisée = djangorestframework (3.1.0)

class test(serializers.Serializer):
  id= serializers.IntegerField()
  name=serializers.CharField(required=False,default='some_default_value')
3
RAJ GUPTA

À cette fin, les sérialiseurs ont l'argument partial. Si, lorsque le sérialiseur est initialisé, vous pouvez transmettre partial=True. Si vous utilisez des génériques ou des mixins, vous pouvez remplacer la fonction get_serializer comme suit:

def get_serializer(self, *args, **kwargs):
    kwargs['partial'] = True
    return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)

Et cela fera l'affaire.

Remarque: Ceci permet à tous les champs d'être optionnels et pas seulement à un champ spécifique. Si vous souhaitez uniquement des informations spécifiques, vous pouvez remplacer la méthode (c'est-à-dire mettre à jour) et ajouter des validations d'existence pour divers champs.

0
Uri Shalit

Du fichier "c’est un terrible piratage qui repose sur des détails d’implémentation spécifiques de DRF et Django, mais cela fonctionne (du moins pour le moment)", voici l’approche que j’avais utilisée pour inclure des données de débogage supplémentaires dans la réponse d’une méthode "create" implémentation sur un sérialiseur:

def create(self, validated_data)
    # Actual model instance creation happens here...
    self.fields["debug_info"] = serializers.DictField(read_only=True)
    my_model.debug_info = extra_data
    return my_model

Il s'agit d'une approche temporaire qui me permet d'utiliser l'API navigable pour afficher certaines des données de réponse brutes reçues d'un service distant particulier au cours du processus de création. À l'avenir, j'ai tendance à conserver cette fonctionnalité, mais à la masquer derrière un indicateur "rapport d'informations de débogage" dans la demande de création plutôt que de renvoyer par défaut les informations de niveau inférieur.

0
ncoghlan