diff --git a/.travis.yml b/.travis.yml index 7f2aad2..0fa7cb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,8 @@ language: python python: - "2.7" env: - - TOX_ENV=py26-django14 - - TOX_ENV=py26-django15 - - TOX_ENV=py26-django16 - - TOX_ENV=py27-django14 - - TOX_ENV=py27-django15 - - TOX_ENV=py27-django16 - - TOX_ENV=py27-django17 - - TOX_ENV=py33-django15 - - TOX_ENV=py33-django16 - - TOX_ENV=py33-django17 + - TOX_ENV=py27-django18 + - TOX_ENV=py33-django18 install: - pip install tox notifications: diff --git a/README.rst b/README.rst index 86b6224..ecc2325 100644 --- a/README.rst +++ b/README.rst @@ -56,6 +56,15 @@ html version using the setup.py:: Changelog: ========== +0.11 (2016-03-29): +----------------- + +* Added Migration in order to support Django 1.8 + +* Dropped Support for Django 1.7 and lower + +* Fix linter issues + 0.10 (2015-12-14): ------------------ diff --git a/authority/__init__.py b/authority/__init__.py index 4b51d64..c4937b7 100644 --- a/authority/__init__.py +++ b/authority/__init__.py @@ -1,8 +1,11 @@ import sys -from authority.sites import site, get_check, get_choices_for, register, unregister + +from authority.sites import site, get_check, get_choices_for, register, unregister # noqa + LOADING = False + def autodiscover(): """ Goes and imports the permissions submodule of every app in INSTALLED_APPS diff --git a/authority/admin.py b/authority/admin.py index 19b6c26..45c25bf 100644 --- a/authority/admin.py +++ b/authority/admin.py @@ -1,4 +1,3 @@ -import django from django import forms, template from django.http import HttpResponseRedirect from django.utils.translation import ugettext, ungettext, ugettext_lazy as _ @@ -21,15 +20,11 @@ try: except ImportError: actions = False -# From 1.7 forward, Django consistenly uses the name "utils", -# not "util". We alias for backwards compatibility. -if django.VERSION[:2] < (1, 7): - forms.utils = forms.util - from authority.models import Permission from authority.widgets import GenericForeignKeyRawIdWidget from authority import get_choices_for + class PermissionInline(generic.GenericTabularInline): model = Permission raw_id_fields = ('user', 'group', 'creator') @@ -42,10 +37,12 @@ class PermissionInline(generic.GenericTabularInline): 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' + class ActionErrorList(forms.utils.ErrorList): def __init__(self, inline_formsets): for inline_formset in inline_formsets: @@ -53,6 +50,7 @@ class ActionErrorList(forms.utils.ErrorList): for errors_in_inline_form in inline_formset.errors: self.extend(errors_in_inline_form.values()) + def edit_permissions(modeladmin, request, queryset): opts = modeladmin.model._meta app_label = opts.app_label @@ -128,6 +126,7 @@ def edit_permissions(modeladmin, request, queryset): context_instance=template.RequestContext(request)) 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') @@ -143,7 +142,10 @@ class PermissionAdmin(admin.ModelAdmin): def formfield_for_dbfield(self, db_field, **kwargs): # For generic foreign keys marked as generic_fields we use a special widget - if db_field.name in [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( @@ -161,7 +163,8 @@ class PermissionAdmin(admin.ModelAdmin): def approve_permissions(self, request, queryset): for permission in queryset: permission.approve(request.user) - message = ungettext("%(count)d permission successfully approved.", + message = ungettext( + "%(count)d permission successfully approved.", "%(count)d permissions successfully approved.", len(queryset)) self.message_user(request, message % {'count': len(queryset)}) approve_permissions.short_description = _("Approve selected permissions") diff --git a/authority/compat.py b/authority/compat.py index 68ac978..2d2d259 100644 --- a/authority/compat.py +++ b/authority/compat.py @@ -11,4 +11,6 @@ try: from django.contrib.auth import get_user_model except ImportError: from django.contrib.auth.models import User - get_user_model = lambda: User + + def get_user_model(): + return User diff --git a/authority/decorators.py b/authority/decorators.py index c4ff802..733387b 100644 --- a/authority/decorators.py +++ b/authority/decorators.py @@ -10,6 +10,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from authority import get_check from authority.views import permission_denied + def permission_required(perm, *lookup_variables, **kwargs): """ Decorator for views that checks whether a user has a particular permission @@ -18,6 +19,7 @@ def permission_required(perm, *lookup_variables, **kwargs): 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(): @@ -60,6 +62,7 @@ def permission_required(perm, *lookup_variables, **kwargs): return wraps(view_func)(decorated) return decorate + def permission_required_or_403(perm, *args, **kwargs): """ Decorator that wraps the permission_required decorator and returns a diff --git a/authority/exceptions.py b/authority/exceptions.py index 9eba1a2..36a9147 100644 --- a/authority/exceptions.py +++ b/authority/exceptions.py @@ -1,11 +1,13 @@ class AuthorityException(Exception): pass + class NotAModel(AuthorityException): def __init__(self, object): super(NotAModel, self).__init__( "Not a model class or instance") + class UnsavedModelInstance(AuthorityException): def __init__(self, object): super(UnsavedModelInstance, self).__init__( diff --git a/authority/forms.py b/authority/forms.py index e4f669b..03a93dd 100644 --- a/authority/forms.py +++ b/authority/forms.py @@ -95,7 +95,8 @@ class GroupPermissionForm(BasePermissionForm): 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'") % { + _("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, diff --git a/authority/migrations/0001_initial.py b/authority/migrations/0001_initial.py new file mode 100644 index 0000000..f17f6db --- /dev/null +++ b/authority/migrations/0001_initial.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + 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)), + ], + 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')), + }, + bases=(models.Model,), + ), + migrations.AlterUniqueTogether( + name='permission', + unique_together=set([('codename', 'object_id', 'content_type', 'user', 'group')]), + ), + ] diff --git a/authority/migrations/__init__.py b/authority/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authority/models.py b/authority/models.py index 50baddd..6ed95e8 100644 --- a/authority/models.py +++ b/authority/models.py @@ -20,11 +20,17 @@ class Permission(models.Model): object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') - user = models.ForeignKey(user_model_label, null=True, blank=True, related_name='granted_permissions') + user = models.ForeignKey( + user_model_label, null=True, blank=True, related_name='granted_permissions') group = models.ForeignKey(Group, null=True, blank=True) - creator = models.ForeignKey(user_model_label, null=True, blank=True, related_name='created_permissions') + creator = models.ForeignKey( + user_model_label, null=True, blank=True, related_name='created_permissions') - approved = models.BooleanField(_('approved'), default=False, help_text=_("Designates whether the permission has been approved and treated as active. Unselect this instead of deleting permissions.")) + approved = models.BooleanField( + _('approved'), + default=False, + 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) diff --git a/authority/sites.py b/authority/sites.py index ef797d4..9db3ae7 100644 --- a/authority/sites.py +++ b/authority/sites.py @@ -6,12 +6,15 @@ from django.core.exceptions import ImproperlyConfigured 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. @@ -64,8 +67,8 @@ 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: @@ -73,8 +76,8 @@ class PermissionSite(object): 'The model %s is already registered' % model.__name__) if options: options['__module__'] = __name__ - permission_class = type("%sPermission" % model.__name__, - (permission_class,), options) + permission_class = type( + "%sPermission" % model.__name__, (permission_class,), options) permission_class.model = model self.setup(model, permission_class) @@ -94,7 +97,9 @@ class PermissionSite(object): 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', + func.short_description = getattr( + check_func, + 'short_description', _("%(object_name)s permission '%(check)s'") % { 'object_name': model._meta.object_name, 'check': check_name}) @@ -122,6 +127,7 @@ class PermissionSite(object): return granted return check + class PermissionDescriptor(object): def get_content_type(self, obj=None): ContentType = models.get_model("contenttypes", "contenttype") diff --git a/authority/templates/admin/permission_change_form.html b/authority/templates/admin/permission_change_form.html index 54e4e2f..aec6208 100644 --- a/authority/templates/admin/permission_change_form.html +++ b/authority/templates/admin/permission_change_form.html @@ -15,7 +15,7 @@ {% block breadcrumbs %}{% if not is_popup %}
diff --git a/authority/templatetags/permissions.py b/authority/templatetags/permissions.py index 3663b79..e72646c 100644 --- a/authority/templatetags/permissions.py +++ b/authority/templatetags/permissions.py @@ -145,7 +145,6 @@ class PermissionFormNode(ResolverNode): @classmethod def handle_token(cls, parser, token, approved): bits = token.contents.split() - tag_name = bits[0] kwargs = { 'obj': cls.next_bit_for(bits, 'for'), 'perm': cls.next_bit_for(bits, 'using', None), @@ -164,7 +163,7 @@ class PermissionFormNode(ResolverNode): obj = self.resolve(self.obj, context) perm = self.resolve(self.perm, context) if self.template_name: - template_name = [self.resolve(obj, context) for obj 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'] @@ -185,12 +184,15 @@ class PermissionFormNode(ResolverNode): '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)), + 'form': UserPermissionForm( + perm, + obj, + approved=self.approved, + initial=dict(codename=perm, user=request.user.username)), } - return template.loader.render_to_string(template_name, extra_context, - context_instance=template.RequestContext(request)) + return template.loader.render_to_string( + template_name, extra_context, context_instance=template.RequestContext(request)) + @register.tag def permission_form(parser, token): @@ -206,6 +208,7 @@ def permission_form(parser, token): """ return PermissionFormNode.handle_token(parser, token, approved=True) + @register.tag def permission_request_form(parser, token): """ @@ -215,7 +218,8 @@ def permission_request_form(parser, token): Syntax:: {% permission_request_form for OBJ and PERMISSION_LABEL.CHECK_NAME [with TEMPLATE] %} - {% permission_request_form for lesson using "lesson_permission.add_lesson" with "authority/permission_request_form.html" %} + {% permission_request_form for lesson using "lesson_permission.add_lesson" + with "authority/permission_request_form.html" %} """ return PermissionFormNode.handle_token(parser, token, approved=False) @@ -254,6 +258,7 @@ class PermissionsForObjectNode(ResolverNode): context[var_name] = perms return '' + @register.tag def get_permissions(parser, token): """ @@ -274,6 +279,7 @@ def get_permissions(parser, token): return PermissionsForObjectNode.handle_token(parser, token, approved=True, name='"permissions"') + @register.tag def get_permission_requests(parser, token): """ @@ -295,6 +301,7 @@ def get_permission_requests(parser, token): approved=False, name='"permission_requests"') + class PermissionForObjectNode(ResolverNode): @classmethod @@ -337,6 +344,7 @@ class PermissionForObjectNode(ResolverNode): context[var_name] = granted return '' + @register.tag def get_permission(parser, token): """ @@ -347,8 +355,10 @@ def get_permission(parser, token): {% get_permission PERMISSION_LABEL.CHECK_NAME for USER and *OBJS [as VARNAME] %} - {% 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" %} + {% 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. @@ -361,6 +371,7 @@ def get_permission(parser, token): approved=True, name='"permission"') + @register.tag def get_permission_request(parser, token): """ @@ -371,8 +382,10 @@ def get_permission_request(parser, token): {% get_permission_request PERMISSION_LABEL.CHECK_NAME for USER and *OBJS [as VARNAME] %} - {% get_permission_request "poll_permission.change_poll" for request.user and poll as "asked_for_permissio" %} - {% get_permission_request "poll_permission.change_poll" for request.user and poll,second_poll as "asked_for_permissio" %} + {% get_permission_request "poll_permission.change_poll" + for request.user and poll as "asked_for_permissio" %} + {% get_permission_request "poll_permission.change_poll" + for request.user and poll,second_poll as "asked_for_permissio" %} {% if asked_for_permissio %} Dude, you already asked for permission! @@ -381,16 +394,17 @@ def get_permission_request(parser, token): {% endif %} """ - return PermissionForObjectNode.handle_token(parser, token, - approved=False, - name='"permission_request"') + return PermissionForObjectNode.handle_token( + 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,}), + 'url': reverse(view_name, kwargs={'permission_pk': perm.pk}), } + @register.inclusion_tag('authority/permission_delete_link.html', takes_context=True) def permission_delete_link(context, perm): """ @@ -400,11 +414,12 @@ def permission_delete_link(context, perm): """ user = context['request'].user if user.is_authenticated(): - if user.has_perm('authority.delete_foreign_permissions') \ - or user.pk == perm.creator.pk: + 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) def permission_request_delete_link(context, perm): """ @@ -424,6 +439,7 @@ def permission_request_delete_link(context, perm): return link_kwargs return {'url': None} + @register.inclusion_tag('authority/permission_request_approve_link.html', takes_context=True) def permission_request_approve_link(context, perm): """ @@ -434,6 +450,5 @@ def permission_request_approve_link(context, perm): 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 base_link(context, perm, 'authority-approve-permission-request') return {'url': None} diff --git a/authority/tests.py b/authority/tests.py index c7faec1..8b49417 100644 --- a/authority/tests.py +++ b/authority/tests.py @@ -1,10 +1,9 @@ -from django import VERSION from django.conf import settings from django.contrib.auth.models import Permission as DjangoPermission from django.contrib.auth.models import Group -from django.db.models import Q from django.test import TestCase from django.contrib.contenttypes.models import ContentType +from django.db.models import Q import authority from authority import permissions @@ -17,13 +16,8 @@ from authority.forms import UserPermissionForm # noqa User = get_user_model() -if VERSION >= (1, 5): - FIXTURES = ['tests_custom.json'] - QUERY = Q(email="jezdez@github.com") -else: - FIXTURES = ['tests.json'] - QUERY = Q(username="jezdez") - +FIXTURES = ['tests_custom.json'] +QUERY = Q(email="jezdez@github.com") class UserPermission(permissions.BasePermission): checks = ('browse',) diff --git a/authority/urls.py b/authority/urls.py index 36b8f9d..dec899b 100644 --- a/authority/urls.py +++ b/authority/urls.py @@ -1,31 +1,24 @@ -try: - from django.conf.urls import * -except ImportError: # django < 1.4 - from django.conf.urls.defaults import * +from django.conf.urls import patterns, url -urlpatterns = patterns('authority.views', +urlpatterns = patterns( + 'authority.views', url(r'^permission/add/(?P