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


110

Tengo una pregunta sobre cómo tratar con modelos m2m / through y su presentación en el marco de descanso de django. 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

Cuando obtengo una instancia de Miembro, recibo con éxito todos los campos del miembro 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 el join_date .

He probado tantas soluciones, incluida, por supuesto, la página oficial de Django Rest-Framework al respecto y nadie parece dar una respuesta clara y adecuada al respecto: ¿qué debo hacer para incluir estos campos adicionales? Lo encontré más sencillo con django-deliciouspie pero tuve algunos otros problemas y prefiero rest-framework.



8
Esto es para un pastel sabroso, estoy trabajando con Django Rest Framework.
mllm

Respuestas:


139

Qué tal si.....

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

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

y luego en su serializador de membresía puede 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 usa un serializador personalizado para extraer los bits que desea mostrar.

EDITAR: como lo comentó @bryanph, serializers.fieldse renombró serializers.ReadOnlyFielden 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


2
Fyi, he probado muchas variantes de esto y no puedo hacer que esto funcione. ¿Esto no está en los documentos oficiales? ¿Dónde se define Members_set?
arcilla

3
membership_setes el nombre relacionado predeterminado para Miembro -> Membresía
dustinfarris

La parte del truco para mí fue descubrir el nombre de "member_set". Tenía un modelo directo sin un nombre explícito "relacionado", así que tuve que adivinar el nombre leyendo los documentos en Django Many to Many .
miceno

esto funciona muy bien, gracias por la pista. Sin embargo, creo que DRF en este caso es algo contrario a la intuición porque el miembro de clase ya define un campo m2m llamado grupos y esta solución parece anular el campo en el serializador forzándolo a apuntar a la relación inversa del modelo directo. No estoy muy interesado en los detalles de implementación de DRF, pero probablemente con la introspección del modelo podría entregarse automáticamente. solo algo para pensar :)
gru

En cualquier caso, ¿podría actualizarnos sobre si esto funciona con la última versión de DRF? ¿O al menos decir qué versión estaba usando? No puedo hacer que DRF devuelva el modelo de campo directo; siempre termina con la relación original (en lugar de Membresía, siempre devolvería Grupo).
Andrey Cizov

18

Estaba enfrentando este problema y mi solución (usando DRF 3.6) era usar SerializerMethodField en el objeto y consultar explícitamente la tabla de Membresía así:

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 MembershipSerializer. Para que se pueda escribir, puede definir su propio método de creación / actualización dentro de MemberSerializer, donde itera sobre los datos de entrada y crea o actualiza explícitamente las instancias del modelo de membresía.


-4

NOTA: Como ingeniero de software, me encanta usar arquitecturas y he trabajado profundamente en el enfoque por capas para el desarrollo, así que voy a 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 de la capa de acceso a datos, pero como tengo que responder la pregunta de manera rápida, ¡ajusté el código en la capa de lógica empresarial!


1
Este es un enfoque totalmente personalizado que utilizo para la mayoría de mis desarrollos de API Rest, ya que no soy un fanático del trabajo con Bounds, ¡aunque Django Rest Framework es bastante flexible!
Syed Faizan

2
Esto está demasiado diseñado, y ni siquiera usa DRF.
michauwilliam
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.