143 votes

Inclure l'intermédiaire (par le biais du modèle) dans les réponses dans le cadre de Django Rest Framework

J'ai une question sur le traitement des modèles m2m / through et leur présentation dans le framework django rest. 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 je récupère une instance de Member, je reçois avec succès tous les champs du membre ainsi que ses groupes, mais je ne reçois que les détails des groupes, sans les détails supplémentaires provenant du modèle Membership.

En d'autres termes, je s'attendre à à 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 date de la réunion .

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

181voto

thebaron Points 1726

Que pensez-vous de.....

Sur votre MemberSerializer, définissez un champ sur celui-ci comme :

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

et ensuite 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, groupes, dont la source est l'appartenance que vous souhaitez, puis d'utiliser un sérialiseur personnalisé pour extraire les éléments que vous souhaitez afficher.

EDIT : comme commenté par @bryanph, serializers.field a été renommé en serializers.ReadOnlyField dans le DRF 3.0, il faut donc 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 toute mise en œuvre moderne

28voto

FriC Points 736

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

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]

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

-6voto

Syed Faizan Points 502

REMARQUE : En tant qu'ingénieur logiciel, j'aime utiliser les architectures et j'ai beaucoup travaillé sur l'approche en couches pour le développement. Je vais donc répondre à la question en respectant les couches.

Comme j'ai 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.p

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

Techniquement, vous devriez passer la requête à 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 d'entreprise !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X