2013-04-25 15:47:34 +04:00
|
|
|
"""
|
|
|
|
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
|
2013-10-30 00:10:06 +04:00
|
|
|
register the viewset with a router and let the URL conf be determined
|
2013-04-25 15:47:34 +04:00
|
|
|
automatically.
|
|
|
|
|
|
|
|
router = DefaultRouter()
|
|
|
|
router.register(r'users', UserViewSet, 'user')
|
|
|
|
urlpatterns = router.urls
|
|
|
|
"""
|
2013-04-29 15:45:00 +04:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2013-04-04 23:00:44 +04:00
|
|
|
from functools import update_wrapper
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2013-04-04 23:00:44 +04:00
|
|
|
from django.utils.decorators import classonlymethod
|
2014-09-18 20:30:13 +04:00
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2015-06-25 23:55:51 +03:00
|
|
|
from rest_framework import generics, mixins, views
|
2017-12-04 13:55:49 +03:00
|
|
|
from rest_framework.reverse import reverse
|
2013-04-04 23:00:44 +04:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2013-04-26 17:59:21 +04:00
|
|
|
def as_view(cls, actions=None, **initkwargs):
|
2013-04-04 23:00:44 +04:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
2017-12-04 13:55:49 +03:00
|
|
|
# The suffix initkwarg is reserved for displaying the viewset type.
|
2013-04-26 17:59:21 +04:00
|
|
|
# eg. 'List' or 'Instance'.
|
|
|
|
cls.suffix = None
|
|
|
|
|
2017-12-04 13:55:49 +03:00
|
|
|
# Setting a basename allows a view to reverse its action urls. This
|
|
|
|
# value is provided by the router through the initkwargs.
|
|
|
|
cls.basename = None
|
|
|
|
|
2014-12-02 07:55:34 +03:00
|
|
|
# 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'})`")
|
|
|
|
|
2013-04-04 23:00:44 +04:00
|
|
|
# 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)
|
2013-05-05 19:48:00 +04:00
|
|
|
# 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
|
2013-04-04 23:00:44 +04:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2017-03-13 15:51:03 +03:00
|
|
|
if hasattr(self, 'get') and not hasattr(self, 'head'):
|
|
|
|
self.head = self.get
|
|
|
|
|
2017-06-22 16:22:17 +03:00
|
|
|
self.request = request
|
|
|
|
self.args = args
|
|
|
|
self.kwargs = kwargs
|
|
|
|
|
2013-04-04 23:00:44 +04:00
|
|
|
# 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=())
|
2013-04-05 00:42:26 +04:00
|
|
|
|
2013-04-26 17:59:21 +04:00
|
|
|
# We need to set these on the view function, so that breadcrumb
|
|
|
|
# generation can pick out these bits of information from a
|
|
|
|
# resolved URL.
|
2013-04-05 00:42:26 +04:00
|
|
|
view.cls = cls
|
2016-08-05 13:19:39 +03:00
|
|
|
view.initkwargs = initkwargs
|
2013-04-26 17:59:21 +04:00
|
|
|
view.suffix = initkwargs.get('suffix', None)
|
2016-07-04 18:38:17 +03:00
|
|
|
view.actions = actions
|
2014-09-18 20:30:13 +04:00
|
|
|
return csrf_exempt(view)
|
2013-04-04 23:00:44 +04:00
|
|
|
|
2014-12-05 02:29:28 +03:00
|
|
|
def initialize_request(self, request, *args, **kwargs):
|
2013-05-05 19:48:00 +04:00
|
|
|
"""
|
|
|
|
Set the `.action` attribute on the view,
|
|
|
|
depending on the request method.
|
|
|
|
"""
|
2014-12-05 02:29:28 +03:00
|
|
|
request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs)
|
2015-07-16 12:08:22 +03:00
|
|
|
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.
|
2016-08-08 11:32:22 +03:00
|
|
|
# Unlike the other explicitly defined actions, 'metadata' is implicit.
|
2015-07-16 12:08:22 +03:00
|
|
|
self.action = 'metadata'
|
|
|
|
else:
|
|
|
|
self.action = self.action_map.get(method)
|
2013-05-05 19:48:00 +04:00
|
|
|
return request
|
|
|
|
|
2017-12-04 13:55:49 +03:00
|
|
|
def reverse_action(self, url_name, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Reverse the action for the given `url_name`.
|
|
|
|
"""
|
|
|
|
url_name = '%s-%s' % (self.basename, url_name)
|
|
|
|
kwargs.setdefault('request', self.request)
|
|
|
|
|
|
|
|
return reverse(url_name, *args, **kwargs)
|
|
|
|
|
2013-04-04 23:00:44 +04:00
|
|
|
|
|
|
|
class ViewSet(ViewSetMixin, views.APIView):
|
2013-04-25 15:47:34 +04:00
|
|
|
"""
|
|
|
|
The base ViewSet class does not provide any actions by default.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-05-09 16:31:42 +04:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2013-04-25 15:47:34 +04:00
|
|
|
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
|
|
|
|
mixins.ListModelMixin,
|
2013-05-09 16:35:01 +04:00
|
|
|
GenericViewSet):
|
2013-04-25 15:47:34 +04:00
|
|
|
"""
|
|
|
|
A viewset that provides default `list()` and `retrieve()` actions.
|
|
|
|
"""
|
2013-04-04 23:00:44 +04:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ModelViewSet(mixins.CreateModelMixin,
|
2014-08-19 16:28:07 +04:00
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
mixins.UpdateModelMixin,
|
|
|
|
mixins.DestroyModelMixin,
|
|
|
|
mixins.ListModelMixin,
|
|
|
|
GenericViewSet):
|
2013-04-25 15:47:34 +04:00
|
|
|
"""
|
|
|
|
A viewset that provides default `create()`, `retrieve()`, `update()`,
|
|
|
|
`partial_update()`, `destroy()` and `list()` actions.
|
|
|
|
"""
|
2013-04-04 23:00:44 +04:00
|
|
|
pass
|