¿Cómo incluyo campos de modelo relacionados con Django Rest Framework?

153

Digamos que tenemos el siguiente modelo:

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

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

Digamos que en lugar de obtener un resultado como este por la función ManyRelatedPrimaryKeyField:

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

que devuelva algo que incluya la representación completa del modelo relacionado, como:

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

es posible? ¿Si es así, cómo? ¿Y es esta una mala idea?

Chaz
fuente

Respuestas:

242

La forma más simple es usar el argumento de profundidad

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

Sin embargo, eso solo incluirá relaciones para relaciones directas, que en este caso no es exactamente lo que necesita, ya que el campo de maestros es una relación inversa.

Si tiene requisitos más complejos (por ejemplo, incluir relaciones inversas, anidar algunos campos, pero no otros, o anidar solo un subconjunto específico de campos) puede anidar serializadores , por ejemplo ...

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

Tenga en cuenta que usamos el argumento fuente en el campo serializador para especificar el atributo que se usará como fuente del campo. Podríamos abandonar el sourceargumento asegurándonos de que el teachersatributo existe usando la opción related_name en su Teachermodelo, es decir.classroom = models.ForeignKey(Classroom, related_name='teachers')

Una cosa a tener en cuenta es que los serializadores anidados no admiten actualmente operaciones de escritura. Para las representaciones de escritura, debe usar representaciones planas regulares, como pk o hipervínculos.

Tom Christie
fuente
Cuando probé la primera solución, no recibí a los Maestros, sin embargo, recibí instancias de los padres del Aula (que este ejemplo no muestra). En la segunda solución recibí un error: "El objeto 'Aula' no tiene el atributo 'maestros'". ¿Me estoy perdiendo de algo?
Chaz
1
@Chaz actualizó la respuesta para explicar por depthqué no haría lo que necesita en este caso y para explicar la excepción que está viendo y cómo lidiar con eso.
Tom Christie
1
Soy un idiota y estaba golpeando el servidor equivocado. Definitivamente funciona en muchas relaciones.
yellottyellott
15
¡Anidar serializadores es increíble! Tenía que hacer esto y estaba usando DRF 3.1.0. Tuve que incluir many=Trueasí ...TeacherSerializer(source='teacher_set', many=True). De lo contrario, recibía el siguiente error: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
El reverso de una ForeignKey se nombrará ..._setpor defecto. Consulte los documentos de Django para más detalles: docs.djangoproject.com/en/1.10/ref/models/relations/…
Tom Christie
36

Gracias @TomChristie !!! ¡Me ayudaste mucho! Me gustaría actualizar eso un poco (debido a un error que encontré)

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
fuente
2

Esto también se puede lograr mediante el uso de un paquete Dandy Django bastante práctico llamado drf-flex-fields . Lo usamos y es bastante impresionante. Simplemente instale pip install drf-flex-fields, pase a través de su serializador, agregue expandable_fieldsy bingo (ejemplo a continuación). También le permite especificar relaciones anidadas profundas mediante la notación de puntos.

from rest_flex_fields import FlexFieldsModelSerializer

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

Luego agrega ?expand=teacher_seta su URL y devuelve una respuesta expandida. Espero que esto ayude a alguien, algún día. ¡Salud!

Paul Tuckett
fuente