Incluir intermediário (por meio do modelo) nas respostas no Django Rest Framework

110

Eu tenho uma pergunta sobre como lidar com modelos m2m / through e sua apresentação no framework django rest. Vamos dar um exemplo clássico:

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

Ao OBTER uma instância de Membro, recebo com sucesso todos os campos de Membros e também seus grupos - porém, só recebo os detalhes dos grupos, sem detalhes extras que vêm do modelo de Membros.

Em outras palavras, espero receber:

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

Observe o join_date .

Eu tentei tantas soluções, incluindo, é claro, a página oficial do Django Rest-Framework sobre isso e ninguém parece dar uma resposta clara e adequada sobre isso - o que eu preciso fazer para incluir esses campos extras? Achei mais direto com django-tastypie, mas tive alguns outros problemas e prefiro rest-framework.

mllm
fonte
Seria eugene-yeo.me/2012/12/4/... ajuda?
karthikr
8
Isso é para uma torta saborosa, estou trabalhando com Django Rest Framework.
mllm

Respostas:

139

E se.....

Em seu MemberSerializer, defina um campo como:

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

e, em seu serializador de associação, você pode criar este:

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

Isso tem o efeito geral de criar um valor serializado, grupos, que tem como origem a associação que você deseja e, em seguida, usa um serializador personalizado para retirar os bits que deseja exibir.

EDITAR: conforme comentado por @bryanph, serializers.fieldfoi renomeado para serializers.ReadOnlyFieldno DRF 3.0, então deve ser:

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 qualquer implementação moderna

thebaron
fonte
2
Fyi, tentei muitas variantes disso e não consigo fazer isso funcionar. Isso não está nos documentos oficiais? Onde é definido o Membership_set?
argila de
3
membership_seté o nome relacionado padrão para Membro -> Associação
dustinfarris
O truque para mim foi descobrir o nome "Membership_set". Eu tinha um modelo direto sem um nome "relacionado" explícito, então tive que adivinhar o nome dele lendo os documentos no Django Many to Many .
miceno
isso funciona muito bem, obrigado pela dica. No entanto, acho que o DRF neste caso é um tanto contra-intuitivo porque a classe Member já define um campo m2m chamado grupos e essa solução parece substituir o campo no serializador, forçando-o a apontar para o relacionamento reverso do modelo direto. Não estou muito interessado em detalhes de implementação de DRF, mas provavelmente com a introspecção do modelo isso poderia ser feito automaticamente. apenas um pouco para pensar :)
gru
Qualquer caso, você poderia nos atualizar se isso funciona com a versão mais recente do DRF? Ou pelo menos dizer qual versão você estava usando? Não posso fazer o DRF retornar o modelo de campo through - sempre termina com a relação original (em vez de Membership - ele sempre retornaria Group).
Andrey Cizov,
18

Eu estava enfrentando esse problema e minha solução (usando DRF 3.6) foi usar SerializerMethodField no objeto e consultar explicitamente a tabela de membros da seguinte maneira:

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]

Isso retornará uma lista de dicts para a chave de grupos, onde cada dict é serializado a partir do MembershipSerializer. Para torná-lo gravável, você pode definir seu próprio método de criação / atualização dentro do MemberSerializer, onde você itera sobre os dados de entrada e cria ou atualiza explicitamente as instâncias do modelo de associação.

FariaC
fonte
-4

NOTA: Como um engenheiro de software, adoro usar arquiteturas e trabalhei profundamente na Abordagem em camadas para o desenvolvimento, portanto, responderei com respeito às camadas.

Pelo que entendi o problema, aqui está a solução 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, você teria que passar a Solicitação para DataAccessLayer, que retornaria os Objetos Filtrados da Camada de Acesso a Dados, mas como tenho que responder à pergunta de maneira rápida, ajustei o código na camada de lógica de negócios!

Syed Faizan
fonte
1
Esta é uma abordagem totalmente customizada que eu uso para a maioria dos meus desenvolvimentos de API Rest, já que não sou muito fã de trabalhar com Bounds, embora o Django Rest Framework seja bastante flexível!
Syed Faizan
2
Isso é muuuuito muito engenheiro, e nem mesmo usa DRF.
michauwilliam