Uwzględnij pośrednika (poprzez model) w odpowiedziach w Django Rest Framework

110

Mam pytanie dotyczące obsługi modeli m2m / through i ich prezentacji w frameworku django rest. Weźmy klasyczny przykład:

models.py:

from django.db import models

class Member(models.Model):
    name = models.CharField(max_length = 20)
    groups = models.ManyToManyField('Group', through = 'Membership')

class Group(models.Model):
    name = models.CharField(max_length = 20)

class Membership(models.Model):
    member = models.ForeignKey('Member')
    group = models.ForeignKey('Group')
    join_date = models.DateTimeField()

serializers.py:

imports...

class MemberSerializer(ModelSerializer):
    class Meta:
        model = Member

class GroupSerializer(ModelSerializer):
    class Meta:
        model = Group

views.py:

imports...

class MemberViewSet(ModelViewSet):
    queryset = Member.objects.all()
    serializer_class = MemberSerializer

class GroupViewSet(ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

UZYSKUJĄC instancję członka pomyślnie otrzymuję wszystkie pola członka, a także jego grupy - jednak otrzymuję tylko szczegóły grup, bez dodatkowych szczegółów pochodzących z modelu członkostwa.

Innymi słowy, oczekuję, że otrzymam:

{
   'id' : 2,
   'name' : 'some member',
   'groups' : [
      {
         'id' : 55,
         'name' : 'group 1'
         'join_date' : 34151564
      },
      {
         'id' : 56,
         'name' : 'group 2'
         'join_date' : 11200299
      }
   ]
}

Zwróć uwagę na join_date .

Wypróbowałem tak wiele rozwiązań, w tym oczywiście oficjalną stronę Django Rest-Framework na ten temat i nikt nie wydaje się udzielić właściwej, prostej odpowiedzi na ten temat - co mam zrobić, aby uwzględnić te dodatkowe pola? Wydało mi się to prostsze dzięki django-tastypie, ale miałem kilka innych problemów i wolę rest-framework.

mllm
źródło
Czy eugene-yeo.me/2012/12/4/… pomogłoby?
karthikr
8
To jest na smaczne ciasto, pracuję z Django Rest Framework.
mllm

Odpowiedzi:

139

Co powiesz na.....

W swoim MemberSerializerze zdefiniuj na nim pole, takie jak:

groups = MembershipSerializer(source='membership_set', many=True)

a następnie w swoim serializatorze członkostwa możesz utworzyć to:

class MembershipSerializer(serializers.HyperlinkedModelSerializer):

    id = serializers.Field(source='group.id')
    name = serializers.Field(source='group.name')

    class Meta:
        model = Membership

        fields = ('id', 'name', 'join_date', )

Ma to ogólny wpływ na utworzenie serializowanej wartości, grup, których źródłem jest pożądane członkostwo, a następnie używa niestandardowego serializatora do wyciągnięcia bitów, które chcesz wyświetlić.

EDYCJA: jak skomentował @bryanph, w DRF 3.0 serializers.fieldzmieniono nazwę na serializers.ReadOnlyField, więc powinno to brzmieć:

class MembershipSerializer(serializers.HyperlinkedModelSerializer):

    id = serializers.ReadOnlyField(source='group.id')
    name = serializers.ReadOnlyField(source='group.name')

    class Meta:
        model = Membership

        fields = ('id', 'name', 'join_date', )

do wszelkich nowoczesnych realizacji

baron
źródło
2
fyi, próbowałem wielu wariantów tego i nie mogę uruchomić tego. Nie ma tego w oficjalnych dokumentach? Gdzie jest zdefiniowany zestaw członkostwa?
glina
3
membership_setjest domyślną nazwą pokrewną dla Członka -> Członkostwo
dustinfarris
Najtrudniejsze dla mnie było odkrycie nazwy „Members_set”. Miałem model przelotowy bez wyraźnej „pokrewnej” nazwy, więc musiałem odgadnąć jego nazwę, czytając dokumentację w Django Many to Many .
miceno
to działa świetnie, dzięki za podpowiedź. Myślę jednak, że w tym przypadku DRF jest nieco sprzeczny z intuicją, ponieważ klasa Member już definiuje pole m2m zwane grupami, a to rozwiązanie wydaje się nadpisywać pole w serializatorze, zmuszając go do wskazania odwrotnej relacji z modelu przelotowego. Nie interesuję się zbytnio szczegółami implementacji DRF, ale prawdopodobnie przy introspekcji modelu można by to przekazać automatycznie. tylko trochę do przemyślenia :)
gru
Czy mógłbyś nas zaktualizować, czy to działa z najnowszą wersją DRF? Albo przynajmniej powiedz, której wersji używasz? Nie mogę zmusić DRF do zwrócenia modelu pola przelotowego - zawsze kończy się to oryginalną relacją (zamiast Członkostwa - zawsze zwróci Group).
Andrey Cizov
18

Miałem do czynienia z tym problemem i moim rozwiązaniem (przy użyciu DRF 3.6) było użycie SerializerMethodField na obiekcie i jawne zapytanie do tabeli członkostwa w następujący sposób:

class MembershipSerializer(serializers.ModelSerializer):
    """Used as a nested serializer by MemberSerializer"""
    class Meta:
        model = Membership
        fields = ('id','group','join_date')

class MemberSerializer(serializers.ModelSerializer):
    groups = serializers.SerializerMethodField()

    class Meta:
        model = Member
        fields = ('id','name','groups')

    def get_groups(self, obj):
        "obj is a Member instance. Returns list of dicts"""
        qset = Membership.objects.filter(member=obj)
        return [MembershipSerializer(m).data for m in qset]

Spowoduje to zwrócenie listy dykt dla klucza grup, w którym każdy dykt jest serializowany z MembershipSerializer. Aby umożliwić zapis, możesz zdefiniować własną metodę tworzenia / aktualizacji w ramach MemberSerializer, w której iterujesz dane wejściowe i jawnie tworzysz lub aktualizujesz wystąpienia modelu członkostwa.

FariaC
źródło
-4

UWAGA: Jako inżynier oprogramowania uwielbiam korzystać z architektur i głęboko pracowałem nad warstwowym podejściem do programowania, więc będę odpowiadać na to z szacunkiem dla poziomów.

Jak zrozumiałem problem, oto rozwiązanie models.py

class Member(models.Model):
    member_id = models.AutoField(primary_key=True)
    member_name = models.CharField(max_length = 

class Group(models.Model):
    group_id = models.AutoField(primary_key=True)
    group_name = models.CharField(max_length = 20)
    fk_member_id = models.ForeignKey('Member', models.DO_NOTHING, 
                             db_column='fk_member_id', blank=True, null=True)

class Membership(models.Model):
    membershipid = models.AutoField(primary_key=True)
    fk_group_id = models.ForeignKey('Group', models.DO_NOTHING, 
                             db_column='fk_member_id', blank=True, null=True)
    join_date = models.DateTimeField()

serializers.py

import serializer

class AllSerializer(serializer.Serializer):
    group_id = serializer.IntegerField()
    group_name = serializer.CharField(max_length = 20)
    join_date = serializer.DateTimeField()

CustomModels.py

imports...

    class AllDataModel():
        group_id = ""
        group_name = ""
        join_date = ""

BusinessLogic.py

imports ....
class getdata(memberid):
    alldataDict = {}
    dto = []
    Member = models.Members.objects.get(member_id=memberid) #or use filter for Name
    alldataDict["MemberId"] = Member.member_id
    alldataDict["MemberName"] = Member.member_name
    Groups = models.Group.objects.filter(fk_member_id=Member)
    for item in Groups:
        Custommodel = CustomModels.AllDataModel()
        Custommodel.group_id = item.group_id
        Custommodel.group_name = item.group_name
        Membership = models.Membership.objects.get(fk_group_id=item.group_id)
        Custommodel.join_date = Membership.join_date
        dto.append(Custommodel)
    serializer = AllSerializer(dto,many=True)
    alldataDict.update(serializer.data)
    return alldataDict

Technicznie rzecz biorąc, musiałbyś przekazać żądanie do DataAccessLayer, który zwróciłby filtrowane obiekty z warstwy dostępu do danych, ale ponieważ muszę odpowiedzieć na pytanie w szybki sposób, dostosowałem kod w warstwie logiki biznesowej!

Syed Faizan
źródło
1
Jest to w pełni spersonalizowane podejście, którego używam w większości moich opracowań Rest API, ponieważ nie jestem fanem pracy z Bounds, mimo że Django Rest Framework jest dość elastyczny!
Syed Faizan
2
To jest zbyt skomplikowane, a także nie używa nawet DRF.
michauwilliam