Incluir intermediario (a través del modelo) en las respuestas en Django Rest Framework

Tengo una pregunta sobre cómo tratar los modelos m2m / through y su presentación en el framework django rest. Tomemos un ejemplo clásico:

modelos.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 

Al OBTENER una instancia de Miembro, recibo con éxito todos los campos de los miembros y también sus grupos; sin embargo, solo obtengo los detalles de los grupos, sin detalles adicionales que provienen del modelo de Membresía.

En otras palabras espero recibir:

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

Tenga en cuenta la join_date .

He intentado tantas soluciones, incluida, por supuesto, la página oficial de Django Rest-Framework al respecto y nadie parece dar una respuesta clara al respecto. ¿Qué debo hacer para incluir estos campos adicionales? Lo encontré más sencillo con django-tastypie pero tuve otros problemas y prefiero el marco de descanso.

Qué tal si…..

En su MemberSerializer, defina un campo en él como:

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

y luego en tu serializador de membresía puedes crear esto:

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

Eso tiene el efecto general de crear un valor serializado, grupos, que tiene como origen la membresía que desea, y luego utiliza un serializador personalizado para extraer los bits que desea mostrar.

EDIT: según lo comentado por @bryanph, serializers.field fue renombrado a serializers.ReadOnlyField en DRF 3.0, por lo que debería leer:

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

Para cualquier implementación moderna.

Me enfrentaba a este problema y mi solución (utilizando DRF 3.6) era usar SerializerMethodField en el objeto y consultar explícitamente la tabla de Membresía de la siguiente manera:

 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] 

Esto devolverá una lista de dictados para la clave de grupos donde cada dictado se serializa desde el MembershipSerializer. Para que pueda escribirse, puede definir su propio método de crear / actualizar dentro del MemberSerializer donde itera sobre los datos de entrada y crea o actualiza explícitamente las instancias del modelo de Membresía.

NOTA: Como ingeniero de software, me encanta usar architectures y he trabajado profundamente en el enfoque por capas para el desarrollo, por lo que responderé con respeto a los niveles.

Como entendí el problema, aquí está la solución 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 

Técnicamente, tendría que pasar la Solicitud a DataAccessLayer que devolvería los Objetos filtrados desde la Capa de acceso a datos, pero como tengo que responder a la pregunta de manera rápida, ¡así que ajusté el Código en la capa de lógica empresarial!