Considérez ce cas où j'ai un modèle Book
et Author
.
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
fields = ('id', 'name')
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
viewsets.py
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
Cela fonctionne très bien si j'envoie une demande GET
pour un livre. J'obtiens une sortie avec un sérialiseur imbriqué contenant les détails du livre et les détails de l'auteur imbriqué, ce que je veux.
Cependant, quand je veux créer/mettre à jour un livre, je dois envoyer un POST
/PUT
/PATCH
avec les détails imbriqués de l'auteur au lieu de juste leur id. Je veux pouvoir créer/mettre à jour un objet livre en spécifiant un identifiant d'auteur et non l'intégralité de l'objet auteur.
Donc, quelque chose où mon sérialiseur ressemble à ceci pour une demande GET
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
et mon sérialiseur ressemble à ceci pour une demande POST
, PUT
, PATCH
class BookSerializer(serializers.ModelSerializer):
author = PrimaryKeyRelatedField(queryset=Author.objects.all())
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
Je ne veux pas non plus créer deux sérialiseurs entièrement distincts pour chaque type de demande. Je voudrais juste modifier le champ author
dans le BookSerializer
.
Enfin, existe-t-il une meilleure façon de faire tout cela?
À mon humble avis, plusieurs sérialiseurs ne feront que créer de plus en plus de confusion.
Je préfère plutôt la solution ci-dessous:
Je pense que c'est l'approche la plus propre.
Voir mon problème et solution similaire sur DRF: autoriser tous les champs dans la demande GET mais restreindre POST à un seul champ
Il y a une fonctionnalité de DRF où vous pouvez changer dynamiquement les champs sur le sérialiseur http://www.Django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
Mon cas d'utilisation: utilisez le champ slug sur GET pour que nous puissions voir le bon représentant d'une relation, mais sur POST/PUT, revenez à la mise à jour de la clé primaire classique. Ajustez votre sérialiseur à quelque chose comme ceci:
class FooSerializer(serializers.ModelSerializer):
bar = serializers.SlugRelatedField(slug_field='baz', queryset=models.Bar.objects.all())
class Meta:
model = models.Foo
fields = '__all__'
def __init__(self, *args, **kwargs):
super(FooSerializer, self).__init__(*args, **kwargs)
try:
if self.context['request'].method in ['POST', 'PUT']:
self.fields['bar'] = serializers.PrimaryKeyRelatedField(queryset=models.Bar.objects.all())
except KeyError:
pass
Le KeyError est parfois lancé lors de l'initialisation du code sans demande, éventuellement des tests unitaires.
Profitez-en et utilisez de manière responsable.
Vous recherchez le get_serializer_class
sur la méthode ViewSet
. Cela vous permet d'activer le type de demande pour quel sérialiseur vous souhaitez utiliser.
from rest_framework import viewsets
class MyModelViewSet(viewsets.ModelViewSet):
model = MyModel
queryset = MyModel.objects.all()
def get_serializer_class(self):
if self.action in ('create', 'update', 'partial_update'):
return MySerializerWithPrimaryKeysForCreatingOrUpdating
else:
return MySerializerWithNestedData
La façon dont j'ai fini par résoudre ce problème était d'avoir un autre sérialiseur pour quand c'est un domaine connexe.
class HumanSerializer(PersonSerializer):
class Meta:
model = Human
fields = PersonSerializer.Meta.fields + (
'firstname',
'middlename',
'lastname',
'sex',
'date_of_birth',
'balance'
)
read_only_fields = ('name',)
class HumanRelatedSerializer(HumanSerializer):
def to_internal_value(self, data):
return self.Meta.model.objects.get(id=data['id'])
class PhoneNumberSerializer(serializers.ModelSerializer):
contact = HumanRelatedSerializer()
class Meta:
model = PhoneNumber
fields = (
'id',
'contact',
'phone',
'extension'
)
Vous pouvez faire quelque chose comme ça, mais pour le RelatedSerializer, faites:
def to_internal_value(self, data):
return self.Meta.model.objects.get(id=data)
Ainsi, lors de la sérialisation, vous sérialisez l'objet associé et lors de la désérialisation, vous n'avez besoin que de l'ID pour obtenir l'objet associé.
Je sais qu'il est un peu tard, mais juste au cas où quelqu'un d'autre en aurait besoin. Il existe des packages tiers pour drf qui permettent le réglage dynamique des champs de sérialiseur inclus via les paramètres de requête de requête (répertoriés dans les documents officiels: https://www.Django-rest-framework.org/api-guide/serializers/#third-party-packages ).
Les OMI les plus complètes sont:
où (1) a plus de fonctionnalités que (2) (peut-être trop, selon ce que vous voulez faire).
Avec (2), vous pouvez faire des choses telles que (extraites du readme du repo):
class CountrySerializer(FlexFieldsModelSerializer):
class Meta:
model = Country
fields = ['name', 'population']
class PersonSerializer(FlexFieldsModelSerializer):
country = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Person
fields = ['id', 'name', 'country', 'occupation']
expandable_fields = {
'country': (CountrySerializer, {'source': 'country', 'fields': ['name']})
}
La réponse par défaut:
{
"id" : 13322,
"name" : "John Doe",
"country" : 12,
"occupation" : "Programmer"
}
Lorsque vous faites un GET/person/13322? Expand = country, la réponse changera en:
{
"id" : 13322,
"name" : "John Doe",
"country" : {
"name" : "United States"
},
"occupation" : "Programmer",
}
Remarquez comment la population a été supprimée de l'objet pays imbriqué. En effet, les champs ont été définis sur ['nom'] lorsqu'ils ont été transmis au CountrySerializer intégré.
De cette façon, vous pouvez conserver vos POST requêtes incluant juste un identifiant et "étendre" les réponses GET pour inclure plus de détails.