mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 09:57:55 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			156 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
ViewSets are essentially just a type of class based view, that doesn't provide
 | 
						|
any method handlers, such as `get()`, `post()`, etc... but instead has actions,
 | 
						|
such as `list()`, `retrieve()`, `create()`, etc...
 | 
						|
 | 
						|
Actions are only bound to methods at the point of instantiating the views.
 | 
						|
 | 
						|
    user_list = UserViewSet.as_view({'get': 'list'})
 | 
						|
    user_detail = UserViewSet.as_view({'get': 'retrieve'})
 | 
						|
 | 
						|
Typically, rather than instantiate views from viewsets directly, you'll
 | 
						|
register the viewset with a router and let the URL conf be determined
 | 
						|
automatically.
 | 
						|
 | 
						|
    router = DefaultRouter()
 | 
						|
    router.register(r'users', UserViewSet, 'user')
 | 
						|
    urlpatterns = router.urls
 | 
						|
"""
 | 
						|
from __future__ import unicode_literals
 | 
						|
 | 
						|
from functools import update_wrapper
 | 
						|
 | 
						|
from django.utils.decorators import classonlymethod
 | 
						|
from django.views.decorators.csrf import csrf_exempt
 | 
						|
 | 
						|
from rest_framework import generics, mixins, views
 | 
						|
 | 
						|
 | 
						|
class ViewSetMixin(object):
 | 
						|
    """
 | 
						|
    This is the magic.
 | 
						|
 | 
						|
    Overrides `.as_view()` so that it takes an `actions` keyword that performs
 | 
						|
    the binding of HTTP methods to actions on the Resource.
 | 
						|
 | 
						|
    For example, to create a concrete view binding the 'GET' and 'POST' methods
 | 
						|
    to the 'list' and 'create' actions...
 | 
						|
 | 
						|
    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
 | 
						|
    """
 | 
						|
 | 
						|
    @classonlymethod
 | 
						|
    def as_view(cls, actions=None, **initkwargs):
 | 
						|
        """
 | 
						|
        Because of the way class based views create a closure around the
 | 
						|
        instantiated view, we need to totally reimplement `.as_view`,
 | 
						|
        and slightly modify the view function that is created and returned.
 | 
						|
        """
 | 
						|
        # The suffix initkwarg is reserved for identifying the viewset type
 | 
						|
        # eg. 'List' or 'Instance'.
 | 
						|
        cls.suffix = None
 | 
						|
 | 
						|
        # actions must not be empty
 | 
						|
        if not actions:
 | 
						|
            raise TypeError("The `actions` argument must be provided when "
 | 
						|
                            "calling `.as_view()` on a ViewSet. For example "
 | 
						|
                            "`.as_view({'get': 'list'})`")
 | 
						|
 | 
						|
        # sanitize keyword arguments
 | 
						|
        for key in initkwargs:
 | 
						|
            if key in cls.http_method_names:
 | 
						|
                raise TypeError("You tried to pass in the %s method name as a "
 | 
						|
                                "keyword argument to %s(). Don't do that."
 | 
						|
                                % (key, cls.__name__))
 | 
						|
            if not hasattr(cls, key):
 | 
						|
                raise TypeError("%s() received an invalid keyword %r" % (
 | 
						|
                    cls.__name__, key))
 | 
						|
 | 
						|
        def view(request, *args, **kwargs):
 | 
						|
            self = cls(**initkwargs)
 | 
						|
            # We also store the mapping of request methods to actions,
 | 
						|
            # so that we can later set the action attribute.
 | 
						|
            # eg. `self.action = 'list'` on an incoming GET request.
 | 
						|
            self.action_map = actions
 | 
						|
 | 
						|
            # Bind methods to actions
 | 
						|
            # This is the bit that's different to a standard view
 | 
						|
            for method, action in actions.items():
 | 
						|
                handler = getattr(self, action)
 | 
						|
                setattr(self, method, handler)
 | 
						|
 | 
						|
            # Patch this in as it's otherwise only present from 1.5 onwards
 | 
						|
            if hasattr(self, 'get') and not hasattr(self, 'head'):
 | 
						|
                self.head = self.get
 | 
						|
 | 
						|
            # And continue as usual
 | 
						|
            return self.dispatch(request, *args, **kwargs)
 | 
						|
 | 
						|
        # take name and docstring from class
 | 
						|
        update_wrapper(view, cls, updated=())
 | 
						|
 | 
						|
        # and possible attributes set by decorators
 | 
						|
        # like csrf_exempt from dispatch
 | 
						|
        update_wrapper(view, cls.dispatch, assigned=())
 | 
						|
 | 
						|
        # We need to set these on the view function, so that breadcrumb
 | 
						|
        # generation can pick out these bits of information from a
 | 
						|
        # resolved URL.
 | 
						|
        view.cls = cls
 | 
						|
        view.suffix = initkwargs.get('suffix', None)
 | 
						|
        return csrf_exempt(view)
 | 
						|
 | 
						|
    def initialize_request(self, request, *args, **kwargs):
 | 
						|
        """
 | 
						|
        Set the `.action` attribute on the view,
 | 
						|
        depending on the request method.
 | 
						|
        """
 | 
						|
        request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs)
 | 
						|
        method = request.method.lower()
 | 
						|
        if method == 'options':
 | 
						|
            # This is a special case as we always provide handling for the
 | 
						|
            # options method in the base `View` class.
 | 
						|
            # Unlike the other explicitly defined actions, 'metadata' is implict.
 | 
						|
            self.action = 'metadata'
 | 
						|
        else:
 | 
						|
            self.action = self.action_map.get(method)
 | 
						|
        return request
 | 
						|
 | 
						|
 | 
						|
class ViewSet(ViewSetMixin, views.APIView):
 | 
						|
    """
 | 
						|
    The base ViewSet class does not provide any actions by default.
 | 
						|
    """
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
 | 
						|
    """
 | 
						|
    The GenericViewSet class does not provide any actions by default,
 | 
						|
    but does include the base set of generic view behavior, such as
 | 
						|
    the `get_object` and `get_queryset` methods.
 | 
						|
    """
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
 | 
						|
                           mixins.ListModelMixin,
 | 
						|
                           GenericViewSet):
 | 
						|
    """
 | 
						|
    A viewset that provides default `list()` and `retrieve()` actions.
 | 
						|
    """
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class ModelViewSet(mixins.CreateModelMixin,
 | 
						|
                   mixins.RetrieveModelMixin,
 | 
						|
                   mixins.UpdateModelMixin,
 | 
						|
                   mixins.DestroyModelMixin,
 | 
						|
                   mixins.ListModelMixin,
 | 
						|
                   GenericViewSet):
 | 
						|
    """
 | 
						|
    A viewset that provides default `create()`, `retrieve()`, `update()`,
 | 
						|
    `partial_update()`, `destroy()` and `list()` actions.
 | 
						|
    """
 | 
						|
    pass
 |