DRF의 클래스 기반 뷰는 강력합니다.

Django Rest Framework로 시작하는 경우 존재하는 다양한 유형의 보기에 약간 압도될 수 있습니다. 그러나 무엇을 언제 사용해야 하는지 이해하고 나면 추상화가 얼마나 시간을 절약하고 강력한지 알게 될 것입니다.

여기에서는 REST API를 빌드할 때 참조할 수 있는 GenericViews 및 ViewSets에 대한 몇 가지 팁을 편집했습니다.

목차


  • Prerequisite
  • Introduction

  • Using GenericViews
  • get_object(self)
  • get_queryset(self)
  • perform_create(self, serializer)


  • The Power of ViewSets
  • Different Serializers for Read and Write
  • Different Permissions for Different Actions
  • ReadOnlyModelViewSet

  • Conclusion

  • 전제 조건

    This article assumes that you have a basic understanding on Django and Django Rest Framework (DRF). A decent understanding of serializers and views is also needed.

    소개

    You might be asking yourself, why should I even use class-based views when I can have more control of what is happening in function-based views? This section will answer that and highlight class-based views.

    A view is a place where you can put the logic of your application. In general, we have 2 types of views in Django:

    • Function-based views
    • Class-based views

    The major problem with function-based views is the lack of an easy way to extend them. You may see yourself using the same kind of code over and over again throughout your project which is not a good design principle.

    In addition, function-based views use conditional branching inside a single view function to handle different HTTP methods which might make your code unreadable.

    Class-based views aren’t a replacement for function-based views. However, they provide you with a way of writing views in an object-oriented fashion. This means that they can be really powerful and highly extensible by using concepts from OOP such as inheritance and Mixin (multiple inheritance).

    Anyways, we have the following class-based views in DRF:

    • APIView
    • GenericView
    • ViewSets

    APIView is similar to Django’s View class (It is actually an extension of it). You may have been using this approach to dispatch your requests to an appropriate handler like get() or post() .

    That being said, let's get started.

    GenericView 사용

    Can you think of some tasks that you repeat very often while working on a project? Tasks like form handling, list view, pagination, and many other common tasks might make your development experience boring because you repeat the same pattern over and over again. GenericViews come to the rescue by taking certain common patterns and abstracting them so that you can quickly write common views of data and save yourself time for a cup of tea🍵

    Here are some of the methods you might often want to override when using GenericViews.

    get_object(자신)

    Assume you want to have a view that will handle a user’s request to retrieve and update their profile.

    class ProfileAPIView(RetrieveUpdateAPIView):
        """
        Get, Update user profile
        """
        queryset = Profile.objects.all()
        serializer_class = ProfileSerializer
        permission_classes = (IsUserProfileOwner,)
    
    • Simple right? you override the queryset attribute to determine the object you want the view to return, as well as your serializer class and permission class. Then, you define the path in urls.py file like this.
    path('profile/<int:pk>/', ProfileAPIView.as_view(), name='profile_detail'),
    
    • This is the kind of pattern you may have used to achieve the goal. However, another way of doing the same thing is by overriding the get_object(self) method to return a profile instance without having to provide a lookup field ( <int:pk> ) in your path.
    class ProfileAPIView(RetrieveUpdateAPIView):
        """
        Get, Update user profile
        """
        queryset = Profile.objects.all()
        serializer_class = ProfileSerializer
        permission_classes = (IsUserProfileOwner,)
    
        def get_object(self):
            return self.request.user.profile
    

    This way, you can modify urls.py file to remove <int:pk> from the path:

    path('profile/', ProfileAPIView.as_view(), name='profile_detail'),
    .
    

    get_queryset(자신)

    Want to return a queryset that is specific to the user making a request? You can do so using get_queryset(self) method

    class OrderListCreate(ListCreateAPIView):
        """
        List, Create orders of a user
        """
        queryset = Order.objects.all()
        permission_classes = (IsOrderByBuyerOrAdmin, )
    
        def get_queryset(self):
            res = super().get_queryset()
            user = self.request.user
            return res.filter(buyer=user)
    
    • The get_queryset(self) method filters the response to include a list of orders of the currently authenticated user.

    perform_create(자체, 직렬 변환기)

    Assume you have a Recipe class. When users want to create a recipe, you need to hide the author field in your serializer:

    author = serializers.PrimaryKeyRelatedField(read_only=True)
    

    and then in your views, you can automatically set author to the currently authenticated user by overriding the perform_create(self, serializer) method.

    class RecipeCreateAPIView(CreateAPIView):
        """
        Create: a recipe
        """
        queryset = Recipe.objects.all()
        serializer_class = RecipeSerializer
        permission_classes = (IsAuthenticated,)
    
        def perform_create(self, serializer):
            serializer.save(author=self.request.user)
    

    Similar to perform_create(self, serializer) , there are also perform_update(self, serializer) and perform_destroy(self, serializer) methods.

    ViewSet의 힘

    ViewSet is a type of class-based view that combines the logic for a set of related views into a single class. The 2 most common types of ViewSets that you are most likely to use are Modelviewset and ReadOnlyModelViewSet

    Say you want to perform CRUD operations on a user's order. Using ModelViewSet , doing so is as simple as:

    class OrderViewSet(ModelViewSet):
        """
        CRUD orders of a user
        """
        queryset = Order.objects.all()
        serializer_class = (SomeSerializer, )
            permission_classes = (SomePermission, )
    
    • The above class provides you with everything you need for CRUD operations on the Order model. In addition, one of the main advantages of using ModelViewSet , or any other type of ViewSet , is to have URL endpoints automatically defined for you through Routers
    # urls.py
    
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    
    from orders.views import OrderViewSet
    
    app_name = 'orders'
    
    router = DefaultRouter()
    router.register(r'', OrderViewSet)
    
    urlpatterns = [
        path('', include(router.urls)),
    ]
    
    • With just that, you have URL endpoints for list , create , retrieve , update , and destroy actions!

    읽기 및 쓰기를 위한 다양한 직렬 변환기

    Ever needed to separate your serializer for read and write operation? Perhaps because you have a lot of nested fields, but you only need a few of them for write operation? You can easily use a single view for both serializers by overriding the get_serializer_class(self) method.

    class ProductViewSet(ModelViewSet):
        """
        CRUD products
        """
        queryset = Product.objects.all()
    
        def get_serializer_class(self):
            if self.action in ('create', 'update', 'partial_update', 'destroy'):
                return ProductWriteSerializer
    
            return ProductReadSerializer
    

    다른 작업에 대한 다른 권한

    get_permissions(self) method helps you separate permissions for different actions inside the same view.

    def get_permissions(self):
        if self.action in ("create", ):
            self.permission_classes = (permissions.IsAuthenticated, )
        elif self.action in ('update', 'partial_update', 'destroy'):
            self.permission_classes = (IsSellerOrAdmin, )
        else:
            self.permission_classes = (permissions.AllowAny, )
    
        return super().get_permissions()
    

    Note: You can use methods like get_queryset(self) , perform_create(self, serializer) et cetera inside Vewsets as well.

    ReadOnlyModelViewSet

    If you plan to make your view read-only, you can use ReadOnlyModelViewSet

    class ProductCategoryViewSet(ReadOnlyModelViewSet):
        """
        List and Retrieve product categories
        """
        queryset = ProductCategory.objects.all()
        serializer_class = ProductCategoryReadSerializer
        permission_classes = (permissions.AllowAny, )
    

    결론

    In general, you can see that ViewSets have the highest level of abstraction and you can use them to avoid writing all the code for basic and repetitive stuff. They are a huge time-saver! However, if you need to have more control or do some custom work, using APIView or GenericAPIView makes sense.

    For instance, in the following code, I’m using APIView to create a Stripe checkout session. I think this is a good candidate for using APIView

    class StripeCheckoutSessionCreateAPIView(APIView):
        """
        Create and return checkout session ID for order payment of type 'Stripe'
        """
        permission_classes = (IsPaymentForOrderNotCompleted,
                              DoesOrderHaveAddress, )
    
        def post(self, request, *args, **kwargs):
            order = get_object_or_404(Order, id=self.kwargs.get('order_id'))
    
            order_items = []
    
            for order_item in order.order_items.all():
                product = order_item.product
                quantity = order_item.quantity
    
                data = {
                    'price_data': {
                        'currency': 'usd',
                        'unit_amount_decimal': product.price,
                        'product_data': {
                            'name': product.name,
                            'description': product.desc,
                            'images': [f'{settings.BACKEND_DOMAIN}{product.image.url}']
                        }
                    },
                    'quantity': quantity
                }
    
                order_items.append(data)
    
            checkout_session = stripe.checkout.Session.create(
                payment_method_types=['card'],
                line_items=order_items,
                metadata={
                    "order_id": order.id
                },
                mode='payment',
                success_url=settings.PAYMENT_SUCCESS_URL,
                cancel_url=settings.PAYMENT_CANCEL_URL
            )
    
            return Response({'sessionId': checkout_session['id']}, status=status.HTTP_201_CREATED)
    
    P.S. Check this out https://www.cdrf.co/ to get a reference of all the methods and attributes of any class-based view in DRF.

    스 니펫은 GitHub 의 내 프로젝트에서 가져옵니다. 당신은 그들을 확인할 수 있습니다.

    즐거운 코딩하세요! 🖤

    좋은 웹페이지 즐겨찾기