У меня есть следующая модель (она сокращена, чтобы избежать лишнего кода:
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