diff --git a/example/exampleapp/permissions.py b/example/exampleapp/permissions.py index 8ed6166..f63d213 100644 --- a/example/exampleapp/permissions.py +++ b/example/exampleapp/permissions.py @@ -1,7 +1,7 @@ from django.contrib.flatpages.models import FlatPage from django.utils.translation import ugettext_lazy as _ -from authority import permissions +from authority import permissions, site class FlatPagePermission(permissions.BasePermission): """ @@ -39,7 +39,6 @@ class FlatPagePermission(permissions.BasePermission): {% endifhasperm %} """ - model = FlatPage label = 'flatpage_permission' checks = ('review', 'top_secret') @@ -48,3 +47,5 @@ class FlatPagePermission(permissions.BasePermission): return self.browse_flatpage(obj=flatpage) return False top_secret.short_description=_('Is allowed to see top secret flatpages') + +site.register(FlatPage, FlatPagePermission) diff --git a/src/authority/__init__.py b/src/authority/__init__.py index bb2f74f..99fbf36 100644 --- a/src/authority/__init__.py +++ b/src/authority/__init__.py @@ -1,4 +1,6 @@ import sys +from authority.sites import site, get_check, get_choices_for + LOADING = False def autodiscover(): diff --git a/src/authority/admin.py b/src/authority/admin.py index 5d6f661..c78b032 100644 --- a/src/authority/admin.py +++ b/src/authority/admin.py @@ -5,7 +5,6 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.text import capfirst, truncate_words from authority.models import Permission -from authority import permissions from authority.widgets import GenericForeignKeyRawIdWidget class PermissionInline(generic.GenericTabularInline): @@ -15,7 +14,7 @@ class PermissionInline(generic.GenericTabularInline): def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name == 'codename': - perm_choices = permissions.registry.get_choices_for(self.parent_model) + perm_choices = permissions.get_choices_for(self.parent_model) kwargs['label'] = _('permission') kwargs['widget'] = forms.Select(choices=perm_choices) return db_field.formfield(**kwargs) diff --git a/src/authority/decorators.py b/src/authority/decorators.py index 6f2ee72..8103f83 100644 --- a/src/authority/decorators.py +++ b/src/authority/decorators.py @@ -9,7 +9,7 @@ from django.conf import settings from django.contrib.auth.decorators import user_passes_test from django.contrib.auth import REDIRECT_FIELD_NAME -from authority import permissions +from authority import permissions, get_check from authority.views import permission_denied def permission_required(perm, *model_lookups, **kwargs): @@ -42,7 +42,7 @@ def permission_required(perm, *model_lookups, **kwargs): raise ValueError( 'The argument %s needs to be a model.' % model) objs.append(get_object_or_404(model_class, **{lookup: value})) - check = permissions.registry.get_check(request.user, perm) + check = get_check(request.user, perm) granted = False if check is not None: granted = check(*objs) diff --git a/src/authority/forms.py b/src/authority/forms.py index 293eb4e..21e811d 100644 --- a/src/authority/forms.py +++ b/src/authority/forms.py @@ -3,8 +3,8 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User, Group +from authority import permissions, get_choices_for from authority.models import Permission -from authority import permissions class BasePermissionForm(forms.ModelForm): codename = forms.CharField(label=_('Permission')) @@ -18,7 +18,7 @@ class BasePermissionForm(forms.ModelForm): if obj and perm: self.base_fields['codename'].widget = forms.HiddenInput() elif obj and not perm: - perm_choices = permissions.registry.get_choices_for(self.obj) + perm_choices = get_choices_for(self.obj) self.base_fields['codename'].widget = forms.Select(choices=perm_choices) super(BasePermissionForm, self).__init__(*args, **kwargs) diff --git a/src/authority/models.py b/src/authority/models.py index 29d13e6..4b943e6 100644 --- a/src/authority/models.py +++ b/src/authority/models.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic from django.contrib.auth.models import User, Group from django.utils.translation import ugettext_lazy as _ + from authority.managers import PermissionManager class Permission(models.Model): diff --git a/src/authority/permissions.py b/src/authority/permissions.py index 98de5a6..169c5a1 100644 --- a/src/authority/permissions.py +++ b/src/authority/permissions.py @@ -1,61 +1,11 @@ from inspect import getmembers, ismethod from django.db.models import Q from django.db.models.base import ModelBase -from django.db.models.fields import BLANK_CHOICE_DASH -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ImproperlyConfigured from django.template.defaultfilters import slugify from django.utils.translation import ugettext_lazy as _ from authority.models import Permission -class AlreadyRegistered(Exception): - pass - -class NotRegistered(Exception): - pass - -class PermissionRegistry(dict): - """ - A dictionary that contains permission instances and their labels. - """ - _choices = {} - def get_permission_by_label(self, label): - for perm_cls, perm_label in self.items(): - if perm_label == label: - return perm_cls - return None - - def get_check(self, user, label): - perm_label, check_name = label.split('.') - perm_cls = self.get_permission_by_label(perm_label) - if perm_cls is None: - return None - perm_instance = perm_cls(user) - return getattr(perm_instance, check_name, None) - - def get_permissions_by_model(self, model): - return [perm for perm in self if perm.model == model] - - def get_choices_for(self, obj, default=BLANK_CHOICE_DASH): - model_cls = obj - if not isinstance(obj, ModelBase): - model_cls = obj.__class__ - if model_cls in self._choices: - choices = self._choices[model_cls] - else: - choices = [] + default - for perm in self.get_permissions_by_model(model_cls): - for name, check in getmembers(perm, ismethod): - if name in perm.checks: - signature = '%s.%s' % (perm.label, name) - label = getattr(check, 'short_description', signature) - choices.append((signature, label)) - self._choices[model_cls] = choices - return choices - -registry = PermissionRegistry() - class PermissionMetaclass(type): """ Used to generate the default set of permission checks "add", "change" and @@ -64,61 +14,15 @@ class PermissionMetaclass(type): def __new__(cls, name, bases, attrs): new_class = super( PermissionMetaclass, cls).__new__(cls, name, bases, attrs) - if new_class.__name__ == "BasePermission": - return new_class - if not new_class.model: - raise ImproperlyConfigured( - "Permission %s requires a model attribute." % new_class) if not new_class.label: new_class.label = "%s_permission" % new_class.__name__.lower() new_class.label = slugify(new_class.label) - if new_class in registry: - raise AlreadyRegistered( - "The permission %s is already registered" % new_class) - if new_class.label in registry.values(): - raise ImproperlyConfigured( - "The name of %s conflicts with %s" % \ - (new_class, registry[new_class.label])) - registry[new_class] = new_class.label if new_class.checks is None: new_class.checks = [] # force check names to be lower case new_class.checks = [check.lower() for check in new_class.checks] - generic_checks = ['add', 'browse', 'change', 'delete'] - for check_name in new_class.checks: - check_func = getattr(new_class, check_name, None) - if check_func is not None: - func = new_class.create_check(check_name, check_func) - func.__name__ = check_name - func.short_description = getattr(check_func, 'short_description', - _("%(object_name)s permission '%(check)s'") % { - 'object_name': new_class.model._meta.object_name, - 'check': check_name}) - setattr(new_class, check_name, func) - else: - generic_checks.append(check_name) - for check_name in generic_checks: - func = new_class.create_check(check_name, generic=True) - object_name = new_class.model._meta.object_name - func_name = "%s_%s" % (check_name, object_name.lower()) - func.short_description = _("Can %(check)s this %(object_name)s") % { - 'object_name': new_class.model._meta.object_name.lower(), - 'check': check_name} - func.check_name = check_name - if func_name not in new_class.checks: - new_class.checks.append(func_name) - setattr(new_class, func_name, func) return new_class - def _create_check(cls, check_name, check_func=None, generic=False): - def check(self, *args, **kwargs): - granted = self.can(check_name, generic, *args, **kwargs) - if check_func and not granted: - return check_func(self, *args, **kwargs) - return granted - return check - create_check = classmethod(_create_check) - class BasePermission(object): """ Base Permission class to be used to define app permissions. @@ -127,7 +31,7 @@ class BasePermission(object): checks = () label = None - model = None + generic_checks = ['add', 'browse', 'change', 'delete'] def __init__(self, user=None, group=None, *args, **kwargs): self.user = user diff --git a/src/authority/templatetags/permissions.py b/src/authority/templatetags/permissions.py index 87802b7..468abb7 100644 --- a/src/authority/templatetags/permissions.py +++ b/src/authority/templatetags/permissions.py @@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse from django.core.exceptions import ImproperlyConfigured from django.contrib.auth.models import User, AnonymousUser -from authority import permissions +from authority import permissions, get_check from authority.models import Permission from authority.views import add_url_for_obj from authority.forms import UserPermissionForm @@ -51,7 +51,7 @@ class ComparisonNode(ResolverNode): objs.append(self.resolve(obj, context)) else: objs = None - check = permissions.registry.get_check(user, perm) + check = get_check(user, perm) if check is not None: if check(*objs): # return True if check was successful @@ -206,7 +206,7 @@ class PermissionForObjectNode(ResolverNode): user = self.resolve(self.user, context) granted = False if not isinstance(user, AnonymousUser): - check = permissions.registry.get_check(user, perm) + check = get_check(user, perm) if check is not None: granted = check(*objs) context[var_name] = granted