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