Вопрос о возврате нескольких объектов из подзапроса в Django Rest Framework или как это сделать по-другому? - PullRequest
0 голосов
/ 29 мая 2020

У меня есть следующая модель (она сокращена, чтобы избежать лишнего кода:

class User(AbstractUser):

    username = None

    master = models.ForeignKey(
        'self',
        blank=True,
        null=True,
        db_index=False,
        verbose_name='Slave account if not null',
        related_name='slaves',
        on_delete=models.SET_NULL
    )
    email = models.EmailField(
        verbose_name='email address',
        unique=True
    )
    first_name = models.CharField(
        max_length=30,
        verbose_name='first name'
    )
    last_name = models.CharField(
        max_length=150,
        verbose_name='last name'
    )
…
…
…

Как вы можете видеть, master модель, ссылающаяся на поля, сама разделяет всех пользователей на master и подчиненный категории, где подчиненный имеет pk из главный в качестве значения поля, но главный имеет значение None.

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

class CustomUserSerializer(
    serializer_mixins.ReadOnlyRaisesException,
    djoser_serializers.UserSerializer
):

    slave_accounts_ids = serializers.CharField
        source='slv',
        read_only=True,
        allow_null=True,
    )
# rest of the field are omitted for brevity.

'slv' в источнике - это аннотированное поле в наборе запросов, которое я собираюсь описать далее. Причина, по которой я не использую PrimaryKeyRelatedField, заключается в том, что это должно быть представление действия списка, которое показывает коллекцию пользователей, и в случае, если я использую вышеупомянутое поле - каждую итерацию (для каждого пользователя в коллекции) Django отправит запрос в БД для получения рабов пользователей, то есть, если у меня будет 100 пользователей, у меня будет более 100 запросов к БД, что, очевидно, довольно отстой ... Иглы, чтобы сказать, что prefetch_related не работает с 'self' связанные внешние ключи.

Я хотел переопределить набор запросов, чтобы аннотировать каждого пользователя набором ведомых устройств и сохранить как можно меньшее количество наборов запросов. 4 в моем случае, если быть точным.

class CustomDjoserUserViewSet(djoser.views.UserViewSet):

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.annotate(slv=F('slaves'))

# rest code is omited

Сначала я попробовал Subquery с Outerref

qs.annotate(slv=Subquery(get_user_model().objects.filter(master_id=OuterRef('pk')).values_list('pk', flat=True)))

Что работает, если master имеет только один slave , но возникает исключение, когда master имеет 2 или более slave , что понятно с точки зрения SQL.

Самый близкий подход, который я нашел:

qs.annotate(slv=F('slaves'))

почти отлично работает, всего 4 запроса в БД, но один большой недостаток:


{
    "count": 7,
    "next": null,
    "previous": null,
    "results": [
        {
            "first_name": "Name",
            "last_name": "Surname",
            "id": 1,
            "email": "email@inbox.ru",
            "user_country": "RU",
            "master": null,
            "slave_accounts_ids": "122"
        },
        {
            "first_name": "Name",
            "last_name": "Surname",
            "id": 1,
            "email": "email@inbox.ru",
            "user_country": "RU",
            "master": null,
            "slave_accounts_ids": "123"
        },
        {
            "first_name": "Super",
            "last_name": "user",
            "id": 120,
            "email": "superuser@inbox.ru",
            "user_country": "RU",
            "master": null,
            "slave_accounts_ids": null
        },

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

Ну, конечно, я могу переопределить что-то вроде get_paginated_response и сделать дополнительный запрос в БД, чтобы получить все подчиненные устройства, а затем настроить reponse.data, добавив slaves информация к нему.
Но я считаю, что это должно быть какое-то решение, аннотируя набор запросов пользователей с ведомыми устройствами на уровне БД либо с помощью аннотации, либо с помощью необработанного sql…

Основная цель всего этого - получить ведомые устройства каждого мастера из БД без дополнительных запросов к БД, вероятно, с помощью аннотации или другой сложной или очевидной (не для меня) вещи.

Есть идеи, как это сделать ??? Спасибо!

PS. SQL, предоставленный запросом с F ('slaves')

Executed SQL
SELECT "users_user"."id",
       "users_user"."password",
       "users_user"."last_login",
       "users_user"."is_superuser",
       "users_user"."is_staff",
       "users_user"."is_active",
       "users_user"."date_joined",
       "users_user"."master_id",
       "users_user"."email",
       "users_user"."first_name",
       "users_user"."last_name",
       "users_user"."user_country",
       "users_user"."deleted",
       T2."id" AS "slv"
  FROM "users_user"
  LEFT OUTER JOIN "users_user" T2
    ON ("users_user"."id" = T2."master_id")
 WHERE NOT ("users_user"."deleted" = true)
 LIMIT 20
Time
1.001119613647461 ms
Database
default
...