PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/braces/views.py

https://bitbucket.org/cld/django-braces
Python | 485 lines | 442 code | 13 blank | 30 comment | 13 complexity | 2a1ff930f39553079aeb1079549e4ff5 MD5 | raw file
  1. import warnings
  2. from django.conf import settings
  3. from django.contrib.auth import REDIRECT_FIELD_NAME
  4. from django.contrib.auth.views import redirect_to_login
  5. from django.core import serializers
  6. from django.core.exceptions import ImproperlyConfigured, PermissionDenied
  7. from django.core.serializers.json import DjangoJSONEncoder
  8. from django.core.urlresolvers import reverse
  9. from django.http import HttpResponse
  10. from django.utils.decorators import method_decorator
  11. from django.views.generic import CreateView
  12. from django.views.decorators.csrf import csrf_exempt
  13. ## Django 1.5+ compat
  14. try:
  15. import json
  16. except ImportError: # pragma: no cover
  17. from django.utils import simplejson as json
  18. class CreateAndRedirectToEditView(CreateView):
  19. """
  20. Subclass of CreateView which redirects to the edit view.
  21. Requires property `success_url_name` to be set to a
  22. reversible url that uses the objects pk.
  23. """
  24. success_url_name = None
  25. def dispatch(self, request, *args, **kwargs):
  26. warnings.warn("CreateAndRedirectToEditView is deprecated and will be "
  27. "removed in a future release.", PendingDeprecationWarning)
  28. return super(CreateAndRedirectToEditView, self).dispatch(request,
  29. *args, **kwargs)
  30. def get_success_url(self):
  31. # First we check for a name to be provided on the view object.
  32. # If one is, we reverse it and finish running the method,
  33. # otherwise we raise a configuration error.
  34. if self.success_url_name:
  35. self.success_url = reverse(self.success_url_name,
  36. kwargs={'pk': self.object.pk})
  37. return super(CreateAndRedirectToEditView, self).get_success_url()
  38. raise ImproperlyConfigured(
  39. "No URL to reverse. Provide a success_url_name.")
  40. class AccessMixin(object):
  41. """
  42. 'Abstract' mixin that gives access mixins the same customizable
  43. functionality.
  44. """
  45. login_url = settings.LOGIN_URL # LOGIN_URL from project settings
  46. raise_exception = False # Default whether to raise an exception to none
  47. redirect_field_name = REDIRECT_FIELD_NAME # Set by django.contrib.auth
  48. def get_login_url(self):
  49. """
  50. Override this method to customize the login_url.
  51. """
  52. if self.login_url is None:
  53. raise ImproperlyConfigured("%(cls)s is missing the login_url. "
  54. "Define %(cls)s.login_url or override "
  55. "%(cls)s.get_login_url()." % {"cls": self.__class__.__name__})
  56. return self.login_url
  57. def get_redirect_field_name(self):
  58. """
  59. Override this method to customize the redirect_field_name.
  60. """
  61. if self.redirect_field_name is None:
  62. raise ImproperlyConfigured("%(cls)s is missing the "
  63. "redirect_field_name. Define %(cls)s.redirect_field_name or "
  64. "override %(cls)s.get_redirect_field_name()." % {
  65. "cls": self.__class__.__name__})
  66. return self.redirect_field_name
  67. class LoginRequiredMixin(AccessMixin):
  68. """
  69. View mixin which verifies that the user is authenticated.
  70. NOTE:
  71. This should be the left-most mixin of a view.
  72. """
  73. def dispatch(self, request, *args, **kwargs):
  74. if not request.user.is_authenticated():
  75. if self.raise_exception:
  76. raise PermissionDenied # return a forbidden response
  77. else:
  78. return redirect_to_login(request.get_full_path(),
  79. self.get_login_url(), self.get_redirect_field_name())
  80. return super(LoginRequiredMixin, self).dispatch(request, *args,
  81. **kwargs)
  82. class CsrfExemptMixin(object):
  83. """
  84. Exempts the view from CSRF requirements.
  85. NOTE:
  86. This should be the left-most mixin of a view.
  87. """
  88. @method_decorator(csrf_exempt)
  89. def dispatch(self, *args, **kwargs):
  90. return super(CsrfExemptMixin, self).dispatch(*args, **kwargs)
  91. class PermissionRequiredMixin(AccessMixin):
  92. """
  93. View mixin which verifies that the logged in user has the specified
  94. permission.
  95. Class Settings
  96. `permission_required` - the permission to check for.
  97. `login_url` - the login url of site
  98. `redirect_field_name` - defaults to "next"
  99. `raise_exception` - defaults to False - raise 403 if set to True
  100. Example Usage
  101. class SomeView(PermissionRequiredMixin, ListView):
  102. ...
  103. # required
  104. permission_required = "app.permission"
  105. # optional
  106. login_url = "/signup/"
  107. redirect_field_name = "hollaback"
  108. raise_exception = True
  109. ...
  110. """
  111. permission_required = None # Default required perms to none
  112. def dispatch(self, request, *args, **kwargs):
  113. # Make sure that the permission_required attribute is set on the
  114. # view, or raise a configuration error.
  115. if self.permission_required is None:
  116. raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
  117. "'permission_required' attribute to be set.")
  118. # Check to see if the request's user has the required permission.
  119. has_permission = request.user.has_perm(self.permission_required)
  120. if not has_permission: # If the user lacks the permission
  121. if self.raise_exception: # *and* if an exception was desired
  122. raise PermissionDenied # return a forbidden response.
  123. else:
  124. return redirect_to_login(request.get_full_path(),
  125. self.get_login_url(),
  126. self.get_redirect_field_name())
  127. return super(PermissionRequiredMixin, self).dispatch(request,
  128. *args, **kwargs)
  129. class MultiplePermissionsRequiredMixin(AccessMixin):
  130. """
  131. View mixin which allows you to specify two types of permission
  132. requirements. The `permissions` attribute must be a dict which
  133. specifies two keys, `all` and `any`. You can use either one on
  134. its own or combine them. The value of each key is required to be a
  135. list or tuple of permissions. The standard Django permissions
  136. style is not strictly enforced. If you have created your own
  137. permissions in a different format, they should still work.
  138. By specifying the `all` key, the user must have all of
  139. the permissions in the passed in list.
  140. By specifying The `any` key , the user must have ONE of the set
  141. permissions in the list.
  142. Class Settings
  143. `permissions` - This is required to be a dict with one or both
  144. keys of `all` and/or `any` containing a list or tuple of
  145. permissions.
  146. `login_url` - the login url of site
  147. `redirect_field_name` - defaults to "next"
  148. `raise_exception` - defaults to False - raise 403 if set to True
  149. Example Usage
  150. class SomeView(MultiplePermissionsRequiredMixin, ListView):
  151. ...
  152. #required
  153. permissions = {
  154. "all": ("blog.add_post", "blog.change_post"),
  155. "any": ("blog.delete_post", "user.change_user")
  156. }
  157. #optional
  158. login_url = "/signup/"
  159. redirect_field_name = "hollaback"
  160. raise_exception = True
  161. """
  162. permissions = None # Default required perms to none
  163. def dispatch(self, request, *args, **kwargs):
  164. self._check_permissions_attr()
  165. perms_all = self.permissions.get('all') or None
  166. perms_any = self.permissions.get('any') or None
  167. self._check_permissions_keys_set(perms_all, perms_any)
  168. self._check_perms_keys("all", perms_all)
  169. self._check_perms_keys("any", perms_any)
  170. # If perms_all, check that user has all permissions in the list/tuple
  171. if perms_all:
  172. if not request.user.has_perms(perms_all):
  173. if self.raise_exception:
  174. raise PermissionDenied
  175. return redirect_to_login(request.get_full_path(),
  176. self.get_login_url(),
  177. self.get_redirect_field_name())
  178. # If perms_any, check that user has at least one in the list/tuple
  179. if perms_any:
  180. has_one_perm = False
  181. for perm in perms_any:
  182. if request.user.has_perm(perm):
  183. has_one_perm = True
  184. break
  185. if not has_one_perm:
  186. if self.raise_exception:
  187. raise PermissionDenied
  188. return redirect_to_login(request.get_full_path(),
  189. self.get_login_url(),
  190. self.get_redirect_field_name())
  191. return super(MultiplePermissionsRequiredMixin, self).dispatch(request,
  192. *args, **kwargs)
  193. def _check_permissions_attr(self):
  194. """
  195. Check permissions attribute is set and that it is a dict.
  196. """
  197. if self.permissions is None or not isinstance(self.permissions, dict):
  198. raise ImproperlyConfigured("'PermissionsRequiredMixin' requires "
  199. "'permissions' attribute to be set to a dict.")
  200. def _check_permissions_keys_set(self, perms_all=None, perms_any=None):
  201. """
  202. Check to make sure the keys `any` or `all` are not both blank.
  203. If both are blank either an empty dict came in or the wrong keys
  204. came in. Both are invalid and should raise an exception.
  205. """
  206. if perms_all is None and perms_any is None:
  207. raise ImproperlyConfigured("'PermissionsRequiredMixin' requires"
  208. "'permissions' attribute to be set to a dict and the 'any' "
  209. "or 'all' key to be set.")
  210. def _check_perms_keys(self, key=None, perms=None):
  211. """
  212. If the permissions list/tuple passed in is set, check to make
  213. sure that it is of the type list or tuple.
  214. """
  215. if perms and not isinstance(perms, (list, tuple)):
  216. raise ImproperlyConfigured("'MultiplePermissionsRequiredMixin' "
  217. "requires permissions dict '%s' value to be a list "
  218. "or tuple." % key)
  219. class UserFormKwargsMixin(object):
  220. """
  221. CBV mixin which puts the user from the request into the form kwargs.
  222. Note: Using this mixin requires you to pop the `user` kwarg
  223. out of the dict in the super of your form's `__init__`.
  224. """
  225. def get_form_kwargs(self):
  226. kwargs = super(UserFormKwargsMixin, self).get_form_kwargs()
  227. # Update the existing form kwargs dict with the request's user.
  228. kwargs.update({"user": self.request.user})
  229. return kwargs
  230. class SuccessURLRedirectListMixin(object):
  231. """
  232. Simple CBV mixin which sets the success url to the list view of
  233. a given app. Set success_list_url as a class attribute of your
  234. CBV and don't worry about overloading the get_success_url.
  235. This is only to be used for redirecting to a list page. If you need
  236. to reverse the url with kwargs, this is not the mixin to use.
  237. """
  238. success_list_url = None # Default the success url to none
  239. def get_success_url(self):
  240. # Return the reversed success url.
  241. if self.success_list_url is None:
  242. raise ImproperlyConfigured("%(cls)s is missing a succes_list_url "
  243. "name to reverse and redirect to. Define "
  244. "%(cls)s.success_list_url or override "
  245. "%(cls)s.get_success_url()"
  246. "." % {"cls": self.__class__.__name__})
  247. return reverse(self.success_list_url)
  248. class SuperuserRequiredMixin(AccessMixin):
  249. """
  250. Mixin allows you to require a user with `is_superuser` set to True.
  251. """
  252. def dispatch(self, request, *args, **kwargs):
  253. if not request.user.is_superuser: # If the user is a standard user,
  254. if self.raise_exception: # *and* if an exception was desired
  255. raise PermissionDenied # return a forbidden response.
  256. else:
  257. return redirect_to_login(request.get_full_path(),
  258. self.get_login_url(),
  259. self.get_redirect_field_name())
  260. return super(SuperuserRequiredMixin, self).dispatch(request,
  261. *args, **kwargs)
  262. class SetHeadlineMixin(object):
  263. """
  264. Mixin allows you to set a static headline through a static property on the
  265. class or programmatically by overloading the get_headline method.
  266. """
  267. headline = None # Default the headline to none
  268. def get_context_data(self, **kwargs):
  269. kwargs = super(SetHeadlineMixin, self).get_context_data(**kwargs)
  270. # Update the existing context dict with the provided headline.
  271. kwargs.update({"headline": self.get_headline()})
  272. return kwargs
  273. def get_headline(self):
  274. if self.headline is None: # If no headline was provided as a view
  275. # attribute and this method wasn't
  276. # overridden raise a configuration error.
  277. raise ImproperlyConfigured("%(cls)s is missing a headline. "
  278. "Define %(cls)s.headline, or override "
  279. "%(cls)s.get_headline()." % {"cls": self.__class__.__name__
  280. })
  281. return self.headline
  282. class SelectRelatedMixin(object):
  283. """
  284. Mixin allows you to provide a tuple or list of related models to
  285. perform a select_related on.
  286. """
  287. select_related = None # Default related fields to none
  288. def get_queryset(self):
  289. if self.select_related is None: # If no fields were provided,
  290. # raise a configuration error
  291. raise ImproperlyConfigured("%(cls)s is missing the "
  292. "select_related property. This must be a tuple or list." % {
  293. "cls": self.__class__.__name__})
  294. if not isinstance(self.select_related, (tuple, list)):
  295. # If the select_related argument is *not* a tuple or list,
  296. # raise a configuration error.
  297. raise ImproperlyConfigured("%(cls)s's select_related property "
  298. "must be a tuple or list." % {"cls": self.__class__.__name__})
  299. # Get the current queryset of the view
  300. queryset = super(SelectRelatedMixin, self).get_queryset()
  301. return queryset.select_related(*self.select_related)
  302. class PrefetchRelatedMixin(object):
  303. """
  304. Mixin allows you to provide a tuple or list of related models to
  305. perform a prefetch_related on.
  306. """
  307. prefetch_related = None # Default prefetch fields to none
  308. def get_queryset(self):
  309. if self.prefetch_related is None: # If no fields were provided,
  310. # raise a configuration error
  311. raise ImproperlyConfigured("%(cls)s is missing the "
  312. "prefetch_related property. This must be a tuple or list." % {
  313. "cls": self.__class__.__name__})
  314. if not isinstance(self.prefetch_related, (tuple, list)):
  315. # If the select_related argument is *not* a tuple or list,
  316. # raise a configuration error.
  317. raise ImproperlyConfigured("%(cls)s's prefetch_related property "
  318. "must be a tuple or list." % {"cls": self.__class__.__name__})
  319. # Get the current queryset of the view
  320. queryset = super(PrefetchRelatedMixin, self).get_queryset()
  321. return queryset.prefetch_related(*self.prefetch_related)
  322. class StaffuserRequiredMixin(AccessMixin):
  323. """
  324. Mixin allows you to require a user with `is_staff` set to True.
  325. """
  326. def dispatch(self, request, *args, **kwargs):
  327. if not request.user.is_staff: # If the request's user is not staff,
  328. if self.raise_exception: # *and* if an exception was desired
  329. raise PermissionDenied # return a forbidden response
  330. else:
  331. return redirect_to_login(request.get_full_path(),
  332. self.get_login_url(),
  333. self.get_redirect_field_name())
  334. return super(StaffuserRequiredMixin, self).dispatch(request,
  335. *args, **kwargs)
  336. class JSONResponseMixin(object):
  337. """
  338. A mixin that allows you to easily serialize simple data such as a dict or
  339. Django models.
  340. """
  341. content_type = "application/json"
  342. json_dumps_kwargs = None
  343. def get_content_type(self):
  344. if self.content_type is None:
  345. raise ImproperlyConfigured("%(cls)s is missing a content type. "
  346. "Define %(cls)s.content_type, or override "
  347. "%(cls)s.get_content_type()." % {
  348. "cls": self.__class__.__name__
  349. })
  350. return self.content_type
  351. def get_json_dumps_kwargs(self):
  352. if self.json_dumps_kwargs is None:
  353. self.json_dumps_kwargs = {}
  354. self.json_dumps_kwargs.setdefault('ensure_ascii', False)
  355. return self.json_dumps_kwargs
  356. def render_json_response(self, context_dict):
  357. """
  358. Limited serialization for shipping plain data. Do not use for models
  359. or other complex or custom objects.
  360. """
  361. json_context = json.dumps(context_dict, cls=DjangoJSONEncoder,
  362. **self.get_json_dumps_kwargs())
  363. return HttpResponse(json_context, content_type=self.get_content_type())
  364. def render_json_object_response(self, objects, **kwargs):
  365. """
  366. Serializes objects using Django's builtin JSON serializer. Additional
  367. kwargs can be used the same way for django.core.serializers.serialize.
  368. """
  369. json_data = serializers.serialize("json", objects, **kwargs)
  370. return HttpResponse(json_data, content_type=self.get_content_type())
  371. class AjaxResponseMixin(object):
  372. """
  373. Mixin allows you to define alternative methods for ajax requests. Similar
  374. to the normal get, post, and put methods, you can use get_ajax, post_ajax,
  375. and put_ajax.
  376. """
  377. def dispatch(self, request, *args, **kwargs):
  378. request_method = request.method.lower()
  379. if request.is_ajax() and request_method in self.http_method_names:
  380. handler = getattr(self, '%s_ajax' % request_method,
  381. self.http_method_not_allowed)
  382. self.request = request
  383. self.args = args
  384. self.kwargs = kwargs
  385. return handler(request, *args, **kwargs)
  386. return super(AjaxResponseMixin, self).dispatch(request, *args,
  387. **kwargs)
  388. def get_ajax(self, request, *args, **kwargs):
  389. return self.get(request, *args, **kwargs)
  390. def post_ajax(self, request, *args, **kwargs):
  391. return self.post(request, *args, **kwargs)
  392. def put_ajax(self, request, *args, **kwargs):
  393. return self.get(request, *args, **kwargs)
  394. def delete_ajax(self, request, *args, **kwargs):
  395. return self.get(request, *args, **kwargs)