Django Rest Framework - эффективное извлечение связанных полей по обратному внешнему ключу - PullRequest
0 голосов
/ 01 ноября 2018

У меня есть следующие модели, которые представляют рабочую группу пользователей. Каждая рабочая группа имеет лидера и членов:

class WorkingGroup(models.Model):
    group_name = models.CharField(max_length=255)
    leader = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)

class WorkingGroupMember(models.Model):
    group = models.ForeignKey(WorkingGroup, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

В DRF я хочу эффективно получить все группы (их несколько сотен) в виде массива следующих объектов json:

{
    'id': <the_group_id>
    'group_name': <the_group_name>
    'leader': <id_of_leader>
    'members': [<id_of_member_1>, <id_of_member_2>, ...]
}

Для этого я настроил следующий сериализатор:

class WorkingGroupSerializer(serializers.ModelSerializer):
    members = serializers.SerializerMethodField()
    class Meta:
        model = WorkingGroup
        fields = ('id', 'group_name', 'leader', 'members',)

    def get_members(self, obj):
        return obj.workinggroupmember_set.all().values_list('user_id', flat=True)

Так что, на мой взгляд, я могу сделать что-то вроде:

groups = WorkingGroup.objects.all().prefetch_related('workinggroupmember_set')
group_serializer = WorkingGroupSerializer(groups, many=True)

Это работает и дает желаемый результат, однако я считаю, что он вообще плохо масштабируется, поскольку предварительная выборка workinggroupmember_set, похоже, не используется внутри метода get_members (Silky показывает один запрос захватить все объекты WorkingGroup, а затем запрос для каждого вызова workinggroupmember_set в методе get_members). Есть ли способ настроить поле members в сериализаторе для получения версии workinggroupmember_set с плоским или отдельным полем без использования SerializerMethodField? Или каким-то другим способом, который позволяет мне правильно использовать предварительную выборку?

Ответы [ 2 ]

0 голосов
/ 18 февраля 2019

В недавнем проекте с DRF v3.9.1 и django 2.1 мне нужно было рекурсивно показать все дочерние объекты объекта, имея только прямую связь с родителем, который мог иметь несколько дочерних элементов.

Раньше, если бы я запрашивал «дерево» объекта, я получал:

{
    "uuid": "b85385c0e0a84785b6ca87ce50132659",
    "name": "a",
    "parent": null
}

Применяя сериализацию, показанную ниже, я получаю:

{
    "uuid": "b85385c0e0a84785b6ca87ce50132659",
    "name": "a",
    "parent": null
    "children": [
        {
            "uuid": "efd26a820b4e4f7c8e56c812a7791fcb",
            "name": "aa",
            "parent": "b85385c0e0a84785b6ca87ce50132659"
            "children": [
                {
                    "uuid": "ca2441fc7abf49b6aa1f3ebbc2dae251",
                    "name": "aaa",
                    "parent": "efd26a820b4e4f7c8e56c812a7791fcb"
                    "children": [],
                }
            ],
        },
        {
            "uuid": "40e09c85775d4f1a8578bba9c812df0e",
            "name": "ab",
            "parent": "b85385c0e0a84785b6ca87ce50132659"
            "children": [],
        }
    ],
}

Вот models.py рекурсивного объекта:

class CategoryDefinition(BaseModelClass):
    name = models.CharField(max_length=100)
    parent = models.ForeignKey('self', related_name='children',
                               on_delete=models.CASCADE,
                               null=True, blank=True)

Чтобы получить все обратные объекты во внешнем ключе, примените поле к классу сериализатора:

class DeepCategorySerializer(serializers.ModelSerializer):
    children = serializers.SerializerMethodField()

    class Meta:
        model = models.CategoryDefinition
        fields = '__all__'

    def get_children(self, obj):
        return [DeepCategorySerializer().to_representation(cat) for cat in obj.children.all()]

Затем примените этот сериализатор к функции представления DRF или к универсальному классу, например:

re_path(r'categories/(?P<pk>[\w\d]{32})/',
        generics.RetrieveUpdateDestroyAPIView.as_view(
            queryset=models.CategoryDefinition.objects.all(),
            serializer_class=serializers.DeepCategorySerializer),
        name='category-update'),
0 голосов
/ 01 ноября 2018

Проблема в том, что вы делаете values_list поверх all, что сводит на нет ваш prefetch_related. В настоящее время нет способа выполнить предварительную выборку с помощью values_list см. https://code.djangoproject.com/ticket/26565. Что вы можете сделать, это перевести это в код Python вместо SQL

class WorkingGroupSerializer(serializers.ModelSerializer):
    members = serializers.SerializerMethodField()
    class Meta:
        model = WorkingGroup
        fields = ('id', 'group_name', 'leader', 'members',)

    def get_members(self, obj):
        return [wgm.user_id for wgm in obj.workinggroupmember_set.all()]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...