در درس قبل، با APIView یک CRUD کامل نوشتید. پنج متد مختلف. دو کلاس جداگانه. منطق پیدا کردن شیء را در سه جای مختلف تکرار کردید.
کار میکرد. اما کد زیادی داشت.
حالا فرض کنید بخواهید همین کار را برای مدل Product یا User هم بنویسید. دوباره همان کدهای تکراری را مینویسید. دوباره get_object میسازید. دوباره اعتبارسنجی دستی میکنید.
اینجا جایی است که ویوهای جنریک وارد میشوند.
ListCreateAPIView و RetrieveUpdateDestroyAPIView دو کلاس آماده در DRF هستند. خیلی از کارهایی که شما در APIView دستی نوشتید، اینها از قبل برایتان انجام میدهند.
نیازی به نوشتن get_object نیست. نیازی به چک کردن ۴۰۴ نیست. نیازی به تکرار منطق در چند متد ندارید.
در این درس، با همین دو کلاس، کل CRUD قبلی را در کمتر از ۱۰ خط کد بازنویسی میکنید.
همچنین یاد میگیرید چطور با get_queryset و get_serializer_class رفتار پیشفرض را تغییر دهید. و در انتها با mixins آشنا میشوید؛ قطعات کوچکی که میتوانید آنها را ترکیب کنید و ویوهای سفارشی بسازید.
اگر درس APIView را خوب درک کرده باشید، این درس برای شما مثل یک اتوماتیکسازی شیرین خواهد بود.
ListCreateAPIView و RetrieveUpdateDestroyAPIView
این دو کلاس، محبوبترین ویوهای جنریک در DRF هستند. بیشتر نیازهای روزمره شما را پوشش میدهند.
ListCreateAPIView – برای لیست و ساختن
این کلاس دو کار را همزمان انجام میدهد:
- GET: لیست همه رکوردها را برمیگرداند
- POST: یک رکورد جدید میسازد
همان کاری که در درس قبل با دو متد جداگانه (get و post) نوشتید.
کد مورد نیاز:
from rest_framework.generics import ListCreateAPIView
from .models import Article
from .serializers import ArticleSerializer
class ArticleListCreateView(ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
تنها سه خط کد. بدون get، بدون post، بدون is_valid()، بدون save().
DRF پشت صحنه همه این کارها را انجام میدهد.
RetrieveUpdateDestroyAPIView – برای جزئیات، بروزرسانی و حذف
این کلاس سه کار را انجام میدهد:
- GET: جزئیات یک رکورد را برمیگرداند
- PUT یا PATCH: رکورد را بروزرسانی میکند
- DELETE: رکورد را حذف میکند
همان کاری که در درس قبل با سه متد (get برای جزئیات، put، delete) نوشتید.
کد مورد نیاز:
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Article
from .serializers import ArticleSerializer
class ArticleDetailView(RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
باز هم فقط سه خط کد.
مقایسه با APIView قبلی
قبلی با APIView: بیش از ۵۰ خط کد برای دو کلاس
حالا با Generic Views: کمتر از ۱۵ خط کد برای دو کلاس
تنظیم مسیرها
دقیقاً مثل قبل، فقط اسم کلاسها عوض شده است:
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'),
]
توجه کنید که RetrieveUpdateDestroyAPIView به یک پارامتر pk در آدرس نیاز دارد. همین pk است که در درس قبل دستی با get_object پیدا میکردیم.
رفتار پیشفرض این کلاسها
ListCreateAPIView وقتی درخواست GET میآید:
- queryset را میخواند
- همه رکوردها را با serializer_class به JSON تبدیل میکند
- برمیگرداند
ListCreateAPIView وقتی درخواست POST میآید:
- داده را از request.data میخواند
- با serializer_class اعتبارسنجی میکند
- اگر معتبر بود، ذخیره میکند
- اگر نامعتبر بود، خطای ۴۰۰ برمیگرداند
RetrieveUpdateDestroyAPIView وقتی درخواست GET میآید:
- رکوردی با pk داده شده را از queryset پیدا میکند
- اگر بود، با Serializer به JSON تبدیل میکند
- اگر نبود، خطای ۴۰۴ برمیگرداند
RetrieveUpdateDestroyAPIView وقتی درخواست PUT یا PATCH میآید:
- رکورد را پیدا میکند
- داده جدید را اعتبارسنجی میکند
- ذخیره میکند
RetrieveUpdateDestroyAPIView وقتی درخواست DELETE میآید:
- رکورد را پیدا میکند
- حذف میکند
- کد ۲۰۴ برمیگرداند
نکته مهم
در RetrieveUpdateDestroyAPIView، متد PUT همه فیلدها را نیاز دارد. اگر فیلدی را نفرستید، مقدار آن خالی میشود. برای بروزرسانی جزیی از متد PATCH استفاده کنید. این کلاس هر دو را پشتیبانی میکند.
get_queryset و get_serializer_class
در Generic Views، دو راه برای مشخص کردن queryset و serializer_class وجود دارد. راه اول ساده است: استفاده از متغیرهای queryset و serializer_class. راه دوم پیشرفتهتر است: استفاده از متدهای get_queryset() و get_serializer_class().
چرا به این متدها نیاز داریم؟
گاهی یک ویو باید رفتارش بر اساس شرایط تغییر کند. مثلاً:
- کاربر معمولی فقط مقالات خودش را ببیند، ادمین همه مقالات را ببیند
- بر اساس پارامتر آدرس، از Serializer متفاوتی استفاده کنیم
- قبل از اجرای کوئری، فیلتری به آن اضافه کنیم
متغیرهای ساده queryset و serializer_class این قابلیت را ندارند. اما متدهای get_queryset و get_serializer_class به شما اجازه میدهند منطق شرطی بنویسید.
get_queryset – تعیین پویای مجموعه داده
این متد مشخص میکند ویو کدام رکوردها را از دیتابیس بخواند.
مثال ساده (بدون شرط):
class ArticleListCreateView(ListCreateAPIView):
serializer_class = ArticleSerializer
def get_queryset(self):
return Article.objects.all()
این همان کاری است که queryset = Article.objects.all() انجام میدهد.
مثال شرطی (بر اساس کاربر):
def get_queryset(self):
user = self.request.user
if user.is_staff:
return Article.objects.all() # ادمین همه مقالات را میبیند
return Article.objects.filter(author=user.username) # کاربر معمولی فقط مقالات خودش
مثال شرطی (بر اساس پارامتر آدرس):
def get_queryset(self):
queryset = Article.objects.all()
category = self.request.query_params.get('category')
if category:
queryset = queryset.filter(category=category)
return queryset
get_serializer_class – تعیین پویای Serializer
این متد مشخص میکند ویو از کدام Serializer برای تبدیل داده استفاده کند.
مثال ساده:
class ArticleDetailView(RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
def get_serializer_class(self):
return ArticleSerializer
مثال شرطی (Serializers متفاوت برای متدهای مختلف):
def get_serializer_class(self):
if self.request.method == 'POST':
return CreateArticleSerializer # برای ساخت، فیلدهای کمتری نیاز است
return ArticleSerializer # برای نمایش، همه فیلدها را نشان بده
مثال شرطی (بر اساس نوع کاربر):
ترکیب هر دو در یک ویو
from rest_framework.generics import ListCreateAPIView
from .models import Article
from .serializers import ArticleSerializer, AdminArticleSerializer
class ArticleListCreateView(ListCreateAPIView):
def get_queryset(self):
user = self.request.user
if user.is_staff:
return Article.objects.all()
return Article.objects.filter(is_published=True)
def get_serializer_class(self):
if self.request.user.is_staff:
return AdminArticleSerializer
return ArticleSerializer
در این مثال:
- کاربر ادمین: همه مقالات را میبیند و Serializer ادمین را دریافت میکند
- کاربر معمولی: فقط مقالات منتشر شده را میبیند و Serializer معمولی را دریافت میکند
نکات مهم
نکته اول: اگر هم queryset و هم get_queryset را تعریف کنید، DRF به get_queryset اولویت میدهد.
نکته دوم: در RetrieveUpdateDestroyAPIView، متد get_queryset فقط برای پیدا کردن رکورد با pk استفاده میشود. منطق پیدا کردن شیء را خودکار انجام میدهد.
نکته سوم: اگر نیازی به منطق شرطی ندارید، از متغیرهای ساده queryset و serializer_class استفاده کنید. کد سادهتر، کمتر خطا دارد.
تمرین برای شما
یک ویو بنویسید که:
- اگر کاربر لاگین کرده بود، همه مقالات را نشان بدهد
- اگر کاربر لاگین نکرده بود، فقط ۵ مقاله آخر را نشان بدهد
- از get_queryset برای این کار استفاده کنید.
در بخش بعدی، میبینید چطور Generic Views را با ModelSerializer ترکیب کنیم.
ترکیب Generic Views با ModelSerializer
در دو زیرعنوان قبل، دیدید که چطور Generic Views با queryset و serializer_class کار میکنند. حالا وقت آن است که این دو را کنار هم بگذاریم.
چرا این ترکیب قدرتمند است؟
ModelSerializer فیلدهای مدل را خودکار میخواند و اعتبارسنجی اولیه را انجام میدهد.
Generic Views منطق تکراری CRUD را خودکار مدیریت میکند.
وقتی این دو را کنار هم بگذارید، یک API کامل با کمتر از ۱۰ خط کد ساخته میشود. بدون نوشتن هیچ منطق تبدیل یا اعتبارسنجی دستی.
مثال کامل: API مقالات با Generic View + ModelSerializer
مدل (models.py):
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Serializer (serializers.py):
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'created_at']
read_only_fields = ['id', 'created_at']
ویو (views.py):
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from .models import Article
from .serializers import ArticleSerializer
class ArticleListCreateView(ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleDetailView(RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
مسیر (urls.py):
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'),
]
هر قطعه چه کاری انجام میدهد؟
ModelSerializer:
- فیلدهای id، title، content، author، created_at را شناسایی میکند
- میداند id و created_at فقط خواندنی هستند (read_only_fields)
- اعتبارسنجی خودکار max_length=200 را از مدل میخواند
ListCreateAPIView:
- queryset را میخواند تا بداند کدام رکوردها را نشان دهد
- serializer_class را میخواند تا بداند چطور تبدیل کند
- متد GET را برای لیست پیادهسازی میکند
- متد POST را برای ساخت جدید پیادهسازی میکند
RetrieveUpdateDestroyAPIView:
- از همان queryset برای پیدا کردن رکورد با pk استفاده میکند
- متد GET را برای جزئیات پیادهسازی میکند
- متد PUT/PATCH را برای بروزرسانی پیادهسازی میکند
- متد DELETE را برای حذف پیادهسازی میکند
اضافه کردن اعتبارسنجی سفارشی
حتی با این ترکیب ساده، میتوانید اعتبارسنجی سفارشی به Serializer اضافه کنید:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'created_at']
read_only_fields = ['id', 'created_at']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("عنوان حداقل باید ۵ کاراکتر باشد")
return value
def validate(self, data):
if "badword" in data['title'].lower():
raise serializers.ValidationError("عنوان شامل کلمات ممنوعه است")
return data
Generic Views این اعتبارسنجیها را خودکار اعمال میکنند. نیازی به تغییر در ویو نیست.
اضافه کردن فیلتر ساده با get_queryset
میتوانید بدون تغییر در Serializer، کوئری را پویا کنید:
class ArticleListCreateView(ListCreateAPIView):
serializer_class = ArticleSerializer
def get_queryset(self):
queryset = Article.objects.all()
author = self.request.query_params.get('author')
if author:
queryset = queryset.filter(author=author)
return queryset
حالا کاربر میتواند ?author=ali را به آدرس اضافه کند و فقط مقالات آن نویسنده را ببیند.
جمعبندی این بخش
ترکیب Generic Views و ModelSerializer قلب توسعه سریع API در DRF است.
- ModelSerializer مسئول تبدیل و اعتبارسنجی
- Generic Views مسئول منطق CRUD و ارتباط با دیتابیس
با این دو، میتوانید یک API کامل و استاندارد را در کمتر از ۳۰ خط کد (شامل مدل، سریالایزر، ویو و مسیر) پیادهسازی کنید.
معرفی mixins و نحوه ترکیب آنها
در دو زیرعنوان قبل، با ListCreateAPIView و RetrieveUpdateDestroyAPIView آشنا شدید. این کلاسها از قبل آماده هستند. اما اگر نیاز شما نصف این امکانات باشد چه؟
مثلاً فقط بخواهید لیست بگیرید (بدون ساخت). یا فقط بخواهید بروزرسانی کنید (بدون حذف).
اینجا mixins وارد میشوند.
mixins چیست؟
mixins قطعات کوچک و قابل استفاده مجدد هستند. هر کدام یک عملیات خاص را پیادهسازی میکند.
مثلاً ListModelMixin فقط منطق لیست گرفتن را دارد. CreateModelMixin فقط منطق ساختن را دارد.
شما میتوانید این قطعات را کنار هم بگذارید و یک ویو سفارشی بسازید. فقط آن عملیاتی که نیاز دارید.
لیست mixinsهای اصلی در DRF
| نام mixin | عملیات | متد معادل |
| ListModelMixin | لیست همه رکوردها | GET |
| CreateModelMixin | ساختن رکورد جدید | POST |
| RetrieveModelMixin | جزئیات یک رکورد | GET (با pk) |
| UpdateModelMixin | بروزرسانی کامل یا جزیی | PUT / PATCH |
| DestroyModelMixin | حذف رکورد | DELETE |
ساختار پایه یک ویو با mixins
همه mixinها باید با GenericAPIView ترکیب شوند. GenericAPIView پایه است و قابلیتهای اساسی مثل get_queryset و get_serializer_class را فراهم میکند.
from rest_framework import mixins, generics
from .models import Article
from .serializers import ArticleSerializer
class ArticleListOnlyView(mixins.ListModelMixin, generics.GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
در این مثال، فقط قابلیت لیست گرفتن را اضافه کردهایم. متد get را خودمان نوشتیم و به self.list (که توسط ListModelMixin فراهم شده) متصل کردیم.
مثال: ویو فقط برای ساختن (بدون لیست)
class ArticleCreateOnlyView(mixins.CreateModelMixin, generics.GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
مثال: ویو فقط برای حذف (بدون نمایش و بروزرسانی)
class ArticleDeleteOnlyView(mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
ترکیب چند mixin با هم
میتوانید چند mixin را در یک کلاس ترکیب کنید. ترتیب ارثبری مهم است. معمولاً mixinها را قبل از GenericAPIView مینویسید.
class ArticleListCreateView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
این همان کاری است که ListCreateAPIView از قبل برای شما انجام میدهد.
مثال پیشرفته: ترکیب سه عملیات
class ArticleRetrieveUpdateView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
چه موقع از mixins استفاده کنیم؟
موقعیت اول: وقتی فقط به بخشی از عملیاتهای CRUD نیاز دارید.
مثلاً یک API که فقط باید لیست را نشان دهد و اجازه ساخت، بروزرسانی یا حذف ندهد.
موقعیت دوم: وقتی میخواهید رفتار پیشفرض را تغییر دهید.
میتوانید از mixin ارثبری کنید و سپس متدهای آن را بازنویسی کنید.
موقعیت سوم: وقتی میخواهید کد خود را با کلاسهای آماده DRF یکسان نگه دارید.
ListCreateAPIView و RetrieveUpdateDestroyAPIView خودشان از همین mixinها ساخته شدهاند.
جدول مقایسه: Generic Views آماده vs ترکیب mixinها
| نیاز شما | راه حل آماده | راه حل با mixin |
| لیست + ساخت | ListCreateAPIView | ListModelMixin + CreateModelMixin |
| جزئیات + بروزرسانی + حذف | RetrieveUpdateDestroyAPIView | RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin |
| فقط لیست | ندارد | ListModelMixin |
| فقط حذف | ندارد | DestroyModelMixin |
| فقط بروزرسانی | ندارد | UpdateModelMixin |
نکته: اگر نیاز شما دقیقاً همان عملیاتهای ListCreateAPIView یا RetrieveUpdateDestroyAPIView است، از همان کلاسهای آماده استفاده کنید. کد کوتاهتر و خواناتر است. از mixinها فقط زمانی استفاده کنید که نیاز به ترکیب سفارشی دارید.
تمرین برای شما
یک ویو بسازید که:
- فقط لیست مقالات را نشان دهد (GET)
- فقط اجازه حذف بدهد (DELETE)
- از mixinها برای این کار استفاده کنید.
جمعبندی درس ویوهای جنریک
در این درس، با قدرتمندترین ابزارهای DRF برای کاهش کدهای تکراری آشنا شدید.
ListCreateAPIView و RetrieveUpdateDestroyAPIView را یاد گرفتید. دو کلاسی که عملیات اصلی CRUD را در کمتر از ۱۰ خط کد انجام میدهند. بدون نیاز به نوشتن متدهای get، post، put، delete و بدون نیاز به منطق پیدا کردن شیء.
با get_queryset و get_serializer_class آشنا شدید. دیدید که چطور میتوانید بر اساس شرایط (نوع کاربر، پارامترهای آدرس، یا متد درخواست) رفتار ویو را تغییر دهید.
ترکیب Generic Views با ModelSerializer را مرور کردید. دیدید که این دو چطور در کنار هم، یک API کامل و استاندارد را با حداقل کدنویسی ممکن میسازند.
در انتها با mixins آشنا شدید. قطعات کوچکی که هر کدام یک عملیات خاص را پیادهسازی میکنند. یاد گرفتید چطور آنها را با GenericAPIView ترکیب کنید و ویوهای سفارشی بسازید.
مقایسه سه روشی که تا الان یاد گرفتید
| روش | حجم کد | کنترل | سرعت توسعه | مناسب برای |
| Function-Based View | متوسط | زیاد | کم | پروژههای خیلی ساده، یادگیری اولیه |
| APIView | زیاد | خیلی زیاد | متوسط | نیاز به کنترل کامل روی منطق |
| Generic Views | کم | متوسط | زیاد | پروژههای استاندارد، نیازهای رایج CRUD |
| Generic Views + mixins | کم | زیاد | زیاد | نیازهای سفارشی، ترکیب دلخواه عملیاتها |
از این درس چه چیزی باید به خاطر بسپارید؟
۱. برای ۹۰٪ نیازهای روزمره، ListCreateAPIView و RetrieveUpdateDestroyAPIView کافی هستند.
۲. اگر نیاز به تغییر رفتار دارید، get_queryset و get_serializer_class را بازنویسی کنید.
۳. اگر نیاز به ترکیب خاصی از عملیاتها دارید (مثلاً فقط لیست و حذف)، از mixinها استفاده کنید.
۴. Generic Views بدون ModelSerializer قدرتش را نشان نمیدهد. این دو را همیشه کنار هم یاد بگیرید.
درس بعدی چیست؟
حالا که با ویوهای جنریک آشنا شدید، وقت آن رسیده که یک سطح بالاتر بروید.
در درس بعد، با ViewSet آشنا میشوید. کلاسی که چندین ویو جنریک را در یک کلاس واحد جمع میکند. با اضافه شدن Router، دیگر نیازی به نوشتن urlpatterns هم نخواهید داشت.
برای درس بعد، همین مدل Article و ArticleSerializer را نگه دارید. ViewSetها روی همان دادهها کار میکنند.