در درس قبل، با 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  # برای نمایش، همه فیلدها را نشان بده

مثال شرطی (بر اساس نوع کاربر):

def get_serializer_class(self):
    if self.request.user.is_staff:
        return AdminArticleSerializer  # شامل فیلدهای حساس مثل وضعیت تایید
    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ها روی همان داده‌ها کار می‌کنند.