viewsets.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
  2. # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
  3. # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. # This code is partially taken from django-rest-framework:
  17. # Copyright (c) 2011-2014, Tom Christie
  18. from functools import update_wrapper
  19. from django.utils.decorators import classonlymethod
  20. from django.utils.translation import ugettext as _
  21. from . import views
  22. from . import mixins
  23. from . import generics
  24. class ViewSetMixin(object):
  25. """
  26. This is the magic.
  27. Overrides `.as_view()` so that it takes an `actions` keyword that performs
  28. the binding of HTTP methods to actions on the Resource.
  29. For example, to create a concrete view binding the 'GET' and 'POST' methods
  30. to the 'list' and 'create' actions...
  31. view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
  32. """
  33. @classonlymethod
  34. def as_view(cls, actions=None, **initkwargs):
  35. """
  36. Because of the way class based views create a closure around the
  37. instantiated view, we need to totally reimplement `.as_view`,
  38. and slightly modify the view function that is created and returned.
  39. """
  40. # The suffix initkwarg is reserved for identifing the viewset type
  41. # eg. 'List' or 'Instance'.
  42. cls.suffix = None
  43. # sanitize keyword arguments
  44. for key in initkwargs:
  45. if key in cls.http_method_names:
  46. raise TypeError("You tried to pass in the %s method name as a "
  47. "keyword argument to %s(). Don't do that."
  48. % (key, cls.__name__))
  49. if not hasattr(cls, key):
  50. raise TypeError("%s() received an invalid keyword %r"
  51. % (cls.__name__, key))
  52. def view(request, *args, **kwargs):
  53. self = cls(**initkwargs)
  54. # We also store the mapping of request methods to actions,
  55. # so that we can later set the action attribute.
  56. # eg. `self.action = 'list'` on an incoming GET request.
  57. self.action_map = actions
  58. # Bind methods to actions
  59. # This is the bit that's different to a standard view
  60. for method, action in actions.items():
  61. handler = getattr(self, action)
  62. setattr(self, method, handler)
  63. # Patch this in as it's otherwise only present from 1.5 onwards
  64. if hasattr(self, 'get') and not hasattr(self, 'head'):
  65. self.head = self.get
  66. # And continue as usual
  67. return self.dispatch(request, *args, **kwargs)
  68. # take name and docstring from class
  69. update_wrapper(view, cls, updated=())
  70. # and possible attributes set by decorators
  71. # like csrf_exempt from dispatch
  72. update_wrapper(view, cls.dispatch, assigned=())
  73. # We need to set these on the view function, so that breadcrumb
  74. # generation can pick out these bits of information from a
  75. # resolved URL.
  76. view.cls = cls
  77. view.suffix = initkwargs.get('suffix', None)
  78. return view
  79. def initialize_request(self, request, *args, **kargs):
  80. """
  81. Set the `.action` attribute on the view,
  82. depending on the request method.
  83. """
  84. request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs)
  85. self.action = self.action_map.get(request.method.lower())
  86. return request
  87. def check_permissions(self, request, action:str=None, obj:object=None):
  88. if action is None:
  89. action = self.action
  90. return super().check_permissions(request, action=action, obj=obj)
  91. class ViewSet(ViewSetMixin, views.APIView):
  92. """
  93. The base ViewSet class does not provide any actions by default.
  94. """
  95. pass
  96. class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
  97. """
  98. The GenericViewSet class does not provide any actions by default,
  99. but does include the base set of generic view behavior, such as
  100. the `get_object` and `get_queryset` methods.
  101. """
  102. pass
  103. class ReadOnlyListViewSet(GenericViewSet):
  104. """
  105. A viewset that provides default `list()` action.
  106. """
  107. pass
  108. class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
  109. mixins.ListModelMixin,
  110. GenericViewSet):
  111. """
  112. A viewset that provides default `list()` and `retrieve()` actions.
  113. """
  114. pass
  115. class ModelViewSet(mixins.CreateModelMixin,
  116. mixins.RetrieveModelMixin,
  117. mixins.UpdateModelMixin,
  118. mixins.DestroyModelMixin,
  119. mixins.ListModelMixin,
  120. GenericViewSet):
  121. """
  122. A viewset that provides default `create()`, `retrieve()`, `update()`,
  123. `partial_update()`, `destroy()` and `list()` actions.
  124. """
  125. pass
  126. class ModelCrudViewSet(ModelViewSet):
  127. pass
  128. class ModelListViewSet(mixins.RetrieveModelMixin,
  129. mixins.ListModelMixin,
  130. GenericViewSet):
  131. pass
  132. class ModelUpdateRetrieveViewSet(mixins.UpdateModelMixin,
  133. mixins.RetrieveModelMixin,
  134. GenericViewSet):
  135. pass