Includi intermediario (tramite il modello) nelle risposte in Django Rest Framework


110

Ho una domanda su come trattare i modelli m2m / through e la loro presentazione nel framework django rest. Facciamo un esempio classico:

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

Quando ottengo un'istanza di Membro, ricevo con successo tutti i campi del membro e anche i suoi gruppi, tuttavia ottengo solo i dettagli dei gruppi, senza dettagli aggiuntivi che derivano dal modello di appartenenza.

In altre parole mi aspetto di ricevere:

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

Nota il join_date .

Ho provato così tante soluzioni, inclusa ovviamente la pagina ufficiale di Django Rest-Framework a riguardo e nessuno sembra dare una risposta chiara e corretta al riguardo: cosa devo fare per includere questi campi extra? L'ho trovato più semplice con django-deliciouspie ma ho avuto altri problemi e preferisco il rest-framework.



8
Questo è per una torta gustosa, sto lavorando con Django Rest Framework.
mllm

Risposte:


139

Che ne dite di.....

Sul tuo MemberSerializer, definisci un campo come:

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

e poi sul serializzatore di appartenenza puoi creare questo:

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', )

Ciò ha l'effetto complessivo di creare un valore serializzato, gruppi, che ha come origine l'appartenenza desiderata e quindi utilizza un serializzatore personalizzato per estrarre i bit che si desidera visualizzare.

EDIT: come commentato da @bryanph, è serializers.fieldstato rinominato serializers.ReadOnlyFieldin DRF 3.0, quindi questo dovrebbe essere:

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', )

per qualsiasi implementazione moderna


2
Cordiali saluti, ho provato molte varianti di questo e non riesco a farlo funzionare. Questo non è nei documenti ufficiali? Dove viene definito membership_set?
terra

3
membership_setè il nome correlato predefinito per Membro -> Iscrizione
dustinfarris

Il trucco per me è stato scoprire il nome "membership_set". Avevo un modello diretto senza un nome "correlato" esplicito, quindi ho dovuto indovinarne il nome, leggendo i documenti di Django Many to Many .
miceno

funziona benissimo, grazie per il suggerimento. Penso tuttavia che DRF in questo caso sia in qualche modo controintuitivo perché la classe Member definisce già un campo m2m chiamato gruppi e questa soluzione sembra sovrascrivere il campo nel serializzatore costringendolo a puntare alla relazione inversa dal modello through. Non sono molto interessato ai dettagli di implementazione di DRF, ma probabilmente con l'introspezione del modello potrebbe essere consegnato automaticamente. solo qualche spunto di riflessione :)
gru

In ogni caso potresti aggiornarci sul fatto che funziona con l'ultima versione di DRF? O almeno dire quale versione stavi usando? Non posso fare in modo che DRF restituisca il modello di campo passante - finisce sempre con la relazione originale (invece di Appartenenza - restituirà sempre Gruppo).
Andrey Cizov

18

Stavo affrontando questo problema e la mia soluzione (utilizzando DRF 3.6) era quella di utilizzare SerializerMethodField sull'oggetto e interrogare esplicitamente la tabella di appartenenza in questo modo:

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]

Ciò restituirà un elenco di dict per la chiave dei gruppi in cui ogni dict viene serializzato da MembershipSerializer. Per renderlo scrivibile, è possibile definire il proprio metodo di creazione / aggiornamento all'interno di MemberSerializer in cui si itera sui dati di input e si creano o si aggiornano esplicitamente le istanze del modello di appartenenza.


-4

NOTA: In qualità di ingegnere del software, adoro utilizzare le architetture e ho lavorato a fondo sull'approccio a strati per lo sviluppo, quindi risponderò con rispetto ai livelli.

Da quando ho capito il problema, ecco la soluzione 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

Tecnicamente, dovresti passare la richiesta a DataAccessLayer che restituirebbe gli oggetti filtrati dal livello di accesso ai dati ma poiché devo rispondere alla domanda in modo veloce, ho regolato il codice nel livello di logica aziendale!


1
Questo è un approccio completamente personalizzato che utilizzo per la maggior parte dei miei sviluppi dell'API Rest poiché non sono un vero fan del lavoro con Bounds anche se Django Rest Framework è abbastanza flessibile!
Syed Faizan il

2
Questo è mooolto troppo ingegnerizzato, inoltre non usa nemmeno DRF.
michauwilliam
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.