Django, DRF: Как использовать пагинацию в необработанных SQL-запросах?
Я хотел бы использовать следующий необработанный запрос в ListAPIView в Django REST Framework. Я не уверен, как использовать необработанный SQL запрос вместо кверисета для поддержки пагинации. Я изучил этот вопрос и не нашел ничего, что имело бы для меня смысл.
Если я хочу использовать набор запросов, он будет выглядеть следующим образом Как это сделать, если я хочу использовать необработанные SQL-запросы?
class VideoListView(generics.ListAPIView):
queryset = Video.objects.raw(...)
serializer_class = VideoSerializer
SELECT DISTINCT "t"."id",
"t"."title",
"t"."thumbnail_url",
"t"."preview_url",
"t"."embed_url",
"t"."duration",
"t"."views",
"t"."is_public",
"t"."published_at",
"t"."created_at",
"t"."updated_at",
EXISTS
(SELECT (1) AS "a"
FROM "videos_history" U0
WHERE (U0."user_id" IS NULL
AND U0."video_id" = "t"."id")
LIMIT 1) AS "is_viewed",
EXISTS
(SELECT (1) AS "a"
FROM "videos_favorite" U0
WHERE (U0."user_id" IS NULL
AND U0."video_id" = "t"."id")
LIMIT 1) AS "is_favorited",
EXISTS
(SELECT (1) AS "a"
FROM "videos_track" U0
INNER JOIN "videos_playlist" U1 ON (U0."playlist_id" = U1."id")
WHERE (U1."is_wl"
AND U1."user_id" IS NULL
AND U0."video_id" = "t"."id")
LIMIT 1) AS "is_wl"
FROM (
(SELECT "videos_video".*
FROM "videos_video"
WHERE ("videos_video"."is_public"
AND "videos_video"."published_at" <= '2022-01-03 05:20:16.725884+00:00'
AND "videos_video"."title" LIKE '%word')
ORDER BY "videos_video"."published_at" DESC
LIMIT 20)
UNION
(SELECT "videos_video".*
FROM "videos_video"
LEFT OUTER JOIN "videos_video_tags" ON ("videos_video"."id" = "videos_video_tags"."video_id")
LEFT OUTER JOIN "videos_tag" ON ("videos_video_tags"."tag_id" = "videos_tag"."id")
WHERE ("videos_video"."is_public"
AND "videos_video"."published_at" <= '2022-01-03 05:20:16.725884+00:00'
AND "videos_tag"."name" LIKE '%word')
ORDER BY "videos_video"."published_at" DESC
LIMIT 20)
) AS t
ORDER BY "t"."published_at" DESC
LIMIT 20;
Нужно ли мне создавать пользовательский пагинатор или что-то еще? Я еще больше запутался, потому что я использую LIMIT и другие методы для подзапросов
--- Добавить models.py и т.д... ---
# models.py
class Tag(models.Model):
name = models.CharField(unique=True, max_length=30)
created_at = models.DateTimeField(default=timezone.now)
...
class Video(models.Model):
title = models.CharField(max_length=300)
tags = models.ManyToManyField(Tag, blank=True)
updated_at = models.DateTimeField(auto_now=True)
...
class History(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
video = models.ForeignKey(Video, on_delete=models.CASCADE)
...
class Favorite(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE
video = models.ForeignKey(Video, on_delete=models.CASCADE)
...
class Playlist(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
is_wl = models.BooleanField(default=False, editable=False)
...
class Track(models.Model):
playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE, null=True)
video = models.ForeignKey(Video, on_delete=models.CASCADE)
...
Медленный набор запросов и SQL-запросы, которые использовались изначально.
Video.objects.annotate(
is_viewed=Exists(History.objects.filter(user=user, video=OuterRef("pk"))),
is_favorited=Exists(
Favorite.objects.filter(user=user, video=OuterRef("pk"))
),
is_wl=Exists(
Track.objects.filter(
playlist__user=user, playlist__is_wl=True, video=OuterRef("pk")
)
),
).filter(
Q(title__contains=value)
| Q(tags__name__contains=value)),
is_public=True,
published_at__lte=timezone.now(),
).order_by("-published_at").distinct()
SELECT DISTINCT "videos_video"."id",
"videos_video"."published_at",
EXISTS
(SELECT (1) AS "a"
FROM "videos_history" U0
WHERE (U0."user_id" IS NULL
AND U0."video_id" = "videos_video"."id")
LIMIT 1) AS "is_viewed",
EXISTS
(SELECT (1) AS "a"
FROM "videos_favorite" U0
WHERE (U0."user_id" IS NULL
AND U0."video_id" = "videos_video"."id")
LIMIT 1) AS "is_favorited",
EXISTS
(SELECT (1) AS "a"
FROM "videos_track" U0
INNER JOIN "videos_playlist" U1 ON (U0."playlist_id" = U1."id")
WHERE (U1."is_wl"
AND U1."user_id" IS NULL
AND U0."video_id" = "videos_video"."id")
LIMIT 1) AS "is_wl"
FROM "videos_video"
LEFT OUTER JOIN "videos_video_tags" ON ("videos_video"."id" = "videos_video_tags"."video_id")
WHERE ("videos_video"."is_public"
AND "videos_video"."published_at" <= '2021-12-27 13:34:29.103369+00:00'
AND ("videos_video"."title" &@~ 'word'
OR "videos_video_tags"."tag_id" IN
(SELECT U0."id"
FROM "videos_tag" U0
WHERE U0."name" &@~ 'word')))
ORDER BY "videos_video"."published_at" DESC
LIMIT 20;
Если вы спрашиваете об общих PageNumberPagination
из rest_framework.pagination
, то обычно это делается так. Вы создаете свой собственный класс пагинации со значениями по умолчанию:
class VideoPagination(PageNumberPagination):
max_page_size = 100
page_size_query_param = 'page_size'
page_size = 25
Затем вы просто добавляете его к вашему представлению:
class VideoListView(generics.ListAPIView):
queryset = Video.objects.all()
serializer_class = VideoSerializer
pagination_class = VideoPagination
Это все, вы можете использовать параметры page
и page_size
.