From 13cc721580421e42edfd79f392e98fc05b90bff2 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 01:07:20 +0200 Subject: [PATCH 01/12] Refactored permission class registration. --- example/exampleapp/permissions.py | 5 +- src/authority/__init__.py | 2 + src/authority/admin.py | 3 +- src/authority/decorators.py | 4 +- src/authority/forms.py | 4 +- src/authority/models.py | 1 + src/authority/permissions.py | 98 +---------------------- src/authority/templatetags/permissions.py | 6 +- 8 files changed, 15 insertions(+), 108 deletions(-) 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 From 56bf2cf20e131badd26e786c3a65258d06938ef9 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 01:09:01 +0200 Subject: [PATCH 02/12] Added missed file from last commit --- src/authority/sites.py | 126 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/authority/sites.py diff --git a/src/authority/sites.py b/src/authority/sites.py new file mode 100644 index 0000000..3b9546f --- /dev/null +++ b/src/authority/sites.py @@ -0,0 +1,126 @@ +from inspect import getmembers, ismethod +from django.db.models.base import ModelBase +from django.db.models.fields import BLANK_CHOICE_DASH +from django.utils.translation import ugettext_lazy as _ + +from authority.permissions import BasePermission + +class AlreadyRegistered(Exception): + pass + +class NotRegistered(Exception): + pass + +class PermissionSite(object): + """ + A dictionary that contains permission instances and their labels. + """ + _registry = {} + _choices = {} + + def get_permission_by_label(self, label): + for perm_cls in self._registry.values(): + if perm_cls.label == label: + return perm_cls + return None + + def get_permissions_by_model(self, model): + return [perm for perm in self._registry.values() if perm.model == model] + + 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_labels(self): + return [perm.label for perm in self._registry] + + 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: + return self._choices[model_cls] + 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 + + def register(self, model_or_iterable, permission_class=None, **options): + if not permission_class: + permission_class = BasePermission + + if isinstance(model_or_iterable, ModelBase): + model_or_iterable = [model_or_iterable] + + if permission_class.label in self.get_labels(): + raise ImproperlyConfigured( + "The name of %s conflicts with %s" % (permission_class, + self.get_permission_by_label(permission_class.label))) + + for model in model_or_iterable: + if model in self._registry: + raise AlreadyRegistered( + 'The model %s is already registered' % model.__name__) + if options: + options['__module__'] = __name__ + permission_class = type("%sPermission" % model.__name__, + (permission_class,), options) + + # Instantiate the admin class to save in the registry + permission_class.model = model + self.setup(model, permission_class) + self._registry[model] = permission_class + + def unregister(self, model_or_iterable): + if isinstance(model_or_iterable, ModelBase): + model_or_iterable = [model_or_iterable] + for model in model_or_iterable: + if model not in self._registry: + raise NotRegistered('The model %s is not registered' % model.__name__) + del self._registry[model] + + def setup(self, model, permission): + for check_name in permission.checks: + check_func = getattr(permission, check_name, None) + if check_func is not None: + func = self.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': model._meta.object_name, + 'check': check_name}) + setattr(permission, check_name, func) + else: + permission.generic_checks.append(check_name) + for check_name in permission.generic_checks: + func = self.create_check(check_name, generic=True) + object_name = 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': model._meta.object_name.lower(), + 'check': check_name} + func.check_name = check_name + if func_name not in permission.checks: + permission.checks.append(func_name) + setattr(permission, func_name, func) + + def create_check(self, 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 + +site = PermissionSite() +get_check = site.get_check +get_choices_for = site.get_choices_for From 991905311f8fcc3af55b89e013f8faf6976dba54 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 01:09:42 +0200 Subject: [PATCH 03/12] Updated setup.py and manifest template --- MANIFEST.in | 1 + setup.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 100e670..a63229b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include LICENSE recursive-include src/authority/templates/authority * +recursive-include src/authority/templates/admin * diff --git a/setup.py b/setup.py index 86a1000..57b4558 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,19 @@ +import os from setuptools import setup, find_packages +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + setup( name='django-authority', - version='0.0.1', + version='0.1', description="A Django app that provides generic per-object-permissions for Django's auth app.", - long_description=open('README').read(), + long_description=read('README'), author='Jannis Leidel', author_email='jannis@leidel.info', + license='BSD', url='http://bitbucket.org/jezdez/django-authority/', + download_url='http://bitbucket.org/jezdez/django-authority/downloads/', packages=find_packages('src'), package_dir = {'': 'src'}, classifiers=[ @@ -22,6 +28,8 @@ setup( package_data = { 'authority': [ 'templates/authority/*.html', + 'templates/admin/edit_inline/action_tabular.html', + 'templates/admin/permission_change_form.html', ] }, zip_safe=False, From 210c0e032b0adc46a751040c2118b7b6f988f125 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 01:21:08 +0200 Subject: [PATCH 04/12] Made the register and unregister methods public --- example/exampleapp/permissions.py | 4 ++-- src/authority/__init__.py | 2 +- src/authority/sites.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/example/exampleapp/permissions.py b/example/exampleapp/permissions.py index f63d213..fb23de2 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, site +from authority import permissions class FlatPagePermission(permissions.BasePermission): """ @@ -48,4 +48,4 @@ class FlatPagePermission(permissions.BasePermission): return False top_secret.short_description=_('Is allowed to see top secret flatpages') -site.register(FlatPage, FlatPagePermission) +permissions.register(FlatPage, FlatPagePermission) diff --git a/src/authority/__init__.py b/src/authority/__init__.py index 99fbf36..5db95f3 100644 --- a/src/authority/__init__.py +++ b/src/authority/__init__.py @@ -1,5 +1,5 @@ import sys -from authority.sites import site, get_check, get_choices_for +from authority.sites import site, get_check, get_choices_for, register, unregister LOADING = False diff --git a/src/authority/sites.py b/src/authority/sites.py index 3b9546f..062f4b5 100644 --- a/src/authority/sites.py +++ b/src/authority/sites.py @@ -124,3 +124,5 @@ class PermissionSite(object): site = PermissionSite() get_check = site.get_check get_choices_for = site.get_choices_for +register = site.register +unregister = site.unregister From 494649b1d4b0150f5fb3a4aed974e2b7bf056d4b Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 01:47:29 +0200 Subject: [PATCH 05/12] Updated example permission to follow new-ish registry layout --- example/exampleapp/permissions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/example/exampleapp/permissions.py b/example/exampleapp/permissions.py index fb23de2..51b9338 100644 --- a/example/exampleapp/permissions.py +++ b/example/exampleapp/permissions.py @@ -1,9 +1,10 @@ from django.contrib.flatpages.models import FlatPage from django.utils.translation import ugettext_lazy as _ -from authority import permissions +import authority +from authority.permissions import BasePermission -class FlatPagePermission(permissions.BasePermission): +class FlatPagePermission(BasePermission): """ This class contains a bunch of checks: @@ -48,4 +49,4 @@ class FlatPagePermission(permissions.BasePermission): return False top_secret.short_description=_('Is allowed to see top secret flatpages') -permissions.register(FlatPage, FlatPagePermission) +authority.register(FlatPage, FlatPagePermission) From 97eb2f99f5ea8c6ae26f2de0a6e7807776f0b581 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 11:44:00 +0200 Subject: [PATCH 06/12] Fixes issue #3 - make the admin work again after API changes --- src/authority/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/authority/admin.py b/src/authority/admin.py index c78b032..a3da005 100644 --- a/src/authority/admin.py +++ b/src/authority/admin.py @@ -6,6 +6,7 @@ from django.utils.text import capfirst, truncate_words from authority.models import Permission from authority.widgets import GenericForeignKeyRawIdWidget +from autority import get_choices_for class PermissionInline(generic.GenericTabularInline): model = Permission @@ -14,7 +15,7 @@ class PermissionInline(generic.GenericTabularInline): def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name == 'codename': - perm_choices = permissions.get_choices_for(self.parent_model) + perm_choices = get_choices_for(self.parent_model) kwargs['label'] = _('permission') kwargs['widget'] = forms.Select(choices=perm_choices) return db_field.formfield(**kwargs) From 428cd35dc017dabc59b78373a07b114d57968880 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 13:36:21 +0200 Subject: [PATCH 07/12] Fixing issue #3 for realz. --- src/authority/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authority/admin.py b/src/authority/admin.py index a3da005..235059b 100644 --- a/src/authority/admin.py +++ b/src/authority/admin.py @@ -6,7 +6,7 @@ from django.utils.text import capfirst, truncate_words from authority.models import Permission from authority.widgets import GenericForeignKeyRawIdWidget -from autority import get_choices_for +from authority import get_choices_for class PermissionInline(generic.GenericTabularInline): model = Permission From e2a49b17fdd186ba693e6407f13131a8bd650df7 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 15:25:18 +0200 Subject: [PATCH 08/12] Fixes oversight in get_labels. --- src/authority/sites.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/authority/sites.py b/src/authority/sites.py index 062f4b5..c2f2728 100644 --- a/src/authority/sites.py +++ b/src/authority/sites.py @@ -36,7 +36,7 @@ class PermissionSite(object): return getattr(perm_instance, check_name, None) def get_labels(self): - return [perm.label for perm in self._registry] + return [perm.label for perm in self._registry.values()] def get_choices_for(self, obj, default=BLANK_CHOICE_DASH): model_cls = obj @@ -75,7 +75,6 @@ class PermissionSite(object): permission_class = type("%sPermission" % model.__name__, (permission_class,), options) - # Instantiate the admin class to save in the registry permission_class.model = model self.setup(model, permission_class) self._registry[model] = permission_class From ab04831a7f7a8f472778d1c62e6879e80fb86ae4 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 15:27:46 +0200 Subject: [PATCH 09/12] Added missing import. --- src/authority/sites.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/authority/sites.py b/src/authority/sites.py index c2f2728..ebba324 100644 --- a/src/authority/sites.py +++ b/src/authority/sites.py @@ -2,6 +2,7 @@ from inspect import getmembers, ismethod from django.db.models.base import ModelBase from django.db.models.fields import BLANK_CHOICE_DASH from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ImproperlyConfigured from authority.permissions import BasePermission From 340cbb9c732b1e6edfdfbb3f1d55b9fbd384001c Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 15:55:25 +0200 Subject: [PATCH 10/12] Fixes issue #2 - Adds ability to pass non-lookup parameters in decorator to permission check. --- example/exampleapp/permissions.py | 2 +- example/exampleapp/views.py | 6 ++-- example/urls.py | 2 +- src/authority/decorators.py | 48 +++++++++++++++++-------------- src/authority/permissions.py | 6 +++- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/example/exampleapp/permissions.py b/example/exampleapp/permissions.py index 51b9338..72263a1 100644 --- a/example/exampleapp/permissions.py +++ b/example/exampleapp/permissions.py @@ -43,7 +43,7 @@ class FlatPagePermission(BasePermission): label = 'flatpage_permission' checks = ('review', 'top_secret') - def top_secret(self, flatpage=None): + def top_secret(self, flatpage=None, lala=None): if flatpage and flatpage.registration_required: return self.browse_flatpage(obj=flatpage) return False diff --git a/example/exampleapp/views.py b/example/exampleapp/views.py index 2727727..8b02090 100644 --- a/example/exampleapp/views.py +++ b/example/exampleapp/views.py @@ -3,11 +3,11 @@ from django.contrib.flatpages.models import FlatPage from authority.decorators import permission_required, permission_required_or_403 -@permission_required_or_403('flatpage_permission.top_secret', # use this to return a 403 page - (FlatPage, 'url__contains', 'url'), (FlatPage, 'url__contains', 'lala')) # @permission_required('flatpage_permission.top_secret', # (FlatPage, 'url__contains', 'url'), (FlatPage, 'url__contains', 'lala')) -#@permission_required_or_403('flatpages.add_flatpage') +# use this to return a 403 page: +@permission_required_or_403('flatpage_permission.top_secret', + (FlatPage, 'url__contains', 'url'), 'lala') def top_secret(request, url, lala=None): """ A wrapping view that performs the permission check given in the decorator diff --git a/example/urls.py b/example/urls.py index 6c33bf8..86fc631 100644 --- a/example/urls.py +++ b/example/urls.py @@ -14,7 +14,7 @@ urlpatterns = patterns('', #('^admin/', include(admin.site.urls)), (r'^perms/', include('authority.urls')), (r'^accounts/login/$', 'django.contrib.auth.views.login'), - url(r'^(?P[\/0-9A-Za-z]+)$', 'example.exampleapp.views.top_secret'), + url(r'^(?P[\/0-9A-Za-z]+)$', 'example.exampleapp.views.top_secret', {'lala': 'oh yeah!'}), ) if settings.DEBUG: diff --git a/src/authority/decorators.py b/src/authority/decorators.py index 8103f83..edd3427 100644 --- a/src/authority/decorators.py +++ b/src/authority/decorators.py @@ -12,7 +12,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from authority import permissions, get_check from authority.views import permission_denied -def permission_required(perm, *model_lookups, **kwargs): +def permission_required(perm, *lookup_variables, **kwargs): """ Decorator for views that checks whether a user has a particular permission enabled, redirecting to the log-in page if necessary. @@ -22,30 +22,36 @@ def permission_required(perm, *model_lookups, **kwargs): redirect_to_login = kwargs.pop('redirect_to_login', True) def decorate(view_func): def decorated(request, *args, **kwargs): - objs = [] if request.user.is_authenticated(): - for model, lookup, varname in model_lookups: - if varname not in kwargs: - continue - value = kwargs.get(varname, None) - if value is None: - continue - if isinstance(model, basestring): - model_class = get_model(*model.split(".")) - else: - model_class = model - if model_class is None: - raise ValueError( - "The given argument '%s' is not a valid model." % model) - if inspect.isclass(model_class) and \ - not issubclass(model_class, Model): - raise ValueError( - 'The argument %s needs to be a model.' % model) - objs.append(get_object_or_404(model_class, **{lookup: value})) + params = [] + for lookup_variable in lookup_variables: + if isinstance(lookup_variable, basestring): + value = kwargs.get(lookup_variable, None) + if value is None: + continue + params.append(value) + elif isinstance(lookup_variable, (tuple, list)): + model, lookup, varname = lookup_variable + value = kwargs.get(varname, None) + if value is None: + continue + if isinstance(model, basestring): + model_class = get_model(*model.split(".")) + else: + model_class = model + if model_class is None: + raise ValueError( + "The given argument '%s' is not a valid model." % model) + if (inspect.isclass(model_class) and + not issubclass(model_class, Model)): + raise ValueError( + 'The argument %s needs to be a model.' % model) + obj = get_object_or_404(model_class, **{lookup: value}) + params.append(obj) check = get_check(request.user, perm) granted = False if check is not None: - granted = check(*objs) + granted = check(*params) if granted or request.user.has_perm(perm): return view_func(request, *args, **kwargs) if redirect_to_login: diff --git a/src/authority/permissions.py b/src/authority/permissions.py index 169c5a1..27903a1 100644 --- a/src/authority/permissions.py +++ b/src/authority/permissions.py @@ -1,6 +1,6 @@ from inspect import getmembers, ismethod from django.db.models import Q -from django.db.models.base import ModelBase +from django.db.models.base import Model, ModelBase from django.template.defaultfilters import slugify from django.utils.translation import ugettext_lazy as _ @@ -74,6 +74,9 @@ class BasePermission(object): args = [self.model] perms = False for obj in args: + # skip this obj if it's not a model class or instance + if not isinstance(obj, (ModelBase, Model)): + continue # first check Django's permission system if self.user: perm = '%s.%s' % (obj._meta.app_label, check.lower()) @@ -83,6 +86,7 @@ class BasePermission(object): perm = '%s.%s' % (self.label, check.lower()) if generic: perm = '%s_%s' % (perm, obj._meta.object_name.lower()) + # then check authority's per object permissions if not isinstance(obj, ModelBase) and isinstance(obj, self.model): # only check the authority if obj is not a model class perms = perms or self.has_perm(perm, obj) From 66af3c16fd7e62cccc76d243544defca4405c0ab Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 15:56:10 +0200 Subject: [PATCH 11/12] Make sure we return to correct page after running admin action. --- src/authority/actions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/authority/actions.py b/src/authority/actions.py index b80edce..6571de7 100644 --- a/src/authority/actions.py +++ b/src/authority/actions.py @@ -9,6 +9,7 @@ from django.utils.text import capfirst from django.utils.translation import ugettext_lazy, ugettext as _ from django.contrib.contenttypes.models import ContentType from django.forms.formsets import all_valid +from django.http import HttpResponseRedirect try: from django.contrib.admin import actions @@ -61,7 +62,8 @@ def edit_permissions(modeladmin, request, queryset): if all_valid(formsets): for formset in formsets: formset.save() - return None + # redirect to full request path to make sure we keep filter + return HttpResponseRedirect(request.get_full_path()) context = { 'errors': ActionErrorList(formsets), From b7ebf0022cc79ae9102d842c0e49c7fed1567b1a Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Jul 2009 17:29:39 +0200 Subject: [PATCH 12/12] Fixing docstring, d'oh --- src/authority/templatetags/permissions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/authority/templatetags/permissions.py b/src/authority/templatetags/permissions.py index 468abb7..59ffc38 100644 --- a/src/authority/templatetags/permissions.py +++ b/src/authority/templatetags/permissions.py @@ -80,7 +80,7 @@ def do_if_has_perm(parser, token): meh {% endifhasperm %} - {% if hasperm "poll_permission.can_change" request.user %} + {% if hasperm "poll_permission.change_poll" request.user %} lalala {% else %} meh @@ -222,8 +222,8 @@ def get_permission(parser, token): {% get_permission [permission_label].[check_name] for [user] and [objs] as [varname] %} - {% get_permission "poll_permission.can_change" for request.user and poll as "is_allowed" %} - {% get_permission "poll_permission.can_change" for request.user and poll,second_poll as "is_allowed" %} + {% get_permission "poll_permission.change_poll" for request.user and poll as "is_allowed" %} + {% get_permission "poll_permission.change_poll" for request.user and poll,second_poll as "is_allowed" %} {% if is_allowed %} I've got ze power to change ze pollllllzzz. Muahahaa.