Darslik · 2026-08-17 · ~6 daqiqa o‘qiladi · boshlang'ich
DRF filtrlash va pagination: «hammasini» emas, «keraklisini» qaytaring
Mundarija
GET /api/movies/ — va javobda bazadagi hamma film. Loyihaning birinchi
haftasida bu hech kimga xalaqit bermaydi, chunki filmlar 50 ta. Bir yildan
keyin ular 50 ming bo'ladi va o'sha endpoint har chaqiruvda megabaytlab JSON
qaytara boshlaydi: mobil ilova qotadi, server terlaydi, hech kim esa bu
javobning 20 tadan ortig'ini baribir o'qimaydi. Shuning uchun ro'yxat
endpoint'ining ikkita majburiy jihozi bor: pagination (bo'lib berish)
va filtrlash (so'ralganini berish). Avvalgi qismda
hujjatlagan Movies API'ni endi shu jihozlar bilan to'ldiramiz.
Pagination: birinchi kundan, global qilib#
DRF'da sahifalash bitta sozlama bilan butun loyihaga yoqiladi:
# config/settings.py
REST_FRAMEWORK = {
# ...
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
Javob shakli endi o'zgaradi — mijozlarga aynan shu shaklni va'da qilasiz:
curl "http://127.0.0.1:8000/api/movies/?page=2"
{
"count": 50000,
"next": "http://127.0.0.1:8000/api/movies/?page=3",
"previous": "http://127.0.0.1:8000/api/movies/?page=1",
"results": [ ... 20 ta film ... ]
}
Mijoz next bo'yicha yuraveradi — sahifa raqamini o'zi hisoblashi shart
emas. Sahifa hajmini mijozga ham berish kerak bo'lsa, PageNumberPaginationni
meros olib page_size_query_param = "page_size" qo'shasiz, lekin
max_page_size bilan chegaralab: aks holda kimdir ?page_size=1000000
deb eski muammoni qaytarib beradi.
Katta jadvalda page raqami sekinlashadi
?page=2500 degani bazaga "50 000 qator sanab o'tib, keyingisini ber"
degani — OFFSET chuqurlashgan sari sekinlashadi. Ro'yxati millionlab
yozuvli API'larda shuning uchun CursorPagination ishlatiladi: u "shu
yozuvdan keyingilarini ber" deb ishlaydi va tezligi chuqurlikka bog'liq
emas. Boshlang'ich loyihaga PageNumber yetadi, lekin bu chegarani
bilib qo'ygan yaxshi.
Filtrlash: django-filter bilan#
Mijozga "faqat 2019-yilgi drama filmlar" kerak. Buni view ichida qo'lda
if bilan yozish mumkin, lekin standart yechim tayyor:
uv add django-filter
# config/settings.py
INSTALLED_APPS = [
# ...
"django_filters",
]
REST_FRAMEWORK = {
# ...
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
],
}
View'da esa qaysi maydonlar bo'yicha nima mumkinligini e'lon qilamiz:
# apps/movies/views.py
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.select_related("genre")
serializer_class = MovieSerializer
# aniq moslik filtrlari: ?genre=drama&year=2019
filterset_fields = ["genre__slug", "year"]
# matnli qidiruv: ?search=matrix — sarlavha va tavsif bo'ylab
search_fields = ["title", "description"]
# tartiblash: ?ordering=-rating (minus — kamayish tartibi)
ordering_fields = ["rating", "year"]
ordering = ["-year"] # default tartib — doim aniq yozib qo'ying
Uchala backend birga ishlaydi va zanjir bo'lib qo'shiladi:
curl "http://127.0.0.1:8000/api/movies/?genre__slug=drama&year=2019&ordering=-rating"
ordering = ["-year"] qatori mayda ko'rinsa ham yozilishi shart: default
tartibsiz PostgreSQL qatorlarni "qanday qulay bo'lsa shunday" qaytaradi va
pagination sahifalari orasida yozuvlar takrorlanishi yoki tushib qolishi
mumkin — mijoz ro'yxatni varaqlaganda bir film ikki marta chiqqani shundan.
Oraliq filtrlar: FilterSet bilan#
filterset_fields aniq moslik uchun yetarli, lekin "reytingi 7 dan yuqori"
kabi oraliq so'rovlarga maxsus klass kerak:
# apps/movies/filters.py
import django_filters
from apps.movies.models import Movie
class MovieFilter(django_filters.FilterSet):
# ?min_rating=7.5 — gte: katta yoki teng
min_rating = django_filters.NumberFilter(field_name="rating", lookup_expr="gte")
# ?year_after=2015&year_before=2020 — oraliq
year_after = django_filters.NumberFilter(field_name="year", lookup_expr="gte")
year_before = django_filters.NumberFilter(field_name="year", lookup_expr="lte")
class Meta:
model = Movie
fields = ["genre__slug"]
# apps/movies/views.py — filterset_fields o'rniga:
filterset_class = MovieFilter
Yoqimli tomoni: avvalgi qismdagi Swagger sahifasi bu parametrlarning hammasini o'zi ko'rsatadi — filtr qo'shdingiz, hujjat yangilandi.
Tezlikni unutmang#
Filtr va qidiruv qo'shilgach, bazaga tushadigan so'rovlar shakli o'zgaradi —
ikki narsani tekshirib qo'ying. Filtrlanadigan ustunlarda indeks bo'lsin
(genre, year, rating — qaysi biri bo'yicha ko'p so'ralsa), buni
EXPLAIN bilan tekshirish
usulini alohida yozganman. Va ro'yxat serializer'ida bog'liq jadvallar
bo'lsa, select_related/prefetch_related turganiga ishonch hosil qiling —
aks holda 20 talik sahifa 21 ta SQL so'rov beradi, bu
N+1 muammosining klassik ko'rinishi.
Yakun#
Ro'yxat endpoint'ining to'liq jihozi shu: global pagination (next/previous
bilan), aniq moslik uchun filterset_fields, matn uchun SearchFilter,
tartib uchun OrderingFilter va default ordering — sahifalar sakramasligi
uchun. Oraliqlar kerak bo'lganda FilterSet klassi bor, hammasi esa Swagger
hujjatiga o'z-o'zidan tushib turadi.
Shu bilan o'quv turkumi ham yakuniga yetdi — endi Netflix-uslub API loyihasi ostida uning har qismini ochib beradigan maqolalar turadi.