Inclure l'intermédiaire (via le modèle) dans les réponses dans Django Rest Framework

110

J'ai une question sur le traitement des modèles m2m / through et leur présentation dans le cadre de repos de django. Prenons un exemple classique:

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

Lorsque j'obtiens une instance de membre, je reçois avec succès tous les champs du membre ainsi que ses groupes - cependant je ne reçois que les détails des groupes, sans détails supplémentaires provenant du modèle d'adhésion.

En d'autres termes, je m'attends à recevoir:

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

Notez le join_date .

J'ai essayé tellement de solutions, y compris bien sûr la page officielle de Django Rest-Framework à ce sujet et personne ne semble donner une réponse claire et appropriée à ce sujet - que dois-je faire pour inclure ces champs supplémentaires? Je l'ai trouvé plus simple avec django-tastypie mais j'ai eu d'autres problèmes et je préfère rest-framework.

mllm
la source
Est-ce que eugene-yeo.me/2012/12/4/... aiderait?
karthikr
8
C'est pour une tarte savoureuse, je travaille avec Django Rest Framework.
mllm

Réponses:

139

Que diriez-vous.....

Sur votre MemberSerializer, définissez un champ dessus comme:

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

puis sur votre sérialiseur d'adhésion, vous pouvez créer ceci:

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

Cela a pour effet global de créer une valeur sérialisée, des groupes, qui a comme source l'appartenance que vous souhaitez, puis utilise un sérialiseur personnalisé pour extraire les bits que vous souhaitez afficher.

EDIT: comme commenté par @bryanph, a serializers.fieldété renommé serializers.ReadOnlyFielddans DRF 3.0, donc cela devrait se lire:

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

pour toutes les implémentations modernes

thebaron
la source
2
Pour info, j'ai essayé de nombreuses variantes de ceci et je ne peux pas faire fonctionner cela. Ce n'est pas dans la documentation officielle? Où est défini Membership_set?
terre battue
3
membership_setest le nom associé par défaut pour Member -> Membership
dustinfarris
Le truc pour moi a été de découvrir le nom "Membership_set". J'avais un modèle sans nom explicite "associé", donc j'ai dû deviner le nom de celui-ci, en lisant la documentation sur Django Many to Many .
miceno
cela fonctionne très bien, merci pour l'indice. Je pense cependant que DRF dans ce cas est quelque peu contre-intuitif car la classe Member définit déjà un champ m2m appelé groups et cette solution semble remplacer le champ dans le sérialiseur en le forçant à pointer vers la relation inverse du modèle through. Je ne suis pas très intéressé par les détails d'implémentation DRF, mais probablement avec l'introspection du modèle, il pourrait être transmis automatiquement. juste un peu de matière à réflexion :)
gru
Dans tous les cas, vous pourriez nous dire si cela fonctionne avec la dernière version de DRF? Ou du moins dire quelle version vous utilisiez? Je ne peux pas faire en sorte que DRF renvoie le modèle de champ traversant - il se termine toujours avec la relation d'origine (au lieu de Membership - il renverrait toujours Group).
Andrey Cizov
18

J'étais confronté à ce problème et ma solution (en utilisant DRF 3.6) était d'utiliser SerializerMethodField sur l'objet et d'interroger explicitement la table Membership comme ceci:

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]

Cela renverra une liste de dictionnaires pour la clé de groupes où chaque dict est sérialisé à partir de MembershipSerializer. Pour le rendre accessible en écriture, vous pouvez définir votre propre méthode de création / mise à jour dans MemberSerializer où vous itérez sur les données d'entrée et créez ou mettez à jour explicitement des instances de modèle Membership.

FariaC
la source
-4

REMARQUE: En tant qu'ingénieur logiciel, j'adore utiliser les architectures et j'ai profondément travaillé sur l'approche par couches pour le développement, donc je vais y répondre par rapport aux niveaux.

Si j'ai bien compris le problème, voici la solution 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

Vous devrez techniquement transmettre la demande à DataAccessLayer qui renverrait les objets filtrés de la couche d'accès aux données, mais comme je dois répondre à la question rapidement, j'ai ajusté le code dans la couche de logique métier!

Syed Faizan
la source
1
Il s'agit d'une approche entièrement personnalisée que j'utilise pour la plupart de mes développements d'API Rest car je ne suis pas vraiment fan du travail avec Bounds même si Django Rest Framework est assez flexible!
Syed Faizan
2
C'est waaaay trop d'ingénierie, et il n'utilise même pas DRF.
michauwilliam