Jak dołączyć powiązane pola modelu za pomocą Django Rest Framework?

154

Powiedzmy, że mamy następujący model:

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

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

Powiedzmy, że zamiast otrzymać taki wynik za pomocą funkcji ManyRelatedPrimaryKeyField:

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

niech zwróci coś, co zawiera pełną reprezentację modelu, na przykład:

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

czy to możliwe? Jeśli tak to jak? Czy to zły pomysł?

Chaz
źródło

Odpowiedzi:

242

Najprostszym sposobem jest użycie argumentu głębi

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

Jednak obejmie to tylko relacje dla relacji naprzód, co w tym przypadku nie jest tym, czego potrzebujesz, ponieważ pole nauczyciela jest relacją odwrotną.

Jeśli masz bardziej złożone wymagania (np. Zawierają odwrotne relacje, zagnieżdżają niektóre pola, ale nie inne, lub zagnieżdżają tylko określony podzbiór pól), możesz zagnieżdżać serializatory , np ...

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

Zauważ, że używamy argumentu source w polu serializatora, aby określić atrybut, który ma być użyty jako źródło pola. Moglibyśmy porzucić sourceargument, upewniając się, że teachersatrybut istnieje, używając opcji related_name w Twoim Teachermodelu, tj.classroom = models.ForeignKey(Classroom, related_name='teachers')

Należy pamiętać, że zagnieżdżone serializatory obecnie nie obsługują operacji zapisu. W przypadku reprezentacji do zapisu należy używać zwykłych płaskich reprezentacji, takich jak pk lub hiperłącza.

Tom Christie
źródło
Kiedy wypróbowałem pierwsze rozwiązanie, nie otrzymałem Nauczycieli, ale otrzymałem wystąpienia rodzica Classroom (czego ten przykład nie pokazuje). W drugim rozwiązaniu otrzymałem błąd - „Obiekt 'Klasa' nie ma atrybutu 'nauczyciele'”. Czy coś mi brakuje?
Chaz,
1
@Chaz Zaktualizował odpowiedź, aby wyjaśnić, dlaczego depthnie zrobiłbyś tego, czego potrzebujesz w tym przypadku, oraz aby wyjaśnić wyjątek, który widzisz, i jak sobie z nim radzić.
Tom Christie,
1
Jestem idiotą i trafiłem na zły serwer. To zdecydowanie działa w wielu lub wielu relacjach.
yellottyellott
15
Zagnieżdżanie serializatorów jest niesamowite! Musiałem to zrobić i używałem DRF 3.1.0. Musiałem uwzględnić w ten many=Truesposób ...TeacherSerializer(source='teacher_set', many=True). W przeciwnym razie otrzymywałem następujący błąd: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
Odwrotna strona klucza obcego zostanie ..._setdomyślnie nazwana . Zobacz dokumentację Django, aby uzyskać więcej informacji: docs.djangoproject.com/en/1.10/ref/models/relations/ ...
Tom Christie
36

Dziękuję @TomChristie !!! Bardzo mi pomogłeś! Chciałbym to trochę zaktualizować (z powodu błędu, który wpadłem)

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
źródło
2

Można to również osiągnąć za pomocą całkiem poręcznego, eleganckiego django, zwanego drf-flex-fields . Używamy go i jest niesamowity. Po prostu instalujesz pip install drf-flex-fields, przepuszczasz przez swój serializator, dodajesz expandable_fieldsi bingo (przykład poniżej). Umożliwia także określenie głęboko zagnieżdżonych relacji przy użyciu notacji kropkowej.

from rest_flex_fields import FlexFieldsModelSerializer

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

Następnie dodajesz ?expand=teacher_setdo swojego adresu URL i zwraca on rozszerzoną odpowiedź. Mam nadzieję, że kiedyś komuś to pomoże. Twoje zdrowie!

Paul Tuckett
źródło