پرش به محتویات

۹. Viewها و API

پنل ادمین بدون کد از قانون‌ها می‌پرسد. viewهای Django و DRF از طریق چند کلاس می‌پرسند، و serializer، فرم و قالب چیزی درباره‌ی دسترسی نمی‌دانند.

viewهای Django

"""The order pages, for a project on templates: the same rules as the API."""

from django.views.generic import ListView, UpdateView

from clearance.views import PolicyRequiredMixin, ProtectedFieldsViewMixin, ScopedQuerySetMixin
from shop.models import Order
from shop.policies import order_access


class OrderList(ProtectedFieldsViewMixin, PolicyRequiredMixin, ScopedQuerySetMixin, ListView):
    """Only the orders the person may view; the price only when they may read it."""

    policy = order_access.policy
    queryset = Order.objects.select_related('branch', 'customer')
    template_name = 'shop/orders.html'


class OrderEdit(ProtectedFieldsViewMixin, PolicyRequiredMixin, ScopedQuerySetMixin, UpdateView):
    """The form offers only the fields, and the status values, the person may write."""

    policy = order_access.policy
    queryset = Order.objects.all()
    fields = ('item', 'status', 'price')
    template_name = 'shop/order_form.html'
    success_url = '/orders/'
  • ScopedQuerySetMixin کوئری را به ردیف‌هایی که شخص به آن‌ها می‌رسد محدود می‌کند.
  • PolicyRequiredMixin از policy می‌پرسد. اکشن از خود view خوانده می‌شود - ListView یعنی list، UpdateView یعنی edit - یا با policy_action نام برده می‌شود.
  • ProtectedFieldsViewMixin فرم را برای شخص می‌سازد، و readable_fields را به قالب می‌دهد:
{% if 'price' in readable_fields %}<td>{{ order.price }}</td>{% endif %}

API

"""
The orders API. The serializer knows nothing of access: the viewset narrows
the rows, asks the policy, and leaves out the price for whoever may not read it.
"""

from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.response import Response

from clearance.drf.viewsets import ScopedModelViewSet
from shop.models import Order


class OrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = ('id', 'customer', 'branch', 'item', 'status', 'price')
        read_only_fields = ('customer',)


class OrderViewSet(ScopedModelViewSet):
    queryset = Order.objects.select_related('branch')
    serializer_class = OrderSerializer

    def perform_create(self, serializer):
        serializer.save(customer=self.request.user)

    @action(detail=True, methods=['post'])
    def print_invoice(self, request, pk=None):
        """Found in the code: shop.order_print_invoice is made by migrate."""
        order = self.get_object()
        return Response({'invoice': f'INV-{order.pk:05d}', 'item': order.item})

ScopedModelViewSet ردیف‌ها را محدود می‌کند، برای هر اکشن از policy مدل می‌پرسد، و فیلدهایی را که شخص اجازه‌ی خواندنشان را ندارد کنار می‌گذارد. نوشتنی که اسم فیلدی را بیاورد که نمی‌تواند بنویسد - یا مقدار وضعیتی که نمی‌تواند بگذارد - یک 400 است که اسم فیلد را می‌برد.

print_invoice اکشنی است که در کد پیدا می‌شود: migrate مجوز آن، shop.order_print_invoice، را می‌سازد، و فقط کسی که آن را دارد می‌تواند صدایش بزند.

viewای که هیچ رکوردی ندارد

وقتی ModelPolicyPermission پیش‌فرض باشد، viewای که نه مدل دارد و نه policy، موقع بالا آمدن پروژه رد می‌شود (E009) - از جمله صفحه‌ی اصلی router خود DRF. فروشگاه مجوزی را که لازم دارد به آن می‌دهد:

from django.urls import include, path
from rest_framework import routers
from rest_framework.permissions import IsAuthenticated

from shop import api, views


class ShopRouter(routers.DefaultRouter):
    class APIRootView(routers.APIRootView):
        """
        The list of endpoints holds no record, so it asks no policy - said here,
        because every view asks one unless it says otherwise (E009).
        """

        permission_classes = (IsAuthenticated,)


router = ShopRouter()
router.register('orders', api.OrderViewSet)

urlpatterns = [
    path('orders/', views.OrderList.as_view(), name='orders'),
    path('orders/<int:pk>/', views.OrderEdit.as_view(), name='order'),
    path('api/', include(router.urls)),
]

امتحان کنید

با alice وارد شوید و به /api/orders/ بفرستید:

{"branch": 1, "item": "Clock", "price": "1.00"}

جواب یک 400 است که اسم price را می‌برد: مشتری می‌تواند سفارش ثبت کند، و هیچ‌وقت قیمتش را تعیین نمی‌کند.