15 β DRF kirish va serializers¶
β¬ οΈ Oldingi: 14 β Sessiyalar, messages va middleware Β· π README Β· Keyingi: 16 β DRF ViewSets va routers β‘οΈ
Bu bobda: Django'ni shu paytgacha HTML qaytaradigan veb-sayt sifatida ishlatdik. Endi undan API yasaymiz β ya'ni HTML emas, JSON qaytaradigan, mobil ilova, React/Vue frontend yoki boshqa server iste'mol qiladigan xizmat. Buni Django'ning eng mashhur kengaytmasi Django REST Framework (DRF) bilan qilamiz. Avval REST API nima va nega kerakligini tushunamiz, DRF'ni o'rnatib
settings.py'ga ulaymiz. Keyin DRF'ning yuragi β Serializer bilan tanishamiz: u Python obyektini JSON'ga (serialize) va JSON'ni qayta Python obyektga (deserialize) o'giradi.serializers.Serializer(qo'lda) vaModelSerializer(modeldan avtomatik) farqini,.data,is_valid(),validated_data,.errors,.save()ni ko'ramiz. Validatsiyani chuqurlashtiramiz:validate_<maydon>()bitta maydon uchun,validate()maydonlararo qoidalar uchun. So'ngAPIViewbilan view yozamiz,Responsevastatuskodlar (200/201/204/400/404) bilan to'g'ri javob qaytaramiz varequest.databilan ishlaymiz. OxiridaModelFormva Node.js/Express bilan solishtiramiz. Hamma kod Django 6.0.6, Python 3.14, djangorestframework 3.17 da haqiqatan ishga tushirib tekshirilgan.
REST API nima va nega kerak?¶
Shu paytgacha bizning view'larimiz render() orqali HTML sahifa qaytarardi. Bu brauzer uchun ajoyib: odam saytni ochadi, HTML ko'radi, bosadi. Lekin tasavvur qiling:
- Sizning ilovangizga mobil app (Android/iOS) ulanmoqchi. Telefon HTML kerakmas β unga toza ma'lumot kerak.
- Frontend'ni React yoki Vue'da yozdingiz. U serverdan ma'lumotni
fetch()bilan oladi va o'zi chizadi. - Boshqa server sizning ma'lumotingizni so'raydi (masalan, to'lov tizimi).
Bu hollarning hammasida bizga HTML emas, mashina o'qiy oladigan format kerak. Eng keng tarqalgani β JSON:
REST API β bu serverga HTTP orqali ma'lumot so'rash va yuborishning bir uslubi. "REST" qoidalari oddiy: har bir resurs (mahsulot, foydalanuvchi, buyurtma) o'z URL'iga ega, va biz unga HTTP metodlari orqali murojaat qilamiz:
| HTTP metod | Ma'no | Misol |
|---|---|---|
GET |
o'qish | GET /api/mahsulotlar/ β hammasini ber |
GET |
bittasini o'qish | GET /api/mahsulotlar/1/ β id=1 ni ber |
POST |
yangi yaratish | POST /api/mahsulotlar/ β yangi mahsulot |
PUT/PATCH |
yangilash | PUT /api/mahsulotlar/1/ β o'zgartirish |
DELETE |
o'chirish | DELETE /api/mahsulotlar/1/ β o'chirish |
Node.js'dan kelganlar uchun: bu xuddi Express'da
app.get('/products', ...),app.post('/products', ...)yozganday. DRF β bu Express + Mongoose validatsiya +JSON.stringify/JSON.parseni bitta tizimga jamlangan, ammo bundan ham ko'proq narsa beradi. Solishtirish uchun Node.js qo'llanmasi'ga qarang.
Django'da bularning hammasini qo'lda yozish mumkin (JsonResponse, json.loads...), lekin bu xuddi 10-bobda formalarsiz xom request.POST bilan ishlaganday β uzun va xatoga moyil. DRF buni soddalashtiradi.
DRF o'rnatish va sozlash¶
Django REST Framework alohida paket. O'rnatish:
Bu kitobda kerakli paketlar (Django 6.0.6, djangorestframework 3.17) global o'rnatilgan, shuning uchun alohida o'rnatishingiz shart emas. O'z loyihangizda esa yuqoridagi buyruq bilan o'rnatasiz.
O'rnatgach, settings.py'da INSTALLED_APPS'ga rest_framework'ni qo'shamiz:
# config/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework", # <-- qo'shildi
"shop", # bizning ilova
]
Ixtiyoriy, lekin foydali β DRF'ning o'z sozlamalar bloki. Hammasi bitta REST_FRAMEWORK lug'atida turadi:
# config/settings.py
REST_FRAMEWORK = {
# standart format JSON bo'lsin
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer", # brauzerda chiroyli ko'rinish
],
# standart parser
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
],
}
BrowsableAPIRenderer β DRF'ning juda yoqimli xususiyati: API'ni oddiy brauzerda ochsangiz, JSON bilan birga chiroyli HTML interfeys ham ko'rsatadi (test qilish uchun qulay). Production'da ko'pincha uni o'chirib, faqat JSONRenderer qoldiriladi.
Ushbu bobning misollarini biz quyidagi modellar ustida quramiz (5β7-boblardagi modellarga o'xshash):
# shop/models.py
from django.db import models
class Kategoriya(models.Model):
nom = models.CharField(max_length=100)
def __str__(self):
return self.nom
class Mahsulot(models.Model):
nom = models.CharField(max_length=200)
narx = models.DecimalField(max_digits=10, decimal_places=2)
soni = models.IntegerField(default=0)
kategoriya = models.ForeignKey(
Kategoriya, on_delete=models.CASCADE, related_name="mahsulotlar"
)
yaratilgan = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.nom
makemigrations va migrate qilib bazani tayyorlaymiz β bu sizga 5-bobdan tanish:
Serializer nima? serialize va deserialize¶
DRF'ning markaziy tushunchasi β Serializer. Uning ikki vazifasi bor va ular bir-biriga teskari:
- serialize (chiqarish) β Python obyektini (model, QuerySet, dict) JSON'ga aylantirish. Bu
GETso'rovda kerak: bazadan obyekt olamiz, klientga JSON yuboramiz. - deserialize (kirish) β kelgan JSON'ni qabul qilib, tekshirib, qayta Python obyektga aylantirish. Bu
POST/PUTso'rovda kerak: klient JSON yuboradi, biz uni tekshirib bazaga yozamiz.
E'tibor bering: serializer shunchaki JSON o'giruvchi emas β u o'rtada validatsiya ham qiladi. Bu xuddi 10-bobdagi Form'ga juda o'xshaydi, faqat HTML o'rniga JSON bilan ishlaydi. Aslida DRF serializer'i g'oyasini Django formalaridan olgan.
serializers.Serializer β qo'lda yozilgan birinchi serializer¶
Eng past darajadan boshlaylik β har bir maydonni qo'lda e'lon qilamiz. Faylni odatda ilova ichida serializers.py deb nomlaymiz:
# shop/serializers.py
from rest_framework import serializers
from .models import Mahsulot
class MahsulotSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
nom = serializers.CharField(max_length=200)
narx = serializers.DecimalField(max_digits=10, decimal_places=2)
soni = serializers.IntegerField(default=0)
def create(self, validated_data):
return Mahsulot.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.nom = validated_data.get("nom", instance.nom)
instance.narx = validated_data.get("narx", instance.narx)
instance.soni = validated_data.get("soni", instance.soni)
instance.save()
return instance
Diqqat qiling β bu deyarli aynan forms.Form'ga o'xshaydi: maydonlarni klass atributlari sifatida e'lon qilamiz. Farq: forms o'rniga serializers, va read_only=True (bu maydonni faqat chiqarishda ko'rsat, kirishda qabul qilma).
create() va update() metodlarini DRF avtomatik chaqiradi: serializer.save() chaqirilganda yangi obyekt bo'lsa create(), mavjud obyektni yangilash bo'lsa update() ishga tushadi.
serialize qilib ko'ramiz (obyekt -> dict)¶
Bazadagi obyektni serializer'ga berib, .data'sini olamiz:
# Django shell ichida (python manage.py shell)
from shop.models import Mahsulot, Kategoriya
from shop.serializers import MahsulotSerializer
kat = Kategoriya.objects.create(nom="Elektronika")
m = Mahsulot.objects.create(nom="Telefon", narx="500.00", soni=10, kategoriya=kat)
s = MahsulotSerializer(m)
print(s.data)
# {'id': 1, 'nom': 'Telefon', 'narx': '500.00', 'soni': 10}
s.data β bu oddiy Python lug'at. Uni JSON'ga o'girish uchun DRF'ning JSONRenderer'i ishlatiladi (view buni avtomatik qiladi, lekin qo'lda ham sinab ko'rsa bo'ladi):
from rest_framework.renderers import JSONRenderer
json_bytes = JSONRenderer().render(s.data)
print(json_bytes.decode())
# {"id":1,"nom":"Telefon","narx":"500.00","soni":10}
deserialize qilib ko'ramiz (JSON -> obyekt)¶
Endi teskari yo'nalish. Kelgan ma'lumotni data= argumenti bilan beramiz, is_valid() chaqiramiz, keyin save() qilamiz:
from shop.serializers import MahsulotSerializer
kirish = {"nom": "Noutbuk", "narx": "1200.50", "soni": 3}
s = MahsulotSerializer(data=kirish)
print(s.is_valid()) # True
print(s.validated_data) # {'nom': 'Noutbuk', 'narx': Decimal('1200.50'), 'soni': 3}
yangi = s.save(kategoriya=kat) # create() chaqiriladi
print(yangi.id, yangi.nom) # 2 Noutbuk
Uchta muhim nuqta:
is_valid()β tekshiradi. Avval chaqirmasdanvalidated_data'ga murojaat qilolmaysiz (xato beradi).validated_dataβ tekshiruvdan o'tgan, to'g'ri tipga o'tkazilgan qiymatlar. E'tibor bering:"1200.50"(string) avtomatikDecimal('1200.50')ga aylandi. Bu xuddi formalardagicleaned_data.save(kategoriya=kat)βsave()'ga qo'shimcha argument berish mumkin; ularvalidated_data'ga qo'shilibcreate()'ga uzatiladi. Bu URL'dan yoki tizim ichidan keladigan ma'lumot uchun qulay (masalan, joriy foydalanuvchi).
ModelSerializer β modeldan avtomatik serializer¶
Yuqorida har maydonni qo'lda yozdik. Lekin maydonlar allaqachon modelda e'lon qilingan β yana takrorlash zerikarli. Bu xuddi 10-bobdagi ModelForm muammosi: yechim ham o'xshash β ModelSerializer.
ModelSerializer modelga qarab maydonlarni o'zi yasaydi, create() va update()'ni ham o'zi yozadi:
# shop/serializers.py
from rest_framework import serializers
from .models import Mahsulot
class MahsulotModelSerializer(serializers.ModelSerializer):
class Meta:
model = Mahsulot
fields = ["id", "nom", "narx", "soni", "kategoriya", "yaratilgan"]
read_only_fields = ["yaratilgan"]
Mana shu 4 qator yuqoridagi 18 qatorli Serializer'ni almashtiradi. Solishtiring:
modelβ qaysi modeldan;fieldsβ qaysi maydonlarni chiqarish (fields = "__all__"ham mumkin, lekin aniq ro'yxat xavfsizroq β keraksiz maydonni tasodifan ochib qo'ymaysiz);read_only_fieldsβ faqat o'qish uchun (klient bularni o'zgartira olmaydi).
Sinab ko'ramiz:
from shop.models import Mahsulot
from shop.serializers import MahsulotModelSerializer
m = Mahsulot.objects.first()
s = MahsulotModelSerializer(m)
print(s.data)
# {'id': 1, 'nom': 'Telefon', 'narx': '500.00', 'soni': 10,
# 'kategoriya': 1, 'yaratilgan': '2026-06-13T07:07:18.413479Z'}
yaratilgan maydoni read_only_fields'da bo'lgani uchun chiqishda ko'rinadi, lekin kirishda qabul qilinmaydi. Sinaymiz:
s = MahsulotModelSerializer(data={
"nom": "Shim", "narx": "80", "soni": 7, "kategoriya": kat.pk,
"yaratilgan": "2000-01-01T00:00:00Z", # klient soxta sana yubormoqchi
})
s.is_valid()
print("yaratilgan" in s.validated_data) # False β e'tiborga olinmadi
Klient yaratilgan'ni o'zgartirmoqchi bo'ldi, lekin DRF uni jimgina rad etdi. Bu xavfsizlik uchun muhim.
many=True β ro'yxatni serialize qilish¶
Bitta obyektni emas, QuerySet'ni (ko'p obyektni) serialize qilish uchun many=True beriladi:
hammasi = Mahsulot.objects.all()
s = MahsulotModelSerializer(hammasi, many=True)
print(s.data)
# [{'id': 1, 'nom': 'Telefon', ...}, {'id': 2, 'nom': 'Noutbuk', ...}] -> JSON da massiv
# Eslatma: DRF 3.16+ oddiy dict qaytaradi; eski versiyalarda OrderedDict edi.
many=True bo'lsa, .data lug'at emas, lug'atlar ro'yxati bo'ladi va JSON'da [...] massiv ko'rinishida chiqadi.
partial=True β qisman yangilash (PATCH)¶
PUT so'rovda barcha maydonlarni yuborish kerak. PATCH (qisman yangilash) uchun esa faqat o'zgaradiganini yuboramiz β buning uchun partial=True:
m = Mahsulot.objects.get(nom="Telefon")
s = MahsulotModelSerializer(m, data={"soni": 99}, partial=True)
print(s.is_valid()) # True β boshqa maydonlar talab qilinmadi
s.save()
m.refresh_from_db()
print(m.soni, m.nom) # 99 Telefon -> faqat soni o'zgardi
Validatsiya: validate_field va validate¶
Serializer'ning eng kuchli tomoni β validatsiya. Maydon turi (DecimalField, IntegerField) avtomatik tekshiradi, lekin biznes qoidalarini biz qo'shamiz. Ikki daraja bor:
validate_<maydon>(self, value)β bitta maydon uchun. Masalan "narx noldan katta bo'lsin".validate(self, data)β bir nechta maydon birgalikda. Masalan "agar qimmat bo'lsa, omborda bo'lishi shart".
# shop/serializers.py
from rest_framework import serializers
from .models import Mahsulot
class MahsulotValidSerializer(serializers.ModelSerializer):
class Meta:
model = Mahsulot
fields = ["id", "nom", "narx", "soni"]
# 1. bitta maydon uchun
def validate_narx(self, value):
if value <= 0:
raise serializers.ValidationError("Narx noldan katta bo'lishi kerak.")
return value # MUHIM: qiymatni qaytarish SHART
def validate_nom(self, value):
return value.strip() # tozalab qaytaramiz
# 2. maydonlararo qoida
def validate(self, data):
if data.get("soni", 0) == 0 and data.get("narx", 0) > 1000:
raise serializers.ValidationError(
"Qimmat mahsulot omborda bo'lishi shart (soni > 0)."
)
return data # MUHIM: data ni qaytarish SHART
Eng tez-tez uchraydigan xato β qiymatni qaytarishni unutish. validate_narx None qaytarsa, narx maydoni None bo'lib qoladi:
# β XATO: return yo'q -> narx None bo'lib ketadi
def validate_narx(self, value):
if value <= 0:
raise serializers.ValidationError("...")
# return value <-- unutilgan!
# β
TO'G'RI: har doim qiymatni qaytaring
def validate_narx(self, value):
if value <= 0:
raise serializers.ValidationError("...")
return value
Validatsiyani amalda ko'ramiz:
from shop.serializers import MahsulotValidSerializer
# 1) narx manfiy -> field xatosi
s = MahsulotValidSerializer(data={"nom": "X", "narx": "-5", "soni": 1})
print(s.is_valid()) # False
print(s.errors)
# {'narx': [ErrorDetail(string="Narx noldan katta bo'lishi kerak.", code='invalid')]}
# 2) qimmat, lekin soni=0 -> validate() xatosi
s = MahsulotValidSerializer(data={"nom": "Server", "narx": "5000", "soni": 0})
print(s.is_valid()) # False
print(s.errors)
# {'non_field_errors': [ErrorDetail(string="Qimmat mahsulot omborda...", ...)]}
# 3) nom bo'shliqlar bilan -> validate_nom tozalaydi
s = MahsulotValidSerializer(data={"nom": " Mishka ", "narx": "10", "soni": 5})
s.is_valid()
print(repr(s.validated_data["nom"])) # 'Mishka'
E'tibor bering: bitta maydon xatosi errors["narx"] ga, maydonlararo xato esa errors["non_field_errors"] ga tushadi. Bu xuddi 10-bobdagi clean_<maydon>() va clean() farqi.
SerializerxatosiValidationError'irest_framework.serializers'dan keladi, Django'ningdjango.core.exceptions.ValidationError'i emas. Adashtirmang β DRF ichida har doimserializers.ValidationErrorishlating.
APIView, Response va status kodlar¶
Endi serializer'ni view'ga ulaymiz. DRF'ning asosiy view klassi β APIView. U Django'ning oddiy View'iga o'xshaydi (11-bobdagi CBV), lekin DRF qulayliklari bilan: request.data, Response, avtomatik parser/renderer.
APIView'da har bir HTTP metod uchun alohida metod yozasiz: get(), post(), put(), delete(). Bu xuddi 11-bobdagi CBV uslubi.
# shop/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from .models import Mahsulot
from .serializers import MahsulotModelSerializer, MahsulotValidSerializer
class MahsulotRoyxat(APIView):
def get(self, request):
qs = Mahsulot.objects.all()
serializer = MahsulotModelSerializer(qs, many=True)
return Response(serializer.data) # status standart 200
def post(self, request):
serializer = MahsulotValidSerializer(data=request.data)
if serializer.is_valid():
serializer.save(kategoriya_id=request.data.get("kategoriya"))
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class MahsulotDetal(APIView):
def get(self, request, pk):
mahsulot = get_object_or_404(Mahsulot, pk=pk)
serializer = MahsulotModelSerializer(mahsulot)
return Response(serializer.data)
def put(self, request, pk):
mahsulot = get_object_or_404(Mahsulot, pk=pk)
serializer = MahsulotModelSerializer(mahsulot, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
mahsulot = get_object_or_404(Mahsulot, pk=pk)
mahsulot.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Bu yerda bir nechta yangi narsa bor, ularni alohida ko'ramiz.
request.data¶
Oddiy Django'da request.POST faqat forma ma'lumotini oladi va hammasi string. DRF'da esa request.data β JSON, forma, fayl β qaysi format kelganidan qat'iy nazar, parserlangan, tipli lug'at qaytaradi. Klient {"narx": "500.00"} JSON yuborsa, request.data["narx"] allaqachon o'qishga tayyor.
Response¶
Response β DRF'ning JsonResponse'i. Unga oddiy Python lug'at yoki ro'yxat beramiz, DRF uni avtomatik JSON'ga o'giradi (renderer orqali). Sizga json.dumps() yozish kerakmas:
return Response({"xabar": "Salom"}) # -> {"xabar": "Salom"}, 200
return Response(serializer.data, status=201) # serializer natijasi + 201
status kodlar¶
HTTP javob har doim status kod bilan keladi. To'g'ri kodni qaytarish β yaxshi API belgisi. rest_framework.status ularni o'qiladigan nom bilan beradi:
| Konstanta | Kod | Qachon |
|---|---|---|
HTTP_200_OK |
200 | muvaffaqiyatli o'qish/yangilash |
HTTP_201_CREATED |
201 | yangi resurs yaratildi (POST) |
HTTP_204_NO_CONTENT |
204 | muvaffaqiyatli, tana bo'sh (DELETE) |
HTTP_400_BAD_REQUEST |
400 | klient noto'g'ri ma'lumot yubordi (validatsiya) |
HTTP_404_NOT_FOUND |
404 | resurs topilmadi |
200 standart, shuning uchun Response(data)'da yozmasak ham bo'ladi. Qolganini aniq yozish kerak. status.is_success(201) kabi yordamchi funksiyalar ham bor.
get_object_or_404 (3-bobdan tanish) topilmasa avtomatik 404 qaytaradi β DRF buni JSON 404 javobga aylantiradi.
URL'ga ulash¶
APIView ham CBV bo'lgani uchun .as_view() bilan ulanadi (11-bobdagiday):
# shop/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("mahsulotlar/", views.MahsulotRoyxat.as_view()),
path("mahsulotlar/<int:pk>/", views.MahsulotDetal.as_view()),
]
# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("shop.urls")),
]
Endi API tayyor. Brauzerda http://127.0.0.1:8000/api/mahsulotlar/'ni ochsangiz (BrowsableAPIRenderer yoqilgan bo'lsa) JSON'ni chiroyli ko'rinishda ko'rasiz, yoki curl bilan:
# Bu buyruqlar ishlaydigan serverga qarata yuboriladi (illustrativ β server ishga tushgan bo'lishi kerak)
curl http://127.0.0.1:8000/api/mahsulotlar/
curl -X POST http://127.0.0.1:8000/api/mahsulotlar/ \
-H "Content-Type: application/json" \
-d '{"nom": "Noutbuk", "narx": "1200.00", "soni": 5, "kategoriya": 1}'
Yuqoridagi
curlbuyruqlari illustrativ β ular ishga tushganrunserver'ga yuboriladi. Kod to'g'ri, lekin bu kitobni tekshirish muhitida jonli server ko'tarilmagani uchun ular o'rniga DRF'ningAPIClient'i bilan avtomatik test orqali tekshirildi (pastdagi "Testlash" bo'limiga qarang).
@api_view β funksiya asosida view (qisqa variant)¶
Agar bitta-ikkita oddiy endpoint kerak bo'lsa, klass ortiqcha tuyulishi mumkin. DRF funksiya asosidagi view uchun @api_view dekoratorini beradi:
# shop/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(["GET"])
def salom(request):
return Response({"xabar": "Salom DRF"})
@api_view(["GET"]) β bu view faqat GET qabul qiladi (boshqa metod kelsa avtomatik 405 qaytaradi). Ichida request.data, Response baribir ishlaydi. Bu xuddi Express'dagi app.get('/salom', (req, res) => res.json({...})) ga o'xshaydi.
Katta loyihalarda esa APIView (yoki keyingi bobdagi ViewSet) afzal β kod tartibli bo'ladi.
ModelSerializer va ModelForm: nima farqi?¶
Agar 10-bobni o'qigan bo'lsangiz, ModelSerializer tanish tuyulishi kerak. Ular juda o'xshash, lekin maqsadi har xil:
| Jihat | ModelForm (10-bob) |
ModelSerializer (bu bob) |
|---|---|---|
| Maqsad | HTML forma + brauzer | JSON API + dasturlar |
| Kirish | request.POST (string) |
request.data (tipli JSON) |
| Tekshirish | is_valid() + cleaned_data |
is_valid() + validated_data |
| Xato | form.errors (HTML uchun) |
serializer.errors (JSON uchun) |
| Chiqish | form.as_p() (HTML) |
serializer.data (dict/JSON) |
| Saqlash | form.save() |
serializer.save() |
Ko'rib turganingizdek, g'oya bir xil β DRF ataylab Django formalariga taqlid qildi, shuning uchun bittasini bilsangiz, ikkinchisi oson. Asosiy farq: forma odam uchun (HTML), serializer dastur uchun (JSON).
Testlash: APIClient bilan API'ni tekshirish¶
DRF API'ni avtomatik test qilishning eng to'g'ri yo'li β APIClient. U Django'ning Client'iga o'xshaydi, lekin JSON yuborish (format="json") qulayligi bor:
# shop/tests.py
from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status
from .models import Mahsulot, Kategoriya
class MahsulotApiTest(TestCase):
def setUp(self):
self.client = APIClient()
self.kat = Kategoriya.objects.create(nom="Elektronika")
self.m = Mahsulot.objects.create(
nom="Telefon", narx="500.00", soni=10, kategoriya=self.kat
)
def test_royxat_get(self):
r = self.client.get("/api/mahsulotlar/")
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(len(r.json()), 1)
def test_yaratish_201(self):
r = self.client.post(
"/api/mahsulotlar/",
{"nom": "Noutbuk", "narx": "1200.00", "soni": 5,
"kategoriya": self.kat.pk},
format="json",
)
self.assertEqual(r.status_code, status.HTTP_201_CREATED)
self.assertEqual(Mahsulot.objects.count(), 2)
def test_yaratish_xato_400(self):
r = self.client.post(
"/api/mahsulotlar/",
{"nom": "Yomon", "narx": "-1", "soni": 1, "kategoriya": self.kat.pk},
format="json",
)
self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("narx", r.json())
def test_ochirish_204(self):
r = self.client.delete(f"/api/mahsulotlar/{self.m.pk}/")
self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Mahsulot.objects.count(), 0)
Bu testlar yuqoridagi curl so'rovlarining avtomatlashtirilgan, takrorlanadigan ekvivalenti. Har commitda ishonch bilan ishlaydi.
Xulosa¶
Bu bobda Django'ni JSON API'ga aylantirdik:
- REST API β resurslarni HTTP metodlari (GET/POST/PUT/DELETE) orqali boshqarish; JSON formatda gaplashish.
- DRF o'rnatish:
pip install djangorestframework,INSTALLED_APPS'garest_framework, ixtiyoriyREST_FRAMEWORKsozlamasi. - Serializer β ikki tomonlama ko'prik: serialize (obyekt -> JSON,
.data) va deserialize (JSON -> obyekt,is_valid()->validated_data->save()). serializers.Serializerβ qo'lda, har maydon;ModelSerializerβ modeldan avtomatik (Meta.model,fields,read_only_fields).- Validatsiya:
validate_<maydon>()bitta maydon uchun,validate()maydonlararo; xatoerrors'da (non_field_errorsumumiy xato uchun). Qiymatni qaytarish shart. APIViewβget/post/put/deletemetodlari,request.data,Response, to'g'ri status kodlar (200/201/204/400/404).- DRF serializer Django formalariga ataylab o'xshatilgan; Node.js'da bu Express + validatsiya + JSON ishini bitta tizimga jamlaydi.
Keyingi bobda takrorlanayotgan APIView kodini yanada qisqartiramiz: ViewSet va router bilan butun CRUD'ni bir necha qatorda yozamiz.
Cross-link: bazasi/SQL SQL qo'llanma; API'ni CI'da test qilish va deploy Git va GitHub qo'llanma; REST g'oyasini Express bilan solishtirish Node.js qo'llanma.
Mashqlar¶
Oson¶
-
DRF ulash. Yangi loyihada
rest_framework'niINSTALLED_APPS'ga qo'shing.python manage.py checkxatosiz o'tishini ko'rsating. -
Birinchi serializer.
Kitobmodeli (nom,sahifa,narx) uchunModelSerializeryozing.fields'da uchala maydon bo'lsin. -
serialize. Bitta
Kitobobyektini serializer'ga berib, uning.data'sini chop eting. Natijada lug'at chiqishini ko'rsating. -
many=True. Hamma kitobni
many=Truebilan serialize qiling..dataro'yxat (massiv) ekanini tushuntiring. -
read_only.
Kitob'gaqoshilgan = DateTimeField(auto_now_add=True)qo'shing va serializer'da uniread_only_fields'ga kiriting. Klient bu maydonni o'zgartira olmasligini tasdiqlang. -
status kod. Quyidagilarning har biri qaysi vaziyatda ishlatiladi:
200,201,204,400,404? Bittadan misol yozing.
O'rta¶
-
deserialize + save.
KitobSerializer(data=...)ga JSON bering,is_valid()chaqiring,validated_data'ni ko'ring vasave()bilan bazaga yozing. Saqlangan obyektid'sini chop eting. -
validate_field.
narxmanfiy yoki nol bo'lsa "Narx noldan katta bo'lsin" xatosini bering (validate_narx). Manfiy narx bilanis_valid()Falseqaytarishini ko'rsating. -
validate (maydonlararo).
Kitob'ga "agarsahifa > 1000bo'lsa,narx50 dan katta bo'lishi shart" qoidasinivalidate()da yozing. Xatonon_field_errors'ga tushishini ko'rsating. -
APIView GET.
KitobRoyxat(APIView)yozing:get()hamma kitobni JSON qaytarsin. URL'ga ulang. -
APIView POST + 201/400. O'sha view'ga
post()qo'shing: to'g'ri ma'lumotda201, xato ma'lumotda400vaserializer.errorsqaytsin. -
@api_view.
statistikadegan funksiya-view yozing (@api_view(["GET"])): u kitoblar soni va o'rtacha narxini JSON qaytarsin.
Qiyin¶
-
To'liq CRUD.
KitobDetal(APIView)yozing:get(bitta, topilmasa 404),put(yangilash),delete(204).get_object_or_404ishlating va APIClient bilan to'rtala metodni test qiling. -
Nested serializer.
MuallifvaKitob(FK) bo'lsin.MuallifSerializerichida muallifning kitoblarini ichma-ich (nested,many=True, read_only=True) ko'rsating. -
SerializerMethodField.
KitobSerializer'ga hisoblanadigan maydon qo'shing:chegirmali_narx=narx * 0.9.SerializerMethodFieldvaget_chegirmali_narx(self, obj)ishlating. -
APIClient test to'plami. Yuqoridagi CRUD uchun kamida 5 ta test yozing (
getro'yxat,getbitta,post201,post400,delete204) vapython manage.py testbilan hammasi PASS bo'lishini ko'rsating.
Yechimlar
1.
# config/settings.py
INSTALLED_APPS = [
# ... standart ilovalar ...
"rest_framework",
"kutubxona", # o'z ilovangiz
]
2.
# kutubxona/models.py
from django.db import models
class Kitob(models.Model):
nom = models.CharField(max_length=200)
sahifa = models.IntegerField()
narx = models.DecimalField(max_digits=10, decimal_places=2)
# kutubxona/serializers.py
from rest_framework import serializers
from .models import Kitob
class KitobSerializer(serializers.ModelSerializer):
class Meta:
model = Kitob
fields = ["id", "nom", "sahifa", "narx"]
3.
from kutubxona.models import Kitob
from kutubxona.serializers import KitobSerializer
k = Kitob.objects.create(nom="Python", sahifa=300, narx="120.00")
s = KitobSerializer(k)
print(s.data)
# {'id': 1, 'nom': 'Python', 'sahifa': 300, 'narx': '120.00'}
4.
s = KitobSerializer(Kitob.objects.all(), many=True)
print(s.data)
# [{'id': 1, 'nom': 'Python', ...}, ...] -> JSON ga o'tkanda massiv [...]
# many=True bo'lsa har obyekt alohida lug'at, hammasi ro'yxatda jamlanadi.
# (DRF 3.16+ oddiy dict; eski versiyalarda OrderedDict edi.)
5.
class Kitob(models.Model):
nom = models.CharField(max_length=200)
sahifa = models.IntegerField()
narx = models.DecimalField(max_digits=10, decimal_places=2)
qoshilgan = models.DateTimeField(auto_now_add=True)
class KitobSerializer(serializers.ModelSerializer):
class Meta:
model = Kitob
fields = ["id", "nom", "sahifa", "narx", "qoshilgan"]
read_only_fields = ["qoshilgan"]
# Tasdiq:
s = KitobSerializer(data={"nom": "X", "sahifa": 10, "narx": "5",
"qoshilgan": "2000-01-01T00:00:00Z"})
s.is_valid()
print("qoshilgan" in s.validated_data) # False β e'tiborga olinmaydi
6.
# 200 OK -> GET muvaffaqiyatli; ma'lumot qaytdi
# 201 CREATED -> POST: yangi resurs yaratildi
# 204 NO CONTENT-> DELETE muvaffaqiyatli; tana bo'sh
# 400 BAD REQUEST -> validatsiya xatosi (klient noto'g'ri yubordi)
# 404 NOT FOUND -> so'ralgan id topilmadi
7.
kirish = {"nom": "Django", "sahifa": 450, "narx": "150.00"}
s = KitobSerializer(data=kirish)
print(s.is_valid()) # True
print(dict(s.validated_data))
# {'nom': 'Django', 'sahifa': 450, 'narx': Decimal('150.00')}
obj = s.save()
print(obj.id) # masalan 2
8.
from rest_framework import serializers
class KitobSerializer(serializers.ModelSerializer):
class Meta:
model = Kitob
fields = ["id", "nom", "sahifa", "narx"]
def validate_narx(self, value):
if value <= 0:
raise serializers.ValidationError("Narx noldan katta bo'lsin.")
return value # qaytarish shart!
# Tekshirish:
s = KitobSerializer(data={"nom": "X", "sahifa": 10, "narx": "-3"})
print(s.is_valid()) # False
print(s.errors) # {'narx': [ErrorDetail(string='Narx noldan katta bo'lsin.', ...)]}
9.
class KitobSerializer(serializers.ModelSerializer):
class Meta:
model = Kitob
fields = ["id", "nom", "sahifa", "narx"]
def validate(self, data):
if data.get("sahifa", 0) > 1000 and data.get("narx", 0) <= 50:
raise serializers.ValidationError(
"Yirik kitob (1000+ sahifa) narxi 50 dan katta bo'lsin."
)
return data # data ni qaytarish shart!
# Tekshirish:
s = KitobSerializer(data={"nom": "Katta", "sahifa": 1200, "narx": "30"})
print(s.is_valid()) # False
print(s.errors) # {'non_field_errors': [...]}
10β11.
# kutubxona/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Kitob
from .serializers import KitobSerializer
class KitobRoyxat(APIView):
def get(self, request):
s = KitobSerializer(Kitob.objects.all(), many=True)
return Response(s.data)
def post(self, request):
s = KitobSerializer(data=request.data)
if s.is_valid():
s.save()
return Response(s.data, status=status.HTTP_201_CREATED)
return Response(s.errors, status=status.HTTP_400_BAD_REQUEST)
# kutubxona/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("kitoblar/", views.KitobRoyxat.as_view()),
]
12.
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.db.models import Avg
from .models import Kitob
@api_view(["GET"])
def statistika(request):
natija = Kitob.objects.aggregate(ortacha=Avg("narx"))
return Response({
"soni": Kitob.objects.count(),
"ortacha_narx": natija["ortacha"],
})
# urls.py
path("statistika/", views.statistika),
13.
# views.py
from django.shortcuts import get_object_or_404
class KitobDetal(APIView):
def get(self, request, pk):
k = get_object_or_404(Kitob, pk=pk)
return Response(KitobSerializer(k).data)
def put(self, request, pk):
k = get_object_or_404(Kitob, pk=pk)
s = KitobSerializer(k, data=request.data)
if s.is_valid():
s.save()
return Response(s.data)
return Response(s.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
k = get_object_or_404(Kitob, pk=pk)
k.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# urls.py
path("kitoblar/<int:pk>/", views.KitobDetal.as_view()),
# tests.py
from rest_framework.test import APIClient
from rest_framework import status
from django.test import TestCase
from .models import Kitob
class KitobCrudTest(TestCase):
def setUp(self):
self.c = APIClient()
self.k = Kitob.objects.create(nom="A", sahifa=100, narx="10.00")
def test_get_bitta(self):
r = self.c.get(f"/kitoblar/{self.k.pk}/")
self.assertEqual(r.status_code, 200)
def test_get_404(self):
r = self.c.get("/kitoblar/9999/")
self.assertEqual(r.status_code, 404)
def test_put(self):
r = self.c.put(f"/kitoblar/{self.k.pk}/",
{"nom": "B", "sahifa": 200, "narx": "20.00"},
format="json")
self.assertEqual(r.status_code, 200)
self.k.refresh_from_db()
self.assertEqual(self.k.nom, "B")
def test_delete(self):
r = self.c.delete(f"/kitoblar/{self.k.pk}/")
self.assertEqual(r.status_code, 204)
self.assertEqual(Kitob.objects.count(), 0)
14.
# models.py
class Muallif(models.Model):
ism = models.CharField(max_length=120)
class Kitob(models.Model):
nom = models.CharField(max_length=200)
narx = models.DecimalField(max_digits=10, decimal_places=2)
muallif = models.ForeignKey(Muallif, on_delete=models.CASCADE,
related_name="kitoblar")
# serializers.py
class KitobKichikSerializer(serializers.ModelSerializer):
class Meta:
model = Kitob
fields = ["id", "nom", "narx"]
class MuallifSerializer(serializers.ModelSerializer):
kitoblar = KitobKichikSerializer(many=True, read_only=True)
class Meta:
model = Muallif
fields = ["id", "ism", "kitoblar"]
# Natija:
# {"id": 1, "ism": "Oybek",
# "kitoblar": [{"id": 1, "nom": "...", "narx": "..."}, ...]}
15.
from rest_framework import serializers
from decimal import Decimal
class KitobSerializer(serializers.ModelSerializer):
chegirmali_narx = serializers.SerializerMethodField()
class Meta:
model = Kitob
fields = ["id", "nom", "narx", "chegirmali_narx"]
def get_chegirmali_narx(self, obj):
return round(obj.narx * Decimal("0.9"), 2)
# Natija: {"id":1,"nom":"X","narx":"100.00","chegirmali_narx":90.0}
# Diqqat: SerializerMethodField qaytargan qiymat to'g'ridan-to'g'ri
# JSON ga o'tadi (Decimal -> son), shuning uchun "90.00" emas, 90.0 chiqadi.
# SerializerMethodField har doim read-only: faqat chiqishda ko'rinadi.
16.
from rest_framework.test import APIClient
from rest_framework import status
from django.test import TestCase
from .models import Kitob
class KitobApiTest(TestCase):
def setUp(self):
self.c = APIClient()
self.k = Kitob.objects.create(nom="A", sahifa=100, narx="10.00")
def test_royxat(self):
r = self.c.get("/kitoblar/")
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(len(r.json()), 1)
def test_bitta(self):
r = self.c.get(f"/kitoblar/{self.k.pk}/")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()["nom"], "A")
def test_post_201(self):
r = self.c.post("/kitoblar/",
{"nom": "B", "sahifa": 50, "narx": "5.00"},
format="json")
self.assertEqual(r.status_code, status.HTTP_201_CREATED)
self.assertEqual(Kitob.objects.count(), 2)
def test_post_400(self):
r = self.c.post("/kitoblar/",
{"nom": "C", "sahifa": 50, "narx": "-1"},
format="json")
self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST)
def test_delete_204(self):
r = self.c.delete(f"/kitoblar/{self.k.pk}/")
self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT)
# python manage.py test -> 5 test PASS
# Eslatma: test_post_400 ishlashi uchun KitobSerializer da
# validate_narx (narx > 0) bo'lishi kerak (8-mashqdagiday).
β¬ οΈ Oldingi: 14 β Sessiyalar, messages va middleware Β· π README Β· Keyingi: 16 β DRF ViewSets va routers β‘οΈ