Comment inclure des champs de modèle associés à l'aide de Django Rest Framework?

154

Disons que nous avons le modèle suivant:

class Classroom(models.Model):
    room_number = [....]

class Teacher(models.Model):
    name = [...]
    tenure = [...]
    classroom = models.ForeignKey(Classroom)

Disons qu'au lieu d'obtenir un résultat comme celui-ci par la fonction ManyRelatedPrimaryKeyField:

{
    "room_number": "42", 
    "teachers": [
        27, 
        24, 
        7
    ]
},

demandez-lui de renvoyer quelque chose qui inclut la représentation complète du modèle associé comme:

{
    "room_number": "42", 
    "teachers": [
        {
           'id':'27,
           'name':'John',
           'tenure':True
        }, 
        {
           'id':'24,
           'name':'Sally',
           'tenure':False
        }, 
    ]
},

Est-ce possible? Si c'est le cas, comment? Et est-ce une mauvaise idée?

Chaz
la source

Réponses:

242

Le moyen le plus simple est d'utiliser l'argument de profondeur

class ClassroomSerializer(serializers.ModelSerializer):
    class Meta:
        model = Classroom
        depth = 1

Cependant, cela n'inclura que les relations pour les relations en aval, ce qui dans ce cas n'est pas tout à fait ce dont vous avez besoin, car le champ des enseignants est une relation inverse.

Si vous avez des exigences plus complexes (par exemple, inclure des relations inverses, imbriquer certains champs, mais pas d'autres, ou imbriquer uniquement un sous-ensemble spécifique de champs), vous pouvez imbriquer des sérialiseurs , par exemple ...

class TeacherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ('id', 'name', 'tenure')

class ClassroomSerializer(serializers.ModelSerializer):
    teachers = TeacherSerializer(source='teacher_set')

    class Meta:
        model = Classroom

Notez que nous utilisons l'argument source sur le champ sérialiseur pour spécifier l'attribut à utiliser comme source du champ. Nous pourrions supprimer l' sourceargument en nous assurant à la place que l' teachersattribut existe en utilisant l' option related_name sur votre Teachermodèle, ie.classroom = models.ForeignKey(Classroom, related_name='teachers')

Une chose à garder à l'esprit est que les sérialiseurs imbriqués ne prennent actuellement pas en charge les opérations d'écriture. Pour les représentations inscriptibles, vous devez utiliser des représentations plates régulières, telles que pk ou hyperliens.

Tom Christie
la source
Quand j'ai essayé la première solution, je n'ai pas reçu les enseignants, mais j'ai reçu des instances du parent de la classe (ce que cet exemple ne montre pas). Dans la deuxième solution, j'ai reçu une erreur - "L'objet 'Classroom' n'a pas d'attribut 'professeurs'". Est-ce que je manque quelque chose?
Chaz
1
@Chaz a mis à jour la réponse pour expliquer pourquoi depthne ferais pas ce dont vous avez besoin dans ce cas, et pour expliquer l'exception que vous voyez et comment y faire face.
Tom Christie
1
Je suis un idiot et je ne suis pas sur le serveur. Cela fonctionne certainement dans de nombreuses relations.
yellottyellott
15
L'imbrication des sérialiseurs est géniale! Je devais le faire et j'utilisais DRF 3.1.0. Je devais inclure many=Truecomme ça ...TeacherSerializer(source='teacher_set', many=True). Sinon, j'obtenais l'erreur suivante:The serializer field might be named incorrectly and not match any attribute or key on the 'RelatedManager' instance. Original exception text was: 'RelatedManager' object has no attribute 'type'.
Karthic Raghupathi
2
Le verso d'une ForeignKey sera nommé ..._setpar défaut. Consultez la documentation Django pour plus de détails: docs.djangoproject.com/en/1.10/ref/models/relations
Tom Christie
36

Merci @TomChristie !!! Tu m'as beaucoup aidé! Je voudrais mettre à jour cela un peu (à cause d'une erreur que j'ai rencontrée)

class TeacherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = ('id', 'name', 'tenure')

class ClassroomSerializer(serializers.ModelSerializer):
    teachers = TeacherSerializer(source='teacher_set', many=True)

    class Meta:
        model = Classroom
        field = ("teachers",)
Eliyahu Tauber
la source
2

Cela peut également être accompli en utilisant un django dandy assez pratique appelé drf-flex-fields . Nous l'utilisons et c'est assez génial. Il vous suffit de l'installer pip install drf-flex-fields, de le passer via votre sérialiseur, d'ajouter expandable_fieldset de bingo (exemple ci-dessous). Il vous permet également de spécifier des relations imbriquées profondes à l'aide de la notation par points.

from rest_flex_fields import FlexFieldsModelSerializer

class ClassroomSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Model
        fields = ("teacher_set",)
        expandable_fields = {"teacher_set": (TeacherSerializer, {"source": "teacher_set"})}

Ensuite, vous ajoutez ?expand=teacher_setà votre URL et elle renvoie une réponse étendue. J'espère que cela aidera quelqu'un, un jour. À votre santé!

Paul Tuckett
la source