Включите посредника (через модель) в ответы в Django Rest Framework

111

У меня вопрос о работе с моделями m2m / through и их представлении в рамках django rest. Возьмем классический пример:

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

При ПОЛУЧЕНИИ экземпляра члена я успешно получаю все поля члена, а также его группы, однако я получаю только данные о группах, без дополнительных деталей, которые поступают из модели членства.

Другими словами, я ожидаю получить:

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

Обратите внимание на файл join_date .

Я перепробовал так много решений, включая, конечно, официальную страницу Django Rest-Framework об этом, и, похоже, никто не дает по этому поводу правильного простого ответа - что мне нужно сделать, чтобы включить эти дополнительные поля? Я нашел его более простым с django-вкусным пирогом, но у меня были другие проблемы, и я предпочитаю rest-framework.

mllm
источник
8
Это для вкусного пирога, я работаю с Django Rest Framework.
mllm

Ответы:

140

Как насчет.....

В своем MemberSerializer определите на нем поле, например:

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

а затем в сериализаторе членства вы можете создать это:

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

Это имеет общий эффект создания сериализованных значений, групп, источником которых является желаемое членство, а затем он использует настраиваемый сериализатор для извлечения битов, которые вы хотите отобразить.

РЕДАКТИРОВАТЬ: как прокомментировал @bryanph, serializers.fieldбыл переименован serializers.ReadOnlyFieldв DRF 3.0, поэтому это должно читаться:

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

для любых современных реализаций

барон
источник
2
fyi, я пробовал много вариантов этого, и я не могу заставить это работать. Этого нет в официальных документах? Где определяется набор членства?
глина
3
membership_setэто связанное имя по умолчанию для Член -> Членство
dustinfarris
Уловкой для меня было открытие имени "members_set". У меня была сквозная модель без явного «связанного» имени, поэтому мне пришлось угадывать ее имя, читая документацию на Django Many to Many .
miceno
это отлично работает, спасибо за подсказку. Однако я думаю, что DRF в этом случае несколько нелогичен, потому что класс Member уже определяет поле m2m, называемое группами, и это решение, похоже, переопределяет поле в сериализаторе, заставляя его указывать на обратную связь из сквозной модели. Я не очень разбираюсь в деталях реализации DRF, но, вероятно, с помощью самоанализа модели его можно будет передать автоматически. просто пища для размышлений :)
gru
В любом случае вы можете сообщить нам, работает ли это с последней версией DRF? Или хотя бы расскажите, какую версию вы использовали? Я не могу заставить DRF возвращать модель сквозного поля - она ​​всегда заканчивается исходным отношением (вместо членства - всегда будет возвращаться группа).
Андрей Цизов
18

Я столкнулся с этой проблемой, и мое решение (с использованием DRF 3.6) заключалось в использовании SerializerMethodField для объекта и явном запросе таблицы членства следующим образом:

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]

Это вернет список dict для ключа группы, где каждый dict сериализуется из MembershipSerializer. Чтобы сделать его доступным для записи, вы можете определить свой собственный метод создания / обновления внутри MemberSerializer, в котором вы перебираете входные данные и явно создаете или обновляете экземпляры модели членства.

FariaC
источник
-4

ПРИМЕЧАНИЕ. Как инженер-программист, я люблю использовать архитектуры, и я глубоко работал над многоуровневым подходом к разработке, поэтому я собираюсь отвечать на него в отношении уровней.

Насколько я понял проблему, вот решение 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

Технически вам придется передать запрос в DataAccessLayer, который вернет отфильтрованные объекты из уровня доступа к данным, но поскольку мне нужно быстро ответить на вопрос, я скорректировал код на уровне бизнес-логики!

Сайед Файзан
источник
1
Это полностью настраиваемый подход, который я использую для большинства своих разработок Rest API, так как я не очень люблю работать с Bounds, хотя Django Rest Framework довольно гибок!
Сайед Файзан 02
2
Это слишком сильно спроектировано, также оно даже не использует DRF.
michauwilliam