diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..5cd9591 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = authority +branch = 1 + +[report] +omit = *tests*,*migrations* diff --git a/.gitignore b/.gitignore index 4f54c99..5304c0b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ docs/build/* .DS_Store .tox/ dist/ +build/ +.coverage +.eggs/ diff --git a/.hgignore b/.hgignore deleted file mode 100644 index cae2f86..0000000 --- a/.hgignore +++ /dev/null @@ -1,26 +0,0 @@ -syntax: glob -*.pyc -*.pyo -*~ -*.swp -*.orig -*.kpf -*.egg-info -.project -.pydevproject -.DS_Store -MANIFEST -dist -build -dev.db -local_settings.py -parts/* -eggs/* -downloads/* -.installed.cfg -bin/* -develop-eggs/*.egg-link -example/example.db -docs/build -TODO -example/example.db \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ca07bf1..b36dc11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,28 @@ language: python -python: 3.5 -sudo: false -cache: - directories: - - "~/.cache/pip" -env: - matrix: - - TOXENV=py27-dj18 - - TOXENV=py33-dj18 - - TOXENV=py34-dj18 - - TOXENV=py35-dj18 -install: -- pip install tox codecov +dist: xenial +python: +- 2.7 +- 3.7 +cache: pip +install: pip install tox-travis codecov script: tox -v -after_success: -- codecov -deploy: - provider: pypi - user: jazzband - distributions: sdist bdist_wheel - password: - secure: XoXuSeFfJ+cM5/eCAAVuT6E/KsvuvMl9u9On5id6miq+wYqftpWiN9e/fjb3pDWkfZITooPeJBTpMJBGu+2Yc5i8JVGsYQgvP9dFqOH14P6NV4JU1ZF8iP7dpdWX3+h3K/QnqTvwwHmRF3mrDd/lnN9AB74bXLFYS282glJLVKM= - on: - tags: true - condition: "$TOXENV = py27-dj18" - repo: jazzband/django-authority +after_success: codecov +jobs: + fast_finish: true + include: + - stage: deploy + env: + python: 3.7 + install: skip + script: skip + after_success: skip + deploy: + provider: pypi + user: jazzband + server: https://jazzband.co/projects/django-authority/upload + distributions: sdist bdist_wheel + password: + secure: TZJdf9naBzdkE+HDyhtoCIIc0ddEXLWW/VUD975gJUWQpOA69h4ZjCCQ6z21AZdrWczCUh6/cOLYYSoC9OeaHqF+Jzunn3iEomvdVRsW7SX7MAxTFUENQHF3S3j8fYlqIPWvNDOgxJ/AERisMUSZkRKWIYjEInYo31RoJ1ySN0w= + on: + tags: true + repo: jazzband/django-authority diff --git a/LICENSE b/LICENSE index ec6db29..2e48fea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009, Jannis Leidel +Copyright (c) 2009-2019, Jannis Leidel All rights reserved. Redistribution and use in source and binary forms, with or without @@ -25,4 +25,4 @@ 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. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst index a9de472..4d1ad22 100644 --- a/README.rst +++ b/README.rst @@ -57,8 +57,32 @@ html version using the setup.py:: Changelog: ========== +0.14 (2020-02-07): +------------------ + +* Add Django 2.2 support +* Add Python 3.7 support +* Various fixes around the test harness. + +0.13.1 (2018-01-28): +-------------------- + +* Minor fixes to the documentation and versioning. + +0.13 (2018-01-28): +------------------ + +* Added support for Django 1.11 +* Drop Support for Python 3.3 +* Fixed a bug with template loader + +0.12 (2017-01-10): +------------------ + +* Added support for Django 1.10 + 0.11 (2016-07-17): ------------------ +------------------ * Added Migration in order to support Django 1.8 @@ -117,7 +141,9 @@ Changelog: * Added ability to override form class in ``add_permission`` view. -* Added easy way to assign permissions via a permission instance, e.g.:: +* Added easy way to assign permissions via a permission instance, e.g.: + + .. code-block:: python from django.contrib.auth.models import User from mysite.articles.permissions import ArticlePermission @@ -145,13 +171,17 @@ Changelog: * The templatetags have also been refactored to be easier to customize which required a change in the template tag signature: - Old:: + Old: + + .. code-block:: html+django {% permission_form flatpage %} {% permission_form flatpage "flatpage_permission.top_secret" %} {% permission_form OBJ PERMISSION_LABEL.CHECK_NAME %} - New:: + New: + + .. code-block:: html+django {% permission_form for flatpage %} {% permission_form for flatpage using "flatpage_permission.top_secret" %} diff --git a/authority/__init__.py b/authority/__init__.py index 71a6420..972a62d 100644 --- a/authority/__init__.py +++ b/authority/__init__.py @@ -1,4 +1,10 @@ -from django.utils.module_loading import autodiscover_modules +from pkg_resources import get_distribution, DistributionNotFound + +try: + __version__ = get_distribution("django-authority").version +except DistributionNotFound: + # package is not installed + pass LOADING = False @@ -13,4 +19,5 @@ def autodiscover(): return LOADING = True + from django.utils.module_loading import autodiscover_modules autodiscover_modules('permissions') diff --git a/authority/admin.py b/authority/admin.py index fe061da..dfc5af1 100644 --- a/authority/admin.py +++ b/authority/admin.py @@ -27,20 +27,20 @@ from authority.utils import get_choices_for class PermissionInline(GenericTabularInline): model = Permission - raw_id_fields = ('user', 'group', 'creator') + raw_id_fields = ("user", "group", "creator") extra = 1 def formfield_for_dbfield(self, db_field, **kwargs): - if db_field.name == 'codename': + if db_field.name == "codename": perm_choices = get_choices_for(self.parent_model) - kwargs['label'] = _('permission') - kwargs['widget'] = forms.Select(choices=perm_choices) + kwargs["label"] = _("permission") + kwargs["widget"] = forms.Select(choices=perm_choices) return super(PermissionInline, self).formfield_for_dbfield(db_field, **kwargs) class ActionPermissionInline(PermissionInline): raw_id_fields = () - template = 'admin/edit_inline/action_tabular.html' + template = "admin/edit_inline/action_tabular.html" class ActionErrorList(forms.utils.ErrorList): @@ -56,9 +56,11 @@ def edit_permissions(modeladmin, request, queryset): app_label = opts.app_label # Check that the user has the permission to edit permissions - if not (request.user.is_superuser or - request.user.has_perm('authority.change_permission') or - request.user.has_perm('authority.change_foreign_permissions')): + if not ( + request.user.is_superuser + or request.user.has_perm("authority.change_permission") + or request.user.has_perm("authority.change_foreign_permissions") + ): raise PermissionDenied inline = ActionPermissionInline(queryset.model, modeladmin.admin_site) @@ -70,9 +72,10 @@ def edit_permissions(modeladmin, request, queryset): prefixes[prefix] = prefixes.get(prefix, 0) + 1 if prefixes[prefix] != 1: prefix = "%s-%s" % (prefix, prefixes[prefix]) - if request.POST.get('post'): - formset = FormSet(data=request.POST, files=request.FILES, - instance=obj, prefix=prefix) + if request.POST.get("post"): + formset = FormSet( + data=request.POST, files=request.FILES, instance=obj, prefix=prefix + ) else: formset = FormSet(instance=obj, prefix=prefix) formsets.append(formset) @@ -86,76 +89,90 @@ def edit_permissions(modeladmin, request, queryset): media = media + inline_admin_formset.media ordered_objects = opts.get_ordered_objects() - if request.POST.get('post'): + if request.POST.get("post"): if all_valid(formsets): for formset in formsets: formset.save() else: - modeladmin.message_user(request, '; '.join( - err.as_text() for formset in formsets for err in formset.errors - )) + modeladmin.message_user( + request, + "; ".join( + err.as_text() for formset in formsets for err in formset.errors + ), + ) # redirect to full request path to make sure we keep filter return HttpResponseRedirect(request.get_full_path()) context = { - 'errors': ActionErrorList(formsets), - 'title': ugettext('Permissions for %s') % force_text(opts.verbose_name_plural), - 'inline_admin_formsets': inline_admin_formsets, - 'app_label': app_label, - 'change': True, - 'ordered_objects': ordered_objects, - 'form_url': mark_safe(''), - 'opts': opts, - 'target_opts': queryset.model._meta, - 'content_type_id': ContentType.objects.get_for_model(queryset.model).id, - 'save_as': False, - 'save_on_top': False, - 'is_popup': False, - 'media': mark_safe(media), - 'show_delete': False, - 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, - 'queryset': queryset, + "errors": ActionErrorList(formsets), + "title": ugettext("Permissions for %s") % force_text(opts.verbose_name_plural), + "inline_admin_formsets": inline_admin_formsets, + "app_label": app_label, + "change": True, + "ordered_objects": ordered_objects, + "form_url": mark_safe(""), + "opts": opts, + "target_opts": queryset.model._meta, + "content_type_id": ContentType.objects.get_for_model(queryset.model).id, + "save_as": False, + "save_on_top": False, + "is_popup": False, + "media": mark_safe(media), + "show_delete": False, + "action_checkbox_name": helpers.ACTION_CHECKBOX_NAME, + "queryset": queryset, "object_name": force_text(opts.verbose_name), } - template_name = getattr(modeladmin, 'permission_change_form_template', [ - "admin/%s/%s/permission_change_form.html" % (app_label, opts.object_name.lower()), - "admin/%s/permission_change_form.html" % app_label, - "admin/permission_change_form.html" - ]) + template_name = getattr( + modeladmin, + "permission_change_form_template", + [ + "admin/%s/%s/permission_change_form.html" + % (app_label, opts.object_name.lower()), + "admin/%s/permission_change_form.html" % app_label, + "admin/permission_change_form.html", + ], + ) return render_to_response(template_name, context, request) -edit_permissions.short_description = _("Edit permissions for selected %(verbose_name_plural)s") + + +edit_permissions.short_description = _( + "Edit permissions for selected %(verbose_name_plural)s" +) class PermissionAdmin(admin.ModelAdmin): - list_display = ('codename', 'content_type', 'user', 'group', 'approved') - list_filter = ('approved', 'content_type') - search_fields = ('user__username', 'group__name', 'codename') - raw_id_fields = ('user', 'group', 'creator') - generic_fields = ('content_object',) - actions = ['approve_permissions'] + list_display = ("codename", "content_type", "user", "group", "approved") + list_filter = ("approved", "content_type") + search_fields = ("user__username", "group__name", "codename") + raw_id_fields = ("user", "group", "creator") + generic_fields = ("content_object",) + actions = ["approve_permissions"] fieldsets = ( - (None, {'fields': ('codename', ('content_type', 'object_id'))}), - (_('Permitted'), {'fields': ('approved', 'user', 'group')}), - (_('Creation'), {'fields': ('creator', 'date_requested', 'date_approved')}), + (None, {"fields": ("codename", ("content_type", "object_id"))}), + (_("Permitted"), {"fields": ("approved", "user", "group")}), + (_("Creation"), {"fields": ("creator", "date_requested", "date_approved")}), ) def formfield_for_dbfield(self, db_field, **kwargs): # For generic foreign keys marked as generic_fields we use a special widget - names = [f.fk_field - for f in self.model._meta.virtual_fields - if f.name in self.generic_fields] + names = [ + f.fk_field + for f in self.model._meta.virtual_fields + if f.name in self.generic_fields + ] if db_field.name in names: for gfk in self.model._meta.virtual_fields: if gfk.fk_field == db_field.name: - kwargs['widget'] = GenericForeignKeyRawIdWidget( - gfk.ct_field, self.admin_site._registry.keys()) + kwargs["widget"] = GenericForeignKeyRawIdWidget( + gfk.ct_field, self.admin_site._registry.keys() + ) break return super(PermissionAdmin, self).formfield_for_dbfield(db_field, **kwargs) def queryset(self, request): user = request.user - if (user.is_superuser or - user.has_perm('permissions.change_foreign_permissions')): + if user.is_superuser or user.has_perm("permissions.change_foreign_permissions"): return super(PermissionAdmin, self).queryset(request) return super(PermissionAdmin, self).queryset(request).filter(creator=user) @@ -164,10 +181,14 @@ class PermissionAdmin(admin.ModelAdmin): permission.approve(request.user) message = ungettext( "%(count)d permission successfully approved.", - "%(count)d permissions successfully approved.", len(queryset)) - self.message_user(request, message % {'count': len(queryset)}) + "%(count)d permissions successfully approved.", + len(queryset), + ) + self.message_user(request, message % {"count": len(queryset)}) + approve_permissions.short_description = _("Approve selected permissions") + admin.site.register(Permission, PermissionAdmin) if actions: diff --git a/authority/compat.py b/authority/compat.py index 2d2d259..a54d6f1 100644 --- a/authority/compat.py +++ b/authority/compat.py @@ -5,7 +5,7 @@ from django.conf import settings # Since get_user_model() causes a circular import if called when app models are # being loaded, the user_model_label should be used when possible, with calls # to get_user_model deferred to execution time -user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') +user_model_label = getattr(settings, "AUTH_USER_MODEL", "auth.User") try: from django.contrib.auth import get_user_model diff --git a/authority/decorators.py b/authority/decorators.py index fa5f582..eceafc6 100644 --- a/authority/decorators.py +++ b/authority/decorators.py @@ -2,6 +2,7 @@ import inspect from django.http import HttpResponseRedirect from django.utils.http import urlquote from django.utils.functional import wraps +from django.utils.six import string_types from django.db.models import Model from django.apps import apps from django.shortcuts import get_object_or_404 @@ -17,16 +18,16 @@ 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. """ - login_url = kwargs.pop('login_url', settings.LOGIN_URL) - redirect_field_name = kwargs.pop('redirect_field_name', REDIRECT_FIELD_NAME) - redirect_to_login = kwargs.pop('redirect_to_login', True) + login_url = kwargs.pop("login_url", settings.LOGIN_URL) + redirect_field_name = kwargs.pop("redirect_field_name", REDIRECT_FIELD_NAME) + redirect_to_login = kwargs.pop("redirect_to_login", True) def decorate(view_func): def decorated(request, *args, **kwargs): - if request.user.is_authenticated(): + if request.user.is_authenticated: params = [] for lookup_variable in lookup_variables: - if isinstance(lookup_variable, basestring): + if isinstance(lookup_variable, string_types): value = kwargs.get(lookup_variable, None) if value is None: continue @@ -36,17 +37,20 @@ def permission_required(perm, *lookup_variables, **kwargs): value = kwargs.get(varname, None) if value is None: continue - if isinstance(model, basestring): + if isinstance(model, string_types): model_class = apps.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)): + "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) + "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) @@ -58,9 +62,11 @@ def permission_required(perm, *lookup_variables, **kwargs): if redirect_to_login: path = urlquote(request.get_full_path()) tup = login_url, redirect_field_name, path - return HttpResponseRedirect('%s?%s=%s' % tup) + return HttpResponseRedirect("%s?%s=%s" % tup) return permission_denied(request) + return wraps(view_func)(decorated) + return decorate @@ -69,5 +75,5 @@ def permission_required_or_403(perm, *args, **kwargs): Decorator that wraps the permission_required decorator and returns a permission denied (403) page instead of redirecting to the login URL. """ - kwargs['redirect_to_login'] = False + kwargs["redirect_to_login"] = False return permission_required(perm, *args, **kwargs) diff --git a/authority/exceptions.py b/authority/exceptions.py index 36a9147..e799d9b 100644 --- a/authority/exceptions.py +++ b/authority/exceptions.py @@ -4,11 +4,11 @@ class AuthorityException(Exception): class NotAModel(AuthorityException): def __init__(self, object): - super(NotAModel, self).__init__( - "Not a model class or instance") + super(NotAModel, self).__init__("Not a model class or instance") class UnsavedModelInstance(AuthorityException): def __init__(self, object): super(UnsavedModelInstance, self).__init__( - "Model instance has no pk, was it saved?") + "Model instance has no pk, was it saved?" + ) diff --git a/authority/forms.py b/authority/forms.py index 9fdcff1..60f9292 100644 --- a/authority/forms.py +++ b/authority/forms.py @@ -14,7 +14,7 @@ User = get_user_model() class BasePermissionForm(forms.ModelForm): - codename = forms.CharField(label=_('Permission')) + codename = forms.CharField(label=_("Permission")) class Meta: model = Permission @@ -25,10 +25,10 @@ class BasePermissionForm(forms.ModelForm): self.obj = obj self.approved = approved if obj and perm: - self.base_fields['codename'].widget = forms.HiddenInput() + self.base_fields["codename"].widget = forms.HiddenInput() elif obj and (not perm or not approved): perms = get_choices_for(self.obj) - self.base_fields['codename'].widget = forms.Select(choices=perms) + self.base_fields["codename"].widget = forms.Select(choices=perms) super(BasePermissionForm, self).__init__(*args, **kwargs) def save(self, request, commit=True, *args, **kwargs): @@ -41,14 +41,14 @@ class BasePermissionForm(forms.ModelForm): class UserPermissionForm(BasePermissionForm): - user = forms.CharField(label=_('User')) + user = forms.CharField(label=_("User")) class Meta(BasePermissionForm.Meta): - fields = ('user',) + fields = ("user",) def __init__(self, *args, **kwargs): - if not kwargs.get('approved', False): - self.base_fields['user'].widget = forms.HiddenInput() + if not kwargs.get("approved", False): + self.base_fields["user"].widget = forms.HiddenInput() super(UserPermissionForm, self).__init__(*args, **kwargs) def clean_user(self): @@ -57,34 +57,41 @@ class UserPermissionForm(BasePermissionForm): user = User.objects.get(username__iexact=username) except User.DoesNotExist: raise forms.ValidationError( - mark_safe(_("A user with that username does not exist."))) + mark_safe(_("A user with that username does not exist.")) + ) check = permissions.BasePermission(user=user) error_msg = None if user.is_superuser: - error_msg = _("The user %(user)s do not need to request " - "access to any permission as it is a super user.") + error_msg = _( + "The user %(user)s do not need to request " + "access to any permission as it is a super user." + ) elif check.has_perm(self.perm, self.obj): - error_msg = _("The user %(user)s already has the permission " - "'%(perm)s' for %(object_name)s '%(obj)s'") + error_msg = _( + "The user %(user)s already has the permission " + "'%(perm)s' for %(object_name)s '%(obj)s'" + ) elif check.requested_perm(self.perm, self.obj): - error_msg = _("The user %(user)s already requested the permission" - " '%(perm)s' for %(object_name)s '%(obj)s'") + error_msg = _( + "The user %(user)s already requested the permission" + " '%(perm)s' for %(object_name)s '%(obj)s'" + ) if error_msg: error_msg = error_msg % { - 'object_name': self.obj._meta.object_name.lower(), - 'perm': self.perm, - 'obj': self.obj, - 'user': user, + "object_name": self.obj._meta.object_name.lower(), + "perm": self.perm, + "obj": self.obj, + "user": user, } raise forms.ValidationError(mark_safe(error_msg)) return user class GroupPermissionForm(BasePermissionForm): - group = forms.CharField(label=_('Group')) + group = forms.CharField(label=_("Group")) class Meta(BasePermissionForm.Meta): - fields = ('group',) + fields = ("group",) def clean_group(self): groupname = self.cleaned_data["group"] @@ -92,14 +99,21 @@ class GroupPermissionForm(BasePermissionForm): group = Group.objects.get(name__iexact=groupname) except Group.DoesNotExist: raise forms.ValidationError( - mark_safe(_("A group with that name does not exist."))) + mark_safe(_("A group with that name does not exist.")) + ) check = permissions.BasePermission(group=group) if check.has_perm(self.perm, self.obj): - raise forms.ValidationError(mark_safe( - _("This group already has the permission '%(perm)s' " - "for %(object_name)s '%(obj)s'") % { - 'perm': self.perm, - 'object_name': self.obj._meta.object_name.lower(), - 'obj': self.obj, - })) + raise forms.ValidationError( + mark_safe( + _( + "This group already has the permission '%(perm)s' " + "for %(object_name)s '%(obj)s'" + ) + % { + "perm": self.perm, + "object_name": self.obj._meta.object_name.lower(), + "obj": self.obj, + } + ) + ) return group diff --git a/authority/managers.py b/authority/managers.py index 0fd26fd..3d20365 100644 --- a/authority/managers.py +++ b/authority/managers.py @@ -4,7 +4,6 @@ from django.contrib.contenttypes.models import ContentType class PermissionManager(models.Manager): - def get_content_type(self, obj): return ContentType.objects.get_for_model(obj) @@ -12,46 +11,41 @@ class PermissionManager(models.Manager): return self.filter(content_type=self.get_content_type(obj)) def for_object(self, obj, approved=True): - return self.get_for_model(obj).select_related( - 'user', 'creator', 'group', 'content_type' - ).filter(object_id=obj.id, approved=approved) + return ( + self.get_for_model(obj) + .select_related("user", "creator", "group", "content_type") + .filter(object_id=obj.id, approved=approved) + ) def for_user(self, user, obj, check_groups=True): perms = self.get_for_model(obj) if not check_groups: - return perms.select_related('user', 'creator').filter(user=user) + return perms.select_related("user", "creator").filter(user=user) # Hacking user to user__pk to workaround deepcopy bug: # http://bugs.python.org/issue2460 # Which is triggered by django's deepcopy which backports that fix in # Django 1.2 - return perms.select_related( - 'user', - 'creator' - ).prefetch_related( - 'user__groups' - ).filter( - Q(user__pk=user.pk) | Q(group__in=user.groups.all()) + return ( + perms.select_related("user", "creator") + .prefetch_related("user__groups") + .filter(Q(user__pk=user.pk) | Q(group__in=user.groups.all())) ) - def user_permissions( - self, user, perm, obj, approved=True, check_groups=True): - return self.for_user( - user, - obj, - check_groups, - ).filter( - codename=perm, - approved=approved, + def user_permissions(self, user, perm, obj, approved=True, check_groups=True): + return self.for_user(user, obj, check_groups,).filter( + codename=perm, approved=approved, ) def group_permissions(self, group, perm, obj, approved=True): """ Get objects that have Group perm permission on """ - return self.get_for_model(obj).select_related( - 'user', 'group', 'creator').filter(group=group, codename=perm, - approved=approved) + return ( + self.get_for_model(obj) + .select_related("user", "group", "creator") + .filter(group=group, codename=perm, approved=approved) + ) def delete_objects_permissions(self, obj): """ diff --git a/authority/migrations/0001_initial.py b/authority/migrations/0001_initial.py index f17f6db..3be9d7d 100644 --- a/authority/migrations/0001_initial.py +++ b/authority/migrations/0001_initial.py @@ -9,35 +9,96 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('auth', '0001_initial'), + ("auth", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0001_initial'), + ("contenttypes", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Permission', + name="Permission", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('codename', models.CharField(max_length=100, verbose_name='codename')), - ('object_id', models.PositiveIntegerField()), - ('approved', models.BooleanField(default=False, help_text='Designates whether the permission has been approved and treated as active. Unselect this instead of deleting permissions.', verbose_name='approved')), - ('date_requested', models.DateTimeField(default=datetime.datetime.now, verbose_name='date requested')), - ('date_approved', models.DateTimeField(null=True, verbose_name='date approved', blank=True)), - ('content_type', models.ForeignKey(related_name='row_permissions', to='contenttypes.ContentType')), - ('creator', models.ForeignKey(related_name='created_permissions', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('group', models.ForeignKey(blank=True, to='auth.Group', null=True)), - ('user', models.ForeignKey(related_name='granted_permissions', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ("codename", models.CharField(max_length=100, verbose_name="codename")), + ("object_id", models.PositiveIntegerField()), + ( + "approved", + models.BooleanField( + default=False, + help_text="Designates whether the permission has been approved and treated as active. Unselect this instead of deleting permissions.", + verbose_name="approved", + ), + ), + ( + "date_requested", + models.DateTimeField( + default=datetime.datetime.now, verbose_name="date requested" + ), + ), + ( + "date_approved", + models.DateTimeField( + null=True, verbose_name="date approved", blank=True + ), + ), + ( + "content_type", + models.ForeignKey( + related_name="row_permissions", + to="contenttypes.ContentType", + on_delete=models.CASCADE, + ), + ), + ( + "creator", + models.ForeignKey( + related_name="created_permissions", + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + on_delete=models.CASCADE, + ), + ), + ( + "group", + models.ForeignKey( + blank=True, to="auth.Group", null=True, on_delete=models.CASCADE + ), + ), + ( + "user", + models.ForeignKey( + related_name="granted_permissions", + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + on_delete=models.CASCADE, + ), + ), ], options={ - 'verbose_name': 'permission', - 'verbose_name_plural': 'permissions', - 'permissions': (('change_foreign_permissions', 'Can change foreign permissions'), ('delete_foreign_permissions', 'Can delete foreign permissions'), ('approve_permission_requests', 'Can approve permission requests')), + "verbose_name": "permission", + "verbose_name_plural": "permissions", + "permissions": ( + ("change_foreign_permissions", "Can change foreign permissions"), + ("delete_foreign_permissions", "Can delete foreign permissions"), + ("approve_permission_requests", "Can approve permission requests"), + ), }, bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='permission', - unique_together=set([('codename', 'object_id', 'content_type', 'user', 'group')]), + name="permission", + unique_together=set( + [("codename", "object_id", "content_type", "user", "group")] + ), ), ] diff --git a/authority/models.py b/authority/models.py index d6cca3b..eb76807 100644 --- a/authority/models.py +++ b/authority/models.py @@ -15,25 +15,41 @@ class Permission(models.Model): This kind of permission is associated with a user/group and an object of any content type. """ - codename = models.CharField(_('codename'), max_length=100) - content_type = models.ForeignKey(ContentType, related_name="row_permissions") + + codename = models.CharField(_("codename"), max_length=100) + content_type = models.ForeignKey( + ContentType, related_name="row_permissions", on_delete=models.CASCADE + ) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") user = models.ForeignKey( - user_model_label, null=True, blank=True, related_name='granted_permissions') - group = models.ForeignKey(Group, null=True, blank=True) + user_model_label, + null=True, + blank=True, + related_name="granted_permissions", + on_delete=models.CASCADE, + ) + group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.CASCADE) creator = models.ForeignKey( - user_model_label, null=True, blank=True, related_name='created_permissions') + user_model_label, + null=True, + blank=True, + related_name="created_permissions", + on_delete=models.CASCADE, + ) approved = models.BooleanField( - _('approved'), + _("approved"), default=False, - help_text=_("Designates whether the permission has been approved and treated as active. " - "Unselect this instead of deleting permissions.")) + help_text=_( + "Designates whether the permission has been approved and treated as active. " + "Unselect this instead of deleting permissions." + ), + ) - date_requested = models.DateTimeField(_('date requested'), default=datetime.now) - date_approved = models.DateTimeField(_('date approved'), blank=True, null=True) + date_requested = models.DateTimeField(_("date requested"), default=datetime.now) + date_approved = models.DateTimeField(_("date approved"), blank=True, null=True) objects = PermissionManager() @@ -42,12 +58,12 @@ class Permission(models.Model): class Meta: unique_together = ("codename", "object_id", "content_type", "user", "group") - verbose_name = _('permission') - verbose_name_plural = _('permissions') + verbose_name = _("permission") + verbose_name_plural = _("permissions") permissions = ( - ('change_foreign_permissions', 'Can change foreign permissions'), - ('delete_foreign_permissions', 'Can delete foreign permissions'), - ('approve_permission_requests', 'Can approve permission requests'), + ("change_foreign_permissions", "Can change foreign permissions"), + ("delete_foreign_permissions", "Can delete foreign permissions"), + ("approve_permission_requests", "Can approve permission requests"), ) def save(self, *args, **kwargs): diff --git a/authority/permissions.py b/authority/permissions.py index e19200f..55ac8b2 100644 --- a/authority/permissions.py +++ b/authority/permissions.py @@ -14,9 +14,9 @@ class PermissionMetaclass(type): Used to generate the default set of permission checks "add", "change" and "delete". """ + def __new__(cls, name, bases, attrs): - new_class = super( - PermissionMetaclass, cls).__new__(cls, name, bases, attrs) + new_class = super(PermissionMetaclass, cls).__new__(cls, name, bases, attrs) if not new_class.label: new_class.label = "%s_permission" % new_class.__name__.lower() new_class.label = slugify(new_class.label) @@ -31,11 +31,12 @@ class BasePermission(object): """ Base Permission class to be used to define app permissions. """ + __metaclass__ = PermissionMetaclass checks = () label = None - generic_checks = ['add', 'browse', 'change', 'delete'] + generic_checks = ["add", "browse", "change", "delete"] def __init__(self, user=None, group=None, *args, **kwargs): self.user = user @@ -48,10 +49,7 @@ class BasePermission(object): """ if not self.user: return {}, {} - group_pks = set(self.user.groups.values_list( - 'pk', - flat=True, - )) + group_pks = set(self.user.groups.values_list("pk", flat=True,)) perms = Permission.objects.filter( Q(user__pk=self.user.pk) | Q(group__pk__in=group_pks), ) @@ -59,22 +57,26 @@ class BasePermission(object): group_permissions = {} for perm in perms: if perm.user_id == self.user.pk: - user_permissions[( - perm.object_id, - perm.content_type_id, - perm.codename, - perm.approved, - )] = True + user_permissions[ + ( + perm.object_id, + perm.content_type_id, + perm.codename, + perm.approved, + ) + ] = True # If the user has the permission do for something, but perm.user != # self.user then by definition that permission came from the # group. else: - group_permissions[( - perm.object_id, - perm.content_type_id, - perm.codename, - perm.approved, - )] = True + group_permissions[ + ( + perm.object_id, + perm.content_type_id, + perm.codename, + perm.approved, + ) + ] = True return user_permissions, group_permissions def _get_group_cached_perms(self): @@ -83,17 +85,12 @@ class BasePermission(object): """ if not self.group: return {} - perms = Permission.objects.filter( - group=self.group, - ) + perms = Permission.objects.filter(group=self.group,) group_permissions = {} for perm in perms: - group_permissions[( - perm.object_id, - perm.content_type_id, - perm.codename, - perm.approved, - )] = True + group_permissions[ + (perm.object_id, perm.content_type_id, perm.codename, perm.approved,) + ] = True return group_permissions def _prime_user_perm_caches(self): @@ -123,11 +120,7 @@ class BasePermission(object): # Check to see if the cache has been primed. if not self.user: return {} - cache_filled = getattr( - self.user, - '_authority_perm_cache_filled', - False, - ) + cache_filled = getattr(self.user, "_authority_perm_cache_filled", False,) if cache_filled: # Don't really like the name for this, but this matches how Django # does it. @@ -145,11 +138,7 @@ class BasePermission(object): # Check to see if the cache has been primed. if not self.group: return {} - cache_filled = getattr( - self.group, - '_authority_perm_cache_filled', - False, - ) + cache_filled = getattr(self.group, "_authority_perm_cache_filled", False,) if cache_filled: # Don't really like the name for this, but this matches how Django # does it. @@ -167,11 +156,7 @@ class BasePermission(object): # Check to see if the cache has been primed. if not self.user: return {} - cache_filled = getattr( - self.user, - '_authority_perm_cache_filled', - False, - ) + cache_filled = getattr(self.user, "_authority_perm_cache_filled", False,) if cache_filled: return self.user._authority_group_perm_cache @@ -194,7 +179,7 @@ class BasePermission(object): @property def use_smart_cache(self): - use_smart_cache = getattr(settings, 'AUTHORITY_USE_SMART_CACHE', True) + use_smart_cache = getattr(settings, "AUTHORITY_USE_SMART_CACHE", True) return (self.user or self.group) and use_smart_cache def has_user_perms(self, perm, obj, approved, check_groups=True): @@ -210,12 +195,7 @@ class BasePermission(object): def _user_has_perms(cached_perms): # Check to see if the permission is in the cache. - return cached_perms.get(( - obj.pk, - content_type_pk, - perm, - approved, - )) + return cached_perms.get((obj.pk, content_type_pk, perm, approved,)) # Check to see if the permission is in the cache. if _user_has_perms(self._user_perm_cache): @@ -227,15 +207,13 @@ class BasePermission(object): return False # Actually hit the DB, no smart cache used. - return Permission.objects.user_permissions( - self.user, - perm, - obj, - approved, - check_groups, - ).filter( - object_id=obj.pk, - ).exists() + return ( + Permission.objects.user_permissions( + self.user, perm, obj, approved, check_groups, + ) + .filter(object_id=obj.pk,) + .exists() + ) def has_group_perms(self, perm, obj, approved): """ @@ -249,24 +227,17 @@ class BasePermission(object): def _group_has_perms(cached_perms): # Check to see if the permission is in the cache. - return cached_perms.get(( - obj.pk, - content_type_pk, - perm, - approved, - )) + return cached_perms.get((obj.pk, content_type_pk, perm, approved,)) # Check to see if the permission is in the cache. return _group_has_perms(self._group_perm_cache) # Actually hit the DB, no smart cache used. - return Permission.objects.group_permissions( - self.group, - perm, obj, - approved, - ).filter( - object_id=obj.pk, - ).exists() + return ( + Permission.objects.group_permissions(self.group, perm, obj, approved,) + .filter(object_id=obj.pk,) + .exists() + ) def has_perm(self, perm, obj, check_groups=True, approved=True): """ @@ -305,25 +276,20 @@ class BasePermission(object): return perms def get_django_codename( - self, check, model_or_instance, generic=False, without_left=False): + self, check, model_or_instance, generic=False, without_left=False + ): if without_left: perm = check else: - perm = '%s.%s' % (model_or_instance._meta.app_label, check.lower()) + perm = "%s.%s" % (model_or_instance._meta.app_label, check.lower()) if generic: - perm = '%s_%s' % ( - perm, - model_or_instance._meta.object_name.lower(), - ) + perm = "%s_%s" % (perm, model_or_instance._meta.object_name.lower(),) return perm def get_codename(self, check, model_or_instance, generic=False): - perm = '%s.%s' % (self.label, check.lower()) + perm = "%s.%s" % (self.label, check.lower()) if generic: - perm = '%s_%s' % ( - perm, - model_or_instance._meta.object_name.lower(), - ) + perm = "%s_%s" % (perm, model_or_instance._meta.object_name.lower(),) return perm def assign(self, check=None, content_object=None, generic=False): @@ -345,7 +311,7 @@ class BasePermission(object): content_objects = content_object if not check: - checks = self.generic_checks + getattr(self, 'checks', []) + checks = self.generic_checks + getattr(self, "checks", []) elif not isinstance(check, (list, tuple)): checks = (check,) else: @@ -364,11 +330,7 @@ class BasePermission(object): for check in checks: if isinstance(content_object, Model): # make an authority per object permission - codename = self.get_codename( - check, - content_object, - generic, - ) + codename = self.get_codename(check, content_object, generic,) try: perm = Permission.objects.get( user=self.user, @@ -390,21 +352,16 @@ class BasePermission(object): elif isinstance(content_object, ModelBase): # make a Django permission codename = self.get_django_codename( - check, - content_object, - generic, - without_left=True, + check, content_object, generic, without_left=True, ) try: perm = DjangoPermission.objects.get(codename=codename) except DjangoPermission.DoesNotExist: name = check - if '_' in name: - name = name[0:name.find('_')] + if "_" in name: + name = name[0 : name.find("_")] perm = DjangoPermission( - name=name, - codename=codename, - content_type=content_type, + name=name, codename=codename, content_type=content_type, ) perm.save() self.user.user_permissions.add(perm) diff --git a/authority/sites.py b/authority/sites.py index 9db3ae7..a6c56bc 100644 --- a/authority/sites.py +++ b/authority/sites.py @@ -19,6 +19,7 @@ class PermissionSite(object): """ A dictionary that contains permission instances and their labels. """ + _registry = {} _choices = {} @@ -32,7 +33,7 @@ class PermissionSite(object): 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_label, check_name = label.split(".") perm_cls = self.get_permission_by_label(perm_label) if perm_cls is None: return None @@ -52,8 +53,8 @@ class PermissionSite(object): 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) + signature = "%s.%s" % (perm.label, name) + label = getattr(check, "short_description", signature) choices.append((signature, label)) self._choices[model_cls] = choices return choices @@ -67,17 +68,23 @@ class PermissionSite(object): 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))) + "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__) + "The model %s is already registered" % model.__name__ + ) if options: - options['__module__'] = __name__ + options["__module__"] = __name__ permission_class = type( - "%sPermission" % model.__name__, (permission_class,), options) + "%sPermission" % model.__name__, (permission_class,), options + ) permission_class.model = model self.setup(model, permission_class) @@ -88,7 +95,7 @@ class PermissionSite(object): 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__) + raise NotRegistered("The model %s is not registered" % model.__name__) del self._registry[model] def setup(self, model, permission): @@ -99,10 +106,10 @@ class PermissionSite(object): 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}) + "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) @@ -111,11 +118,12 @@ class PermissionSite(object): 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} + "object_name": model._meta.object_name.lower(), + "check": check_name, + } func.check_name = check_name if func_name not in permission.checks: - permission.checks = (list(permission.checks) + [func_name]) + permission.checks = list(permission.checks) + [func_name] setattr(permission, func_name, func) setattr(model, "permissions", PermissionDescriptor()) @@ -125,6 +133,7 @@ class PermissionSite(object): if check_func and not granted: return check_func(self, *args, **kwargs) return granted + return check @@ -134,7 +143,9 @@ class PermissionDescriptor(object): if obj: return ContentType.objects.get_for_model(obj) else: - raise Exception("Invalid arguments given to PermissionDescriptor.get_content_type") + raise Exception( + "Invalid arguments given to PermissionDescriptor.get_content_type" + ) def __get__(self, instance, owner): if instance is None: @@ -142,6 +153,7 @@ class PermissionDescriptor(object): ct = self.get_content_type(instance) return ct.row_permissions.all() + site = PermissionSite() get_check = site.get_check get_choices_for = site.get_choices_for diff --git a/authority/templatetags/permissions.py b/authority/templatetags/permissions.py index 3a0a00c..26976d5 100644 --- a/authority/templatetags/permissions.py +++ b/authority/templatetags/permissions.py @@ -1,6 +1,6 @@ from django import template from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import reverse +from django.urls import reverse from django.contrib.auth.models import AnonymousUser from authority.utils import get_check @@ -16,27 +16,31 @@ register = template.Library() @register.simple_tag def url_for_obj(view_name, obj): - return reverse(view_name, kwargs={ - 'app_label': obj._meta.app_label, - 'module_name': obj._meta.module_name, - 'pk': obj.pk} + return reverse( + view_name, + kwargs={ + "app_label": obj._meta.app_label, + "module_name": obj._meta.module_name, + "pk": obj.pk, + }, ) @register.simple_tag def add_url_for_obj(obj): - return url_for_obj('authority-add-permission', obj) + return url_for_obj("authority-add-permission", obj) @register.simple_tag def request_url_for_obj(obj): - return url_for_obj('authority-add-permission-request', obj) + return url_for_obj("authority-add-permission-request", obj) class ResolverNode(template.Node): """ A small wrapper that adds a convenient resolve method. """ + def resolve(self, var, context): """Resolves a variable out of context if it's not in quotes""" if var is None: @@ -49,7 +53,7 @@ class ResolverNode(template.Node): @classmethod def next_bit_for(cls, bits, key, if_none=None): try: - return bits[bits.index(key)+1] + return bits[bits.index(key) + 1] except ValueError: return if_none @@ -58,18 +62,18 @@ class PermissionComparisonNode(ResolverNode): """ Implements a node to provide an "if user/group has permission on object" """ + @classmethod def handle_token(cls, parser, token): bits = token.contents.split() if 5 < len(bits) < 3: raise template.TemplateSyntaxError( - "'%s' tag takes three, " - "four or five arguments" % bits[0] + "'%s' tag takes three, " "four or five arguments" % bits[0] ) - end_tag = 'endifhasperm' - nodelist_true = parser.parse(('else', end_tag)) + end_tag = "endifhasperm" + nodelist_true = parser.parse(("else", end_tag)) token = parser.next_token() - if token.contents == 'else': # there is an 'else' clause in the tag + if token.contents == "else": # there is an 'else' clause in the tag nodelist_false = parser.parse((end_tag,)) parser.delete_first_token() else: @@ -107,13 +111,13 @@ class PermissionComparisonNode(ResolverNode): return self.nodelist_true.render(context) # If the app couldn't be found except (ImproperlyConfigured, ImportError): - return '' + return "" # If either variable fails to resolve, return nothing. except template.VariableDoesNotExist: - return '' + return "" # If the types don't permit comparison, return nothing. except (TypeError, AttributeError): - return '' + return "" return self.nodelist_false.render(context) @@ -141,15 +145,14 @@ def ifhasperm(parser, token): class PermissionFormNode(ResolverNode): - @classmethod def handle_token(cls, parser, token, approved): bits = token.contents.split() kwargs = { - 'obj': cls.next_bit_for(bits, 'for'), - 'perm': cls.next_bit_for(bits, 'using', None), - 'template_name': cls.next_bit_for(bits, 'with', ''), - 'approved': approved, + "obj": cls.next_bit_for(bits, "for"), + "perm": cls.next_bit_for(bits, "using", None), + "template_name": cls.next_bit_for(bits, "with", ""), + "approved": approved, } return cls(**kwargs) @@ -163,35 +166,39 @@ class PermissionFormNode(ResolverNode): obj = self.resolve(self.obj, context) perm = self.resolve(self.perm, context) if self.template_name: - template_name = [self.resolve(o, context) for o in self.template_name.split(',')] + template_name = [ + self.resolve(o, context) for o in self.template_name.split(",") + ] else: - template_name = 'authority/permission_form.html' - request = context['request'] + template_name = "authority/permission_form.html" + request = context["request"] extra_context = {} if self.approved: - if (request.user.is_authenticated() and - request.user.has_perm('authority.add_permission')): + if request.user.is_authenticated and request.user.has_perm( + "authority.add_permission" + ): extra_context = { - 'form_url': url_for_obj('authority-add-permission', obj), - 'next': request.build_absolute_uri(), - 'approved': self.approved, - 'form': UserPermissionForm(perm, obj, approved=self.approved, - initial=dict(codename=perm)), + "form_url": url_for_obj("authority-add-permission", obj), + "next": request.build_absolute_uri(), + "approved": self.approved, + "form": UserPermissionForm( + perm, obj, approved=self.approved, initial=dict(codename=perm) + ), } else: - if request.user.is_authenticated() and not request.user.is_superuser: + if request.user.is_authenticated and not request.user.is_superuser: extra_context = { - 'form_url': url_for_obj('authority-add-permission-request', obj), - 'next': request.build_absolute_uri(), - 'approved': self.approved, - 'form': UserPermissionForm( + "form_url": url_for_obj("authority-add-permission-request", obj), + "next": request.build_absolute_uri(), + "approved": self.approved, + "form": UserPermissionForm( perm, obj, approved=self.approved, - initial=dict(codename=perm, user=request.user.username)), + initial=dict(codename=perm, user=request.user.username), + ), } - return template.loader.render_to_string(template_name, extra_context, - request) + return template.loader.render_to_string(template_name, extra_context, request) @register.tag @@ -226,16 +233,15 @@ def permission_request_form(parser, token): class PermissionsForObjectNode(ResolverNode): - @classmethod def handle_token(cls, parser, token, approved, name): bits = token.contents.split() tag_name = bits[0] kwargs = { - 'obj': cls.next_bit_for(bits, tag_name), - 'user': cls.next_bit_for(bits, 'for'), - 'var_name': cls.next_bit_for(bits, 'as', name), - 'approved': approved, + "obj": cls.next_bit_for(bits, tag_name), + "user": cls.next_bit_for(bits, "for"), + "var_name": cls.next_bit_for(bits, "as", name), + "approved": approved, } return cls(**kwargs) @@ -256,7 +262,7 @@ class PermissionsForObjectNode(ResolverNode): if isinstance(user, User): perms = perms.filter(user=user) context[var_name] = perms - return '' + return "" @register.tag @@ -276,8 +282,9 @@ def get_permissions(parser, token): {% get_permissions obj for request.user as "my_permissions" %} """ - return PermissionsForObjectNode.handle_token(parser, token, approved=True, - name='"permissions"') + return PermissionsForObjectNode.handle_token( + parser, token, approved=True, name='"permissions"' + ) @register.tag @@ -297,23 +304,22 @@ def get_permission_requests(parser, token): {% get_permission_requests obj for request.user as "my_permissions" %} """ - return PermissionsForObjectNode.handle_token(parser, token, - approved=False, - name='"permission_requests"') + return PermissionsForObjectNode.handle_token( + parser, token, approved=False, name='"permission_requests"' + ) class PermissionForObjectNode(ResolverNode): - @classmethod def handle_token(cls, parser, token, approved, name): bits = token.contents.split() tag_name = bits[0] kwargs = { - 'perm': cls.next_bit_for(bits, tag_name), - 'user': cls.next_bit_for(bits, 'for'), - 'objs': cls.next_bit_for(bits, 'and'), - 'var_name': cls.next_bit_for(bits, 'as', name), - 'approved': approved, + "perm": cls.next_bit_for(bits, tag_name), + "user": cls.next_bit_for(bits, "for"), + "objs": cls.next_bit_for(bits, "and"), + "var_name": cls.next_bit_for(bits, "as", name), + "approved": approved, } return cls(**kwargs) @@ -325,7 +331,7 @@ class PermissionForObjectNode(ResolverNode): self.approved = approved def render(self, context): - objs = [self.resolve(obj, context) for obj in self.objs.split(',')] + objs = [self.resolve(obj, context) for obj in self.objs.split(",")] var_name = self.resolve(self.var_name, context) perm = self.resolve(self.perm, context) user = self.resolve(self.user, context) @@ -342,7 +348,7 @@ class PermissionForObjectNode(ResolverNode): if granted: break context[var_name] = granted - return '' + return "" @register.tag @@ -367,9 +373,9 @@ def get_permission(parser, token): {% endif %} """ - return PermissionForObjectNode.handle_token(parser, token, - approved=True, - name='"permission"') + return PermissionForObjectNode.handle_token( + parser, token, approved=True, name='"permission"' + ) @register.tag @@ -395,60 +401,66 @@ def get_permission_request(parser, token): """ return PermissionForObjectNode.handle_token( - parser, token, approved=False, name='"permission_request"') + parser, token, approved=False, name='"permission_request"' + ) def base_link(context, perm, view_name): return { - 'next': context['request'].build_absolute_uri(), - 'url': reverse(view_name, kwargs={'permission_pk': perm.pk}), + "next": context["request"].build_absolute_uri(), + "url": reverse(view_name, kwargs={"permission_pk": perm.pk}), } -@register.inclusion_tag('authority/permission_delete_link.html', takes_context=True) +@register.inclusion_tag("authority/permission_delete_link.html", takes_context=True) def permission_delete_link(context, perm): """ Renders a html link to the delete view of the given permission. Returns no content if the request-user has no permission to delete foreign permissions. """ - user = context['request'].user - if user.is_authenticated(): - if (user.has_perm('authority.delete_foreign_permissions') or - user.pk == perm.creator.pk): - return base_link(context, perm, 'authority-delete-permission') - return {'url': None} + user = context["request"].user + if user.is_authenticated: + if ( + user.has_perm("authority.delete_foreign_permissions") + or user.pk == perm.creator.pk + ): + return base_link(context, perm, "authority-delete-permission") + return {"url": None} -@register.inclusion_tag('authority/permission_request_delete_link.html', takes_context=True) +@register.inclusion_tag( + "authority/permission_request_delete_link.html", takes_context=True +) def permission_request_delete_link(context, perm): """ Renders a html link to the delete view of the given permission request. Returns no content if the request-user has no permission to delete foreign permissions. """ - user = context['request'].user - if user.is_authenticated(): - link_kwargs = base_link(context, perm, - 'authority-delete-permission-request') - if user.has_perm('authority.delete_permission'): - link_kwargs['is_requestor'] = False + user = context["request"].user + if user.is_authenticated: + link_kwargs = base_link(context, perm, "authority-delete-permission-request") + if user.has_perm("authority.delete_permission"): + link_kwargs["is_requestor"] = False return link_kwargs if not perm.approved and perm.user == user: - link_kwargs['is_requestor'] = True + link_kwargs["is_requestor"] = True return link_kwargs - return {'url': None} + return {"url": None} -@register.inclusion_tag('authority/permission_request_approve_link.html', takes_context=True) +@register.inclusion_tag( + "authority/permission_request_approve_link.html", takes_context=True +) def permission_request_approve_link(context, perm): """ Renders a html link to the approve view of the given permission request. Returns no content if the request-user has no permission to delete foreign permissions. """ - user = context['request'].user - if user.is_authenticated(): - if user.has_perm('authority.approve_permission_requests'): - return base_link(context, perm, 'authority-approve-permission-request') - return {'url': None} + user = context["request"].user + if user.is_authenticated: + if user.has_perm("authority.approve_permission_requests"): + return base_link(context, perm, "authority-approve-permission-request") + return {"url": None} diff --git a/authority/tests.py b/authority/tests.py index 5570745..20d3c39 100644 --- a/authority/tests.py +++ b/authority/tests.py @@ -16,18 +16,23 @@ from authority.forms import UserPermissionForm # noqa User = get_user_model() -FIXTURES = ['tests_custom.json'] +FIXTURES = ["tests_custom.json"] QUERY = Q(email="jezdez@github.com") + class UserPermission(permissions.BasePermission): - checks = ('browse',) - label = 'user_permission' + checks = ("browse",) + label = "user_permission" + + authority.utils.register(User, UserPermission) class GroupPermission(permissions.BasePermission): - checks = ('browse',) - label = 'group_permission' + checks = ("browse",) + label = "group_permission" + + authority.utils.register(Group, GroupPermission) @@ -43,6 +48,7 @@ class DjangoPermissionChecksTestCase(TestCase): This permissions are given in the test case and not in the fixture, for later reference. """ + fixtures = FIXTURES def setUp(self): @@ -56,7 +62,7 @@ class DjangoPermissionChecksTestCase(TestCase): def test_add(self): # setup - perm = DjangoPermission.objects.get(codename='add_user') + perm = DjangoPermission.objects.get(codename="add_user") self.user.user_permissions.add(perm) # test @@ -66,8 +72,8 @@ class DjangoPermissionChecksTestCase(TestCase): perm = Permission( user=self.user, content_object=self.user, - codename='user_permission.delete_user', - approved=True + codename="user_permission.delete_user", + approved=True, ) perm.save() @@ -83,6 +89,7 @@ class AssignBehaviourTest(TestCase): - permission delete_user for him (test_delete), - all existing codenames permissions: a/b/c/d (test_all), """ + fixtures = FIXTURES def setUp(self): @@ -90,16 +97,13 @@ class AssignBehaviourTest(TestCase): self.check = UserPermission(self.user) def test_add(self): - result = self.check.assign(check='add_user') + result = self.check.assign(check="add_user") self.assertTrue(isinstance(result[0], DjangoPermission)) self.assertTrue(self.check.add_user()) def test_delete(self): - result = self.check.assign( - content_object=self.user, - check='delete_user', - ) + result = self.check.assign(content_object=self.user, check="delete_user",) self.assertTrue(isinstance(result[0], Permission)) self.assertFalse(self.check.delete_user()) @@ -120,6 +124,7 @@ class GenericAssignBehaviourTest(TestCase): - permission add (test_add), - permission delete for him (test_delete), """ + fixtures = FIXTURES def setUp(self): @@ -127,16 +132,14 @@ class GenericAssignBehaviourTest(TestCase): self.check = UserPermission(self.user) def test_add(self): - result = self.check.assign(check='add', generic=True) + result = self.check.assign(check="add", generic=True) self.assertTrue(isinstance(result[0], DjangoPermission)) self.assertTrue(self.check.add_user()) def test_delete(self): result = self.check.assign( - content_object=self.user, - check='delete', - generic=True, + content_object=self.user, check="delete", generic=True, ) self.assertTrue(isinstance(result[0], Permission)) @@ -149,6 +152,7 @@ class AssignExceptionsTest(TestCase): Tests that exceptions are thrown if assign() was called with inconsistent arguments. """ + fixtures = FIXTURES def setUp(self): @@ -164,7 +168,7 @@ class AssignExceptionsTest(TestCase): def test_not_model_content_object(self): try: - self.check.assign(content_object='fail') + self.check.assign(content_object="fail") except NotAModel: return True self.fail() @@ -174,6 +178,7 @@ class SmartCachingTestCase(TestCase): """ The base test case for all tests that have to do with smart caching. """ + fixtures = FIXTURES def setUp(self): @@ -198,21 +203,14 @@ class SmartCachingTestCase(TestCase): # This is what the old, pre-cache system would check to see if a user # had a given permission. return Permission.objects.user_permissions( - self.user, - 'foo', - self.user, - approved=True, - check_groups=True, + self.user, "foo", self.user, approved=True, check_groups=True, ) def _old_group_permission_check(self): # This is what the old, pre-cache system would check to see if a user # had a given permission. return Permission.objects.group_permissions( - self.group, - 'foo', - self.group, - approved=True, + self.group, "foo", self.group, approved=True, ) @@ -237,20 +235,13 @@ class PerformanceTest(SmartCachingTestCase): for _ in range(5): # Need to assert it so the query actually gets executed. assert not self.user_check.has_user_perms( - 'foo', - self.user, - True, - False, + "foo", self.user, True, False, ) def test_group_has_perms(self): with self.assertNumQueries(2): for _ in range(5): - assert not self.group_check.has_group_perms( - 'foo', - self.group, - True, - ) + assert not self.group_check.has_group_perms("foo", self.group, True,) def test_has_user_perms_check_group(self): # Regardless of the number groups permissions, it should only take one @@ -258,10 +249,7 @@ class PerformanceTest(SmartCachingTestCase): # Content type and permissions (2 queries) with self.assertNumQueries(3): self.user_check.has_user_perms( - 'foo', - self.user, - approved=True, - check_groups=True, + "foo", self.user, approved=True, check_groups=True, ) def test_invalidate_user_permissions_cache(self): @@ -273,10 +261,7 @@ class PerformanceTest(SmartCachingTestCase): with self.assertNumQueries(6): for _ in range(5): assert not self.user_check.has_user_perms( - 'foo', - self.user, - True, - False, + "foo", self.user, True, False, ) # Invalidate the cache to show that a query will be generated when @@ -287,10 +272,7 @@ class PerformanceTest(SmartCachingTestCase): # One query to re generate the cache. for _ in range(5): assert not self.user_check.has_user_perms( - 'foo', - self.user, - True, - False, + "foo", self.user, True, False, ) def test_invalidate_group_permissions_cache(self): @@ -300,11 +282,7 @@ class PerformanceTest(SmartCachingTestCase): # will need to do one query to get content type and one to get with self.assertNumQueries(4): for _ in range(5): - assert not self.group_check.has_group_perms( - 'foo', - self.group, - True, - ) + assert not self.group_check.has_group_perms("foo", self.group, True,) # Invalidate the cache to show that a query will be generated when # checking perms again. @@ -313,18 +291,14 @@ class PerformanceTest(SmartCachingTestCase): # One query to re generate the cache. for _ in range(5): - assert not self.group_check.has_group_perms( - 'foo', - self.group, - True, - ) + assert not self.group_check.has_group_perms("foo", self.group, True,) def test_has_user_perms_check_group_multiple(self): # Create a permission with just a group. Permission.objects.create( content_type=Permission.objects.get_content_type(User), object_id=self.user.pk, - codename='foo', + codename="foo", group=self.group, approved=True, ) @@ -333,17 +307,17 @@ class PerformanceTest(SmartCachingTestCase): # Check the number of queries. with self.assertNumQueries(2): - assert self.user_check.has_user_perms('foo', self.user, True, True) + assert self.user_check.has_user_perms("foo", self.user, True, True) # Create a second group. - new_group = Group.objects.create(name='new_group') + new_group = Group.objects.create(name="new_group") new_group.user_set.add(self.user) # Create a permission object for it. Permission.objects.create( content_type=Permission.objects.get_content_type(User), object_id=self.user.pk, - codename='foo', + codename="foo", group=new_group, approved=True, ) @@ -352,7 +326,7 @@ class PerformanceTest(SmartCachingTestCase): # Make sure it is the same number of queries. with self.assertNumQueries(2): - assert self.user_check.has_user_perms('foo', self.user, True, True) + assert self.user_check.has_user_perms("foo", self.user, True, True) class GroupPermissionCacheTestCase(SmartCachingTestCase): @@ -367,10 +341,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase): # Use the new cached user perms to show that the user does not have the # perms. can_foo_with_group = self.user_check.has_user_perms( - 'foo', - self.user, - approved=True, - check_groups=True, + "foo", self.user, approved=True, check_groups=True, ) self.assertFalse(can_foo_with_group) @@ -378,7 +349,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase): perm = Permission.objects.create( content_type=Permission.objects.get_content_type(User), object_id=self.user.pk, - codename='foo', + codename="foo", group=self.group, approved=True, ) @@ -390,10 +361,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase): # Invalidate the cache. self.user_check.invalidate_permissions_cache() can_foo_with_group = self.user_check.has_user_perms( - 'foo', - self.user, - approved=True, - check_groups=True, + "foo", self.user, approved=True, check_groups=True, ) self.assertTrue(can_foo_with_group) @@ -401,9 +369,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase): # Make sure calling has_user_perms on a permission that does not have a # user does not throw any errors. can_foo_with_group = self.group_check.has_group_perms( - 'foo', - self.user, - approved=True, + "foo", self.user, approved=True, ) self.assertFalse(can_foo_with_group) @@ -414,7 +380,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase): perm = Permission.objects.create( content_type=Permission.objects.get_content_type(Group), object_id=self.group.pk, - codename='foo', + codename="foo", group=self.group, approved=True, ) @@ -427,8 +393,6 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase): self.group_check.invalidate_permissions_cache() can_foo_with_group = self.group_check.has_group_perms( - 'foo', - self.group, - approved=True, + "foo", self.group, approved=True, ) self.assertTrue(can_foo_with_group) diff --git a/authority/urls.py b/authority/urls.py index bedad1d..a11568c 100644 --- a/authority/urls.py +++ b/authority/urls.py @@ -1,26 +1,40 @@ from django.conf.urls import url -from authority.views import add_permission, delete_permission, approve_permission_request, \ - delete_permission +from authority.views import ( + add_permission, + delete_permission, + approve_permission_request, + delete_permission, +) urlpatterns = [ - url(r'^permission/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$', + url( + r"^permission/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$", view=add_permission, name="authority-add-permission", - kwargs={'approved': True}), - url(r'^permission/delete/(?P\d+)/$', + kwargs={"approved": True}, + ), + url( + r"^permission/delete/(?P\d+)/$", view=delete_permission, name="authority-delete-permission", - kwargs={'approved': True}), - url(r'^request/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$', + kwargs={"approved": True}, + ), + url( + r"^request/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$", view=add_permission, name="authority-add-permission-request", - kwargs={'approved': False}), - url(r'^request/approve/(?P\d+)/$', + kwargs={"approved": False}, + ), + url( + r"^request/approve/(?P\d+)/$", view=approve_permission_request, - name="authority-approve-permission-request"), - url(r'^request/delete/(?P\d+)/$', + name="authority-approve-permission-request", + ), + url( + r"^request/delete/(?P\d+)/$", view=delete_permission, name="authority-delete-permission-request", - kwargs={'approved': False}), -] \ No newline at end of file + kwargs={"approved": False}, + ), +] diff --git a/authority/utils.py b/authority/utils.py index da6e6cf..0ed662b 100644 --- a/authority/utils.py +++ b/authority/utils.py @@ -1,3 +1,7 @@ -import sys - -from authority.sites import site, get_check, get_choices_for, register, unregister # noqa +from authority.sites import ( + site, + get_check, + get_choices_for, + register, + unregister, +) # noqa diff --git a/authority/views.py b/authority/views.py index c833d2b..44a1764 100644 --- a/authority/views.py +++ b/authority/views.py @@ -11,54 +11,70 @@ from authority.templatetags.permissions import url_for_obj def get_next(request, obj=None): - next = request.REQUEST.get('next') + next = request.REQUEST.get("next") if not next: - if obj and hasattr(obj, 'get_absolute_url'): + if obj and hasattr(obj, "get_absolute_url"): next = obj.get_absolute_url() else: - next = '/' + next = "/" return next @login_required -def add_permission(request, app_label, module_name, pk, approved=False, - template_name='authority/permission_form.html', - extra_context=None, form_class=UserPermissionForm): - codename = request.POST.get('codename', None) - model = apps.get_model(app_label, module_name) - if model is None: +def add_permission( + request, + app_label, + module_name, + pk, + approved=False, + template_name="authority/permission_form.html", + extra_context=None, + form_class=UserPermissionForm, +): + codename = request.POST.get("codename", None) + try: + model = apps.get_model(app_label, module_name) + except LookupError: return permission_denied(request) obj = get_object_or_404(model, pk=pk) next = get_next(request, obj) if approved: - if not request.user.has_perm('authority.add_permission'): + if not request.user.has_perm("authority.add_permission"): return HttpResponseRedirect( - url_for_obj('authority-add-permission-request', obj)) - view_name = 'authority-add-permission' + url_for_obj("authority-add-permission-request", obj) + ) + view_name = "authority-add-permission" else: - view_name = 'authority-add-permission-request' - if request.method == 'POST': + view_name = "authority-add-permission-request" + if request.method == "POST": if codename is None: return HttpResponseForbidden(next) - form = form_class(data=request.POST, obj=obj, approved=approved, - perm=codename, initial=dict(codename=codename)) + form = form_class( + data=request.POST, + obj=obj, + approved=approved, + perm=codename, + initial=dict(codename=codename), + ) if not approved: # Limit permission request to current user - form.data['user'] = request.user + form.data["user"] = request.user if form.is_valid(): form.save(request) request.user.message_set.create( - message=_('You added a permission request.')) + message=_("You added a permission request.") + ) return HttpResponseRedirect(next) else: - form = form_class(obj=obj, approved=approved, perm=codename, - initial=dict(codename=codename)) + form = form_class( + obj=obj, approved=approved, perm=codename, initial=dict(codename=codename) + ) context = { - 'form': form, - 'form_url': url_for_obj(view_name, obj), - 'next': next, - 'perm': codename, - 'approved': approved, + "form": form, + "form_url": url_for_obj(view_name, obj), + "next": next, + "perm": codename, + "approved": approved, } if extra_context: context.update(extra_context) @@ -68,25 +84,27 @@ def add_permission(request, app_label, module_name, pk, approved=False, @login_required def approve_permission_request(request, permission_pk): requested_permission = get_object_or_404(Permission, pk=permission_pk) - if request.user.has_perm('authority.approve_permission_requests'): + if request.user.has_perm("authority.approve_permission_requests"): requested_permission.approve(request.user) request.user.message_set.create( - message=_('You approved the permission request.')) + message=_("You approved the permission request.") + ) next = get_next(request, requested_permission) return HttpResponseRedirect(next) @login_required def delete_permission(request, permission_pk, approved): - permission = get_object_or_404(Permission, pk=permission_pk, - approved=approved) - if (request.user.has_perm('authority.delete_foreign_permissions') or - request.user == permission.creator): + permission = get_object_or_404(Permission, pk=permission_pk, approved=approved) + if ( + request.user.has_perm("authority.delete_foreign_permissions") + or request.user == permission.creator + ): permission.delete() if approved: - msg = _('You removed the permission.') + msg = _("You removed the permission.") else: - msg = _('You removed the permission request.') + msg = _("You removed the permission request.") request.user.message_set.create(message=msg) next = get_next(request) return HttpResponseRedirect(next) @@ -102,11 +120,14 @@ def permission_denied(request, template_name=None, extra_context=None): The path of the requested URL (e.g., '/app/pages/bad_page/') """ if template_name is None: - template_name = ('403.html', 'authority/403.html') + template_name = ("403.html", "authority/403.html") context = { - 'request_path': request.path, + "request_path": request.path, } if extra_context: context.update(extra_context) - return HttpResponseForbidden(loader.render_to_string(template_name, - context, request)) + return HttpResponseForbidden( + loader.render_to_string( + template_name=template_name, context=context, request=request, + ) + ) diff --git a/authority/widgets.py b/authority/widgets.py index a239ee6..fb5821b 100644 --- a/authority/widgets.py +++ b/authority/widgets.py @@ -28,14 +28,14 @@ class GenericForeignKeyRawIdWidget(ForeignKeyRawIdWidget): def render(self, name, value, attrs=None): if attrs is None: attrs = {} - related_url = '../../../' + related_url = "../../../" params = self.url_parameters() if params: - url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.iteritems()]) + url = "?" + "&".join(["%s=%s" % (k, v) for k, v in params.iteritems()]) else: - url = '' - if 'class' not in attrs: - attrs['class'] = 'vForeignKeyRawIdAdminField' + url = "" + if "class" not in attrs: + attrs["class"] = "vForeignKeyRawIdAdminField" output = [forms.TextInput.render(self, name, value, attrs)] output.append( """%(generic_script)s @@ -44,30 +44,41 @@ class GenericForeignKeyRawIdWidget(ForeignKeyRawIdWidget): id="lookup_id_%(name)s" onclick="return showGenericRelatedObjectLookupPopup( document.getElementById('id_%(ct_field)s'), this, '%(related)s%(url)s');"> - """ % { - 'generic_script': generic_script, - 'related': related_url, - 'url': url, - 'name': name, - 'ct_field': self.ct_field - }) + """ + % { + "generic_script": generic_script, + "related": related_url, + "url": url, + "name": name, + "ct_field": self.ct_field, + } + ) output.append( '%s' - % (settings.STATIC_URL, _('Lookup'))) + % (settings.STATIC_URL, _("Lookup")) + ) from django.contrib.contenttypes.models import ContentType + content_types = """ - """ % ('\n'.join([ - "content_types[%s] = '%s/%s/';" % ( - ContentType.objects.get_for_model(ct).id, - ct._meta.app_label, - ct._meta.object_name.lower() - ) for ct in self.cts])) - return mark_safe(u''.join(output) + content_types) + """ % ( + "\n".join( + [ + "content_types[%s] = '%s/%s/';" + % ( + ContentType.objects.get_for_model(ct).id, + ct._meta.app_label, + ct._meta.object_name.lower(), + ) + for ct in self.cts + ] + ) + ) + return mark_safe(u"".join(output) + content_types) def url_parameters(self): return {} diff --git a/docs/conf.py b/docs/conf.py index 1897bc5..9b0bf40 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,7 @@ import os import sys +from pkg_resources import get_distribution # 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 @@ -40,17 +41,16 @@ master_doc = 'index' # General information about the project. project = u'django-authority' -copyright = u'2009, the django-authority team' +copyright = u'2009-2019, Jannis Leidel' # 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 = '0.9' - # The full version, including alpha/beta/rc tags. -release = '0.9dev' +release = get_distribution('django-authority').version +# The short X.Y version. +version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/example/development.py b/example/development.py index 1d8c9c4..b43bf1a 100644 --- a/example/development.py +++ b/example/development.py @@ -1,4 +1,4 @@ - from example.settings import * -DEBUG=True -TEMPLATE_DEBUG=DEBUG + +DEBUG = True +TEMPLATE_DEBUG = DEBUG diff --git a/example/exampleapp/forms.py b/example/exampleapp/forms.py index 21f47f0..3c9c05e 100644 --- a/example/exampleapp/forms.py +++ b/example/exampleapp/forms.py @@ -3,5 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from authority.forms import UserPermissionForm + class SpecialUserPermissionForm(UserPermissionForm): - user = forms.CharField(label=_('Special user'), widget=forms.Textarea()) + user = forms.CharField(label=_("Special user"), widget=forms.Textarea()) diff --git a/example/exampleapp/permissions.py b/example/exampleapp/permissions.py index 72263a1..b0f74cd 100644 --- a/example/exampleapp/permissions.py +++ b/example/exampleapp/permissions.py @@ -4,10 +4,11 @@ from django.utils.translation import ugettext_lazy as _ import authority from authority.permissions import BasePermission + class FlatPagePermission(BasePermission): """ This class contains a bunch of checks: - + 1. the default checks 'add_flatpage', 'browse_flatpage', 'change_flatpage' and 'delete_flatpage' 2. the custom checks: @@ -40,13 +41,16 @@ class FlatPagePermission(BasePermission): {% endifhasperm %} """ - label = 'flatpage_permission' - checks = ('review', 'top_secret') + + label = "flatpage_permission" + checks = ("review", "top_secret") def top_secret(self, flatpage=None, lala=None): if flatpage and flatpage.registration_required: return self.browse_flatpage(obj=flatpage) return False - top_secret.short_description=_('Is allowed to see top secret flatpages') -authority.register(FlatPage, FlatPagePermission) + top_secret.short_description = _("Is allowed to see top secret flatpages") + + +authority.sites.register(FlatPage, FlatPagePermission) diff --git a/example/exampleapp/tests.py b/example/exampleapp/tests.py index 2247054..c0f2801 100644 --- a/example/exampleapp/tests.py +++ b/example/exampleapp/tests.py @@ -1,23 +1,19 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - +from django.urls import reverse from django.test import TestCase -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) +from authority.compat import get_user_model -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. ->>> 1 + 1 == 2 -True -"""} +class AddPermissionTestCase(TestCase): + def test_add_permission_permission_denied_is_403(self): + user = get_user_model().objects.create(username="foo", email="foo@example.com",) + user.set_password("pw") + user.save() + assert self.client.login(username="foo@example.com", password="pw") + url = reverse( + "authority-add-permission-request", + kwargs={"app_label": "foo", "module_name": "Bar", "pk": 1,}, + ) + r = self.client.get(url) + self.assertEqual(r.status_code, 403) diff --git a/example/exampleapp/views.py b/example/exampleapp/views.py index 63de554..4a5b3a1 100644 --- a/example/exampleapp/views.py +++ b/example/exampleapp/views.py @@ -8,8 +8,9 @@ from authority.decorators import permission_required, permission_required_or_403 # @permission_required('flatpage_permission.top_secret', # (FlatPage, 'url__contains', 'url'), (FlatPage, 'url__contains', 'lala')) # use this to return a 403 page: -@permission_required_or_403('flatpage_permission.top_secret', - (FlatPage, 'url__contains', 'url'), 'lala') +@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/manage.py b/example/manage.py index 94ffe48..cffbb6d 100755 --- a/example/manage.py +++ b/example/manage.py @@ -7,13 +7,16 @@ from django.core.management import execute_from_command_line try: import settings as settings_mod # Assumed to be in the same directory. except ImportError: - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.stderr.write( + "Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" + % __file__ + ) sys.exit(1) sys.path.insert(0, settings_mod.PROJECT_ROOT) -sys.path.insert(0, settings_mod.PROJECT_ROOT + '/../') +sys.path.insert(0, settings_mod.PROJECT_ROOT + "/../") -os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'example.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") if __name__ == "__main__": execute_from_command_line(sys.argv) diff --git a/example/production.py b/example/production.py index 7652ef0..6bc863d 100644 --- a/example/production.py +++ b/example/production.py @@ -1,2 +1 @@ - from example.settings import * diff --git a/example/settings.py b/example/settings.py index f438c00..5d91a9c 100644 --- a/example/settings.py +++ b/example/settings.py @@ -11,90 +11,93 @@ ADMINS = ( MANAGERS = ADMINS DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(PROJECT_ROOT, 'example.db'), - 'USER': '', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(PROJECT_ROOT, "example.db"), + "USER": "", + "PASSWORD": "", + "HOST": "", + "PORT": "", + "TEST": {"NAME": ":memory:", "ENGINE": "django.db.backends.sqlite3",}, } } -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', - } -} +CACHES = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache",}} -TIME_ZONE = 'America/Chicago' +TIME_ZONE = "America/Chicago" -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') +MEDIA_ROOT = os.path.join(PROJECT_ROOT, "media") # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = '/media/' +MEDIA_URL = "/media/" # Don't share this with anybody. -SECRET_KEY = 'ljlv2lb2d&)#by6th=!v=03-c^(o4lop92i@z4b3f1&ve0yx6d' +SECRET_KEY = "ljlv2lb2d&)#by6th=!v=03-c^(o4lop92i@z4b3f1&ve0yx6d" -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', +MIDDLEWARE = ( + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", # 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', ) -INTERNAL_IPS = ('127.0.0.1',) +INTERNAL_IPS = ("127.0.0.1",) -TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.core.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.request', -) +TEMPLATE_CONTEXT_PROCESSORS = () -ROOT_URLCONF = 'example.urls' +ROOT_URLCONF = "example.urls" SITE_ID = 1 INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.flatpages', - 'django.contrib.admin', - 'authority', - 'example.exampleapp', + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.flatpages", + "django.contrib.messages", + "django.contrib.admin", + "authority", + "example.exampleapp", ) if VERSION >= (1, 5): - INSTALLED_APPS = INSTALLED_APPS + ('example.users',) - AUTH_USER_MODEL = 'users.User' + INSTALLED_APPS = INSTALLED_APPS + ("example.users",) + AUTH_USER_MODEL = "users.User" -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', -) - -TEMPLATE_DIRS = ( - os.path.join(PROJECT_ROOT, "templates"), -) +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + # insert your TEMPLATE_DIRS here + ], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this + # list if you haven't customized them: + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] # Use local_settings.py for things to override privately try: from local_settings import * # noqa except ImportError: pass - - -if VERSION >= (1, 6): - TEST_RUNNER = 'django.test.runner.DiscoverRunner' diff --git a/example/urls.py b/example/urls.py index 27a3498..978ceb5 100644 --- a/example/urls.py +++ b/example/urls.py @@ -1,39 +1,44 @@ -try: - from django.conf.urls import patterns, include, handler500, url -except ImportError: # django < 1.4 - from django.conf.urls.defaults import patterns, include, handler500, url +import django.contrib.auth.views +from django.conf.urls import include, handler500, url from django.conf import settings -from django.contrib import admin -import authority -admin.autodiscover() -authority.autodiscover() - -handler500 # Pyflakes +import authority.views +import authority.urls +import example.exampleapp.views from exampleapp.forms import SpecialUserPermissionForm -urlpatterns = patterns('', - (r'^admin/(.*)', admin.site.root), - #('^admin/', include(admin.site.urls)), - url(r'^authority/permission/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$', - view='authority.views.add_permission', +authority.autodiscover() + +handler500 # flake8 + +urlpatterns = ( + url( + r"^authority/permission/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$", # noqa + view=authority.views.add_permission, name="authority-add-permission", - kwargs={'approved': True, 'form_class': SpecialUserPermissionForm} + kwargs={"approved": True, "form_class": SpecialUserPermissionForm}, ), - url(r'^request/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$', - view='authority.views.add_permission', + url( + r"^request/add/(?P[\w\-]+)/(?P[\w\-]+)/(?P\d+)/$", # noqa + view=authority.views.add_permission, name="authority-add-permission-request", - kwargs={'approved': False, 'form_class': SpecialUserPermissionForm} + kwargs={"approved": False, "form_class": SpecialUserPermissionForm}, + ), + url(r"^authority/", include(authority.urls)), + url(r"^accounts/login/$", django.contrib.auth.views.LoginView.as_view()), + url( + r"^(?P[\/0-9A-Za-z]+)$", + example.exampleapp.views.top_secret, + {"lala": "oh yeah!"}, ), - (r'^authority/', include('authority.urls')), - (r'^accounts/login/$', 'django.contrib.auth.views.login'), - url(r'^(?P[\/0-9A-Za-z]+)$', 'example.exampleapp.views.top_secret', {'lala': 'oh yeah!'}), ) if settings.DEBUG: - urlpatterns += patterns('', - (r'^media/(?P.*)$', 'django.views.static.serve', { - 'document_root': settings.MEDIA_ROOT, - }), + urlpatterns += ( + url( + r"^media/(?P.*)$", + django.views.static.serve, + {"document_root": settings.MEDIA_ROOT,}, + ), ) diff --git a/example/users/migrations/0001_initial.py b/example/users/migrations/0001_initial.py new file mode 100644 index 0000000..cd5a5d7 --- /dev/null +++ b/example/users/migrations/0001_initial.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.22 on 2019-07-12 16:01 +from __future__ import unicode_literals + +import django.contrib.auth.models +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0008_alter_user_username_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ("username", models.CharField(max_length=100)), + ("first_name", models.CharField(max_length=50)), + ("last_name", models.CharField(max_length=50)), + ("email", models.EmailField(max_length=254, unique=True)), + ("greeting_message", models.TextField()), + ("is_staff", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=True)), + ( + "date_joined", + models.DateTimeField(default=django.utils.timezone.now), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), + ], + options={"abstract": False,}, + managers=[("objects", django.contrib.auth.models.UserManager()),], + ), + ] diff --git a/example/users/migrations/__init__.py b/example/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/users/models.py b/example/users/models.py index dc42bed..e890d33 100644 --- a/example/users/models.py +++ b/example/users/models.py @@ -1,12 +1,14 @@ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin +from django.contrib.auth.models import UserManager from django.db import models from django.utils import timezone class User(AbstractBaseUser, PermissionsMixin): - USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['first_name', 'last_name'] + USERNAME_FIELD = "email" + REQUIRED_FIELDS = ["first_name", "last_name"] + username = models.CharField(max_length=100) first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) email = models.EmailField(unique=True) @@ -14,3 +16,5 @@ class User(AbstractBaseUser, PermissionsMixin): is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) date_joined = models.DateTimeField(default=timezone.now) + + objects = UserManager() diff --git a/setup.py b/setup.py index edc06e2..4f87963 100644 --- a/setup.py +++ b/setup.py @@ -5,42 +5,36 @@ 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.11', + name="django-authority", + use_scm_version=True, description=( "A Django app that provides generic per-object-permissions " "for Django's auth app." ), - long_description=read('README.rst'), - author='Jannis Leidel', - author_email='jannis@leidel.info', - license='BSD', - url='https://github.com/jazzband/django-authority/', - packages=find_packages(), + long_description=read("README.rst"), + long_description_content_type="text/x-rst", + author="Jannis Leidel", + author_email="jannis@leidel.info", + license="BSD", + url="https://github.com/jazzband/django-authority/", + packages=find_packages(exclude=("example", "example.*")), classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Framework :: Django', + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Framework :: Django", ], - install_requires=['django'], - package_data = { - 'authority': [ - 'fixtures/*.json', - 'templates/authority/*.html', - 'templates/admin/edit_inline/action_tabular.html', - 'templates/admin/permission_change_form.html', - ] - }, + install_requires=["django"], + setup_requires=["setuptools_scm"], + include_package_data=True, zip_safe=False, ) diff --git a/tox.ini b/tox.ini index c8d52c7..5647415 100644 --- a/tox.ini +++ b/tox.ini @@ -3,17 +3,25 @@ skipsdist = True usedevelop = True minversion = 1.8 envlist = - py{27,33,34,35}-dj{18,19,110} + py27-dj111 + py37-dj{111,22} + py37-check [testenv] -basepython = - py27: python2.7 - py33: python3.3 - py34: python3.4 - py35: python3.5 usedevelop = true -commands = python example/manage.py test authority +commands = + coverage run -a example/manage.py test authority exampleapp + coverage report deps = - dj18: Django<1.9 - dj19: Django<1.10 - dj110: Django<1.11 + coverage + dj111: Django>=1.11,<2.0 + dj22: Django>=2.2,<2.3 + + +[testenv:py37-check] +deps = + twine + wheel +commands = + python setup.py sdist bdist_wheel + twine check dist/*