pax_global_header00006660000000000000000000000064127233747610014526gustar00rootroot0000000000000052 comment=df5a70256cf7228af32a68a57b6ed9caf93c7db7 django-braces-1.9.0/000077500000000000000000000000001272337476100142345ustar00rootroot00000000000000django-braces-1.9.0/.coveragerc000066400000000000000000000002301272337476100163500ustar00rootroot00000000000000# django-braces coverage config file [run] branch = true [report] omit = *site-packages* *tests* *.tox* *conftest* show_missing = True django-braces-1.9.0/.gitignore000066400000000000000000000002011272337476100162150ustar00rootroot00000000000000.DS_Store .python-version ._* *.pyc *.egg-info /docs/_build/ /.coverage /.coverage.xml /htmlcov /.tox /.cache dist/ .idea build/ django-braces-1.9.0/.travis.yml000066400000000000000000000016311272337476100163460ustar00rootroot00000000000000language: python services: sqlite env: - DJANGO='django>=1.5,<1.6' - DJANGO='django>=1.6,<1.7' - DJANGO='django>=1.7,<1.8' - DJANGO='django>=1.8,<1.9' - DJANGO='django>=1.9,<1.10' python: - 3.5 - 3.4 - 3.3 - 3.2 - 2.7 install: - pip install $DJANGO - python setup.py install - pip install factory_boy mock pytest-django script: py.test tests/ matrix: exclude: - python: 3.2 env: DJANGO='django>=1.8,<1.9' - python: 3.2 env: DJANGO='django>=1.9,<1.10' - python: 3.3 env: DJANGO='django>=1.8,<1.9' - python: 3.3 env: DJANGO='django>=1.9,<1.10' - python: 3.4 env: DJANGO='django>=1.4,<1.5' - python: 3.5 env: DJANGO='django>=1.5,<1.6' - python: 3.5 env: DJANGO='django>=1.6,<1.7' - python: 3.5 env: DJANGO='django>=1.7,<1.8'django-braces-1.9.0/CONTRIBUTORS.txt000066400000000000000000000011431272337476100167310ustar00rootroot00000000000000==== Team ==== Project Leads ============= * Kenneth Love * Chris Jones Direct Contributors =================== * Daniel Greenfeld * Drew Tempelmeyer * Baptiste Mispelon * Derek Payton * Rafal Stozek * Ethan Soergel * Piotr Kilczuk * Rodney Folz * Markus Zapke-Gründemann * Kamil Gałuszka * Danilo Bargen * Jon Bolt * Kit Sunde * Ben Cardy * Rag Sagar.V * Lacey Williams Henschel * Gregory Shikhman * Mike Bryant Other Contributors ================== * The entire Python and Django communities for providing us the tools and desire we to build these things. django-braces-1.9.0/LICENSE000066400000000000000000000027761272337476100152550ustar00rootroot00000000000000Copyright (c) Brack3t and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Brack3t nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-braces-1.9.0/MANIFEST.in000066400000000000000000000001311272337476100157650ustar00rootroot00000000000000include README.md include LICENSE include CONTRIBUTORS.txt recursive-include braces *.py django-braces-1.9.0/README.md000066400000000000000000000033041272337476100155130ustar00rootroot00000000000000# django-braces Mixins for Django's class-based views. [![Latest Travis CI status](https://travis-ci.org/brack3t/django-braces.svg)](https://travis-ci.org/brack3t/django-braces) [![PyPI version](https://badge.fury.io/py/django-braces.png)](http://badge.fury.io/py/django-braces) ## Documentation [Read The Docs](http://django-braces.readthedocs.io/en/latest/index.html) ## Installation Install from PyPI with `pip`: `pip install django-braces` ## Building the Docs 1. Install docs requirements: `pip install -r requirements-docs.txt`. 2. `cd docs`. 3. `make html`. 4. Open `_build/index.html` in your browser. ## Contributing See our [contribution guide](https://django-braces.readthedocs.io/en/latest/contributing.html) Add yourself to `CONTRIBUTORS.txt` if you want. All development dependencies are available in `requirements.txt` file. To run the test suite, execute the following in your shell (Django install is required): `py.test tests/ --cov=braces --cov-report=html` Or test with `tox` if you have `tox` installed. ## Change Log [Changelog on Read The Docs](https://django-braces.readthedocs.io/en/latest/changelog.html) ## Use Django 1.4.x? `django-braces` 1.4.x will be the last version to officially support Django 1.4.x. Since Django 1.4.x is an LTS, we'll update `django-braces` 1.4.x as needed for bug fixes but it won't receive new functionality unless backporting is 100% painless. Our policy going forward is that `django-braces` officially supports the current version of Django and one version each direction (e.g. 1.6.x is current, so 1.5.x, 1.6.x, and 1.7.x are all supported). There won't be any restraints on using other versions of Django, though, but it will be a "buyer beware" situation. django-braces-1.9.0/braces/000077500000000000000000000000001272337476100154735ustar00rootroot00000000000000django-braces-1.9.0/braces/__init__.py000066400000000000000000000006531272337476100176100ustar00rootroot00000000000000""" django-braces mixins library ---------------------------- Several mixins for making Django's generic class-based views more useful. :copyright: (c) 2013 by Kenneth Love and Chris Jones :license: BSD 3-clause. See LICENSE for more details """ __title__ = 'braces' __version__ = '1.9.0' __author__ = 'Kenneth Love and Chris Jones' __license__ = 'BSD 3-clause' __copyright__ = 'Copyright 2013 Kenneth Love and Chris Jones' django-braces-1.9.0/braces/forms.py000066400000000000000000000011061272337476100171710ustar00rootroot00000000000000class UserKwargModelFormMixin(object): """ Generic model form mixin for popping user out of the kwargs and attaching it to the instance. This mixin must precede forms.ModelForm/forms.Form. The form is not expecting these kwargs to be passed in, so they must be popped off before anything else is done. """ def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) # Pop the user off the # passed in kwargs. super(UserKwargModelFormMixin, self).__init__(*args, **kwargs) django-braces-1.9.0/braces/views/000077500000000000000000000000001272337476100166305ustar00rootroot00000000000000django-braces-1.9.0/braces/views/__init__.py000066400000000000000000000032511272337476100207420ustar00rootroot00000000000000from __future__ import absolute_import from ._access import ( AnonymousRequiredMixin, GroupRequiredMixin, LoginRequiredMixin, MultiplePermissionsRequiredMixin, PermissionRequiredMixin, StaffuserRequiredMixin, SuperuserRequiredMixin, UserPassesTestMixin, SSLRequiredMixin, RecentLoginRequiredMixin ) from ._ajax import ( AjaxResponseMixin, JSONRequestResponseMixin, JSONResponseMixin, JsonRequestResponseMixin ) from ._forms import ( CsrfExemptMixin, FormInvalidMessageMixin, FormMessagesMixin, FormValidMessageMixin, MessageMixin, SuccessURLRedirectListMixin, UserFormKwargsMixin, _MessageAPIWrapper ) from ._other import ( AllVerbsMixin, CanonicalSlugDetailMixin, SetHeadlineMixin, StaticContextMixin ) from ._queries import ( OrderableListMixin, PrefetchRelatedMixin, SelectRelatedMixin ) __all__ = [ 'AjaxResponseMixin', 'AllVerbsMixin', 'AnonymousRequiredMixin', 'CanonicalSlugDetailMixin', 'CsrfExemptMixin', 'FormInvalidMessageMixin', 'FormMessagesMixin', 'FormValidMessageMixin', 'GroupRequiredMixin', 'JSONRequestResponseMixin', 'JsonRequestResponseMixin', 'JSONResponseMixin', 'LoginRequiredMixin', 'MessageMixin', 'MultiplePermissionsRequiredMixin', 'OrderableListMixin', 'PermissionRequiredMixin', 'PrefetchRelatedMixin', 'SelectRelatedMixin', 'SetHeadlineMixin', 'StaffuserRequiredMixin', 'StaticContextMixin', 'SuccessURLRedirectListMixin', 'SuperuserRequiredMixin', 'UserFormKwargsMixin', 'UserPassesTestMixin', 'SSLRequiredMixin', 'RecentLoginRequiredMixin' ] django-braces-1.9.0/braces/views/_access.py000066400000000000000000000367361272337476100206210ustar00rootroot00000000000000import inspect import datetime import re from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.views import redirect_to_login, logout_then_login from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.http import (HttpResponseRedirect, HttpResponsePermanentRedirect, Http404, HttpResponse) from django.shortcuts import resolve_url from django.utils import six from django.utils.encoding import force_text from django.utils.timezone import now # StreamingHttpResponse has been added in 1.5, and gets used for verification # only. try: from django.http import StreamingHttpResponse except ImportError: class StreamingHttpResponse(object): pass class AccessMixin(object): """ 'Abstract' mixin that gives access mixins the same customizable functionality. """ login_url = None raise_exception = False redirect_field_name = REDIRECT_FIELD_NAME # Set by django.contrib.auth redirect_unauthenticated_users = False def get_login_url(self): """ Override this method to customize the login_url. """ login_url = self.login_url or settings.LOGIN_URL if not login_url: raise ImproperlyConfigured( 'Define {0}.login_url or settings.LOGIN_URL or override ' '{0}.get_login_url().'.format(self.__class__.__name__)) return force_text(login_url) def get_redirect_field_name(self): """ Override this method to customize the redirect_field_name. """ if self.redirect_field_name is None: raise ImproperlyConfigured( '{0} is missing the ' 'redirect_field_name. Define {0}.redirect_field_name or ' 'override {0}.get_redirect_field_name().'.format( self.__class__.__name__)) return self.redirect_field_name def handle_no_permission(self, request): if self.raise_exception: if (self.redirect_unauthenticated_users and not self.request.user.is_authenticated()): return self.no_permissions_fail(request) else: if (inspect.isclass(self.raise_exception) and issubclass(self.raise_exception, Exception)): raise self.raise_exception if callable(self.raise_exception): ret = self.raise_exception(request) if isinstance(ret, (HttpResponse, StreamingHttpResponse)): return ret raise PermissionDenied return self.no_permissions_fail(request) def no_permissions_fail(self, request=None): """ Called when the user has no permissions and no exception was raised. This should only return a valid HTTP response. By default we redirect to login. """ return redirect_to_login(request.get_full_path(), self.get_login_url(), self.get_redirect_field_name()) class LoginRequiredMixin(AccessMixin): """ View mixin which verifies that the user is authenticated. NOTE: This should be the left-most mixin of a view, except when combined with CsrfExemptMixin - which in that case should be the left-most mixin. """ def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated(): return self.handle_no_permission(request) return super(LoginRequiredMixin, self).dispatch( request, *args, **kwargs) class AnonymousRequiredMixin(object): """ View mixin which redirects to a specified URL if authenticated. Can be useful if you wanted to prevent authenticated users from accessing signup pages etc. NOTE: This should be the left-most mixin of a view. Example Usage class SomeView(AnonymousRequiredMixin, ListView): ... # required authenticated_redirect_url = "/accounts/profile/" ... """ authenticated_redirect_url = settings.LOGIN_REDIRECT_URL def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated(): return HttpResponseRedirect(self.get_authenticated_redirect_url()) return super(AnonymousRequiredMixin, self).dispatch( request, *args, **kwargs) def get_authenticated_redirect_url(self): """ Return the reversed authenticated redirect url. """ if not self.authenticated_redirect_url: raise ImproperlyConfigured( '{0} is missing an authenticated_redirect_url ' 'url to redirect to. Define ' '{0}.authenticated_redirect_url or override ' '{0}.get_authenticated_redirect_url().'.format( self.__class__.__name__)) return resolve_url(self.authenticated_redirect_url) class PermissionRequiredMixin(AccessMixin): """ View mixin which verifies that the logged in user has the specified permission. Class Settings `permission_required` - the permission to check for. `login_url` - the login url of site `redirect_field_name` - defaults to "next" `raise_exception` - defaults to False - raise 403 if set to True Example Usage class SomeView(PermissionRequiredMixin, ListView): ... # required permission_required = "app.permission" # optional login_url = "/signup/" redirect_field_name = "hollaback" raise_exception = True ... """ permission_required = None # Default required perms to none def get_permission_required(self, request=None): """ Get the required permissions and return them. Override this to allow for custom permission_required values. """ # Make sure that the permission_required attribute is set on the # view, or raise a configuration error. if self.permission_required is None: raise ImproperlyConfigured( '{0} requires the "permission_required" attribute to be ' 'set.'.format(self.__class__.__name__)) return self.permission_required def check_permissions(self, request): """ Returns whether or not the user has permissions """ perms = self.get_permission_required(request) return request.user.has_perm(perms) def dispatch(self, request, *args, **kwargs): """ Check to see if the user in the request has the required permission. """ has_permission = self.check_permissions(request) if not has_permission: return self.handle_no_permission(request) return super(PermissionRequiredMixin, self).dispatch( request, *args, **kwargs) class MultiplePermissionsRequiredMixin(PermissionRequiredMixin): """ View mixin which allows you to specify two types of permission requirements. The `permissions` attribute must be a dict which specifies two keys, `all` and `any`. You can use either one on its own or combine them. The value of each key is required to be a list or tuple of permissions. The standard Django permissions style is not strictly enforced. If you have created your own permissions in a different format, they should still work. By specifying the `all` key, the user must have all of the permissions in the passed in list. By specifying The `any` key , the user must have ONE of the set permissions in the list. Class Settings `permissions` - This is required to be a dict with one or both keys of `all` and/or `any` containing a list or tuple of permissions. `login_url` - the login url of site `redirect_field_name` - defaults to "next" `raise_exception` - defaults to False - raise 403 if set to True Example Usage class SomeView(MultiplePermissionsRequiredMixin, ListView): ... #required permissions = { "all": ("blog.add_post", "blog.change_post"), "any": ("blog.delete_post", "user.change_user") } #optional login_url = "/signup/" redirect_field_name = "hollaback" raise_exception = True """ permissions = None # Default required perms to none def get_permission_required(self, request=None): self._check_permissions_attr() return self.permissions def check_permissions(self, request): permissions = self.get_permission_required(request) perms_all = permissions.get('all') or None perms_any = permissions.get('any') or None self._check_permissions_keys_set(perms_all, perms_any) self._check_perms_keys("all", perms_all) self._check_perms_keys("any", perms_any) # If perms_all, check that user has all permissions in the list/tuple if perms_all: if not request.user.has_perms(perms_all): return False # If perms_any, check that user has at least one in the list/tuple if perms_any: has_one_perm = False for perm in perms_any: if request.user.has_perm(perm): has_one_perm = True break if not has_one_perm: return False return True def _check_permissions_attr(self): """ Check permissions attribute is set and that it is a dict. """ if self.permissions is None or not isinstance(self.permissions, dict): raise ImproperlyConfigured( '{0} requires the "permissions" attribute to be set as a ' 'dict.'.format(self.__class__.__name__)) def _check_permissions_keys_set(self, perms_all=None, perms_any=None): """ Check to make sure the keys `any` or `all` are not both blank. If both are blank either an empty dict came in or the wrong keys came in. Both are invalid and should raise an exception. """ if perms_all is None and perms_any is None: raise ImproperlyConfigured( '{0} requires the "permissions" attribute to be set to a ' 'dict and the "any" or "all" key to be set.'.format( self.__class__.__name__)) def _check_perms_keys(self, key=None, perms=None): """ If the permissions list/tuple passed in is set, check to make sure that it is of the type list or tuple. """ if perms and not isinstance(perms, (list, tuple)): raise ImproperlyConfigured( '{0} requires the permisions dict {1} value to be a ' 'list or tuple.'.format(self.__class__.__name__, key)) class GroupRequiredMixin(AccessMixin): group_required = None def get_group_required(self): if self.group_required is None or ( not isinstance(self.group_required, (list, tuple) + six.string_types) ): raise ImproperlyConfigured( '{0} requires the "group_required" attribute to be set and be ' 'one of the following types: string, unicode, list or ' 'tuple'.format(self.__class__.__name__)) if not isinstance(self.group_required, (list, tuple)): self.group_required = (self.group_required,) return self.group_required def check_membership(self, groups): """ Check required group(s) """ if self.request.user.is_superuser: return True user_groups = self.request.user.groups.values_list("name", flat=True) return set(groups).intersection(set(user_groups)) def dispatch(self, request, *args, **kwargs): self.request = request in_group = False if self.request.user.is_authenticated(): in_group = self.check_membership(self.get_group_required()) if not in_group: return self.handle_no_permission(request) return super(GroupRequiredMixin, self).dispatch( request, *args, **kwargs) class UserPassesTestMixin(AccessMixin): """ CBV Mixin allows you to define test that every user should pass to get access into view. Class Settings `test_func` - This is required to be a method that takes user instance and return True or False after checking conditions. `login_url` - the login url of site `redirect_field_name` - defaults to "next" `raise_exception` - defaults to False - raise 403 if set to True """ def test_func(self, user): raise NotImplementedError( '{0} is missing implementation of the ' 'test_func method. You should write one.'.format( self.__class__.__name__)) def get_test_func(self): return getattr(self, "test_func") def dispatch(self, request, *args, **kwargs): user_test_result = self.get_test_func()(request.user) if not user_test_result: return self.handle_no_permission(request) return super(UserPassesTestMixin, self).dispatch( request, *args, **kwargs) class SuperuserRequiredMixin(AccessMixin): """ Mixin allows you to require a user with `is_superuser` set to True. """ def dispatch(self, request, *args, **kwargs): if not request.user.is_superuser: return self.handle_no_permission(request) return super(SuperuserRequiredMixin, self).dispatch( request, *args, **kwargs) class StaffuserRequiredMixin(AccessMixin): """ Mixin allows you to require a user with `is_staff` set to True. """ def dispatch(self, request, *args, **kwargs): if not request.user.is_staff: return self.handle_no_permission(request) return super(StaffuserRequiredMixin, self).dispatch( request, *args, **kwargs) class SSLRequiredMixin(object): """ Simple mixin that allows you to force a view to be accessed via https. """ raise_exception = False # Default whether to raise an exception to none def dispatch(self, request, *args, **kwargs): if getattr(settings, 'DEBUG', False): return super(SSLRequiredMixin, self).dispatch( request, *args, **kwargs) if not request.is_secure(): if self.raise_exception: raise Http404 return HttpResponsePermanentRedirect( self._build_https_url(request)) return super(SSLRequiredMixin, self).dispatch(request, *args, **kwargs) def _build_https_url(self, request): """ Get the full url, replace http with https """ url = request.build_absolute_uri(request.get_full_path()) return re.sub(r'^http', 'https', url) class RecentLoginRequiredMixin(LoginRequiredMixin): """ Mixin allows you to require a login to be within a number of seconds. """ max_last_login_delta = 1800 # Defaults to 30 minutes def dispatch(self, request, *args, **kwargs): resp = super(RecentLoginRequiredMixin, self).dispatch( request, *args, **kwargs) if resp.status_code == 200: delta = datetime.timedelta(seconds=self.max_last_login_delta) if now() > (request.user.last_login + delta): return logout_then_login(request, self.get_login_url()) else: return resp return resp django-braces-1.9.0/braces/views/_ajax.py000066400000000000000000000124431272337476100202700ustar00rootroot00000000000000from __future__ import unicode_literals from django.core import serializers from django.core.exceptions import ImproperlyConfigured from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse, HttpResponseBadRequest from django.utils import six ## Django 1.5+ compat try: import json except ImportError: # pragma: no cover from django.utils import simplejson as json class JSONResponseMixin(object): """ A mixin that allows you to easily serialize simple data such as a dict or Django models. """ content_type = None json_dumps_kwargs = None json_encoder_class = DjangoJSONEncoder def get_content_type(self): if (self.content_type is not None and not isinstance(self.content_type, (six.string_types, six.text_type))): raise ImproperlyConfigured( '{0} is missing a content type. Define {0}.content_type, ' 'or override {0}.get_content_type().'.format( self.__class__.__name__)) return self.content_type or "application/json" def get_json_dumps_kwargs(self): if self.json_dumps_kwargs is None: self.json_dumps_kwargs = {} self.json_dumps_kwargs.setdefault('ensure_ascii', False) return self.json_dumps_kwargs def render_json_response(self, context_dict, status=200): """ Limited serialization for shipping plain data. Do not use for models or other complex or custom objects. """ json_context = json.dumps( context_dict, cls=self.json_encoder_class, **self.get_json_dumps_kwargs()).encode('utf-8') return HttpResponse(json_context, content_type=self.get_content_type(), status=status) def render_json_object_response(self, objects, **kwargs): """ Serializes objects using Django's builtin JSON serializer. Additional kwargs can be used the same way for django.core.serializers.serialize. """ json_data = serializers.serialize("json", objects, **kwargs) return HttpResponse(json_data, content_type=self.get_content_type()) class AjaxResponseMixin(object): """ Mixin allows you to define alternative methods for ajax requests. Similar to the normal get, post, and put methods, you can use get_ajax, post_ajax, and put_ajax. """ def dispatch(self, request, *args, **kwargs): request_method = request.method.lower() if request.is_ajax() and request_method in self.http_method_names: handler = getattr(self, "{0}_ajax".format(request_method), self.http_method_not_allowed) self.request = request self.args = args self.kwargs = kwargs return handler(request, *args, **kwargs) return super(AjaxResponseMixin, self).dispatch( request, *args, **kwargs) def get_ajax(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def post_ajax(self, request, *args, **kwargs): return self.post(request, *args, **kwargs) def put_ajax(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def delete_ajax(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) class JsonRequestResponseMixin(JSONResponseMixin): """ Extends JSONResponseMixin. Attempts to parse request as JSON. If request is properly formatted, the json is saved to self.request_json as a Python object. request_json will be None for imparsible requests. Set the attribute require_json to True to return a 400 "Bad Request" error for requests that don't contain JSON. Note: To allow public access to your view, you'll need to use the csrf_exempt decorator or CsrfExemptMixin. Example Usage: class SomeView(CsrfExemptMixin, JsonRequestResponseMixin): def post(self, request, *args, **kwargs): do_stuff_with_contents_of_request_json() return self.render_json_response( {'message': 'Thanks!'}) """ require_json = False error_response_dict = {"errors": ["Improperly formatted request"]} def render_bad_request_response(self, error_dict=None): if error_dict is None: error_dict = self.error_response_dict json_context = json.dumps( error_dict, cls=self.json_encoder_class, **self.get_json_dumps_kwargs() ).encode('utf-8') return HttpResponseBadRequest( json_context, content_type=self.get_content_type()) def get_request_json(self): try: return json.loads(self.request.body.decode('utf-8')) except ValueError: return None def dispatch(self, request, *args, **kwargs): self.request = request self.args = args self.kwargs = kwargs self.request_json = self.get_request_json() if self.require_json and self.request_json is None: return self.render_bad_request_response() return super(JsonRequestResponseMixin, self).dispatch( request, *args, **kwargs) class JSONRequestResponseMixin(JsonRequestResponseMixin): pass django-braces-1.9.0/braces/views/_forms.py000066400000000000000000000143351272337476100204750ustar00rootroot00000000000000from django.contrib import messages from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse from django.utils import six from django.utils.decorators import method_decorator from django.utils.encoding import force_text from django.utils.functional import curry, Promise from django.views.decorators.csrf import csrf_exempt class CsrfExemptMixin(object): """ Exempts the view from CSRF requirements. NOTE: This should be the left-most mixin of a view. """ @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs): return super(CsrfExemptMixin, self).dispatch(*args, **kwargs) class UserFormKwargsMixin(object): """ CBV mixin which puts the user from the request into the form kwargs. Note: Using this mixin requires you to pop the `user` kwarg out of the dict in the super of your form's `__init__`. """ def get_form_kwargs(self): kwargs = super(UserFormKwargsMixin, self).get_form_kwargs() # Update the existing form kwargs dict with the request's user. kwargs.update({"user": self.request.user}) return kwargs class SuccessURLRedirectListMixin(object): """ Simple CBV mixin which sets the success url to the list view of a given app. Set success_list_url as a class attribute of your CBV and don't worry about overloading the get_success_url. This is only to be used for redirecting to a list page. If you need to reverse the url with kwargs, this is not the mixin to use. """ success_list_url = None # Default the success url to none def get_success_url(self): # Return the reversed success url. if self.success_list_url is None: raise ImproperlyConfigured( '{0} is missing a success_list_url ' 'name to reverse and redirect to. Define ' '{0}.success_list_url or override ' '{0}.get_success_url().'.format(self.__class__.__name__)) return reverse(self.success_list_url) class _MessageAPIWrapper(object): """ Wrap the django.contrib.messages.api module to automatically pass a given request object as the first parameter of function calls. """ API = set([ 'add_message', 'get_messages', 'get_level', 'set_level', 'debug', 'info', 'success', 'warning', 'error', ]) def __init__(self, request): for name in self.API: api_fn = getattr(messages.api, name) setattr(self, name, curry(api_fn, request)) class _MessageDescriptor(object): """ A descriptor that binds the _MessageAPIWrapper to the view's request. """ def __get__(self, instance, owner): return _MessageAPIWrapper(instance.request) class MessageMixin(object): """ Add a `messages` attribute on the view instance that wraps `django.contrib .messages`, automatically passing the current request object. """ messages = _MessageDescriptor() class FormValidMessageMixin(MessageMixin): """ Mixin allows you to set static message which is displayed by Django's messages framework through a static property on the class or programmatically by overloading the get_form_valid_message method. """ form_valid_message = None # Default to None def get_form_valid_message(self): """ Validate that form_valid_message is set and is either a unicode or str object. """ if self.form_valid_message is None: raise ImproperlyConfigured( '{0}.form_valid_message is not set. Define ' '{0}.form_valid_message, or override ' '{0}.get_form_valid_message().'.format(self.__class__.__name__) ) if not isinstance(self.form_valid_message, (six.string_types, six.text_type, Promise)): raise ImproperlyConfigured( '{0}.form_valid_message must be a str or unicode ' 'object.'.format(self.__class__.__name__) ) return force_text(self.form_valid_message) def form_valid(self, form): """ Call the super first, so that when overriding get_form_valid_message, we have access to the newly saved object. """ response = super(FormValidMessageMixin, self).form_valid(form) self.messages.success(self.get_form_valid_message(), fail_silently=True) return response def delete(self, *args, **kwargs): response = super(FormValidMessageMixin, self).delete(*args, **kwargs) self.messages.success(self.get_form_valid_message(), fail_silently=True) return response class FormInvalidMessageMixin(MessageMixin): """ Mixin allows you to set static message which is displayed by Django's messages framework through a static property on the class or programmatically by overloading the get_form_invalid_message method. """ form_invalid_message = None def get_form_invalid_message(self): """ Validate that form_invalid_message is set and is either a unicode or str object. """ if self.form_invalid_message is None: raise ImproperlyConfigured( '{0}.form_invalid_message is not set. Define ' '{0}.form_invalid_message, or override ' '{0}.get_form_invalid_message().'.format( self.__class__.__name__)) if not isinstance(self.form_invalid_message, (six.string_types, six.text_type, Promise)): raise ImproperlyConfigured( '{0}.form_invalid_message must be a str or unicode ' 'object.'.format(self.__class__.__name__)) return force_text(self.form_invalid_message) def form_invalid(self, form): response = super(FormInvalidMessageMixin, self).form_invalid(form) self.messages.error(self.get_form_invalid_message(), fail_silently=True) return response class FormMessagesMixin(FormValidMessageMixin, FormInvalidMessageMixin): """ Mixin is a shortcut to use both FormValidMessageMixin and FormInvalidMessageMixin. """ pass django-braces-1.9.0/braces/views/_other.py000066400000000000000000000111501272337476100204600ustar00rootroot00000000000000from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import resolve from django.shortcuts import redirect from django.utils.encoding import force_text class SetHeadlineMixin(object): """ Mixin allows you to set a static headline through a static property on the class or programmatically by overloading the get_headline method. """ headline = None # Default the headline to none def get_context_data(self, **kwargs): kwargs = super(SetHeadlineMixin, self).get_context_data(**kwargs) # Update the existing context dict with the provided headline. kwargs.update({"headline": self.get_headline()}) return kwargs def get_headline(self): if self.headline is None: # If no headline was provided as a view # attribute and this method wasn't # overridden raise a configuration error. raise ImproperlyConfigured( '{0} is missing a headline. ' 'Define {0}.headline, or override ' '{0}.get_headline().'.format(self.__class__.__name__)) return force_text(self.headline) class StaticContextMixin(object): """ Mixin allows you to set static context through a static property on the class. """ static_context = None def get_context_data(self, **kwargs): kwargs = super(StaticContextMixin, self).get_context_data(**kwargs) try: kwargs.update(self.get_static_context()) except (TypeError, ValueError): raise ImproperlyConfigured( '{0}.static_context must be a dictionary or container ' 'of two-tuples.'.format(self.__class__.__name__)) else: return kwargs def get_static_context(self): if self.static_context is None: raise ImproperlyConfigured( '{0} is missing the static_context property. Define ' '{0}.static_context, or override ' '{0}.get_static_context()'.format(self.__class__.__name__) ) return self.static_context class CanonicalSlugDetailMixin(object): """ A mixin that enforces a canonical slug in the url. If a urlpattern takes a object's pk and slug as arguments and the slug url argument does not equal the object's canonical slug, this mixin will redirect to the url containing the canonical slug. """ def dispatch(self, request, *args, **kwargs): # Set up since we need to super() later instead of earlier. self.request = request self.args = args self.kwargs = kwargs # Get the current object, url slug, and # urlpattern name (namespace aware). obj = self.get_object() slug = self.kwargs.get(self.slug_url_kwarg, None) match = resolve(request.path_info) url_parts = match.namespaces url_parts.append(match.url_name) current_urlpattern = ":".join(url_parts) # Figure out what the slug is supposed to be. if hasattr(obj, "get_canonical_slug"): canonical_slug = obj.get_canonical_slug() else: canonical_slug = self.get_canonical_slug() # If there's a discrepancy between the slug in the url and the # canonical slug, redirect to the canonical slug. if canonical_slug != slug: params = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: canonical_slug, 'permanent': True} return redirect(current_urlpattern, **params) return super(CanonicalSlugDetailMixin, self).dispatch( request, *args, **kwargs) def get_canonical_slug(self): """ Override this method to customize what slug should be considered canonical. Alternatively, define the get_canonical_slug method on this view's object class. In that case, this method will never be called. """ return self.get_object().slug class AllVerbsMixin(object): """Call a single method for all HTTP verbs. The name of the method should be specified using the class attribute ``all_handler``. The default value of this attribute is 'all'. """ all_handler = 'all' def dispatch(self, request, *args, **kwargs): if not self.all_handler: raise ImproperlyConfigured( '{0} requires the all_handler attribute to be set.'.format( self.__class__.__name__)) handler = getattr(self, self.all_handler, self.http_method_not_allowed) return handler(request, *args, **kwargs) django-braces-1.9.0/braces/views/_queries.py000066400000000000000000000106561272337476100210260ustar00rootroot00000000000000from django.core.exceptions import ImproperlyConfigured class SelectRelatedMixin(object): """ Mixin allows you to provide a tuple or list of related models to perform a select_related on. """ select_related = None # Default related fields to none def get_queryset(self): if self.select_related is None: # If no fields were provided, # raise a configuration error raise ImproperlyConfigured( '{0} is missing the select_related property. This must be ' 'a tuple or list.'.format(self.__class__.__name__)) if not isinstance(self.select_related, (tuple, list)): # If the select_related argument is *not* a tuple or list, # raise a configuration error. raise ImproperlyConfigured( "{0}'s select_related property must be a tuple or " "list.".format(self.__class__.__name__)) # Get the current queryset of the view queryset = super(SelectRelatedMixin, self).get_queryset() if not self.select_related: return queryset return queryset.select_related(*self.select_related) class PrefetchRelatedMixin(object): """ Mixin allows you to provide a tuple or list of related models to perform a prefetch_related on. """ prefetch_related = None # Default prefetch fields to none def get_queryset(self): if self.prefetch_related is None: # If no fields were provided, # raise a configuration error raise ImproperlyConfigured( '{0} is missing the prefetch_related property. This must be ' 'a tuple or list.'.format(self.__class__.__name__)) if not isinstance(self.prefetch_related, (tuple, list)): # If the prefetch_related argument is *not* a tuple or list, # raise a configuration error. raise ImproperlyConfigured( "{0}'s prefetch_related property must be a tuple or " "list.".format(self.__class__.__name__)) # Get the current queryset of the view queryset = super(PrefetchRelatedMixin, self).get_queryset() if not self.prefetch_related: return queryset return queryset.prefetch_related(*self.prefetch_related) class OrderableListMixin(object): """ Mixin allows your users to order records using GET parameters """ orderable_columns = None orderable_columns_default = None order_by = None ordering = None def get_context_data(self, **kwargs): """ Augments context with: * ``order_by`` - name of the field * ``ordering`` - order of ordering, either ``asc`` or ``desc`` """ context = super(OrderableListMixin, self).get_context_data(**kwargs) context["order_by"] = self.order_by context["ordering"] = self.ordering return context def get_orderable_columns(self): if not self.orderable_columns: raise ImproperlyConfigured( '{0} needs the ordering columns defined.'.format( self.__class__.__name__)) return self.orderable_columns def get_orderable_columns_default(self): if not self.orderable_columns_default: raise ImproperlyConfigured( '{0} needs the default ordering column defined.'.format( self.__class__.__name__)) return self.orderable_columns_default def get_ordered_queryset(self, queryset=None): """ Augments ``QuerySet`` with order_by statement if possible :param QuerySet queryset: ``QuerySet`` to ``order_by`` :return: QuerySet """ get_order_by = self.request.GET.get("order_by") if get_order_by in self.get_orderable_columns(): order_by = get_order_by else: order_by = self.get_orderable_columns_default() self.order_by = order_by self.ordering = "asc" if order_by and self.request.GET.get("ordering", "asc") == "desc": order_by = "-" + order_by self.ordering = "desc" return queryset.order_by(order_by) def get_queryset(self): """ Returns ordered ``QuerySet`` """ unordered_queryset = super(OrderableListMixin, self).get_queryset() return self.get_ordered_queryset(unordered_queryset) django-braces-1.9.0/conftest.py000066400000000000000000000003561272337476100164370ustar00rootroot00000000000000import os from django.conf import settings from tests import settings as test_settings def pytest_configure(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') settings.configure(default_settings=test_settings) django-braces-1.9.0/docs/000077500000000000000000000000001272337476100151645ustar00rootroot00000000000000django-braces-1.9.0/docs/Makefile000066400000000000000000000127301272337476100166270ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-braces.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-braces.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-braces" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-braces" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-braces-1.9.0/docs/access.rst000066400000000000000000000300241272337476100171560ustar00rootroot00000000000000Access Mixins ============= These mixins all control a user's access to a given view. Since many of them extend the ``AccessMixin``, the following are common attributes: :: login_url = settings.LOGIN_URL raise_exception = False redirect_field_name = REDIRECT_FIELD_NAME redirect_unauthenticated_users = False The ``raise_exception`` attribute allows for these scenarios, in case a permission is denied: * ``False`` (default): redirects to the provided login view. * ``True``: raises a ``PermissionDenied`` exception. * A subclass of ``Exception``: raises this exception. * A callable: gets called with the ``request`` argument. The function has to return a ``HttpResponse`` or ``StreamingHttpResponse`` (Django 1.5+), otherwise a ``PermissionDenied`` exception gets raised. This gets done in ``handle_no_permission``, which can be overridden itself. .. contents:: .. _LoginRequiredMixin: LoginRequiredMixin ------------------ This mixin is rather simple and is generally the first inherited class in any view. If you don't have an authenticated user, there's no need to go any further. If you've used Django before you are probably familiar with the ``login_required`` decorator. This mixin replicates the decorator's functionality. .. note:: As of version 1.0, the LoginRequiredMixin has been rewritten to behave like the rest of the ``access`` mixins. It now accepts ``login_url``, ``redirect_field_name`` and ``raise_exception``. .. note:: This should be the left-most mixin of a view, except when combined with :ref:`CsrfExemptMixin` - which in that case should be the left-most mixin. :: from django.views.generic import TemplateView from braces.views import LoginRequiredMixin class SomeSecretView(LoginRequiredMixin, TemplateView): template_name = "path/to/template.html" #optional login_url = "/signup/" redirect_field_name = "hollaback" raise_exception = True def get(self, request): return self.render_to_response({}) An optional class attribute of ``redirect_unauthenticated_users`` can be set to ``True`` if you are using another ``access`` mixin with ``raise_exception`` set to ``True``. This will redirect to the login page if the user is not authenticated, but raises an exception if they are but do not have the required access defined by the other mixins. This defaults to ``False``. .. _PermissionRequiredMixin: PermissionRequiredMixin ----------------------- This mixin was originally written by `Daniel Sokolowski`_ (`code here`_), but this version eliminates an unneeded render if the permissions check fails. Rather than overloading the dispatch method manually on every view that needs to check for the existence of a permission, use this mixin and set the ``permission_required`` class attribute on your view. If you don't specify ``permission_required`` on your view, an ``ImproperlyConfigured`` exception is raised reminding you that you haven't set it. The one limitation of this mixin is that it can **only** accept a single permission. If you need multiple permissions use :ref:`MultiplePermissionsRequiredMixin`. In normal use of this mixin, :ref:`LoginRequiredMixin` comes first, then the ``PermissionRequiredMixin``. If the user isn't an authenticated user, there is no point in checking for any permissions. .. note:: If you are using Django's built in auth system, ``superusers`` automatically have all permissions in your system. :: from django.views import TemplateView from braces import views class SomeProtectedView(views.LoginRequiredMixin, views.PermissionRequiredMixin, TemplateView): permission_required = "auth.change_user" template_name = "path/to/template.html" The ``PermissionRequiredMixin`` also offers a ``check_permissions`` method that should be overridden if you need custom permissions checking. .. _MultiplePermissionsRequiredMixin: MultiplePermissionsRequiredMixin -------------------------------- The ``MultiplePermissionsRequiredMixin`` is a more powerful version of the :ref:`PermissionRequiredMixin`. This view mixin can handle multiple permissions by setting the mandatory ``permissions`` attribute as a dict with the keys ``any`` and/or ``all`` to a list or tuple of permissions. The ``all`` key requires the ``request.user`` to have **all** of the specified permissions. The ``any`` key requires the ``request.user`` to have **at least one** of the specified permissions. If you only need to check a single permission, the :ref:`PermissionRequiredMixin` is a better choice. .. note:: If you are using Django's built in auth system, ``superusers`` automatically have all permissions in your system. :: from django.views import TemplateView from braces import views class SomeProtectedView(views.LoginRequiredMixin, views.MultiplePermissionsRequiredMixin, TemplateView): #required permissions = { "all": ("blog.add_post", "blog.change_post"), "any": ("blog.delete_post", "user.change_user") } The ``MultiplePermissionsRequiredMixin`` also offers a ``check_permissions`` method that should be overridden if you need custom permissions checking. .. _GroupRequiredMixin: GroupRequiredMixin ------------------ .. versionadded:: 1.2 The ``GroupRequiredMixin`` ensures that the requesting user is in the group or groups specified. This view mixin can handle multiple groups by setting the mandatory ``group_required`` attribute as a list or tuple. .. note:: The mixin assumes you're using Django's default Group model and that your user model provides ``groups`` as a ManyToMany relationship. If this **is not** the case, you'll need to override ``check_membership`` in the mixin to handle your custom set up. Standard Django Usage ^^^^^^^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import GroupRequiredMixin class SomeProtectedView(GroupRequiredMixin, TemplateView): #required group_required = u"editors" Multiple Groups Possible Usage ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import GroupRequiredMixin class SomeProtectedView(GroupRequiredMixin, TemplateView): #required group_required = [u"editors", u"admins"] Custom Group Usage ^^^^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import GroupRequiredMixin class SomeProtectedView(GroupRequiredMixin, TemplateView): #required group_required = u"editors" def check_membership(self, group): ... # Check some other system for group membership if user_in_group: return True else: return False Dynamically Build Groups ^^^^^^^^^^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import GroupRequiredMixin class SomeProtectedView(GroupRequiredMixin, TemplateView): def get_group_required(self): # Get group or groups however you wish group = 'secret_group' return group .. _UserPassesTestMixin: UserPassesTestMixin ------------------- .. versionadded:: 1.3.0 Mixin that reimplements the `user_passes_test`_ decorator. This is helpful for much more complicated cases than checking if user ``is_superuser`` (for example if their email is from a specific domain). :: from django.views import TemplateView from braces.views import UserPassesTestMixin class SomeUserPassView(UserPassesTestMixin, TemplateView): def test_func(self, user): return (user.is_staff and not user.is_superuser and user.email.endswith(u"mydomain.com")) .. _SuperuserRequiredMixin: SuperuserRequiredMixin ---------------------- Another permission-based mixin. This is specifically for requiring a user to be a superuser. Comes in handy for tools that only privileged users should have access to. :: from django.views import TemplateView from braces import views class SomeSuperuserView(views.LoginRequiredMixin, views.SuperuserRequiredMixin, TemplateView): template_name = u"path/to/template.html" .. _AnonymousRequiredMixin: AnonymousRequiredMixin ---------------------- .. versionadded:: 1.4.0 Mixin that will redirect authenticated users to a different view. The default redirect is to Django's `settings.LOGIN_REDIRECT_URL`_. Static Examples ^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import AnonymousRequiredMixin class SomeView(AnonymousRequiredMixin, TemplateView): authenticated_redirect_url = u"/send/away/" :: from django.core.urlresolvers import reverse_lazy from django.views import TemplateView from braces.views import AnonymousRequiredMixin class SomeLazyView(AnonymousRequiredMixin, TemplateView): authenticated_redirect_url = reverse_lazy(u"view_url") Dynamic Example ^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import AnonymousRequiredMixin class SomeView(AnonymousRequiredMixin, TemplateView): """ Redirect based on user level """ def get_authenticated_redirect_url(self): if self.request.user.is_superuser: return u"/admin/" return u"/somewhere/else/" .. _StaffuserRequiredMixin: StaffuserRequiredMixin ---------------------- Similar to :ref:`SuperuserRequiredMixin`, this mixin allows you to require a user with ``is_staff`` set to ``True``. :: from django.views import TemplateView from braces import views class SomeStaffuserView(views.LoginRequiredMixin, views.StaffuserRequiredMixin, TemplateView): template_name = u"path/to/template.html" .. _SSLRequiredMixin: SSLRequiredMixin ---------------- .. versionadded:: 1.8.0 Simple view mixin that requires the incoming request to be secure by checking Django's `request.is_secure()` method. By default the mixin will return a permanent (301) redirect to the https verison of the current url. Optionally you can set `raise_exception=True` and a 404 will be raised. Standard Django Usage ^^^^^^^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import SSLRequiredMixin class SomeSecureView(SSLRequiredMixin, TemplateView): """ Redirects from http -> https """ template_name = "path/to/template.html" Standard Django Usage ^^^^^^^^^^^^^^^^^^^^^ :: from django.views import TemplateView from braces.views import SSLRequiredMixin class SomeSecureView(SSLRequiredMixin, TemplateView): """ http request would raise 404. https renders view """ raise_exception = True template_name = "path/to/template.html" .. _RecentLoginRequiredMixin: RecentLoginRequiredMixin ------------------------ .. versionadded:: 1.8.0 This mixin requires a user to have logged in within a certain number of seconds. This is to prevent stale sessions or to create a session time-out, as is often used for financial applications and the like. This mixin includes the functionality of `LoginRequiredMixin`_, so you don't need to use both on the same view. :: from django.views.generic import TemplateView from braces.views import RecentLoginRequiredMixin class SomeSecretView(RecentLoginRequiredMixin, TemplateView): max_last_login_delta = 600 # Require a login within the last 10 minutes template_name = "path/to/template.html" .. _Daniel Sokolowski: https://github.com/danols .. _code here: https://github.com/lukaszb/django-guardian/issues/48 .. _user_passes_test: https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.decorators.user_passes_test .. _settings.LOGIN_REDIRECT_URL: https://docs.djangoproject.com/en/1.6/ref/settings/#login-redirect-url django-braces-1.9.0/docs/changelog.rst000066400000000000000000000201401272337476100176420ustar00rootroot00000000000000:orphan: ========= Changelog ========= * :release:`1.9.0 <2016-5-31>` * :bug:`208 major` Fixed errors from combining certain access mixins. * :bug:`196 major` Refactor how users without permissions are handled. * :bug:`181 major` Fixed redirect loops based on user permissions. * :bug:`161 major` Fixed redirect loop for users without proper groups for ``MultipleGroupRequiredMixin`` and ``GroupRequiredMixin``. * :support:`209` Fixed link to Django documentation for ``user_passes_test`` decorator. * :feature:`203` Use Django's supplied version of ``six`` to remove an external dependency. * :support:`202` Fixed typo in ``PermissionsRequiredMixin`` and ``MultiplePermissionsRequiredMixin``. * :support:`201` Fixed typo in ``SuccessURLRedirectListMixin``. * :support:`192` Added example for ``OrderableListView``. * :release:`1.8.1 <2015-7-12>` * :bug:`176` Only check time delta for authenticated users in :ref:`RecentLoginRequiredMixin`. * :bug:`-` Changed :ref:`JsonRequestResponseMixin` docs to not use `ugettext_lazy`. * :bug:`-` Updated tests to include Python 3.2. * :bug:`185` Removed `u` prefixes to allow Python 3.2 support. * :support:`-` Added note to docs about Python and Django versions used in tests. * :bug:`-` Fix small issue in docs for :ref:JsonResponseMixin. The accepted kwarg for the render_to_response method is status not status_code. * :release:`1.8.0 <2015-04-16>` * :support:`145` Allow custom exceptions to be raised by all AccessMixins. * :feature:`171` New ``SSLRequiredMixin``. Redirect http -> https. * :feature:`138` New :ref:`RecentLoginRequiredMixin` to require user sessions to have a given freshness. * :bug:`164 major` Use `resolve_url` to handle `LOGIN_REDIRECT_URL`s in `settings.py` that are just URL names. * :bug:`130 major` New attribute on :ref:`JSONResponseMixin` to allow setting a custom JSON encoder class. * :bug:`131 major` New attribute on :ref:`LoginRequiredMixin` so it's possible to redirect unauthenticated users while using ``AccessMixin``-derived mixins instead of throwing an exception. * :release:`1.4.0 <2014-03-04>` * :support:`129` Split ``views.py`` out into multiple files since it was approaching 1000 LoC. * :feature:`119` :ref:`SetHeadlineMixin` now accepts ``headline`` with ``ugettext_lazy()``-wrapped strings. * :bug:`94 major` Fixed a bug where :ref:`JSONResponseMixin` would override the ``content_type`` of Django's ``TemplateView`` in Django 1.6. * :bug:`- major` Fixed bug in :ref:`PermissionRequiredMixin` where if ``PermissionRequiredMixin.no_permissions_fail`` returned a false-y value, the user lacking the permission would pass instead of being denied access. * :support:`73` Added doc for how to contribute. * :feature:`120` Added :ref:`MessageMixin` to allow easier access to Django's ``contrib.messages`` messages. :ref:`FormValidMessageMixin` and :ref:`FormInvalidMessageMixin` were updated to use it. * :bug:`98 major` Fixed bug in :ref:`CanonicalSlugDetailMixin` to allow it to use custom URL kwargs. * :bug:`105 major` Fixed bug in :ref:`GroupRequiredMixin` where superusers were blocked by lack of group memberships. * :bug:`106 major` Fixed bug in :ref:`GroupRequiredMixin` which now correctly checks for group membership against a list. * :feature:`102` Added new :ref:`StaticContextMixin` mixin which lets you pass in ``static_context`` as a property of the view. * :feature:`89` Added new :ref:`AnonymousRequiredMixin` which redirects authenticated users to another view. * :feature:`104` Added new :ref:`AllVerbsMixin` which allows a single method to response to all HTTP verbs. * :bug:`- major` Provided ``JSONRequestResponseMixin`` as a mirror of :ref:`JsonRequestResponseMixin` because we're not PHP. * :feature:`107` :ref:`FormValidMessageMixin`, :ref:`FormInvalidMessageMixin`, and :ref:`FormMessagesMixin` all allow ``ugettext_lazy``-wrapped strings. * :feature:`67` Extended :ref:`PermissionRequiredMixin` and :ref:`MultiplePermissionsRequiredMixin` to accept django-guardian-style custom/object permissions. * :release:`1.3.1 <2014-01-04>` * :bug:`95` Removed accidentally-added breakpoint. * :support:`96 backported` Added ``build/`` to ``.gitignore`` * :release:`1.3.0 <2014-01-03>` * :support:`59` Removed ``CreateAndRedirectToEditView`` mixin which was marked for deprecation and removal since 1.0.0. * :feature:`51` Added :ref:`JsonRequestResponseMixin` which attempts to parse requests as JSON. * :feature:`61` Added :ref:`CanonicalSlugDetailMixin` mixin which allows for the specification of a canonical slug on a ``DetailView`` to help with SEO by redirecting on non-canonical requests. * :feature:`76` Added :ref:`UserPassesTestMixin` mixin to replicate the behavior of Django's ``@user_passes_test`` decorator. * :bug:`- major` Some fixes for :ref:`CanonicalSlugDetailMixin`. * :feature:`92` ``AccessMixin`` now has a runtime-overridable ``login_url`` attribute. * :bug:`- major` Fixed problem with :ref:`GroupRequiredMixin` that made it not actually work. * :support:`-` All tests pass for Django versions 1.4 through 1.6 and Python versions 2.6, 2.7, and 3.3 (Django 1.4 and 1.5 not tested with Python 3.3). * :release:`1.2.2 <2013-08-07>` * :support:`-` Uses ``six.string_types`` instead of explicitly relying on ``str`` and ``unicode`` types. * :release:`1.2.1 <2013-07-28>` * :bug:`-` Fix to allow ``reverse_lazy`` to work for all ``AccessMixin``-derived mixins. * :release:`1.2.0 <2013-07-27>` * :feature:`57` :ref:`FormValidMessageMixin` which provides a ``messages`` message when the processed form is valid. * :feature:`-` :ref:`FormInvalidMessageMixin` which provides a ``messages`` message when the processed form is invalid. * :feature:`-` :ref:`FormMessagesMixin` which provides the functionality of both of the above mixins. * :feature:`-` :ref:`GroupRequiredMixin` which is a new access-level mixin which requires that a user be part of a specified group to access a view. * :release:`1.1.0 <2013-07-18>` * :bug:`52 major` :ref:`JSONResponseMixin` ``.render_json_response`` method updated to accept a status code. * :bug:`43 major` :ref:`JSONResponseMixin` added ``json_dumps_kwargs`` attribute & get method to pass args to the JSON encoder. * :feature:`45` New :ref:`OrderableListMixin` allows ordering of list views by GET params. * :support:`-` Tests updated to test against latest stable Django release (1.5.1) * :support:`-` Small fixes and additions to documentation. * :release:`1.0.0 <2013-02-28>` * :feature:`-` New 'abstract' ``AccessMixin`` which provides overridable ``get_login_url`` and ``get_redirect_field_name`` methods for all access-based mixins. * :feature:`32` Rewritten :ref:`LoginRequiredMixin` which provides same customization as other access mixins with ``login_url``, ``raise_exception`` & ``redirect_field_name``. * :feature:`33` New :ref:`PrefetchRelatedMixin`. Works the same as :ref:`SelectRelatedMixin` but uses Django's ``prefetch_related`` method. * :support:`-` ``CreateAndRedirectToEditView`` is marked for deprecation. * :bug:`- major` :ref:`PermissionRequiredMixin` no longer requires dot syntax for permission names. * :support:`-` Marked package as supporting 2.6 thru 3.3 (from rafales). * :support:`-` Fixes to documentation. * :support:`-` Tests to cover new additions and changes. * :release:`0.2.3 <2013-02-22>` * :support:`30 backported` Tests for all mixins (from rafales). * :feature:`26 backported` New :ref:`CsrfExemptMixin` for marking views as being CSRF exempt (from jarcoal). * :support:`- backported` Some documentation updates and a spelling error correction (from shabda). * :bug:`-` :ref:`SuccessURLRedirectListMixin` raises ``ImproperlyConfigured`` if no ``success_list_url`` attribute is supplied (from kennethlove). * :release:`0.2.2 <2013-01-21>` * :bug:`25` Try importing the built-in ``json`` module first, drop back to Django if necessary. * :support:`- backported` Django 1.5 compatibility. * :release:`0.2.1 <2012-12-10>` * :bug:`21 major` Fixed signature of :ref:`UserFormKwargsMixin` ``.get_form_kwargs`` * :feature:`22` Updated :ref:`JSONResponseMixin` to work with non-ASCII characters and other datatypes (such as datetimes) * :bug:`- major` Fixed all mixins that have ``raise_exception`` as an argument to properly raise a ``PermissionDenied`` exception to allow for custom 403s. django-braces-1.9.0/docs/conf.py000066400000000000000000000177331272337476100164760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # django-braces documentation build configuration file, created by # sphinx-quickstart on Mon Apr 30 10:31:44 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os sys.path.insert(0, os.path.abspath('..')) import braces from braces import __version__ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['releases'] releases_issue_uri = "https://github.com/brack3t/django-braces/issues/%s" releases_release_uri = "https://github.com/brack3t/django-braces/tree/%s" releases_unstable_prehistory = True # releases_debug = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'django-braces' copyright = u'2013, Kenneth Love and Chris Jones' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'django-bracesdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'django-braces.tex', 'django-braces Documentation', 'Kenneth Love and Chris Jones', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'django-braces', 'django-braces Documentation', ['Kenneth Love and Chris Jones'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'django-braces', 'django-braces Documentation', 'Kenneth Love and Chris Jones', 'django-braces', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' django-braces-1.9.0/docs/contributing.rst000066400000000000000000000036251272337476100204330ustar00rootroot00000000000000:orphan: ============ Contributing ============ First of all, thank you for wanting to make **django-braces** better! We love getting input and suggestions from the community. Secondly, we just want to put out a few ground rules for contributing so that we can get your pull requests in sooner and cause less headaches all around. .. _Code Style: Code Style ---------- We stick to `PEP8 `_ as much as possible, so please make sure your code passes a lint test. That means two blank lines before class names and all of those other wonderful rules. We like docstrings in the classes, too. This helps us and those that come later what the class is meant to do. Docstrings in methods are great, too, especially if the method makes any assumptions about how it'll be used. .. _Docs: Docs ---- If you're reading this, you should already know that docs are important to this project and, honestly, all of them. We like any new mixins, or changes in existing mixins, to come with documentation changes showing how to use the mixin. Ideally, you show at least one example usage, but if your mixin provides multiple paths, perhaps a static attribute or a dynamic method, it's really great if your documentation shows both avenues, too. .. _Tests: Tests ----- All code changes should come with test changes. We use `py.test `_ instead of Python's ``unittest``. This seems to only be really important when marking tests for skipping. We try to keep the project at 100% test coverage but know this isn't something we can achieve forever. So long as your tests cover your contribution 80% or better, we're happy to try and bump up that last bit, or just accept the code. We currently test Braces against late (usually latest) versions of Python 2.6, 2.7, 3.2, 3.3, and 3.4. We also test against the latest released version of Django from 1.5 to 1.8. django-braces-1.9.0/docs/form.rst000066400000000000000000000175771272337476100167020ustar00rootroot00000000000000Form Mixins =========== All of these mixins, with one exception, modify how forms are handled within views. The ``UserKwargModelFormMixin`` is a mixin for use in forms to auto-pop a ``user`` kwarg. .. contents:: .. _CsrfExemptMixin: CsrfExemptMixin --------------- If you have Django's `CSRF protection`_ middleware enabled you can exempt views using the `csrf_exempt`_ decorator. This mixin exempts POST requests from the CSRF protection middleware without requiring that you decorate the ``dispatch`` method. .. note:: This mixin should always be the left-most plugin. :: from django.views.generic import UpdateView from braces.views import LoginRequiredMixin, CsrfExemptMixin from profiles.models import Profile class UpdateProfileView(CsrfExemptMixin, LoginRequiredMixin, UpdateView): model = Profile .. _UserFormKwargsMixin: UserFormKwargsMixin ------------------- A common pattern in Django is to have forms that are customized to a user. To custom tailor the form for users, you have to pass that user instance into the form and, based on their permission level or other details, change certain fields or add specific options within the forms ``__init__`` method. This mixin automates the process of overloading the ``get_form_kwargs`` (this method is available in any generic view which handles a form) method and stuffs the user instance into the form kwargs. The user can then be ``pop()``\ ped off in the form. **Always** remember to pop the user from the kwargs before calling ``super()`` on your form, otherwise the form will get an unexpected keyword argument. Usage ^^^^^ :: from django.views.generic import CreateView from braces.views import LoginRequiredMixin, UserFormKwargsMixin from next.example import UserForm class SomeSecretView(LoginRequiredMixin, UserFormKwargsMixin, CreateView): form_class = UserForm model = User template_name = "path/to/template.html" This obviously pairs very nicely with the following mixin. .. _UserKwargModelFormMixin: UserKwargModelFormMixin ----------------------- The ``UserKwargModelFormMixin`` is a form mixin to go along with our :ref:`UserFormKwargsMixin`. This becomes the first inherited class of our forms that receive the ``user`` keyword argument. With this mixin, the ``pop()``\ ping of the ``user`` is automated and no longer has to be done manually on every form that needs this behavior. Usage ^^^^^ :: from braces.forms import UserKwargModelFormMixin class UserForm(UserKwargModelFormMixin, forms.ModelForm): class Meta: model = User def __init__(self, *args, **kwargs): super(UserForm, self).__init__(*args, **kwargs) if not self.user.is_superuser: del self.fields["group"] .. _SuccessURLRedirectListMixin: SuccessURLRedirectListMixin --------------------------- The ``SuccessURLRedirectListMixin`` is a bit more tailored to how CRUD_ is often handled within CMSes. Many CMSes, by design, redirect the user to the ``ListView`` for whatever model they are working with, whether they are creating a new instance, editing an existing one, or deleting one. Rather than having to override ``get_success_url`` on every view, use this mixin and pass it a reversible route name. Example: :: # urls.py url(r"^users/$", UserListView.as_view(), name="users_list"), # views.py from django.views import CreateView from braces import views class UserCreateView(views.LoginRequiredMixin, views.PermissionRequiredMixin, views.SuccessURLRedirectListMixin, CreateView): form_class = UserForm model = User permission_required = "auth.add_user" success_list_url = "users_list" ... .. _FormValidMessageMixin: FormValidMessageMixin --------------------- .. versionadded:: 1.2 The ``FormValidMessageMixin`` allows you to to *statically* or *programmatically* set a message to be returned using Django's `messages`_ framework when the form is valid. The returned message is controlled by the ``form_valid_message`` property which can either be set on the view or returned by the ``get_form_valid_message`` method. The message is not processed until the end of the ``form_valid`` method. .. warning:: This mixin requires the Django `messages`_ app to be enabled. .. note:: This mixin is designed for use with Django's generic form class-based views, e.g. ``FormView``, ``CreateView``, ``UpdateView`` Static Example ^^^^^^^^^^^^^^ :: from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView from braces.views import FormValidMessageMixin class BlogPostCreateView(FormValidMessageMixin, CreateView): form_class = PostForm model = Post form_valid_message = _(u"Blog post created!") Dynamic Example ^^^^^^^^^^^^^^^ :: from django.views.generic import CreateView from braces.views import FormValidMessageMixin class BlogPostCreateView(FormValidMessageMixin, CreateView): form_class = PostForm model = Post def get_form_valid_message(self): return u"{0} created!".format(self.object.title) .. _FormInvalidMessageMixin: FormInvalidMessageMixin ----------------------- .. versionadded:: 1.2 The ``FormInvalidMessageMixin`` allows you to to *statically* or *programmatically* set a message to be returned using Django's `messages`_ framework when the form is invalid. The returned message is controlled by the ``form_invalid_message`` property which can either be set on the view or returned by the ``get_form_invalid_message`` method. The message is not processed until the end of the ``form_invalid`` method. .. warning:: This mixin requires the Django `messages`_ app to be enabled. .. note:: This mixin is designed for use with Django's generic form class-based views, e.g. ``FormView``, ``CreateView``, ``UpdateView`` Static Example ^^^^^^^^^^^^^^ :: from django.utils.translation import ugettext_lazy from django.views.generic import CreateView from braces.views import FormInvalidMessageMixin class BlogPostCreateView(FormInvalidMessageMixin, CreateView): form_class = PostForm model = Post form_invalid_message = _(u"Oh snap, something went wrong!") Dynamic Example ^^^^^^^^^^^^^^^ :: from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView from braces.views import FormInvalidMessageMixin class BlogPostCreateView(FormInvalidMessageMixin, CreateView): form_class = PostForm model = Post def get_form_invalid_message(self): return _(u"Some custom message") .. _FormMessagesMixin: FormMessagesMixin ----------------- .. versionadded:: 1.2 ``FormMessagesMixin`` is a convenience mixin which combines :ref:`FormValidMessageMixin` and :ref:`FormInvalidMessageMixin` since we commonly provide messages for both states (``form_valid``, ``form_invalid``). .. warning:: This mixin requires the Django `messages`_ app to be enabled. Static & Dynamic Example ^^^^^^^^^^^^^^^^^^^^^^^^ :: from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView from braces.views import FormMessagesMixin class BlogPostCreateView(FormMessagesMixin, CreateView): form_class = PostForm form_invalid_message = _(u"Something went wrong, post was not saved") model = Post def get_form_valid_message(self): return u"{0} created!".format(self.object.title) .. _CRUD: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete .. _CSRF protection: https://docs.djangoproject.com/en/1.5/ref/contrib/csrf/ .. _csrf_exempt: https://docs.djangoproject.com/en/1.5/ref/contrib/csrf/#django.views.decorators.csrf.csrf_exempt .. _messages: https://docs.djangoproject.com/en/1.5/ref/contrib/messages/ django-braces-1.9.0/docs/index.rst000066400000000000000000000013771272337476100170350ustar00rootroot00000000000000.. django-braces documentation master file, created by sphinx-quickstart on Mon Apr 30 10:31:44 2012. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to django-braces's documentation! ========================================= You can view the code of our project or fork it and add your own mixins (please, send them back to us), on `Github`_. .. toctree:: :maxdepth: 2 Access Mixins Form Mixins
Other Mixins `View our Changelog `_ `Want to contribute? `_ Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _Github: https://github.com/brack3t/django-braces django-braces-1.9.0/docs/other.rst000066400000000000000000000417131272337476100170450ustar00rootroot00000000000000Other Mixins ============ These mixins handle other random bits of Django's views, like controlling output, controlling content types, or setting values in the context. .. contents:: .. _SetHeadlineMixin: SetHeadlineMixin ---------------- The ``SetHeadlineMixin`` allows you to *statically* or *programmatically* set the headline of any of your views. Ideally, you'll write as few templates as possible, so a mixin like this helps you reuse generic templates. Its usage is amazingly straightforward and works much like Django's built-in ``get_queryset`` method. This mixin has two ways of being used: Static Example ^^^^^^^^^^^^^^ :: from django.utils.translation import ugettext_lazy as _ from django.views import TemplateView from braces.views import SetHeadlineMixin class HeadlineView(SetHeadlineMixin, TemplateView): headline = _(u"This is our headline") template_name = u"path/to/template.html" Dynamic Example ^^^^^^^^^^^^^^^ :: from datetime import date from django.views import TemplateView from braces.views import SetHeadlineMixin class HeadlineView(SetHeadlineMixin, TemplateView): template_name = u"path/to/template.html" def get_headline(self): return u"This is our headline for {0}".format(date.today().isoformat()) For both usages, the context now contains a ``headline`` key with your headline. .. _StaticContextMixin: StaticContextMixin ------------------ .. versionadded:: 1.4 The ``StaticContextMixin`` allows you to easily set static context data by using the ``static_context`` property. .. note:: While it's possible to override the ``StaticContextMixin.get_static_context method``, it's not very practical. If you have a need to override a method for dynamic context data it's best to override the standard ``get_context_data`` method of Django's generic class-based views. View Example ^^^^^^^^^^^^ :: # views.py from django.views import TemplateView from braces.views import StaticContextMixin class ContextTemplateView(StaticContextMixin, TemplateView): static_context = {u"nav_home": True} URL Example ^^^^^^^^^^^ :: # urls.py urlpatterns = patterns( '', url(ur"^$", ContextTemplateView.as_view( template_name=u"index.html", static_context={u"nav_home": True} ), name=u"index") ) .. _SelectRelatedMixin: SelectRelatedMixin ------------------ A simple mixin which allows you to specify a list or tuple of foreign key fields to perform a `select_related`_ on. See Django's docs for more information on `select_related`_. :: # views.py from django.views.generic import DetailView from braces.views import SelectRelatedMixin from profiles.models import Profile class UserProfileView(SelectRelatedMixin, DetailView): model = Profile select_related = [u"user"] template_name = u"profiles/detail.html" .. _select_related: https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related .. _PrefetchRelatedMixin: PrefetchRelatedMixin -------------------- A simple mixin which allows you to specify a list or tuple of reverse foreign key or ManyToMany fields to perform a `prefetch_related`_ on. See Django's docs for more information on `prefetch_related`_. :: # views.py from django.contrib.auth.models import User from django.views.generic import DetailView from braces.views import PrefetchRelatedMixin class UserView(PrefetchRelatedMixin, DetailView): model = User prefetch_related = [u"post_set"] # where the Post model has an FK to the User model as an author. template_name = u"users/detail.html" .. _prefetch_related: https://docs.djangoproject.com/en/dev/ref/models/querysets/#prefetch-related .. _JSONResponseMixin: JSONResponseMixin ----------------- .. versionchanged:: 1.1 ``render_json_response`` now accepts a ``status`` keyword argument. ``json_dumps_kwargs`` class-attribute and ``get_json_dumps_kwargs`` method to provide arguments to the ``json.dumps()`` method. A simple mixin to handle very simple serialization as a response to the browser. :: # views.py from django.views.generic import DetailView from braces.views import JSONResponseMixin class UserProfileAJAXView(JSONResponseMixin, DetailView): model = Profile json_dumps_kwargs = {u"indent": 2} def get(self, request, *args, **kwargs): self.object = self.get_object() context_dict = { u"name": self.object.user.name, u"location": self.object.location } return self.render_json_response(context_dict) You can additionally use the `AjaxResponseMixin` :: # views.py from django.views import DetailView from braces import views class UserProfileView(views.JSONResponseMixin, views.AjaxResponseMixin, DetailView): model = Profile def get_ajax(self, request, *args, **kwargs): return self.render_json_object_response(self.get_object()) The `JSONResponseMixin` provides a class-level variable to control the response type as well. By default it is `application/json`, but you can override that by providing the `content_type` variable a different value or, programmatically, by overriding the `get_content_type()` method. :: from django.views import DetailView from braces.views import JSONResponseMixin class UserProfileAJAXView(JSONResponseMixin, DetailView): content_type = u"application/javascript" model = Profile def get(self, request, *args, **kwargs): self.object = self.get_object() context_dict = { u"name": self.object.user.name, u"location": self.object.location } return self.render_json_response(context_dict) def get_content_type(self): # Shown just for illustrative purposes return u"application/javascript" The `JSONResponseMixin` provides another class-level variable `json_encoder_class` to use a custom json encoder with `json.dumps`. By default it is `django.core.serializers.json.DjangoJsonEncoder` :: from django.core.serializers.json import DjangoJSONEncoder from braces.views import JSONResponseMixin class SetJSONEncoder(DjangoJSONEncoder): """ A custom JSONEncoder extending `DjangoJSONEncoder` to handle serialization of `set`. """ def default(self, obj): if isinstance(obj, set): return list(obj) return super(DjangoJSONEncoder, self).default(obj) class GetSetDataView(JSONResponseMixin, View): json_encoder_class = SetJSONEncoder def get(self, request, *args, **kwargs): numbers_set = set(range(10)) data = {'numbers': numbers_set} return self.render_json_response(data) .. _JsonRequestResponseMixin: JsonRequestResponseMixin ------------------------ .. versionadded:: 1.3 A mixin that attempts to parse the request as JSON. If the request is properly formatted, the JSON is saved to ``self.request_json`` as a Python object. ``request_json`` will be ``None`` for imparsible requests. To catch requests that aren't JSON-formatted, set the class attribute ``require_json`` to ``True``. Override the class attribute ``error_response_dict`` to customize the default error message. It extends :ref:`JSONResponseMixin`, so those utilities are available as well. .. note:: To allow public access to your view, you'll need to use the ``csrf_exempt`` decorator or :ref:`CsrfExemptMixin`. :: from django.views.generic import View from braces import views class SomeView(views.CsrfExemptMixin, views.JsonRequestResponseMixin, View): require_json = True def post(self, request, *args, **kwargs): try: burrito = self.request_json[u"burrito"] toppings = self.request_json[u"toppings"] except KeyError: error_dict = {u"message": u"your order must include a burrito AND toppings"} return self.render_bad_request_response(error_dict) place_order(burrito, toppings) return self.render_json_response( {u"message": u"Your order has been placed!"}) .. _AjaxResponseMixin: AjaxResponseMixin ----------------- This mixin provides hooks for altenate processing of AJAX requests based on HTTP verb. To control AJAX-specific behavior, override ``get_ajax``, ``post_ajax``, ``put_ajax``, or ``delete_ajax``. All four methods take ``request``, ``*args``, and ``**kwargs`` like the standard view methods. :: # views.py from django.views.generic import View from braces import views class SomeView(views.JSONResponseMixin, views.AjaxResponseMixin, View): def get_ajax(self, request, *args, **kwargs): json_dict = { 'name': "Benny's Burritos", 'location': "New York, NY" } return self.render_json_response(json_dict) .. note:: This mixin is only useful if you need to have behavior in your view fork based on ``request.is_ajax()``. .. _OrderableListMixin: OrderableListMixin ------------------ .. versionadded:: 1.1 A mixin to allow easy ordering of your queryset basing on the GET parameters. Works with `ListView`. To use it, define columns that the data can be ordered by, as well as the default column to order by in your view. This can be done either by simply setting the class attributes: :: # views.py from django.views import ListView from braces.views import OrderableListMixin class OrderableListView(OrderableListMixin, ListView): model = Article orderable_columns = (u"id", u"title",) orderable_columns_default = u"id" Or by using similarly-named methods to set the ordering constraints more dynamically: :: # views.py from django.views import ListView from braces.views import OrderableListMixin class OrderableListView(OrderableListMixin, ListView): model = Article def get_orderable_columns(self): # return an iterable return (u"id", u"title",) def get_orderable_columns_default(self): # return a string return u"id" The ``orderable_columns`` restriction is here in order to stop your users from launching inefficient queries, like ordering by binary columns. ``OrderableListMixin`` will order your queryset basing on following GET params: * ``order_by``: column name, e.g. ``"title"`` * ``ordering``: ``"asc"`` (default) or ``"desc"`` Example url: `http://127.0.0.1:8000/articles/?order_by=title&ordering=asc` **Front-end Example Usage** If you're using bootstrap you could create a template like the following: .. code:: html
{% for object in object_list %} {% endfor %}
ID Title
{{ object.id }} {{ object.title }}
.. _CanonicalSlugDetailMixin: CanonicalSlugDetailMixin ------------------------ .. versionadded:: 1.3 A mixin that enforces a canonical slug in the URL. Works with ``DetailView``. If a ``urlpattern`` takes a object's ``pk`` and ``slug`` as arguments and the ``slug`` URL argument does not equal the object's canonical slug, this mixin will redirect to the URL containing the canonical slug. To use it, the ``urlpattern`` must accept both a ``pk`` and ``slug`` argument in its regex: :: # urls.py urlpatterns = patterns('', url(r"^article/(?P\d+)-(?P[-\w]+)$") ArticleView.as_view(), "view_article" ) Then create a standard ``DetailView`` that inherits this mixin: :: class ArticleView(CanonicalSlugDetailMixin, DetailView): model = Article Now, given an ``Article`` object with ``{pk: 1, slug: 'hello-world'}``, the URL `http://127.0.0.1:8000/article/1-goodbye-moon` will redirect to `http://127.0.0.1:8000/article/1-hello-world` with the HTTP status code 301 Moved Permanently. Any other non-canonical slug, not just 'goodbye-moon', will trigger the redirect as well. Control the canonical slug by either implementing the method ``get_canonical_slug()`` on the model class: :: class Article(models.Model): blog = models.ForeignKey('Blog') slug = models.SlugField() def get_canonical_slug(self): return "{0}-{1}".format(self.blog.get_canonical_slug(), self.slug) Or by overriding the ``get_canonical_slug()`` method on the view: :: class ArticleView(CanonicalSlugDetailMixin, DetailView): model = Article def get_canonical_slug(): import codecs return codecs.encode(self.get_object().slug, "rot_13") Given the same Article as before, this will generate urls of `http://127.0.0.1:8000/article/1-my-blog-hello-world` and `http://127.0.0.1:8000/article/1-uryyb-jbeyq`, respectively. .. _MessageMixin: MessageMixin ------------ .. versionadded:: 1.4 A mixin that adds a ``messages`` attribute on the view which acts as a wrapper to ``django.contrib.messages`` and passes the ``request`` object automatically. .. warning:: If you're using Django 1.4, then the ``message`` attribute is only available after the base view's ``dispatch`` method has been called (so our second example would not work for instance). :: from django.views.generic import TemplateView from braces.views import MessageMixin class MyView(MessageMixin, TemplateView): """ This view will add a debug message which can then be displayed in the template. """ template_name = "my_template.html" def get(self, request, *args, **kwargs): self.messages.debug("This is a debug message.") return super(MyView, self).get(request, *args, **kwargs) :: from django.contrib import messages from django.views.generic import TemplateView from braces.views import MessageMixin class OnlyWarningView(MessageMixin, TemplateView): """ This view will only show messages that have a level above `warning`. """ template_name = "my_template.html" def dispatch(self, request, *args, **kwargs): self.messages.set_level(messages.WARNING) return super(OnlyWarningView, self).dispatch(request, *args, **kwargs) .. _AllVerbsMixin: AllVerbsMixin ------------- .. versionadded:: 1.4 This mixin allows you to specify a single method that will response to all HTTP verbs, making a class-based view behave much like a function-based view. :: from django.views import TemplateView from braces.views import AllVerbsMixin class JustShowItView(AllVerbsMixin, TemplateView): template_name = "just/show_it.html" def all(self, request, *args, **kwargs): return super(JustShowItView, self).get(request, *args, **kwargs) If you need to change the name of the method called, provide a new value to the ``all_handler`` attribute (default is ``'all'``) django-braces-1.9.0/requirements-docs.txt000066400000000000000000000000201272337476100204360ustar00rootroot00000000000000sphinx releases django-braces-1.9.0/requirements.txt000066400000000000000000000000631272337476100175170ustar00rootroot00000000000000factory_boy mock pytest-django pytest-cov coverage django-braces-1.9.0/setup.cfg000066400000000000000000000000261272337476100160530ustar00rootroot00000000000000[wheel] universal = 1 django-braces-1.9.0/setup.py000066400000000000000000000025501272337476100157500ustar00rootroot00000000000000from setuptools import setup import braces setup( name="django-braces", version=braces.__version__, description="Reusable, generic mixins for Django", long_description="Mixins to add easy functionality to Django class-based views, forms, and models.", keywords="django, views, forms, mixins", author="Kenneth Love , Chris Jones ", author_email="devs@brack3t.com", url="https://github.com/brack3t/django-braces/", license="BSD", packages=["braces"], zip_safe=False, include_package_data=True, classifiers=[ "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: BSD License", "Environment :: Web Environment", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Framework :: Django", "Framework :: Django :: 1.5", "Framework :: Django :: 1.6", "Framework :: Django :: 1.7", "Framework :: Django :: 1.8", "Framework :: Django :: 1.9" ], ) django-braces-1.9.0/tests/000077500000000000000000000000001272337476100153765ustar00rootroot00000000000000django-braces-1.9.0/tests/__init__.py000066400000000000000000000000001272337476100174750ustar00rootroot00000000000000django-braces-1.9.0/tests/compat.py000066400000000000000000000005711272337476100172360ustar00rootroot00000000000000try: from django.utils.encoding import force_text except ImportError: from django.utils.encoding import force_unicode as force_text try: import json except ImportError: from django.utils import simplejson as json try: from django.conf.urls import patterns, url, include except ImportError: from django.conf.urls.defaults import patterns, url, include django-braces-1.9.0/tests/factories.py000066400000000000000000000035121272337476100177300ustar00rootroot00000000000000from __future__ import absolute_import import factory from django.contrib.auth.models import Group, Permission, User from .models import Article def _get_perm(perm_name): """ Returns permission instance with given name. Permission name is a string like 'auth.add_user'. """ app_label, codename = perm_name.split('.') return Permission.objects.get( content_type__app_label=app_label, codename=codename) class ArticleFactory(factory.django.DjangoModelFactory): title = factory.Sequence(lambda n: 'Article number {0}'.format(n)) body = factory.Sequence(lambda n: 'Body of article {0}'.format(n)) class Meta: model = Article abstract = False class GroupFactory(factory.django.DjangoModelFactory): name = factory.Sequence(lambda n: 'group{0}'.format(n)) class Meta: model = Group abstract = False class UserFactory(factory.django.DjangoModelFactory): username = factory.Sequence(lambda n: 'user{0}'.format(n)) first_name = factory.Sequence(lambda n: 'John {0}'.format(n)) last_name = factory.Sequence(lambda n: 'Doe {0}'.format(n)) email = factory.Sequence(lambda n: 'user{0}@example.com'.format(n)) password = 'asdf1234' class Meta: model = User abstract = False @classmethod def _prepare(cls, create, **kwargs): password = kwargs.pop('password', None) user = super(UserFactory, cls)._prepare(create, **kwargs) if password: user.set_password(password) if create: user.save() return user @factory.post_generation def permissions(self, create, extracted, **kwargs): if create and extracted: # We have a saved object and a list of permission names self.user_permissions.add(*[_get_perm(pn) for pn in extracted]) django-braces-1.9.0/tests/forms.py000066400000000000000000000005621272337476100171010ustar00rootroot00000000000000from __future__ import absolute_import from django import forms from braces.forms import UserKwargModelFormMixin from .models import Article class FormWithUserKwarg(UserKwargModelFormMixin, forms.Form): field1 = forms.CharField() class ArticleForm(forms.ModelForm): class Meta: model = Article fields = ['author', 'title', 'body', 'slug'] django-braces-1.9.0/tests/helpers.py000066400000000000000000000034631272337476100174200ustar00rootroot00000000000000from django import test from django.contrib.auth.models import AnonymousUser from django.core.serializers.json import DjangoJSONEncoder class TestViewHelper(object): """ Helper class for unit-testing class based views. """ view_class = None request_factory_class = test.RequestFactory def setUp(self): super(TestViewHelper, self).setUp() self.factory = self.request_factory_class() def build_request(self, method='GET', path='/test/', user=None, **kwargs): """ Creates a request using request factory. """ fn = getattr(self.factory, method.lower()) if user is None: user = AnonymousUser() req = fn(path, **kwargs) req.user = user return req def build_view(self, request, args=None, kwargs=None, view_class=None, **viewkwargs): """ Creates a `view_class` view instance. """ if not args: args = () if not kwargs: kwargs = {} if view_class is None: view_class = self.view_class return view_class( request=request, args=args, kwargs=kwargs, **viewkwargs) def dispatch_view(self, request, args=None, kwargs=None, view_class=None, **viewkwargs): """ Creates and dispatches `view_class` view. """ view = self.build_view(request, args, kwargs, view_class, **viewkwargs) return view.dispatch(request, *view.args, **view.kwargs) class SetJSONEncoder(DjangoJSONEncoder): """ A custom JSONEncoder extending `DjangoJSONEncoder` to handle serialization of `set`. """ def default(self, obj): if isinstance(obj, set): return list(obj) return super(DjangoJSONEncoder, self).default(obj) django-braces-1.9.0/tests/models.py000066400000000000000000000011731272337476100172350ustar00rootroot00000000000000from django.db import models class Article(models.Model): author = models.ForeignKey('auth.User', null=True, blank=True) title = models.CharField(max_length=30) body = models.TextField() slug = models.SlugField(blank=True) class CanonicalArticle(models.Model): author = models.ForeignKey('auth.User', null=True, blank=True) title = models.CharField(max_length=30) body = models.TextField() slug = models.SlugField(blank=True) def get_canonical_slug(self): if self.author: return "{0.author.username}-{0.slug}".format(self) return "unauthored-{0.slug}".format(self) django-braces-1.9.0/tests/settings.py000066400000000000000000000033051272337476100176110ustar00rootroot00000000000000from django.conf.global_settings import * DEBUG = False TEMPLATE_DEBUG = DEBUG TIME_ZONE = 'UTC' LANGUAGE_CODE = 'en-US' SITE_ID = 1 USE_L10N = True USE_TZ = True SECRET_KEY = 'local' ROOT_URLCONF = 'tests.urls' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', } } MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] TEMPLATE_CONTEXT_PROCESSORS = [ 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.core.context_processors.static', 'django.core.context_processors.tz', 'django.core.context_processors.request', 'django.contrib.messages.context_processors.messages' ] STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', 'tests', ) PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', ) import django if django.VERSION < (1, 4): TEMPLATE_CONTEXT_PROCESSORS.remove('django.core.context_processors.tz') MIDDLEWARE_CLASSES.remove( 'django.middleware.clickjacking.XFrameOptionsMiddleware' ) django-braces-1.9.0/tests/templates/000077500000000000000000000000001272337476100173745ustar00rootroot00000000000000django-braces-1.9.0/tests/templates/404.html000066400000000000000000000000211272337476100205620ustar00rootroot00000000000000

404!!!!

django-braces-1.9.0/tests/templates/blank.html000066400000000000000000000000001272337476100213370ustar00rootroot00000000000000django-braces-1.9.0/tests/templates/form.html000066400000000000000000000002541272337476100212260ustar00rootroot00000000000000 {% if messages %} {% for message in messages %} {{ message }} {% endfor %} {% endif %} {{ form.as_p }} django-braces-1.9.0/tests/test_access_mixins.py000066400000000000000000000652341272337476100216510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import pytest import datetime from django import test from django import VERSION as DJANGO_VERSION from django.test.utils import override_settings from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.core.urlresolvers import reverse_lazy from django.http import Http404, HttpResponse from .compat import force_text from .factories import GroupFactory, UserFactory from .helpers import TestViewHelper from .views import (PermissionRequiredView, MultiplePermissionsRequiredView, SuperuserRequiredView, StaffuserRequiredView, LoginRequiredView, GroupRequiredView, UserPassesTestView, UserPassesTestNotImplementedView, AnonymousRequiredView, SSLRequiredView, RecentLoginRequiredView, UserPassesTestLoginRequiredView) class _TestAccessBasicsMixin(TestViewHelper): """ A set of basic tests for access mixins. """ view_url = None def build_authorized_user(self): """ Returns user authorized to access view. """ raise NotImplementedError def build_unauthorized_user(self): """ Returns user not authorized to access view. """ raise NotImplementedError def test_success(self): """ If user is authorized then view should return normal response. """ user = self.build_authorized_user() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) def test_redirects_to_login(self): """ Browser should be redirected to login page if user is not authorized to view this page. """ user = self.build_unauthorized_user() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertRedirects(resp, '/accounts/login/?next={0}'.format( self.view_url)) def test_raise_permission_denied(self): """ PermissionDenied should be raised if user is not authorized and raise_exception attribute is set to True. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) with self.assertRaises(PermissionDenied): self.dispatch_view(req, raise_exception=True) def test_raise_custom_exception(self): """ Http404 should be raised if user is not authorized and raise_exception attribute is set to Http404. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) with self.assertRaises(Http404): self.dispatch_view(req, raise_exception=Http404) def test_raise_func_pass(self): """ An exception should be raised if user is not authorized and raise_exception attribute is set to a function that returns nothing. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) def func(request): pass with self.assertRaises(PermissionDenied): self.dispatch_view(req, raise_exception=func) def test_raise_func_response(self): """ A custom response should be returned if user is not authorized and raise_exception attribute is set to a function that returns a response. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) def func(request): return HttpResponse("CUSTOM") resp = self.dispatch_view(req, raise_exception=func) assert resp.status_code == 200 assert force_text(resp.content) == 'CUSTOM' def test_raise_func_false(self): """ PermissionDenied should be raised, if a custom raise_exception function does not return HttpResponse or StreamingHttpResponse. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) def func(request): return False with self.assertRaises(PermissionDenied): self.dispatch_view(req, raise_exception=func) def test_raise_func_raises(self): """ A custom exception should be raised if user is not authorized and raise_exception attribute is set to a callable that raises an exception. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) def func(request): raise Http404 with self.assertRaises(Http404): self.dispatch_view(req, raise_exception=func) def test_custom_login_url(self): """ Login url should be customizable. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) resp = self.dispatch_view(req, login_url='/login/') self.assertEqual( '/login/?next={0}'.format(self.view_url), resp['Location']) # Test with reverse_lazy resp = self.dispatch_view(req, login_url=reverse_lazy('headline')) self.assertEqual('/headline/?next={0}'.format( self.view_url), resp['Location']) def test_custom_redirect_field_name(self): """ Redirect field name should be customizable. """ user = self.build_unauthorized_user() req = self.build_request(user=user, path=self.view_url) resp = self.dispatch_view(req, redirect_field_name='foo') expected_url = '/accounts/login/?foo={0}'.format(self.view_url) self.assertEqual(expected_url, resp['Location']) @override_settings(LOGIN_URL=None) def test_get_login_url_raises_exception(self): """ Test that get_login_url from AccessMixin raises ImproperlyConfigured. """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view( self.build_request(path=self.view_url), login_url=None) def test_get_redirect_field_name_raises_exception(self): """ Test that get_redirect_field_name from AccessMixin raises ImproperlyConfigured. """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view( self.build_request(path=self.view_url), redirect_field_name=None) @override_settings(LOGIN_URL="/auth/login/") def test_overridden_login_url(self): """ Test that login_url is not set in stone on module load but can be overridden dynamically. """ user = self.build_unauthorized_user() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertRedirects(resp, '/auth/login/?next={0}'.format( self.view_url)) def test_redirect_unauthenticated(self): resp = self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True, redirect_unauthenticated_users=True) assert resp.status_code == 302 assert resp['Location'] == '/accounts/login/?next={0}'.format( self.view_url) def test_redirect_unauthenticated_false(self): with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True, redirect_unauthenticated_users=False) class TestLoginRequiredMixin(TestViewHelper, test.TestCase): """ Tests for LoginRequiredMixin. """ view_class = LoginRequiredView view_url = '/login_required/' def test_anonymous(self): resp = self.client.get(self.view_url) self.assertRedirects(resp, '/accounts/login/?next=/login_required/') def test_anonymous_raises_exception(self): with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True) def test_authenticated(self): user = UserFactory() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) assert resp.status_code == 200 assert force_text(resp.content) == 'OK' def test_anonymous_redirects(self): resp = self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True, redirect_unauthenticated_users=True) assert resp.status_code == 302 assert resp['Location'] == '/accounts/login/?next=/login_required/' class TestChainedLoginRequiredMixin(TestViewHelper, test.TestCase): """ Tests for LoginRequiredMixin combined with another AccessMixin. """ view_class = UserPassesTestLoginRequiredView view_url = '/chained_view/' def assert_redirect_to_login(self, response): """ Check that the response is a redirect to the login view. """ assert response.status_code == 302 assert response['Location'] == '/accounts/login/?next=/chained_view/' def test_anonymous(self): """ Check that anonymous users redirect to login by default. """ resp = self.dispatch_view( self.build_request(path=self.view_url)) self.assert_redirect_to_login(resp) def test_anonymous_raises_exception(self): """ Check that when anonymous users hit a view that has only raise_exception set, they get a PermissionDenied. """ with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True) def test_authenticated_raises_exception(self): """ Check that when authenticated users hit a view that has raise_exception set, they get a PermissionDenied. """ user = UserFactory() with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(path=self.view_url, user=user), raise_exception=True) with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(path=self.view_url, user=user), raise_exception=True, redirect_unauthenticated_users=True) def test_anonymous_redirects(self): """ Check that anonymous users are redirected to login when raise_exception is overridden by redirect_unauthenticated_users. """ resp = self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True, redirect_unauthenticated_users=True) self.assert_redirect_to_login(resp) class TestAnonymousRequiredMixin(TestViewHelper, test.TestCase): """ Tests for AnonymousRequiredMixin. """ view_class = AnonymousRequiredView view_url = '/unauthenticated_view/' def test_anonymous(self): """ As a non-authenticated user, it should be possible to access the URL. """ resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) # Test with reverse_lazy resp = self.dispatch_view( self.build_request(), login_url=reverse_lazy(self.view_url)) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) def test_authenticated(self): """ Check that the authenticated user has been successfully directed to the approparite view. """ user = UserFactory() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(302, resp.status_code) resp = self.client.get(self.view_url, follow=True) self.assertRedirects(resp, '/authenticated_view/') def test_no_url(self): self.view_class.authenticated_redirect_url = None user = UserFactory() self.client.login(username=user.username, password='asdf1234') with self.assertRaises(ImproperlyConfigured): self.client.get(self.view_url) def test_bad_url(self): self.view_class.authenticated_redirect_url = '/epicfailurl/' user = UserFactory() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url, follow=True) self.assertEqual(404, resp.status_code) class TestPermissionRequiredMixin(_TestAccessBasicsMixin, test.TestCase): """ Tests for PermissionRequiredMixin. """ view_class = PermissionRequiredView view_url = '/permission_required/' def build_authorized_user(self): return UserFactory(permissions=['auth.add_user']) def build_unauthorized_user(self): return UserFactory() def test_invalid_permission(self): """ ImproperlyConfigured exception should be raised in two situations: if permission is None or if permission has invalid name. """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), permission_required=None) class TestMultiplePermissionsRequiredMixin( _TestAccessBasicsMixin, test.TestCase): view_class = MultiplePermissionsRequiredView view_url = '/multiple_permissions_required/' def build_authorized_user(self): return UserFactory(permissions=[ 'tests.add_article', 'tests.change_article', 'auth.change_user']) def build_unauthorized_user(self): return UserFactory(permissions=['tests.add_article']) def test_redirects_to_login(self): """ User should be redirected to login page if he or she does not have sufficient permissions. """ url = '/multiple_permissions_required/' test_cases = ( # missing one permission from 'any' ['tests.add_article', 'tests.change_article'], # missing one permission from 'all' ['tests.add_article', 'auth.add_user'], # no permissions at all [], ) for permissions in test_cases: user = UserFactory(permissions=permissions) self.client.login(username=user.username, password='asdf1234') resp = self.client.get(url) self.assertRedirects(resp, '/accounts/login/?next={0}'.format( url)) def test_invalid_permissions(self): """ ImproperlyConfigured exception should be raised if permissions attribute is set incorrectly. """ permissions = ( None, # permissions must be set (), # and they must be a dict {}, # at least one of 'all', 'any' keys must be present {'all': None}, # both all and any must be list or a tuple {'all': {'a': 1}}, {'any': None}, {'any': {'a': 1}}, ) for attr in permissions: with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), permissions=attr) def test_raise_permission_denied(self): """ PermissionDenied should be raised if user does not have sufficient permissions and raise_exception is set to True. """ test_cases = ( # missing one permission from 'any' ['tests.add_article', 'tests.change_article'], # missing one permission from 'all' ['tests.add_article', 'auth.add_user'], # no permissions at all [], ) for permissions in test_cases: user = UserFactory(permissions=permissions) req = self.build_request(user=user) with self.assertRaises(PermissionDenied): self.dispatch_view(req, raise_exception=True) def test_all_permissions_key(self): """ Tests if everything works if only 'all' permissions has been set. """ permissions = {'all': ['auth.add_user', 'tests.add_article']} user = UserFactory(permissions=permissions['all']) req = self.build_request(user=user) resp = self.dispatch_view(req, permissions=permissions) self.assertEqual('OK', force_text(resp.content)) user = UserFactory(permissions=['auth.add_user']) with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(user=user), raise_exception=True, permissions=permissions) def test_any_permissions_key(self): """ Tests if everything works if only 'any' permissions has been set. """ permissions = {'any': ['auth.add_user', 'tests.add_article']} user = UserFactory(permissions=['tests.add_article']) req = self.build_request(user=user) resp = self.dispatch_view(req, permissions=permissions) self.assertEqual('OK', force_text(resp.content)) user = UserFactory(permissions=[]) with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(user=user), raise_exception=True, permissions=permissions) class TestSuperuserRequiredMixin(_TestAccessBasicsMixin, test.TestCase): view_class = SuperuserRequiredView view_url = '/superuser_required/' def build_authorized_user(self): return UserFactory(is_superuser=True, is_staff=True) def build_unauthorized_user(self): return UserFactory() class TestStaffuserRequiredMixin(_TestAccessBasicsMixin, test.TestCase): view_class = StaffuserRequiredView view_url = '/staffuser_required/' def build_authorized_user(self): return UserFactory(is_staff=True) def build_unauthorized_user(self): return UserFactory() class TestGroupRequiredMixin(_TestAccessBasicsMixin, test.TestCase): view_class = GroupRequiredView view_url = '/group_required/' def build_authorized_user(self): user = UserFactory() group = GroupFactory(name='test_group') user.groups.add(group) return user def build_superuser(self): user = UserFactory() user.is_superuser = True user.save() return user def build_unauthorized_user(self): return UserFactory() def test_with_string(self): self.assertEqual('test_group', self.view_class.group_required) user = self.build_authorized_user() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) def test_with_group_list(self): group_list = ['test_group', 'editors'] # the test client will instantiate a new view on request, so we have to # modify the class variable (and restore it when the test finished) self.view_class.group_required = group_list self.assertEqual(group_list, self.view_class.group_required) user = self.build_authorized_user() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) self.view_class.group_required = 'test_group' self.assertEqual('test_group', self.view_class.group_required) def test_superuser_allowed(self): user = self.build_superuser() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) def test_improperly_configured(self): view = self.view_class() view.group_required = None with self.assertRaises(ImproperlyConfigured): view.get_group_required() view.group_required = {'foo': 'bar'} with self.assertRaises(ImproperlyConfigured): view.get_group_required() def test_with_unicode(self): self.view_class.group_required = 'niño' self.assertEqual('niño', self.view_class.group_required) user = self.build_authorized_user() group = user.groups.all()[0] group.name = 'niño' group.save() self.assertEqual('niño', user.groups.all()[0].name) self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) self.view_class.group_required = 'test_group' self.assertEqual('test_group', self.view_class.group_required) class TestUserPassesTestMixin(_TestAccessBasicsMixin, test.TestCase): view_class = UserPassesTestView view_url = '/user_passes_test/' view_not_implemented_class = UserPassesTestNotImplementedView view_not_implemented_url = '/user_passes_test_not_implemented/' # for testing with passing and not passsing func_test def build_authorized_user(self, is_superuser=False): return UserFactory(is_superuser=is_superuser, is_staff=True, email="user@mydomain.com") def build_unauthorized_user(self): return UserFactory() def test_with_user_pass(self): user = self.build_authorized_user() self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual('OK', force_text(resp.content)) def test_with_user_not_pass(self): user = self.build_authorized_user(is_superuser=True) self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.view_url) self.assertRedirects(resp, '/accounts/login/?next=/user_passes_test/') def test_with_user_raise_exception(self): with self.assertRaises(PermissionDenied): self.dispatch_view( self.build_request(path=self.view_url), raise_exception=True) def test_not_implemented(self): view = self.view_not_implemented_class() with self.assertRaises(NotImplementedError): view.dispatch( self.build_request(path=self.view_not_implemented_url), raise_exception=True) class TestSSLRequiredMixin(test.TestCase): view_class = SSLRequiredView view_url = '/sslrequired/' @pytest.mark.skipif(DJANGO_VERSION[:2] < (1, 9), reason='Django 1.9 and above behave differently') def test_ssl_redirection_django_19_up(self): self.view_url = 'https://testserver' + self.view_url self.view_class.raise_exception = False resp = self.client.get(self.view_url) self.assertRedirects(resp, self.view_url, status_code=301) resp = self.client.get(self.view_url, follow=True) self.assertEqual(200, resp.status_code) self.assertEqual('https', resp.request.get('wsgi.url_scheme')) @pytest.mark.skipif(DJANGO_VERSION[:2] < (1, 7), reason='Django 1.6 and below behave differently') @pytest.mark.skipif(DJANGO_VERSION[:2] > (1, 8), reason='Django 1.6 and below behave differently') def test_ssl_redirection_django_17_up(self): self.view_class.raise_exception = False resp = self.client.get(self.view_url) self.assertRedirects(resp, self.view_url, status_code=301) resp = self.client.get(self.view_url, follow=True) self.assertEqual(200, resp.status_code) self.assertEqual('https', resp.request.get('wsgi.url_scheme')) @pytest.mark.skipif(DJANGO_VERSION[:2] > (1, 6), reason='Django 1.7 and above behave differently') def test_ssl_redirection_django_16_down(self): self.view_class.raise_exception = False resp = self.client.get(self.view_url) self.assertEqual(301, resp.status_code) resp = self.client.get(self.view_url, follow=True) self.assertEqual(200, resp.status_code) self.assertEqual('https', resp.request.get('wsgi.url_scheme')) def test_raises_exception(self): self.view_class.raise_exception = True resp = self.client.get(self.view_url) self.assertEqual(404, resp.status_code) @override_settings(DEBUG=True) def test_debug_bypasses_redirect(self): self.view_class.raise_exception = False resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) @pytest.mark.skipif( DJANGO_VERSION[:2] < (1, 7), reason='Djanog 1.6 and below does not have the secure=True option') def test_https_does_not_redirect_django_17_up(self): self.view_class.raise_exception = False resp = self.client.get(self.view_url, secure=True) self.assertEqual(200, resp.status_code) self.assertEqual('https', resp.request.get('wsgi.url_scheme')) @pytest.mark.skipif( DJANGO_VERSION[:2] > (1, 6), reason='Django 1.7 and above have secure=True option, below does not') def test_https_does_not_redirect_django_16_down(self): self.view_class.raise_exception = False resp = self.client.get(self.view_url, **{'wsgi.url_scheme': 'https'}) self.assertEqual(200, resp.status_code) self.assertEqual('https', resp.request.get('wsgi.url_scheme')) class TestRecentLoginRequiredMixin(test.TestCase): """ Tests for RecentLoginRequiredMixin. """ view_class = RecentLoginRequiredView recent_view_url = '/recent_login/' outdated_view_url = '/outdated_login/' def test_recent_login(self): self.view_class.max_last_login_delta = 1800 last_login = datetime.datetime.now() user = UserFactory(last_login=last_login) self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.recent_view_url) assert resp.status_code == 200 assert force_text(resp.content) == 'OK' def test_outdated_login(self): self.view_class.max_last_login_delta = 0 last_login = datetime.datetime.now() - datetime.timedelta(hours=2) user = UserFactory(last_login=last_login) self.client.login(username=user.username, password='asdf1234') resp = self.client.get(self.outdated_view_url) assert resp.status_code == 302 def test_not_logged_in(self): last_login = datetime.datetime.now() user = UserFactory(last_login=last_login) resp = self.client.get(self.recent_view_url) assert resp.status_code != 200 django-braces-1.9.0/tests/test_ajax_mixins.py000066400000000000000000000161041272337476100213230ustar00rootroot00000000000000from __future__ import absolute_import, unicode_literals import mock from django import test from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponse from braces.views import AjaxResponseMixin from .compat import force_text from .factories import ArticleFactory, UserFactory from .helpers import TestViewHelper from .views import (SimpleJsonView, JsonRequestResponseView, CustomJsonEncoderView) from .compat import json class TestAjaxResponseMixin(TestViewHelper, test.TestCase): """ Tests for AjaxResponseMixin. """ methods = ['get', 'post', 'put', 'delete'] def test_xhr(self): """ Checks if ajax_* method has been called for every http method. """ # AjaxResponseView returns 'AJAX_OK' when requested with XmlHttpRequest for m in self.methods: fn = getattr(self.client, m) resp = fn('/ajax_response/', HTTP_X_REQUESTED_WITH='XMLHttpRequest') assert force_text(resp.content) == 'AJAX_OK' def test_not_xhr(self): """ Normal methods (get, post, etc) should be used when handling non-ajax requests. """ for m in self.methods: fn = getattr(self.client, m) resp = fn('/ajax_response/') assert force_text(resp.content) == 'OK' def test_fallback_to_normal_methods(self): """ Ajax methods should fallback to normal methods by default. """ test_cases = [ ('get', 'get'), ('post', 'post'), ('put', 'get'), ('delete', 'get'), ] for ajax_method, fallback in test_cases: m, mixin = mock.Mock(), AjaxResponseMixin() m.return_value = HttpResponse() req = self.build_request() setattr(mixin, fallback, m) fn = getattr(mixin, "{0}_ajax".format(ajax_method)) ret = fn(req, 1, 2, meth=ajax_method) # check if appropriate method has been called m.assert_called_once_with(req, 1, 2, meth=ajax_method) # check if appropriate value has been returned self.assertIs(m.return_value, ret) class TestJSONResponseMixin(TestViewHelper, test.TestCase): """ Tests for JSONResponseMixin. """ view_class = SimpleJsonView def assert_json_response(self, resp, status_code=200): self.assertEqual(status_code, resp.status_code) self.assertEqual('application/json', resp['content-type'].split(';')[0]) def get_content(self, url): """ GET url and return content """ resp = self.client.get(url) self.assert_json_response(resp) content = force_text(resp.content) return content def test_simple_json(self): """ Tests render_json_response() method. """ user = UserFactory() self.client.login(username=user.username, password='asdf1234') data = json.loads(self.get_content('/simple_json/')) self.assertEqual({'username': user.username}, data) def test_serialization(self): """ Tests render_json_object_response() method which serializes objects using django's serializer framework. """ a1, a2 = [ArticleFactory() for __ in range(2)] data = json.loads(self.get_content('/article_list_json/')) self.assertIsInstance(data, list) self.assertEqual(2, len(data)) titles = [] for row in data: # only title has been serialized self.assertEqual(1, len(row['fields'])) titles.append(row['fields']['title']) self.assertIn(a1.title, titles) self.assertIn(a2.title, titles) def test_bad_content_type(self): """ ImproperlyConfigured exception should be raised if content_type attribute is not set correctly. """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), content_type=['a']) def test_pretty_json(self): """ Success if JSON responses are the same, and the well-indented response is longer than the normal one. """ user = UserFactory() self.client.login(username=user.username, password='asfa') normal_content = self.get_content('/simple_json/') self.view_class.json_dumps_kwargs = {'indent': 2} pretty_content = self.get_content('/simple_json/') normal_json = json.loads('{0}'.format(normal_content)) pretty_json = json.loads('{0}'.format(pretty_content)) self.assertEqual(normal_json, pretty_json) self.assertTrue(len(pretty_content) > len(normal_content)) def test_json_encoder_class_atrribute(self): """ Tests setting custom `json_encoder_class` attribute. """ data = json.loads(self.get_content('/simple_json_custom_encoder/')) self.assertEqual({'numbers': [1, 2, 3]}, data) class TestJsonRequestResponseMixin(TestViewHelper, test.TestCase): view_class = JsonRequestResponseView request_dict = {'status': 'operational'} def test_get_request_json_properly_formatted(self): """ Properly formatted JSON requests should result in a JSON object """ data = json.dumps(self.request_dict).encode('utf-8') response = self.client.post( '/json_request/', content_type='application/json', data=data ) response_json = json.loads(response.content.decode('utf-8')) self.assertEqual(response.status_code, 200) self.assertEqual(response_json, self.request_dict) def test_get_request_json_improperly_formatted(self): """ Improperly formatted JSON requests should make request_json == None """ response = self.client.post( '/json_request/', data=self.request_dict ) response_json = json.loads(response.content.decode('utf-8')) self.assertEqual(response.status_code, 200) self.assertEqual(response_json, None) def test_bad_request_response(self): """ If a view calls render_bad_request_response when request_json is empty or None, the client should get a 400 error """ response = self.client.post( '/json_bad_request/', data=self.request_dict ) response_json = json.loads(response.content.decode('utf-8')) self.assertEqual(response.status_code, 400) self.assertEqual(response_json, self.view_class.error_response_dict) def test_bad_request_response_with_custom_error_message(self): """ If a view calls render_bad_request_response when request_json is empty or None, the client should get a 400 error """ response = self.client.post( '/json_custom_bad_request/', data=self.request_dict ) response_json = json.loads(response.content.decode('utf-8')) self.assertEqual(response.status_code, 400) self.assertEqual(response_json, {'error': 'you messed up'}) django-braces-1.9.0/tests/test_forms.py000066400000000000000000000014051272337476100201350ustar00rootroot00000000000000from __future__ import absolute_import from django import test from django.contrib.auth.models import User from . import forms class TestUserKwargModelFormMixin(test.TestCase): """ Tests for UserKwargModelFormMixin. """ def test_without_user_kwarg(self): """ It should be possible to create form without 'user' kwarg. In that case 'user' attribute should be set to None. """ form = forms.FormWithUserKwarg() assert form.user is None def test_with_user_kwarg(self): """ Form's 'user' attribute should be set to value passed as 'user' argument. """ user = User(username='test') form = forms.FormWithUserKwarg(user=user) assert form.user is user django-braces-1.9.0/tests/test_other_mixins.py000066400000000000000000000637401272337476100215310ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import mock import pytest from django import VERSION as DJANGO_VERSION from django.contrib import messages from django.contrib.messages.middleware import MessageMiddleware from django.contrib.messages.storage.base import Message from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponse from django import test from django.test.utils import override_settings from django.views.generic import View from braces.views import (SetHeadlineMixin, MessageMixin, _MessageAPIWrapper, FormValidMessageMixin, FormInvalidMessageMixin) from .compat import force_text from .factories import UserFactory from .helpers import TestViewHelper from .models import Article, CanonicalArticle from .views import (ArticleListView, ArticleListViewWithCustomQueryset, AuthorDetailView, OrderableListView, FormMessagesView, ContextView) class TestSuccessURLRedirectListMixin(test.TestCase): """ Tests for SuccessURLRedirectListMixin. """ def test_redirect(self): """ Test if browser is redirected to list view. """ data = {'title': "Test body", 'body': "Test body"} resp = self.client.post('/article_list/create/', data) self.assertRedirects(resp, '/article_list/') def test_no_url_name(self): """ Test that ImproperlyConfigured is raised. """ data = {'title': "Test body", 'body': "Test body"} with self.assertRaises(ImproperlyConfigured): self.client.post('/article_list_bad/create/', data) class TestUserFormKwargsMixin(test.TestCase): """ Tests for UserFormKwargsMixin. """ def test_post_method(self): user = UserFactory() self.client.login(username=user.username, password='asdf1234') resp = self.client.post('/form_with_user_kwarg/', {'field1': 'foo'}) assert force_text(resp.content) == "username: %s" % user.username def test_get_method(self): user = UserFactory() self.client.login(username=user.username, password='asdf1234') resp = self.client.get('/form_with_user_kwarg/') assert resp.context['form'].user == user class TestSetHeadlineMixin(test.TestCase): """ Tests for SetHeadlineMixin. """ def test_dynamic_headline(self): """ Tests if get_headline() is called properly. """ resp = self.client.get('/headline/test-headline/') self.assertEqual('test-headline', resp.context['headline']) def test_context_data(self): """ Tests if mixin adds proper headline to template context. """ resp = self.client.get('/headline/foo-bar/') self.assertEqual("foo-bar", resp.context['headline']) def test_get_headline(self): """ Tests if get_headline() method works correctly. """ mixin = SetHeadlineMixin() with self.assertRaises(ImproperlyConfigured): mixin.get_headline() mixin.headline = "Test headline" self.assertEqual("Test headline", mixin.get_headline()) def test_get_headline_lazy(self): resp = self.client.get('/headline/lazy/') self.assertEqual('Test Headline', resp.context['headline']) class TestStaticContextMixin(test.TestCase): """ Tests for StaticContextMixin. """ view_class = ContextView view_url = '/context/' def test_dict(self): self.view_class.static_context = {'test': True} resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual(True, resp.context['test']) def test_two_tuple(self): self.view_class.static_context = [('a', 1), ('b', 2)] resp = self.client.get(self.view_url) self.assertEqual(200, resp.status_code) self.assertEqual(1, resp.context['a']) self.assertEqual(2, resp.context['b']) def test_not_set(self): self.view_class.static_context = None with self.assertRaises(ImproperlyConfigured): self.client.get(self.view_url) def test_string_value_error(self): self.view_class.static_context = 'Fail' with self.assertRaises(ImproperlyConfigured): self.client.get(self.view_url) def test_list_error(self): self.view_class.static_context = ['fail', 'fail'] with self.assertRaises(ImproperlyConfigured): self.client.get(self.view_url) class TestCsrfExemptMixin(test.TestCase): """ Tests for TestCsrfExemptMixin. """ def setUp(self): super(TestCsrfExemptMixin, self).setUp() self.client = self.client_class(enforce_csrf_checks=True) def test_csrf_token_is_not_required(self): """ Tests if csrf token is not required. """ resp = self.client.post('/csrf_exempt/', {'field1': 'test'}) self.assertEqual(200, resp.status_code) self.assertEqual("OK", force_text(resp.content)) class TestSelectRelatedMixin(TestViewHelper, test.TestCase): view_class = ArticleListView def test_missing_select_related(self): """ ImproperlyConfigured exception should be raised if select_related attribute is missing. """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), select_related=None) def test_invalid_select_related(self): """ ImproperlyConfigured exception should be raised if select_related is not a tuple or a list. :return: """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), select_related={'a': 1}) @mock.patch('django.db.models.query.QuerySet.select_related') def test_select_related_called(self, m): """ Checks if QuerySet's select_related() was called with correct arguments. """ qs = Article.objects.all() m.return_value = qs.select_related('author') qs.select_related = m m.reset_mock() resp = self.dispatch_view(self.build_request()) self.assertEqual(200, resp.status_code) m.assert_called_once_with('author') @mock.patch('django.db.models.query.QuerySet.select_related') def test_select_related_keeps_select_related_from_queryset(self, m): """ Checks that an empty select_related attribute does not cancel a select_related provided by queryset. """ qs = Article.objects.all() qs.select_related = m m.reset_mock() resp = self.dispatch_view( self.build_request(), view_class=ArticleListViewWithCustomQueryset) self.assertEqual(200, resp.status_code) self.assertEqual(0, m.call_count) class TestPrefetchRelatedMixin(TestViewHelper, test.TestCase): view_class = AuthorDetailView def test_missing_prefetch_related(self): """ ImproperlyConfigured exception should be raised if prefetch_related attribute is missing. """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), prefetch_related=None) def test_invalid_prefetch_related(self): """ ImproperlyConfigured exception should be raised if prefetch_related is not a tuple or a list. :return: """ with self.assertRaises(ImproperlyConfigured): self.dispatch_view(self.build_request(), prefetch_related={'a': 1}) @mock.patch('django.db.models.query.QuerySet.prefetch_related') def test_prefetch_related_called(self, m): """ Checks if QuerySet's prefetch_related() was called with correct arguments. """ qs = Article.objects.all() m.return_value = qs.prefetch_related('article_set') qs.prefetch_related = m m.reset_mock() resp = self.dispatch_view(self.build_request()) self.assertEqual(200, resp.status_code) m.assert_called_once_with('article_set') @mock.patch('django.db.models.query.QuerySet.prefetch_related') def test_prefetch_related_keeps_select_related_from_queryset(self, m): """ Checks that an empty prefetch_related attribute does not cancel a prefetch_related provided by queryset. """ qs = Article.objects.all() qs.prefetch_related = m m.reset_mock() resp = self.dispatch_view( self.build_request(), view_class=ArticleListViewWithCustomQueryset) self.assertEqual(200, resp.status_code) self.assertEqual(0, m.call_count) class TestOrderableListMixin(TestViewHelper, test.TestCase): view_class = OrderableListView def __make_test_articles(self): a1 = Article.objects.create(title='Alpha', body='Zet') a2 = Article.objects.create(title='Zet', body='Alpha') return a1, a2 def test_correct_order(self): """ Objects must be properly ordered if requested with valid column names """ a1, a2 = self.__make_test_articles() resp = self.dispatch_view( self.build_request(path='?order_by=title&ordering=asc'), orderable_columns=None, get_orderable_columns=lambda: ('id', 'title', )) self.assertEqual(list(resp.context_data['object_list']), [a1, a2]) resp = self.dispatch_view( self.build_request(path='?order_by=id&ordering=desc'), orderable_columns=None, get_orderable_columns=lambda: ('id', 'title', )) self.assertEqual(list(resp.context_data['object_list']), [a2, a1]) def test_default_column(self): """ When no ordering specified in GET, use View.get_orderable_columns_default() """ a1, a2 = self.__make_test_articles() resp = self.dispatch_view(self.build_request()) self.assertEqual(list(resp.context_data['object_list']), [a1, a2]) def test_get_orderable_columns_returns_correct_values(self): """ OrderableListMixin.get_orderable_columns() should return View.orderable_columns attribute by default or raise ImproperlyConfigured exception in the attribute is None """ view = self.view_class() self.assertEqual(view.get_orderable_columns(), view.orderable_columns) view.orderable_columns = None self.assertRaises(ImproperlyConfigured, lambda: view.get_orderable_columns()) def test_get_orderable_columns_default_returns_correct_values(self): """ OrderableListMixin.get_orderable_columns_default() should return View.orderable_columns_default attribute by default or raise ImproperlyConfigured exception in the attribute is None """ view = self.view_class() self.assertEqual(view.get_orderable_columns_default(), view.orderable_columns_default) view.orderable_columns_default = None self.assertRaises(ImproperlyConfigured, lambda: view.get_orderable_columns_default()) def test_only_allowed_columns(self): """ If column is not in Model.Orderable.columns iterable, the objects should be ordered by default column. """ a1, a2 = self.__make_test_articles() resp = self.dispatch_view( self.build_request(path='?order_by=body&ordering=asc'), orderable_columns_default=None, get_orderable_columns_default=lambda: 'title') self.assertEqual(list(resp.context_data['object_list']), [a1, a2]) class TestCanonicalSlugDetailView(test.TestCase): def setUp(self): Article.objects.create(title='Alpha', body='Zet', slug='alpha') Article.objects.create(title='Zet', body='Alpha', slug='zet') def test_canonical_slug(self): """ Test that no redirect occurs when slug is canonical. """ resp = self.client.get('/article-canonical/1-alpha/') self.assertEqual(resp.status_code, 200) resp = self.client.get('/article-canonical/2-zet/') self.assertEqual(resp.status_code, 200) def test_non_canonical_slug(self): """ Test that a redirect occurs when the slug is non-canonical. """ resp = self.client.get('/article-canonical/1-bad-slug/') self.assertEqual(resp.status_code, 301) resp = self.client.get('/article-canonical/2-bad-slug/') self.assertEqual(resp.status_code, 301) class TestNamespaceAwareCanonicalSlugDetailView(test.TestCase): def setUp(self): Article.objects.create(title='Alpha', body='Zet', slug='alpha') Article.objects.create(title='Zet', body='Alpha', slug='zet') def test_canonical_slug(self): """ Test that no redirect occurs when slug is canonical. """ resp = self.client.get( '/article-canonical-namespaced/article/1-alpha/') self.assertEqual(resp.status_code, 200) resp = self.client.get( '/article-canonical-namespaced/article/2-zet/') self.assertEqual(resp.status_code, 200) def test_non_canonical_slug(self): """ Test that a redirect occurs when the slug is non-canonical and that the redirect is namespace aware. """ resp = self.client.get( '/article-canonical-namespaced/article/1-bad-slug/') self.assertEqual(resp.status_code, 301) resp = self.client.get( '/article-canonical-namespaced/article/2-bad-slug/') self.assertEqual(resp.status_code, 301) class TestOverriddenCanonicalSlugDetailView(test.TestCase): def setUp(self): Article.objects.create(title='Alpha', body='Zet', slug='alpha') Article.objects.create(title='Zet', body='Alpha', slug='zet') def test_canonical_slug(self): """ Test that no redirect occurs when slug is canonical according to the overridden canonical slug. """ resp = self.client.get('/article-canonical-override/1-nycun/') self.assertEqual(resp.status_code, 200) resp = self.client.get('/article-canonical-override/2-mrg/') self.assertEqual(resp.status_code, 200) def test_non_canonical_slug(self): """ Test that a redirect occurs when the slug is non-canonical. """ resp = self.client.get('/article-canonical-override/1-bad-slug/') self.assertEqual(resp.status_code, 301) resp = self.client.get('/article-canonical-override/2-bad-slug/') self.assertEqual(resp.status_code, 301) class TestCustomUrlKwargsCanonicalSlugDetailView(test.TestCase): def setUp(self): Article.objects.create(title='Alpha', body='Zet', slug='alpha') Article.objects.create(title='Zet', body='Alpha', slug='zet') def test_canonical_slug(self): """ Test that no redirect occurs when slug is canonical """ resp = self.client.get('/article-canonical-custom-kwargs/1-alpha/') self.assertEqual(resp.status_code, 200) resp = self.client.get('/article-canonical-custom-kwargs/2-zet/') self.assertEqual(resp.status_code, 200) def test_non_canonical_slug(self): """ Test that a redirect occurs when the slug is non-canonical. """ resp = self.client.get('/article-canonical-custom-kwargs/1-bad-slug/') self.assertEqual(resp.status_code, 301) resp = self.client.get('/article-canonical-custom-kwargs/2-bad-slug/') self.assertEqual(resp.status_code, 301) class TestModelCanonicalSlugDetailView(test.TestCase): def setUp(self): CanonicalArticle.objects.create( title='Alpha', body='Zet', slug='alpha') CanonicalArticle.objects.create( title='Zet', body='Alpha', slug='zet') def test_canonical_slug(self): """ Test that no redirect occurs when slug is canonical according to the model's canonical slug. """ resp = self.client.get('/article-canonical-model/1-unauthored-alpha/') self.assertEqual(resp.status_code, 200) resp = self.client.get('/article-canonical-model/2-unauthored-zet/') self.assertEqual(resp.status_code, 200) def test_non_canonical_slug(self): """ Test that a redirect occurs when the slug is non-canonical. """ resp = self.client.get('/article-canonical-model/1-bad-slug/') self.assertEqual(resp.status_code, 301) resp = self.client.get('/article-canonical-model/2-bad-slug/') self.assertEqual(resp.status_code, 301) # CookieStorage is used because it doesn't require middleware to be installed @override_settings( MESSAGE_STORAGE='django.contrib.messages.storage.cookie.CookieStorage') class MessageMixinTests(test.TestCase): def setUp(self): self.rf = test.RequestFactory() self.middleware = MessageMiddleware() def get_request(self, *args, **kwargs): request = self.rf.get('/') self.middleware.process_request(request) return request def get_response(self, request, view): response = view(request) self.middleware.process_response(request, response) return response def get_request_response(self, view, *args, **kwargs): request = self.get_request(*args, **kwargs) response = self.get_response(request, view) return request, response def test_add_messages(self): class TestView(MessageMixin, View): def get(self, request): self.messages.add_message(messages.SUCCESS, 'test') return HttpResponse('OK') request, response = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0].message, 'test') self.assertEqual(msg[0].level, messages.SUCCESS) def test_get_messages(self): class TestView(MessageMixin, View): def get(self, request): self.messages.add_message(messages.SUCCESS, 'success') self.messages.add_message(messages.WARNING, 'warning') content = ','.join( m.message for m in self.messages.get_messages()) return HttpResponse(content) _, response = self.get_request_response(TestView.as_view()) self.assertEqual(response.content, b"success,warning") def test_get_level(self): class TestView(MessageMixin, View): def get(self, request): return HttpResponse(self.messages.get_level()) _, response = self.get_request_response(TestView.as_view()) self.assertEqual(int(response.content), messages.INFO) # default def test_set_level(self): class TestView(MessageMixin, View): def get(self, request): self.messages.set_level(messages.WARNING) self.messages.add_message(messages.SUCCESS, 'success') self.messages.add_message(messages.WARNING, 'warning') return HttpResponse('OK') request, _ = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(msg, [Message(messages.WARNING, 'warning')]) @override_settings(MESSAGE_LEVEL=messages.DEBUG) def test_debug(self): class TestView(MessageMixin, View): def get(self, request): self.messages.debug("test") return HttpResponse('OK') request, _ = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0], Message(messages.DEBUG, 'test')) def test_info(self): class TestView(MessageMixin, View): def get(self, request): self.messages.info("test") return HttpResponse('OK') request, _ = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0], Message(messages.INFO, 'test')) def test_success(self): class TestView(MessageMixin, View): def get(self, request): self.messages.success("test") return HttpResponse('OK') request, _ = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0], Message(messages.SUCCESS, 'test')) def test_warning(self): class TestView(MessageMixin, View): def get(self, request): self.messages.warning("test") return HttpResponse('OK') request, _ = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0], Message(messages.WARNING, 'test')) def test_error(self): class TestView(MessageMixin, View): def get(self, request): self.messages.error("test") return HttpResponse('OK') request, _ = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0], Message(messages.ERROR, 'test')) def test_invalid_attribute(self): class TestView(MessageMixin, View): def get(self, request): self.messages.invalid() return HttpResponse('OK') with self.assertRaises(AttributeError): self.get_request_response(TestView.as_view()) @pytest.mark.skipif( DJANGO_VERSION < (1, 5), reason='Some features of MessageMixin are only available in ' 'Django >= 1.5') def test_wrapper_available_in_dispatch(self): """ Make sure that self.messages is available in dispatch() even before calling the parent's implementation. """ class TestView(MessageMixin, View): def dispatch(self, request): self.messages.add_message(messages.SUCCESS, 'test') return super(TestView, self).dispatch(request) def get(self, request): return HttpResponse('OK') request, response = self.get_request_response(TestView.as_view()) msg = list(request._messages) self.assertEqual(len(msg), 1) self.assertEqual(msg[0].message, 'test') self.assertEqual(msg[0].level, messages.SUCCESS) def test_API(self): """ Make sure that our assumptions about messages.api are still valid. """ # This test is designed to break when django.contrib.messages.api # changes (items being added or removed). excluded_API = set() if DJANGO_VERSION >= (1, 7): excluded_API.add('MessageFailure') self.assertEqual( _MessageAPIWrapper.API | excluded_API, set(messages.api.__all__) ) class TestFormMessageMixins(test.TestCase): def setUp(self): self.good_data = { 'title': 'Good', 'body': 'Body' } self.bad_data = { 'body': 'Missing title' } def test_valid_message(self): url = '/form_messages/' response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, self.good_data, follow=True) self.assertEqual(response.status_code, 200) self.assertContains(response, FormMessagesView().form_valid_message) def test_invalid_message(self): url = '/form_messages/' response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, self.bad_data, follow=True) self.assertEqual(response.status_code, 200) self.assertContains(response, FormMessagesView().form_invalid_message) def test_form_valid_message_not_set(self): mixin = FormValidMessageMixin() with self.assertRaises(ImproperlyConfigured): mixin.get_form_valid_message() def test_form_valid_message_not_str(self): mixin = FormValidMessageMixin() mixin.form_valid_message = ['bad'] with self.assertRaises(ImproperlyConfigured): mixin.get_form_valid_message() def test_form_valid_returns_message(self): mixin = FormValidMessageMixin() mixin.form_valid_message = 'Good øø' self.assertEqual('Good øø', mixin.get_form_valid_message()) def test_form_invalid_message_not_set(self): mixin = FormInvalidMessageMixin() with self.assertRaises(ImproperlyConfigured): mixin.get_form_invalid_message() def test_form_invalid_message_not_str(self): mixin = FormInvalidMessageMixin() mixin.form_invalid_message = ['bad'] with self.assertRaises(ImproperlyConfigured): mixin.get_form_invalid_message() def test_form_invalid_returns_message(self): mixin = FormInvalidMessageMixin() mixin.form_invalid_message = 'Bad øø' self.assertEqual('Bad øø', mixin.get_form_invalid_message()) class TestAllVerbsMixin(test.TestCase): def setUp(self): self.url = "/all_verbs/" self.no_handler_url = "/all_verbs_no_handler/" def test_options(self): response = self.client.options(self.url) self.assertEqual(response.status_code, 200) def test_get(self): response = self.client.get(self.url) self.assertEqual(response.status_code, 200) def test_head(self): response = self.client.head(self.url) self.assertEqual(response.status_code, 200) def test_post(self): response = self.client.post(self.url) self.assertEqual(response.status_code, 200) def test_put(self): response = self.client.put(self.url) self.assertEqual(response.status_code, 200) def test_delete(self): response = self.client.delete(self.url) self.assertEqual(response.status_code, 200) def test_no_all_handler(self): with self.assertRaises(ImproperlyConfigured): self.client.get('/all_verbs_no_handler/') django-braces-1.9.0/tests/urls.py000066400000000000000000000110211272337476100167300ustar00rootroot00000000000000from __future__ import absolute_import from . import views from .compat import patterns, include, url urlpatterns = patterns( '', # LoginRequiredMixin tests url(r'^login_required/$', views.LoginRequiredView.as_view()), # AnonymousRequiredView tests url(r'^unauthenticated_view/$', views.AnonymousRequiredView.as_view(), name='unauthenticated_view'), url(r'^authenticated_view/$', views.AuthenticatedView.as_view(), name='authenticated_view'), # AjaxResponseMixin tests url(r'^ajax_response/$', views.AjaxResponseView.as_view()), # CreateAndRedirectToEditView tests url(r'^article/create/$', views.CreateArticleView.as_view()), url(r'^article/(?P\d+)/edit/$', views.EditArticleView.as_view(), name="edit_article"), url(r'^article_list/create/$', views.CreateArticleAndRedirectToListView.as_view()), url(r'^article_list_bad/create/$', views.CreateArticleAndRedirectToListViewBad.as_view()), url(r'^article_list/$', views.ArticleListView.as_view(), name='article_list'), # CanonicalSlugDetailMixin tests url(r'^article-canonical/(?P\d+)-(?P[-\w]+)/$', views.CanonicalSlugDetailView.as_view(), name="canonical_slug"), url(r'^article-canonical-namespaced/', include('tests.urls_namespaced', namespace='some_namespace')), url(r'^article-canonical-override/(?P\d+)-(?P[-\w]+)/$', views.OverriddenCanonicalSlugDetailView.as_view(), name="canonical_override"), url(r'^article-canonical-custom-kwargs/(?P\d+)-(?P[-\w]+)/$', views.CanonicalSlugDetailCustomUrlKwargsView.as_view(), name="canonical_custom_kwargs"), url(r'^article-canonical-model/(?P\d+)-(?P[-\w]+)/$', views.ModelCanonicalSlugDetailView.as_view(), name="canonical_model"), # UserFormKwargsMixin tests url(r'^form_with_user_kwarg/$', views.FormWithUserKwargView.as_view()), # SetHeadlineMixin tests url(r'^headline/$', views.HeadlineView.as_view(), name='headline'), url(r'^headline/lazy/$', views.LazyHeadlineView.as_view()), url(r'^headline/(?P[\w-]+)/$', views.DynamicHeadlineView.as_view()), # ExtraContextMixin tests url(r'^context/$', views.ContextView.as_view(), name='context'), # PermissionRequiredMixin tests url(r'^permission_required/$', views.PermissionRequiredView.as_view()), # MultiplePermissionsRequiredMixin tests url(r'^multiple_permissions_required/$', views.MultiplePermissionsRequiredView.as_view()), # SuperuserRequiredMixin tests url(r'^superuser_required/$', views.SuperuserRequiredView.as_view()), # StaffuserRequiredMixin tests url(r'^staffuser_required/$', views.StaffuserRequiredView.as_view()), # GroupRequiredMixin tests url(r'^group_required/$', views.GroupRequiredView.as_view()), # UserPassesTestMixin tests url(r'^user_passes_test/$', views.UserPassesTestView.as_view()), # UserPassesTestMixin tests url(r'^user_passes_test_not_implemented/$', views.UserPassesTestNotImplementedView.as_view()), # CsrfExemptMixin tests url(r'^csrf_exempt/$', views.CsrfExemptView.as_view()), # JSONResponseMixin tests url(r'^simple_json/$', views.SimpleJsonView.as_view()), url(r'^simple_json_custom_encoder/$', views.CustomJsonEncoderView.as_view()), url(r'^simple_json_400/$', views.SimpleJsonBadRequestView.as_view()), url(r'^article_list_json/$', views.ArticleListJsonView.as_view()), # JsonRequestResponseMixin tests url(r'^json_request/$', views.JsonRequestResponseView.as_view()), url(r'^json_bad_request/$', views.JsonBadRequestView.as_view()), url(r'^json_custom_bad_request/$', views.JsonCustomBadRequestView.as_view()), # FormMessagesMixin tests url(r'form_messages/$', views.FormMessagesView.as_view()), # AllVerbsMixin tests url(r'all_verbs/$', views.AllVerbsView.as_view()), url(r'all_verbs_no_handler/$', views.AllVerbsView.as_view(all_handler=None)), # SSLRequiredMixin tests url(r'^sslrequired/$', views.SSLRequiredView.as_view()), # RecentLoginRequiredMixin tests url(r'^recent_login/$', views.RecentLoginRequiredView.as_view()), url(r'^outdated_login/$', views.RecentLoginRequiredView.as_view()), ) urlpatterns += patterns( 'django.contrib.auth.views', # login page, required by some tests url(r'^accounts/login/$', 'login', {'template_name': 'blank.html'}), url(r'^auth/login/$', 'login', {'template_name': 'blank.html'}), ) django-braces-1.9.0/tests/urls_namespaced.py000066400000000000000000000004721272337476100211200ustar00rootroot00000000000000from __future__ import absolute_import from . import views from .compat import patterns, url urlpatterns = patterns( '', # CanonicalSlugDetailMixin namespace tests url(r'^article/(?P\d+)-(?P[\w-]+)/$', views.CanonicalSlugDetailView.as_view(), name="namespaced_article"), ) django-braces-1.9.0/tests/views.py000066400000000000000000000230111272337476100171020ustar00rootroot00000000000000from __future__ import absolute_import import codecs from django.contrib.auth.models import User from django.http import HttpResponse from django.utils.translation import ugettext_lazy as _ from django.views.generic import (View, UpdateView, FormView, TemplateView, ListView, DetailView, CreateView) from braces import views from .models import Article, CanonicalArticle from .forms import ArticleForm, FormWithUserKwarg from .helpers import SetJSONEncoder class OkView(View): """ A view which simply returns "OK" for every request. """ def get(self, request): return HttpResponse("OK") def post(self, request): return self.get(request) def put(self, request): return self.get(request) def delete(self, request): return self.get(request) class LoginRequiredView(views.LoginRequiredMixin, OkView): """ A view for testing LoginRequiredMixin. """ class AnonymousRequiredView(views.AnonymousRequiredMixin, OkView): """ A view for testing AnonymousRequiredMixin. Should accept unauthenticated users and redirect authenticated users to the authenticated_redirect_url set on the view. """ authenticated_redirect_url = '/authenticated_view/' class AuthenticatedView(views.LoginRequiredMixin, OkView): """ A view for testing AnonymousRequiredMixin. Should accept authenticated users. """ class AjaxResponseView(views.AjaxResponseMixin, OkView): """ A view for testing AjaxResponseMixin. """ def get_ajax(self, request): return HttpResponse("AJAX_OK") def post_ajax(self, request): return self.get_ajax(request) def put_ajax(self, request): return self.get_ajax(request) def delete_ajax(self, request): return self.get_ajax(request) class SimpleJsonView(views.JSONResponseMixin, View): """ A view for testing JSONResponseMixin's render_json_response() method. """ def get(self, request): object = {'username': request.user.username} return self.render_json_response(object) class CustomJsonEncoderView(views.JSONResponseMixin, View): """ A view for testing JSONResponseMixin's `json_encoder_class` attribute with custom JSONEncoder class. """ json_encoder_class = SetJSONEncoder def get(self, request): object = {'numbers': set([1, 2, 3])} return self.render_json_response(object) class SimpleJsonBadRequestView(views.JSONResponseMixin, View): """ A view for testing JSONResponseMixin's render_json_response() method with 400 HTTP status code. """ def get(self, request): object = {'username': request.user.username} return self.render_json_response(object, status=400) class ArticleListJsonView(views.JSONResponseMixin, View): """ A view for testing JSONResponseMixin's render_json_object_response() method. """ def get(self, request): queryset = Article.objects.all() return self.render_json_object_response( queryset, fields=('title',)) class JsonRequestResponseView(views.JsonRequestResponseMixin, View): """ A view for testing JsonRequestResponseMixin's json conversion """ def post(self, request): return self.render_json_response(self.request_json) class JsonBadRequestView(views.JsonRequestResponseMixin, View): """ A view for testing JsonRequestResponseMixin's require_json and render_bad_request_response methods """ require_json = True def post(self, request, *args, **kwargs): return self.render_json_response(self.request_json) class JsonCustomBadRequestView(views.JsonRequestResponseMixin, View): """ A view for testing JsonRequestResponseMixin's render_bad_request_response method with a custom error message """ def post(self, request, *args, **kwargs): if not self.request_json: return self.render_bad_request_response( {'error': 'you messed up'}) return self.render_json_response(self.request_json) class CreateArticleView(CreateView): """ View for testing CreateAndRedirectEditToView. """ fields = ['author', 'title', 'body', 'slug'] model = Article template_name = 'form.html' class EditArticleView(UpdateView): """ View for testing CreateAndRedirectEditToView. """ model = Article template_name = 'form.html' class CreateArticleAndRedirectToListView(views.SuccessURLRedirectListMixin, CreateArticleView): """ View for testing SuccessURLRedirectListMixin """ success_list_url = 'article_list' class CreateArticleAndRedirectToListViewBad(views.SuccessURLRedirectListMixin, CreateArticleView): """ View for testing SuccessURLRedirectListMixin """ success_list_url = None class ArticleListView(views.SelectRelatedMixin, ListView): """ A list view for articles, required for testing SuccessURLRedirectListMixin. Also used to test SelectRelatedMixin. """ model = Article template_name = 'blank.html' select_related = ('author',) class ArticleListViewWithCustomQueryset(views.SelectRelatedMixin, ListView): """ Another list view for articles, required to test SelectRelatedMixin. """ queryset = Article.objects.select_related('author').prefetch_related( 'article_set') template_name = 'blank.html' select_related = () class FormWithUserKwargView(views.UserFormKwargsMixin, FormView): """ View for testing UserFormKwargsMixin. """ form_class = FormWithUserKwarg template_name = 'form.html' def form_valid(self, form): return HttpResponse("username: %s" % form.user.username) class HeadlineView(views.SetHeadlineMixin, TemplateView): """ View for testing SetHeadlineMixin. """ template_name = 'blank.html' headline = "Test headline" class LazyHeadlineView(views.SetHeadlineMixin, TemplateView): """ View for testing SetHeadlineMixin. """ template_name = 'blank.html' headline = _("Test Headline") class ContextView(views.StaticContextMixin, TemplateView): """ View for testing StaticContextMixin. """ template_name = 'blank.html' static_context = {'test': True} class DynamicHeadlineView(views.SetHeadlineMixin, TemplateView): """ View for testing SetHeadlineMixin's get_headline() method. """ template_name = 'blank.html' def get_headline(self): return self.kwargs['s'] class PermissionRequiredView(views.PermissionRequiredMixin, OkView): """ View for testing PermissionRequiredMixin. """ permission_required = 'auth.add_user' class MultiplePermissionsRequiredView(views.MultiplePermissionsRequiredMixin, OkView): permissions = { 'all': ['tests.add_article', 'tests.change_article'], 'any': ['auth.add_user', 'auth.change_user'], } class SuperuserRequiredView(views.SuperuserRequiredMixin, OkView): pass class StaffuserRequiredView(views.StaffuserRequiredMixin, OkView): pass class CsrfExemptView(views.CsrfExemptMixin, OkView): pass class AuthorDetailView(views.PrefetchRelatedMixin, ListView): model = User prefetch_related = ['article_set'] template_name = 'blank.html' class OrderableListView(views.OrderableListMixin, ListView): model = Article orderable_columns = ('id', 'title', ) orderable_columns_default = 'id' class CanonicalSlugDetailView(views.CanonicalSlugDetailMixin, DetailView): model = Article template_name = 'blank.html' class OverriddenCanonicalSlugDetailView(views.CanonicalSlugDetailMixin, DetailView): model = Article template_name = 'blank.html' def get_canonical_slug(self): return codecs.encode(self.get_object().slug, 'rot_13') class CanonicalSlugDetailCustomUrlKwargsView(views.CanonicalSlugDetailMixin, DetailView): model = Article template_name = 'blank.html' pk_url_kwarg = 'my_pk' slug_url_kwarg = 'my_slug' class ModelCanonicalSlugDetailView(views.CanonicalSlugDetailMixin, DetailView): model = CanonicalArticle template_name = 'blank.html' class FormMessagesView(views.FormMessagesMixin, CreateView): form_class = ArticleForm form_invalid_message = _('Invalid') form_valid_message = _('Valid') model = Article success_url = '/form_messages/' template_name = 'form.html' class GroupRequiredView(views.GroupRequiredMixin, OkView): group_required = 'test_group' class UserPassesTestView(views.UserPassesTestMixin, OkView): def test_func(self, user): return user.is_staff and not user.is_superuser \ and user.email.endswith('@mydomain.com') class UserPassesTestLoginRequiredView(views.LoginRequiredMixin, views.UserPassesTestMixin, OkView): def test_func(self, user): return user.is_staff and not user.is_superuser \ and user.email.endswith('@mydomain.com') class UserPassesTestNotImplementedView(views.UserPassesTestMixin, OkView): pass class AllVerbsView(views.AllVerbsMixin, View): def all(self, request, *args, **kwargs): return HttpResponse('All verbs return this!') class SSLRequiredView(views.SSLRequiredMixin, OkView): pass class RecentLoginRequiredView(views.RecentLoginRequiredMixin, OkView): """ A view for testing RecentLoginRequiredMixin. """ django-braces-1.9.0/tox.ini000066400000000000000000000012311272337476100155440ustar00rootroot00000000000000[tox] envlist = py{27,32,33,34}-django{15,16,17,18},py{27,34,35}-django{18,19} install_command = pip install {opts} {packages} [testenv] basepython = py27: python2.7 py32: python3.2 py33: python3.3 py34: python3.4 py35: python3.5 commands = {envbindir}/coverage erase {envbindir}/coverage run {envbindir}/{posargs:py.test} #{envbindir}/coverage report deps = mock pytest-django factory_boy py32: coverage==3.7 py{27,33,34,35}: coverage==4.1 argparse django15: Django>=1.5,<1.6 django16: Django>=1.6,<1.7 django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10