Commit e41e17af authored by Michael Fladischer's avatar Michael Fladischer

importing python-django-rules_1.2.1.orig.tar.gz

parents
[report]
exclude_lines =
pragma: no cover
omit =
*/python?.?/*
*/site-packages/nose/*
*/rules/compat/*
*/rules/apps.py
*.egg-info
*.pyc
*~
._*
.coverage
.DS_Store
.Python
__pycache__
dist/
docs/_build
pip-log.txt
.tox
.eggs/
# ----------------------------------------
# Python support matrix per Django version
#
# 2.6 1.5 1.6
# 2.7 1.5 1.6 1.7 1.8 1.9 1.10
# 3.3 1.5 1.6 1.7 1.8
# 3.4 1.7 1.8 1.9 1.10
# 3.5 1.8 1.9 1.10
# ----------------------------------------
sudo: false
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "pypy"
- "pypy3"
env:
- DJANGO='Django<1.6' # Django 1.5
- DJANGO='Django<1.7' # Django 1.6
- DJANGO='Django<1.8' # Django 1.7
- DJANGO='Django<1.9' # Django 1.8
- DJANGO='Django<1.10' # Django 1.9
- DJANGO='Django<1.11' # Django 1.10
matrix:
exclude:
- python: "2.6"
env: DJANGO='Django<1.8'
- python: "2.6"
env: DJANGO='Django<1.9'
- python: "2.6"
env: DJANGO='Django<1.10'
- python: "2.6"
env: DJANGO='Django<1.11'
- python: "3.3"
env: DJANGO='Django<1.10'
- python: "3.3"
env: DJANGO='Django<1.11'
- python: "3.4"
env: DJANGO='Django<1.6'
- python: "3.4"
env: DJANGO='Django<1.7'
- python: "3.5"
env: DJANGO='Django<1.6'
- python: "3.5"
env: DJANGO='Django<1.7'
- python: "3.5"
env: DJANGO='Django<1.8'
- python: "pypy3"
env: DJANGO='Django<1.10'
- python: "pypy3"
env: DJANGO='Django<1.11'
install:
- 'pip install "${DJANGO}"'
- pip install coveralls
- pip install -e .
script: ./runtests.sh -v
after_success:
- coveralls
Please use::
$ python setup.py install
See README.rst
Copyright (c) 2014 Akis Kesoglou
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
include INSTALL
include LICENSE
include README.rst
recursive-include tests *
global-exclude *.pyc
global-exclude .coverage
global-exclude .DS_Store
This diff is collapsed.
from .rulesets import RuleSet, add_rule, remove_rule, rule_exists, test_rule
from .permissions import add_perm, remove_perm, perm_exists, has_perm
from .predicates import (Predicate, predicate, always_true, always_false,
always_allow, always_deny, is_authenticated,
is_superuser, is_staff, is_active, is_group_member)
VERSION = (1, 2, 1, 'final', 1)
default_app_config = 'rules.apps.RulesConfig'
from django.apps import AppConfig
class RulesConfig(AppConfig):
name = 'rules'
class AutodiscoverRulesConfig(RulesConfig):
def ready(self):
from django.utils.module_loading import autodiscover_modules
autodiscover_modules('rules')
"""
This is a copy of django.contrib.auth.mixins module, shipped with django-rules
for compatibility with Django versions before 1.9. Used under permission by
the Django Software Foundation.
Copyright (c) Django Software Foundation 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 Django 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.
"""
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils import six
from django.utils.encoding import force_text
class AccessMixin(object):
"""
Abstract CBV mixin that gives access mixins the same customizable
functionality.
"""
login_url = None
permission_denied_message = ''
raise_exception = False
redirect_field_name = REDIRECT_FIELD_NAME
def get_login_url(self):
"""
Override this method to override the login_url attribute.
"""
login_url = self.login_url or settings.LOGIN_URL
if not login_url:
raise ImproperlyConfigured(
'{0} is missing the login_url attribute. Define {0}.login_url, settings.LOGIN_URL, or override '
'{0}.get_login_url().'.format(self.__class__.__name__)
)
return force_text(login_url)
def get_permission_denied_message(self):
"""
Override this method to override the permission_denied_message attribute.
"""
return self.permission_denied_message
def get_redirect_field_name(self):
"""
Override this method to override the redirect_field_name attribute.
"""
return self.redirect_field_name
def handle_no_permission(self):
if self.raise_exception:
raise PermissionDenied(self.get_permission_denied_message())
return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())
class LoginRequiredMixin(AccessMixin):
"""
CBV mixin which verifies that the current user is authenticated.
"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return self.handle_no_permission()
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
class PermissionRequiredMixin(AccessMixin):
"""
CBV mixin which verifies that the current user has all specified
permissions.
"""
permission_required = None
def get_permission_required(self):
"""
Override this method to override the permission_required attribute.
Must return an iterable.
"""
if self.permission_required is None:
raise ImproperlyConfigured(
'{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
'{0}.get_permission_required().'.format(self.__class__.__name__)
)
if isinstance(self.permission_required, six.string_types):
perms = (self.permission_required, )
else:
perms = self.permission_required
return perms
def has_permission(self):
"""
Override this method to customize the way permissions are checked.
"""
perms = self.get_permission_required()
return self.request.user.has_perms(perms)
def dispatch(self, request, *args, **kwargs):
if not self.has_permission():
return self.handle_no_permission()
return super(PermissionRequiredMixin, self).dispatch(request, *args, **kwargs)
class UserPassesTestMixin(AccessMixin):
"""
CBV Mixin that allows you to define a test function which must return True
if the current user can access the view.
"""
def test_func(self):
raise NotImplementedError(
'{0} is missing the implementation of the test_func() method.'.format(self.__class__.__name__)
)
def get_test_func(self):
"""
Override this method to use a different test_func method.
"""
return self.test_func
def dispatch(self, request, *args, **kwargs):
user_test_result = self.get_test_func()()
if not user_test_result:
return self.handle_no_permission()
return super(UserPassesTestMixin, self).dispatch(request, *args, **kwargs)
from django.contrib import admin
try:
from django.contrib.auth import get_permission_codename
except ImportError: # pragma: no cover
# Django < 1.6
def get_permission_codename(action, opts):
return '%s_%s' % (action, opts.object_name.lower())
class ObjectPermissionsModelAdminMixin(object):
def has_change_permission(self, request, obj=None):
opts = self.opts
codename = get_permission_codename('change', opts)
return request.user.has_perm('%s.%s' % (opts.app_label, codename), obj)
def has_delete_permission(self, request, obj=None):
opts = self.opts
codename = get_permission_codename('delete', opts)
return request.user.has_perm('%s.%s' % (opts.app_label, codename), obj)
class ObjectPermissionsInlineModelAdminMixin(ObjectPermissionsModelAdminMixin):
def has_change_permission(self, request, obj=None): # pragma: no cover
opts = self.opts
if opts.auto_created:
for field in opts.fields:
if field.rel and field.rel.to != self.parent_model:
opts = field.rel.to._meta
break
codename = get_permission_codename('change', opts)
return request.user.has_perm('%s.%s' % (opts.app_label, codename), obj)
def has_delete_permission(self, request, obj=None): # pragma: no cover
if self.opts.auto_created:
return self.has_change_permission(request, obj)
return super(ObjectPermissionsInlineModelAdminMixin, self).has_delete_permission(request, obj)
class ObjectPermissionsModelAdmin(ObjectPermissionsModelAdminMixin, admin.ModelAdmin):
pass
class ObjectPermissionsStackedInline(ObjectPermissionsInlineModelAdminMixin, admin.StackedInline):
pass
class ObjectPermissionsTabularInline(ObjectPermissionsInlineModelAdminMixin, admin.TabularInline):
pass
from functools import wraps
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import PermissionDenied, ImproperlyConfigured, FieldError
from django.shortcuts import get_object_or_404
from django.utils import six
from django.utils.decorators import available_attrs
from django.utils.encoding import force_text
try:
from django.contrib.auth import mixins
except ImportError: # pragma: no cover
# Django < 1.9
from ..compat import access_mixins as mixins
# These are made available for convenience, as well as for use in Django
# versions before 1.9. For usage help see Django's docs for 1.9 or later.
LoginRequiredMixin = mixins.LoginRequiredMixin
UserPassesTestMixin = mixins.UserPassesTestMixin
class PermissionRequiredMixin(mixins.PermissionRequiredMixin):
"""
CBV mixin to provide object-level permission checking to views. Best used
with views that inherit from ``SingleObjectMixin`` (``DetailView``,
``UpdateView``, etc.), though not required.
The single requirement is for a ``get_object`` method to be available
in the view. If there's no ``get_object`` method, permission checking
is model-level, that is exactly like Django's ``PermissionRequiredMixin``.
"""
def get_permission_object(self):
"""
Override this method to provide the object to check for permission
against. By default uses ``self.get_object()`` as provided by
``SingleObjectMixin``. Returns None if there's no ``get_object``
method.
"""
try:
# Requires SingleObjectMixin or equivalent ``get_object`` method
return self.get_object()
except AttributeError: # pragma: no cover
return None
def has_permission(self):
obj = self.get_permission_object()
perms = self.get_permission_required()
return self.request.user.has_perms(perms, obj)
def objectgetter(model, attr_name='pk', field_name='pk'):
"""
Helper that returns a function suitable for use as the ``fn`` argument
to the ``permission_required`` decorator. Internally uses
``get_object_or_404``, so keep in mind that this may raise ``Http404``.
``model`` can be a model class, manager or queryset.
``attr_name`` is the name of the view attribute.
``field_name`` is the model's field name by which the lookup is made, eg.
"id", "slug", etc.
"""
def _getter(request, *view_args, **view_kwargs):
if attr_name not in view_kwargs:
raise ImproperlyConfigured(
'Argument {0} is not available. Given arguments: [{1}]'
.format(attr_name, ', '.join(view_kwargs.keys())))
try:
return get_object_or_404(model, **{field_name: view_kwargs[attr_name]})
except FieldError:
raise ImproperlyConfigured(
'Model {0} has no field named {1}'
.format(model, field_name))
return _getter
def permission_required(perm, fn=None, login_url=None, raise_exception=False, redirect_field_name=REDIRECT_FIELD_NAME):
"""
View decorator that checks for the given permissions before allowing the
view to execute. Use it like this::
from django.shortcuts import get_object_or_404
from rules.contrib.views import permission_required
from posts.models import Post
def get_post_by_pk(request, post_id):
return get_object_or_404(Post, pk=post_id)
@permission_required('posts.change_post', fn=get_post_by_pk)
def post_update(request, post_id):
# ...
``perm`` is either a permission name as a string, or a list of permission
names.
``fn`` is an optional callback that receives the same arguments as those
passed to the decorated view and must return the object to check
permissions against. If omitted, the decorator behaves just like Django's
``permission_required`` decorator, i.e. checks for model-level permissions.
``raise_exception`` is a boolean specifying whether to raise a
``django.core.exceptions.PermissionDenied`` exception if the check fails.
You will most likely want to set this argument to ``True`` if you have
specified a custom 403 response handler in your urlconf. If ``False``,
the user will be redirected to the URL specified by ``login_url``.
``login_url`` is an optional custom URL to redirect the user to if
permissions check fails. If omitted or empty, ``settings.LOGIN_URL`` is
used.
"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
# Normalize to a list of permissions
if isinstance(perm, six.string_types):
perms = (perm,)
else:
perms = perm
# Get the object to check permissions against
if callable(fn):
obj = fn(request, *args, **kwargs)
else:
obj = fn
# Get the user
user = request.user
# Check for permissions and return a response
if not user.has_perms(perms, obj):
# User does not have a required permission
if raise_exception:
raise PermissionDenied()
else:
return _redirect_to_login(request, view_func.__name__,
login_url, redirect_field_name)
else:
# User has all required permissions -- allow the view to execute
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
def _redirect_to_login(request, view_name, login_url, redirect_field_name):
redirect_url = login_url or settings.LOGIN_URL
if not redirect_url: # pragma: no cover
raise ImproperlyConfigured(
'permission_required({0}): You must either provide '
'the "login_url" argument to the "permission_required" '
'decorator or configure settings.LOGIN_URL'.format(view_name)
)
redirect_url = force_text(redirect_url)
return redirect_to_login(request.get_full_path(), redirect_url, redirect_field_name)
from .rulesets import RuleSet
permissions = RuleSet()
def add_perm(name, pred):
permissions.add_rule(name, pred)
def remove_perm(name):
permissions.remove_rule(name)
def perm_exists(name):
return permissions.rule_exists(name)
def has_perm(name, *args, **kwargs):
return permissions.test_rule(name, *args, **kwargs)
class ObjectPermissionBackend(object):
def authenticate(self, username, password):
return None
def has_perm(self, user, perm, *args, **kwargs):
return has_perm(perm, user, *args, **kwargs)
def has_module_perms(self, user, app_label):
return has_perm(app_label, user)
import inspect
import logging
import operator
import threading
from functools import partial, update_wrapper
from warnings import warn
logger = logging.getLogger('rules')
class SkipPredicate(Exception):
"""
Use to reject usage of a predicate.
"""
def __init__(self, *args, **kwargs):
warn('Skipping predicates by raising the SkipPredicate exception '
'has been deprecated. Return `None` from your predicate instead.',
DeprecationWarning)
super(SkipPredicate, self).__init__(*args, **kwargs)
class Context(dict):
def __init__(self, args):
super(Context, self).__init__()
self.args = args
class localcontext(threading.local):
def __init__(self):
self.stack = []
_context = localcontext()
class NoValueSentinel(object):
def __bool__(self):
return False
__nonzero__ = __bool__ # python 2
NO_VALUE = NoValueSentinel()
del NoValueSentinel
class Predicate(object):
def __init__(self, fn, name=None, bind=False):
# fn can be a callable with any of the following signatures:
# - fn(obj=None, target=None)
# - fn(obj=None)
# - fn()
assert callable(fn), 'The given predicate is not callable.'
if isinstance(fn, Predicate):
fn, num_args, var_args, name = fn.fn, fn.num_args, fn.var_args, name or fn.name
elif isinstance(fn, partial):
argspec = inspect.getargspec(fn.func)
var_args = argspec.varargs is not None
num_args = len(argspec.args) - len(fn.args)
if inspect.ismethod(fn.func):
num_args -= 1 # skip `self`
name = fn.func.__name__
elif inspect.ismethod(fn):
argspec = inspect.getargspec(fn)
var_args = argspec.varargs is not None
num_args = len(argspec.args) - 1 # skip `self`
elif inspect.isfunction(fn):
argspec = inspect.getargspec(fn)
var_args = argspec.varargs is not None
num_args = len(argspec.args)
elif isinstance(fn, object):
callfn = getattr(fn, '__call__')
argspec = inspect.getargspec(callfn)
var_args = argspec.varargs is not None
num_args = len(argspec.args) - 1 # skip `self`
name = name or type(fn).__name__
else: # pragma: no cover
# We handle all cases, so there's no way we can reach here
raise TypeError('Incompatible predicate.')
if bind:
num_args -= 1
assert num_args <= 2, 'Incompatible predicate.'
self.fn = fn
self.num_args = num_args
self.var_args = var_args
self.name = name or fn.__name__
self.bind = bind
def __repr__(self):
return '<%s:%s object at %s>' % (
type(self).__name__, str(self), hex(id(self)))
def __str__(self):
return self.name
def __call__(self, *args, **kwargs):
# this method is defined as variadic in order to not mask the
# underlying callable's signature that was most likely decorated
# as a predicate. internally we consistently call ``_apply`` that
# provides a single interface to the callable.
if self.bind:
return self.fn(self, *args, **kwargs)
return self.fn(*args, **kwargs)
@property
def context(self):
"""
The currently active invocation context. A new context is created as a
result of invoking ``test()`` and is only valid for the duration of
the invocation.
Can be used by predicates to store arbitrary data, eg. for caching
computed values, setting flags, etc., that can be used by predicates
later on in the chain.
Inside a predicate function it can be used like so::
>>> @predicate
... def mypred(a, b):
... value = compute_expensive_value(a)
... mypred.context['value'] = value
... return True
...
Other predicates can later use stored values::
>>> @predicate
... def myotherpred(a, b):
... value = myotherpred.context.get('value')
... if value is not None:
... return do_something_with_value(value)
... else:
... return do_something_without_value()
...
"""
try:
return _context.stack[-1]
except IndexError:
return None
def skip(self):
"""
Use this method in a predicate body to signal that it should be
ignored for the current invocation.
"""
raise SkipPredicate()
def test(self, obj=NO_VALUE, target=NO_VALUE):
"""
The canonical method to invoke predicates.
"""
args = tuple(arg for arg in (obj, target) if arg is not NO_VALUE)
_context.stack.append(Context(args))
logger.debug('Testing %s', self)
try:
return bool(self._apply(*args))
finally:
_context.stack.pop()
def __and__(self, other):
def AND(*args):
return self._combine(other, operator.and_, args)
return type(self)(AND, '(%s & %s)' % (self.name, other.name))
def __or__(self, other):
def OR(*args):
return self._combine(other, operator.or_, args)
return type(self)(OR, '(%s | %s)' % (self.name, other.name))
def __xor__(self, other):
def XOR(*args):
return self._combine(other, operator.xor, args)
return type(self)(XOR, '(%s ^ %s)' % (self.name, other.name))
def __invert__(self):
def INVERT(*args):
result = self._apply(*args)
return None if result is None else not result
if self.name.startswith('~'):