تا الان دو روش برای نوشتن ویو در DRF امتحان کردهاید.
اولی با @api_view دکوریتور روی توابع ساده. همان روشی که در درس های قبلی کار کردید. این روش برای چند خط کد و پروژههای کوچک، مناسب است.
دومی با Serializerها که در درس قبل یاد گرفتید. حالا مدل دارید، Serializer دارید، اما ویوها هنوز تابعی هستند.
اینجا یک مشکل ظاهر میشود.
فرض کنید میخواهید پنج API مختلف بنویسید. هر کدام نیاز به منطق مشابهی دارند. مثلاً همه باید چک کنند کاربر لاگین کرده یا نه. یا همه باید خطاهای مشابهی برگردانند.
با روش تابعی، مجبورید این کدهای تکراری را در هر تابع بنویسید. یا توابع کمکی بسازید و در جای دیگر صدا بزنید.
کلاسبیس ویوها این مشکل را حل میکنند.
APIView پایهترین کلاس در DRF است. با آن میتوانید متدهای GET، POST، PUT، DELETE را جداگانه در یک کلاس تعریف کنید. کد تمیزتر میشود. قابلیت استفاده مجدد بالا میرود.
در این درس، یک CRUD کامل با APIView مینویسیم. یعنی چهار عمل اصلی: خواندن لیست، خواندن جزئیات، ساختن، بروزرسانی، و حذف.
همچنین یاد میگیرید چطور داده را از request.data و request.query_params بخوانید. و چطور پاسخ را با Response برگردانید.
اگر درس Serializerها را خوب متوجه شدهاید، این درس برای شما ساده خواهد بود. چون منطق اصلی همان است. فقط ساختار ویوها را از تابع به کلاس تغییر میدهیم.
معرفی APIView
APIView پایهترین کلاس در DRF است. همه ویوهای کلاسبیس دیگر در DRF (مثل GenericAPIView، ViewSet) یا از آن ارثبری میکنند یا بر اساس آن ساخته شدهاند.
یک کلاس است که شما از آن ارثبری میکنید. سپس متدهایی به اسم get، post، put، patch، delete داخل آن مینویسید. هر متد مسئول پاسخ دادن به یک نوع درخواست HTTP است.
ساختار پایه یک APIView
from rest_framework.views import APIView
from rest_framework.response import Response
class MyView(APIView):
def get(self, request):
return Response({'message': 'این یک درخواست GET است'})
def post(self, request):
return Response({'message': 'این یک درخواست POST است'})
در این مثال، اگر کاربر درخواست GET بفرستد، متد get اجرا میشود. اگر POST بفرستد، متد post اجرا میشود.
نیازی به نوشتن if request.method == 'GET' نیست. خود DRF بر اساس متد درخواست، متد مناسب کلاس را صدا میزند.
چرا APIView بهتر از Function-Based View است؟
دلیل اول: جدا بودن منطق متدها
در روش تابعی، همه متدها در یک تابع و پشت چند شرط if پنهان میشدند.
@api_view(['GET', 'POST'])
def my_view(request):
if request.method == 'GET':
# منطق GET
elif request.method == 'POST':
# منطق POST
در APIView، هر متد در جای خودش قرار دارد. خواندن و فهمیدن کد راحتتر است.
دلیل دوم: قابلیت ارثبری و استفاده مجدد
میتوانید یک کلاس پایه بسازید و منطق مشترک را در آن قرار دهید. سپس کلاسهای دیگر از آن ارثبری کنند.
class BaseAPIView(APIView):
def handle_exception(self, exc):
# منطق مشترک برای مدیریت خطا
return super().handle_exception(exc)
class ArticleView(BaseAPIView):
def get(self, request):
# منطق خاص این ویو
در روش تابعی، این کار سختتر است.
دلیل سوم: یکپارچگی بهتر با سایر ابزارهای DRF
بسیاری از قابلیتهای پیشرفته DRF مثل احراز هویت، مجوزها، و throttleها با کلاسها بهتر کار میکنند. اگر از APIView استفاده کنید، اضافه کردن این قابلیتها فقط با چند خط کد است.
چه موقع از APIView استفاده کنیم؟
APIView برای زمانی مناسب است که:
- کنترل کامل روی منطق ویو میخواهید
- نیاز به چند متد مختلف (GET، POST، PUT، DELETE) دارید
- میخواهید کد خود را تمیز و ماژولار نگه دارید
- قصد دارید از قابلیتهای پیشرفته DRF استفاده کنید
برای پروژههای کوچک و چند خط کد، روش تابعی همچنان گزینه خوبی است. اما برای پروژههای متوسط و بزرگ، APIView انتخاب بهتری است.
قدم بعدی
در بخش های بعدی، تفاوت APIView با View عادی جنگو را میبینیم. سپس یک CRUD کامل با APIView پیادهسازی میکنیم.
تفاوت APIView با View عادی جنگو
جنگو خودش یک کلاس به اسم View دارد. در برنامههای سنتی جنگو، برای نوشتن ویوهای کلاسبیس از آن استفاده میشود.
from django.views import View
from django.http import JsonResponse
class MyDjangoView(View):
def get(self, request):
return JsonResponse({'message': 'سلام'})
این ویو کار میکند. اما برای ساختن API، محدودیتهایی دارد.
تفاوت اصلی در خروجی
View عادی جنگو برای برگرداندن صفحه HTML یا JSON دستی طراحی شده. شما باید از JsonResponse استفاده کنید و داده را خودتان به JSON تبدیل کنید.
APIView اما از Response استفاده میکند. این کلاس خودکار داده را به JSON تبدیل میکند. همچنین فرمت پاسخ را بر اساس درخواست کاربر انتخاب میکند (JSON یا HTML برای Browsable API).
تفاوت در مدیریت خطا
در View عادی جنگو، اگر خطایی رخ دهد، باید خودتان خطا را مدیریت کنید و پاسخ مناسب بفرستید.
در APIView، DRF بیشتر خطاها را خودکار مدیریت میکند. مثلاً اگر کاربر داده نامعتبر بفرستد، DRF خودکار خطای ۴۰۰ را برمیگرداند.
تفاوت در اعتبارسنجی و سریالایز
View عادی جنگو هیچ ارتباطی با Serializerهای DRF ندارد. اگر بخواهید از Serializer استفاده کنید، باید خودتان آن را صدا بزنید و خروجی را به JsonResponse بدهید.
APIView اما با Serializerها هماهنگ است. میتوانید مستقیماً از Serializer در ویو استفاده کنید.
جدول مقایسه سریع
| ویژگی | View عادی جنگو | APIView در DRF |
| تبدیل خودکار به JSON | خیر | بله |
| پشتیبانی از Browsable API | خیر | بله |
| مدیریت خودکار خطا | خیر | بله |
| هماهنگی با Serializer | خیر | بله |
| نیاز به نصب DRF | خیر | بله |
| مناسب برای API | محدود | عالی |
کدام را انتخاب کنیم؟
اگر پروژه شما فقط وبسایت معمولی است و API جداگانه نمیخواهید، View عادی جنگو کافی است.
اما اگر در حال ساختن API هستید (حتی ساده)، APIView انتخاب درستی است. چون ابزارهای تخصصی برای این کار دارد.
در پروژههایی که هم وبسایت معمولی دارید و هم API، میتوانید از هر دو استفاده کنید. برای صفحات HTML از View جنگو، برای APIها از APIView DRF.
یک مثال مقایسه
با View عادی جنگو:
from django.views import View
from django.http import JsonResponse
from .models import Article
class ArticleList(View):
def get(self, request):
articles = list(Article.objects.values('id', 'title'))
return JsonResponse(articles, safe=False)
با APIView در DRF:
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleSerializer
class ArticleList(APIView):
def get(self, request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
تفاوت اصلی در خطوط مربوط به تبدیل داده است. در نسخه DRF، سریالایزر کار تبدیل را انجام میدهد. در نسخه عادی، باید دستی فیلدها را مشخص کنید.
در زیرعنوان بعدی، یک CRUD کامل با APIView پیادهسازی میکنیم.
پیادهسازی CRUD کامل با APIView
CRUD مخفف چهار عمل اصلی در هر اپلیکیشن است: Create، Read، Update، Delete. در دنیای APIها، این چهار عمل به متدهای HTTP زیر تبدیل میشوند:
- Create = POST (ساختن داده جدید)
- Read = GET (خواندن داده)
- Update = PUT یا PATCH (بروزرسانی داده)
- Delete = DELETE (حذف داده)
در این بخش، یک API کامل برای مدل Article میسازیم. دو ویو خواهیم داشت: یکی برای لیست و ساختن، دیگری برای جزئیات، بروزرسانی و حذف.
قدم اول: ویو برای لیست و ساختن (ListCreateView)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Article
from .serializers import ArticleSerializer
class ArticleListCreateView(APIView):
def get(self, request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
توضیح متد GET:
خط اول: همه مقالات را از دیتابیس میخواند.
خط دوم: Serializer را با many=True صدا میزند. یعنی چند شیء را همزمان تبدیل به JSON کند.
خط سوم: پاسخ را با کد وضعیت ۲۰۰ (موفقیت) برمیگرداند.
توضیح متد POST:
خط اول: داده ورودی کاربر را به Serializer میدهد.
خط دوم: بررسی میکند داده معتبر است یا نه.
خط سوم: اگر معتبر بود، در دیتابیس ذخیره میکند.
خط چهارم: داده ساخته شده را با کد ۲۰۱ (Created) برمیگرداند.
خط پنجم و ششم: اگر داده نامعتبر بود، خطاها را با کد ۴۰۰ (Bad Request) برمیگرداند.
قدم دوم: ویو برای جزئیات، بروزرسانی و حذف (DetailView)
class ArticleDetailView(APIView):
def get_object(self, pk):
try:
return Article.objects.get(pk=pk)
except Article.DoesNotExist:
return None
def get(self, request, pk):
article = self.get_object(pk)
if not article:
return Response({'error': 'مقاله یافت نشد'}, status=status.HTTP_404_NOT_FOUND)
serializer = ArticleSerializer(article)
return Response(serializer.data)
def put(self, request, pk):
article = self.get_object(pk)
if not article:
return Response({'error': 'مقاله یافت نشد'}, status=status.HTTP_404_NOT_FOUND)
serializer = ArticleSerializer(article, 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):
article = self.get_object(pk)
if not article:
return Response({'error': 'مقاله یافت نشد'}, status=status.HTTP_404_NOT_FOUND)
article.delete()
return Response({'message': 'مقاله حذف شد'}, status=status.HTTP_204_NO_CONTENT)
توضیح متد get_object:
این متد کمکی است. یک شناسه (pk) میگیرد و مقاله مربوطه را از دیتابیس میخواند. اگر وجود نداشت، None برمیگرداند.
توضیح متد GET (جزئیات):
مقاله را با get_object پیدا میکند. اگر نبود، خطای ۴۰۴ برمیگرداند. اگر بود، Serializer را صدا میزند و داده را برمیگرداند.
توضیح متد PUT (بروزرسانی کامل):
مشابه GET، اول مقاله را پیدا میکند. سپس Serializer را با instance=article (شیء قدیمی) و data=request.data (داده جدید) صدا میزند. بعد از اعتبارسنجی، ذخیره میکند.
توضیح متد DELETE:
مقاله را پیدا میکند. اگر بود، حذف میکند. کد وضعیت ۲۰۴ (No Content) یعنی درخواست موفق بوده اما محتوایی برای برگرداندن وجود ندارد.
قدم سوم: تنظیم مسیرها
در فایل urls.py اپ myapi:
from django.urls import path
from .views import ArticleListCreateView, ArticleDetailView
urlpatterns = [
path('articles/', ArticleListCreateView.as_view(), name='article-list'),
path('articles/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),
]
قدم چهارم: تست با Postman
درخواست GET به آدرس: http://127.0.0.1:8000/api/articles/
لیست همه مقالات را نشان میدهد.
درخواست POST به آدرس: http://127.0.0.1:8000/api/articles/
بدنه JSON حاوی title، content، author. مقاله جدید میسازد.
درخواست GET به آدرس: http://127.0.0.1:8000/api/articles/1/
جزئیات مقاله با شناسه ۱ را نشان میدهد.
درخواست PUT به آدرس: http://127.0.0.1:8000/api/articles/1/
بدنه JSON با فیلدهای جدید. مقاله را بروزرسانی میکند.
درخواست DELETE به آدرس: http://127.0.0.1:8000/api/articles/1/
مقاله با شناسه ۱ را حذف میکند.
نکات مهم این پیادهسازی
۱. متد put همه فیلدها را نیاز دارد. اگر فیلدی را نفرستید، مقدار آن خالی میشود. برای بروزرسانی جزیی باید از patch استفاده کنید.
۲. کد وضعیت ۲۰۱ برای ساخت موفق، ۲۰۴ برای حذف موفق، و ۴۰۴ برای پیدا نشدن منبع استفاده شده است.
۳. منطق پیدا کردن مقاله در متد get_object تکرار شده تا در سه متد (get، put، delete) دوباره ننویسیم.
ورودیخوانی (request.data, request.query_params)
در ویوهای DRF، دادهای که کاربر میفرستد از دو راه وارد میشود. تشخیص این دو راه خیلی مهم است.
request.data – برای داده بدنه درخواست
وقتی کاربر یک درخواست POST، PUT یا PATCH میفرستد، داده معمولاً در بدنه (Body) درخواست قرار میگیرد. این داده میتواند JSON، فرم، یا حتی فایل باشد.
request.data این داده را به صورت یک دیکشنری پایتون در اختیار شما میگذارد. نیازی نیست خودتان JSON را پارس کنید.
class SampleView(APIView):
def post(self, request):
title = request.data.get('title')
content = request.data.get('content')
# عنوان و محتوا را از بدنه درخواست میخواند
return Response({'received': title})
مثال واقعی از پروژه قبلی ما:
در متد POST مقاله، از request.data استفاده کردیم:
def post(self, request):
serializer = ArticleSerializer(data=request.data)
# request.data شامل title, content, author است
نکته مهم: اگر کاربر JSON نفرستاده باشد، request.data依然 سعی میکند داده را بخواند. DRF چند فرمت مختلف را پشتیبانی میکند.
request.query_params – برای پارامترهای آدرس
وقتی کاربر داده را در خود آدرس قرار میدهد، به آن Query Parameter میگویند. این داده بعد از علامت سوال در آدرس میآید.
مثال: api/articles/?category=python&page=2
request.query_params این پارامترها را میخواند.
class SearchView(APIView):
def get(self, request):
category = request.query_params.get('category')
page = request.query_params.get('page', 1)
# category برابر 'python' و page برابر 2 میشود
return Response({'category': category, 'page': page})
توجه: در DRF نباید از request.GET استفاده کنید. request.query_params جایگزین استاندارد است. خروجی هر دو یکی است، اما اسم query_params معنای واضحتری دارد.
تفاوت این دو در یک نگاه
| ویژگی | request.data | request.query_params |
| محل داده | بدنه درخواست | آدرس (بعد از علامت سوال) |
| متدهای معمول | POST, PUT, PATCH | GET |
| فرمت داده | JSON، فرم، فایل | رشته ساده |
| حجم داده | میتواند زیاد باشد | محدود (حداکثر طول آدرس) |
| امنیت | مناسب برای داده حساس | داده در آدرس نمایان است |
مثال ترکیبی
فرض کنید میخواهید API جستجوی پیشرفته بسازید. کاربر عبارت جستجو را در آدرس میفرستد، اما فیلترهای اضافی را در بدنه.
class AdvancedSearchView(APIView):
def post(self, request):
# خواندن پارامترهای آدرس
query = request.query_params.get('q', '')
page = int(request.query_params.get('page', 1))
# خواندن فیلترها از بدنه
filters = request.data.get('filters', {})
# منطق جستجو
results = {
'query': query,
'page': page,
'filters': filters,
'results': [] # نتایج واقعی
}
return Response(results)
درخواست نمونه:
خطاهای رایج
اشتباه اول: استفاده از request.GET در DRF
# درست
query = request.query_params.get('q')
# نادرست (گرچه کار میکند، اما استاندارد نیست)
query = request.GET.get('q')
اشتباه دوم: فراموشی get به جای دسترسی مستقیم به کلید
# این خطا میدهد اگر کلید 'title' وجود نداشته باشد
title = request.data['title']
# این خیال شما را راحت میکند
title = request.data.get('title')
اشتباه سوم: استفاده از request.data برای درخواست GET
درخواست GET بدنه ندارد. request.data در متد GET خالی میماند. برای GET فقط از request.query_params استفاده کنید.
خلاصه
- request.data: برای خواندن داده بدنه (POST، PUT، PATCH)
- request.query_params: برای خواندن پارامترهای آدرس (GET)
هر دو یک شیء شبیه دیکشنری برمیگردانند. برای دسترسی به مقدار از متد get استفاده کنید تا در صورت نبودن کلید، خطا نخورید.
ارسال پاسخ با Response
در جنگوی معمولی، برای برگرداندن پاسخ از JsonResponse یا HttpResponse استفاده میکنید. در DRF اما یک کلاس اختصاصی به اسم Response داریم.
Response چیست؟
Response یک کلاس در DRF است. داده شما را میگیرد و به فرمت مناسبی تبدیل میکند. این فرمت میتواند JSON باشد. میتواند HTML برای Browsable API باشد. یا هر فرمت دیگری که شما تنظیم کنید.
from rest_framework.response import Response
from rest_framework.views import APIView
class SampleView(APIView):
def get(self, request):
data = {'message': 'سلام دنیا'}
return Response(data)
مزایای Response نسبت به JsonResponse
مزیت اول: تبدیل خودکار انواع داده
JsonResponse فقط دیکشنری پایتون را میپذیرد. اگر لیست بفرستید، باید safe=False اضافه کنید.
# JsonResponse
return JsonResponse([1, 2, 3], safe=False) # نیاز به safe=False# JsonResponse
return JsonResponse([1, 2, 3], safe=False) # نیاز به safe=False
Response اما لیست را هم بدون مشکل قبول میکند:
# Response
return Response([1, 2, 3]) # بدون نیاز به تنظیم اضافه
مزیت دوم: پشتیبانی از Browsable API
JsonResponse فقط JSON خام برمیگرداند. Response اگر درخواست از مرورگر آمده باشد، میتواند صفحه HTML زیبای Browsable API را نشان دهد.
مزیت سوم: مدیریت خودکار کد وضعیت
میتوانید کد وضعیت را به عنوان آرگومان دوم بدهید:
return Response(data, status=status.HTTP_201_CREATED)
ساختار Response
Response(data, status=None, headers=None, content_type=None)
data: داده اصلی پاسخ. میتواند دیکشنری، لیست، رشته، یا هر شیء قابل سریالایز شدن باشد.
status: کد وضعیت HTTP. اگر ندهید، پیشفرض ۲۰۰ است.
headers: دیکشنری هدرهای سفارشی.
content_type: نوع محتوای پاسخ. معمولاً نیاز به تنظیم دستی نیست.
کدهای وضعیت پرکاربرد در DRF
DRF یک ماژول به اسم status دارد. به جای نوشتن عدد، از اسمهای معنادار استفاده کنید.
from rest_framework import status
# موفقیت
status.HTTP_200_OK # 200
status.HTTP_201_CREATED # 201
status.HTTP_204_NO_CONTENT # 204
# خطای کلاینت
status.HTTP_400_BAD_REQUEST # 400
status.HTTP_401_UNAUTHORIZED # 401
status.HTTP_403_FORBIDDEN # 403
status.HTTP_404_NOT_FOUND # 404
# خطای سرور
status.HTTP_500_INTERNAL_SERVER_ERROR # 500
مثالهای عملی
پاسخ موفق با داده:
def get(self, request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
پاسخ موفق بدون داده (برای حذف):
def delete(self, request, pk):
article = self.get_object(pk)
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
پاسخ خطای کلاینت:
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# ادامه کد
پاسخ خطای منبع پیدا نشد:
def get(self, request, pk):
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return Response(
{'error': 'مقاله با این شناسه وجود ندارد'},
status=status.HTTP_404_NOT_FOUND
)
serializer = ArticleSerializer(article)
return Response(serializer.data)
اضافه کردن هدر سفارشی
گاهی نیاز دارید هدر خاصی به پاسخ اضافه کنید. مثلاً برای CORS یا کش کردن.
def get(self, request):
data = {'message': 'سلام'}
headers = {'X-Custom-Header': 'my-value'}
return Response(data, headers=headers)
نکات مهم
نکته اول: همیشه از Response استفاده کنید، نه JsonResponse. حتی اگر Browsable API را غیرفعال کرده باشید، Response مزایای دیگری دارد.
نکته دوم: کد وضعیت را فراموش نکنید. به خصوص برای عملیات POST (۲۰۱) و DELETE (۲۰۴). پیشفرض ۲۰۰ است که برای همه موارد مناسب نیست.
نکته سوم: داده ارسالی به Response باید قابل سریالایز شدن باشد. اگر شیء مدل را مستقیماً بفرستید، خطا میگیرید:
# اشتباه
article = Article.objects.get(pk=1)
return Response(article) # خطا
# درست
serializer = ArticleSerializer(article)
return Response(serializer.data)
جمعبندی
Response کلاس اصلی ارسال پاسخ در DRF است. سه کار را خودکار انجام میدهد: تبدیل داده به JSON، تنظیم هدر مناسب، و پشتیبانی از Browsable API.
همیشه آن را با ماژول status همراه کنید تا کد وضعیتهای معناداری بفرستید.
جمعبندی و آماده شدن برای Generic Views
در این درس، با کلاس پایه APIView آشنا شدید.
دیدید که چطور متدهای get، post، put و delete را در یک کلاس جداگانه تعریف کنید. یاد گرفتید که APIView چه تفاوتهایی با View عادی جنگو دارد و چرا برای ساختن API انتخاب بهتری است.
یک CRUD کامل برای مدل Article نوشتید. دو ویو ساختید: یکی برای لیست و ساختن، دیگری برای جزئیات، بروزرسانی و حذف.
همچنین با دو روش ورودیخوانی آشنا شدید. request.data برای داده بدنه درخواست (POST، PUT). request.query_params برای پارامترهای آدرس (GET).
در انتها یاد گرفتید چطور با کلاس Response پاسخ بفرستید و کد وضعیت مناسب را با ماژول status تنظیم کنید.
چه چیزهایی در APIView خوب است؟
کنترل کامل روی منطق ویو دارید. هر متد را جداگانه مینویسید. کد تمیز و خواناست. به راحتی میتوانید خطاها را مدیریت کنید.
چه محدودیتهایی دارد؟
برای کارهای تکراری، مجبورید کدهای مشابه را در چند جا بنویسید. مثلاً منطق گرفتن یک شیء از دیتابیس (get_object) را در سه متد تکرار کردید. یا اعتبارسنجی دسترسی کاربر را باید در هر متد جداگانه چک کنید.
Generic Views چه کمکی میکند؟
ویوهای جنریک (Generic Views) بیشتر این کارهای تکراری را برای شما انجام میدهند.
با یک کلاس ListCreateAPIView، هم لیست گرفتن و هم ساختن شیء جدید در کمتر از ۱۰ خط کد تمام میشود. بدون نیاز به نوشتن get_object، بدون نیاز به تکرار منطق پیدا کردن شیء.
در درس بعدی، با Generic Views آشنا میشوید. میبینید چطور میتوانید با کد کمتر، کار بیشتری انجام دهید.
برای درس بعد چه چیزی نیاز دارید؟
همین مدل Article و ArticleSerializer که الان دارید، کافی است. Generic Views از همان Serializer استفاده میکند. فقط ویوها را عوض میکنیم.
اگر APIView را خوب فهمیده باشید، Generic Views برای شما خیلی ساده خواهد بود. چون فقط یاد میگیرید چه کلاسهایی از قبل آماده شدهاند و چطور از آنها استفاده کنید.