فصل ۷: API و امکانات حرفه‌ای — DRF، کش، Celery و امنیت

Django REST Framework: Serializer، ModelViewSet و Router

وقتی مشتری شما یک اپ موبایل است

اپ اندرویدی فروشندگان، داشبورد React یا نرم‌افزار حسابداری که باید سفارش‌ها را بخواند، HTML نمی‌خواهند؛ JSON می‌خواهند. Django REST Framework (DRF) استاندارد عملی ساخت API در جنگوست: سریال‌سازی، اعتبارسنجی، احراز هویت، مجوز، صفحه‌بندی و مستندات قابل‌مرور، همه آماده.

pip install djangorestframework
# settings.py
INSTALLED_APPS += ["rest_framework"]

Serializer: پل بین مدل و JSON

Serializer همان نقشی را در API دارد که Form در HTML: داده‌ی ورودی را اعتبارسنجی و به شیء تبدیل می‌کند و شیء را برای خروجی به dict تبدیل می‌کند.

# apps/api/serializers.py
from rest_framework import serializers
from apps.catalog.models import Carpet
from apps.orders.models import Order, OrderItem


class CarpetSerializer(serializers.ModelSerializer):
    price_toman = serializers.SerializerMethodField()
    density_label = serializers.CharField(source="get_density_display", read_only=True)

    class Meta:
        model = Carpet
        fields = ["id", "code", "name", "density", "density_label", "price", "price_toman", "stock"]
        read_only_fields = ["stock"]

    def get_price_toman(self, obj):
        return obj.price // 10

    def validate_price(self, value):
        if value % 10_000:
            raise serializers.ValidationError("قیمت باید مضرب ده هزار ریال باشد.")
        return value


class OrderItemSerializer(serializers.ModelSerializer):
    carpet = CarpetSerializer(read_only=True)

    class Meta:
        model = OrderItem
        fields = ["carpet", "quantity", "unit_price"]


class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True, read_only=True)
    customer_name = serializers.CharField(source="customer.full_name", read_only=True)

    class Meta:
        model = Order
        fields = ["id", "tracking_code", "status", "total", "customer_name", "items", "created_at"]

ViewSet و Router

# apps/api/views.py
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response


class CarpetViewSet(viewsets.ModelViewSet):
    queryset = Carpet.objects.filter(status="active").order_by("code")
    serializer_class = CarpetSerializer
    lookup_field = "code"

    @action(detail=True, methods=["get"])
    def stock(self, request, code=None):
        carpet = self.get_object()
        return Response({"code": carpet.code, "stock": carpet.stock})


class OrderViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = OrderSerializer

    def get_queryset(self):
        return (Order.objects.for_user(self.request.user)
                .select_related("customer").prefetch_related("items__carpet"))

# apps/api/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register("carpets", CarpetViewSet)
router.register("orders", OrderViewSet, basename="order")
urlpatterns = [path("v1/", include(router.urls))]

همین چند خط این endpointها را می‌سازد: GET/POST /api/v1/carpets/، GET/PUT/PATCH/DELETE /api/v1/carpets/{code}/، GET /api/v1/carpets/{code}/stock/ و فهرست و جزئیات سفارش‌های خود کاربر.

نکته‌هایی که کمتر کسی می‌داند

  • وقتی ViewSet به‌جای ویژگی queryset متد get_queryset دارد، basename در router الزامی است؛ وگرنه خطای «basename argument not specified» می‌گیرید.
  • ModelSerializer متد clean() مدل را صدا نمی‌زند؛ قواعد مدل را در validate() سریالایزر تکرار کنید یا در آن‌جا instance.full_clean() را صدا بزنید.
  • Serializerهای تودرتو N+1 می‌سازند؛ همیشه در get_queryset ViewSet، select_related و prefetch_related متناسب با فیلدهای سریالایزر بگذارید.
  • SerializerMethodField فقط‌خواندنی است؛ برای فیلد محاسبه‌شده‌ی قابل‌نوشتن از source یا بازنویسی to_internal_value استفاده کنید.
  • در سرور می‌توانید Browsable API را با حذف BrowsableAPIRenderer از DEFAULT_RENDERER_CLASSES خاموش کنید؛ هم سطح حمله کمتر می‌شود هم ساختار داخلی لو نمی‌رود.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.