Compare commits

..

No commits in common. "master" and "0.8" have entirely different histories.
master ... 0.8

60 changed files with 1051 additions and 1457 deletions

View file

@ -1,6 +0,0 @@
[run]
source = authority
branch = 1
[report]
omit = *tests*,*migrations*

View file

@ -1,53 +0,0 @@
name: Release
on:
push:
tags:
- '*'
jobs:
build:
if: github.repository == 'jazzband/django-authority'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: release-${{ hashFiles('**/setup.py') }}
restore-keys: |
release-
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U setuptools twine wheel
- name: Build package
run: |
python setup.py --version
python setup.py sdist --format=gztar bdist_wheel
twine check dist/*
- name: Upload packages to Jazzband
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository_url: https://jazzband.co/projects/django-authority/upload

View file

@ -1,48 +0,0 @@
name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
python-version: ['2.7', '3.6', '3.7', '3.8']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ matrix.python-version }}-v1-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions
- name: Tox tests
run: |
tox -v
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
name: Python ${{ matrix.python-version }}

7
.gitignore vendored
View file

@ -2,9 +2,4 @@
*.egg-info
*.sql
docs/build/*
.DS_Store
.tox/
dist/
build/
.coverage
.eggs/
.DS_Store

26
.hgignore Normal file
View file

@ -0,0 +1,26 @@
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

22
.travis.yml Normal file
View file

@ -0,0 +1,22 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
script: ./example/manage.py test authority
env:
- TRAVIS_DJANGO_VERSION=1.3
- TRAVIS_DJANGO_VERSION=1.4
- TRAVIS_DJANGO_VERSION=1.5
- TRAVIS_DJANGO_VERSION=1.6
install:
- pip install django==$TRAVIS_DJANGO_VERSION --use-mirrors
matrix:
exclude:
- python: "3.3"
env: TRAVIS_DJANGO_VERSION=1.3
- python: "3.3"
env: TRAVIS_DJANGO_VERSION=1.4
notifications:
email:
- jason.louard.ward@gmail.com

View file

@ -7,5 +7,3 @@ Kyle Gibson <kyle.gibson@policystat.com>
Jason Ward <jason.ward@policystat.com>
Travis Chase <http://www.supercodepoet.com/>
Remigiusz Dymecki <remigiusz@gmail.com>
Gunnlaugur Thor Briem <https://github.com/gthb>
Bob Cribbs <https://github.com/bocribbz>

View file

@ -1,46 +0,0 @@
# Code of Conduct
As contributors and maintainers of the Jazzband projects, and in the interest of
fostering an open and welcoming community, we pledge to respect all people who
contribute through reporting issues, posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in the Jazzband a harassment-free experience
for everyone, regardless of the level of experience, gender, gender identity and
expression, sexual orientation, disability, personal appearance, body size, race,
ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery
- Personal attacks
- Trolling or insulting/derogatory comments
- Public or private harassment
- Publishing other's private information, such as physical or electronic addresses,
without explicit permission
- Other unethical or unprofessional conduct
The Jazzband roadies have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are not
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, the roadies commit themselves to fairly and
consistently applying these principles to every aspect of managing the jazzband
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
removed from the Jazzband roadies.
This code of conduct applies both within project spaces and in public spaces when an
individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
investigated and will result in a response that is deemed necessary and appropriate to
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
reporter of an incident.
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/3/0/

View file

@ -1,5 +0,0 @@
[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/)
This is a [Jazzband](https://jazzband.co/) project.
By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct)
and follow the [guidelines](https://jazzband.co/about/guidelines).

View file

@ -1,4 +1,4 @@
Copyright (c) 2009-2020, Jannis Leidel
Copyright (c) 2009, 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.
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,6 +1,8 @@
include AUTHORS
include LICENSE
include README.rst
include buildout.cfg
include bootstrap.py
recursive-include authority/templates/authority *.html
recursive-include authority/templates/admin *.html
recursive-include authority/fixtures *.json

View file

@ -2,17 +2,6 @@
django-authority
================
.. image:: https://jazzband.co/static/img/badge.svg
:target: https://jazzband.co/
:alt: Jazzband
.. image:: https://github.com/jazzband/django-authority/workflows/Test/badge.svg
:target: https://github.com/jazzband/django-authority/actions
:alt: GitHub Actions
.. image:: https://codecov.io/gh/jazzband/django-authority/branch/master/graph/badge.svg
:target: https://codecov.io/gh/jazzband/django-authority
This is a Django app for per-object-permissions that includes a bunch of
helpers to create custom permission checks.
@ -21,31 +10,36 @@ The main website for django-authority is
`in-development version`_ of django-authority with
``pip install django-authority==dev`` or ``easy_install django-authority==dev``.
.. _`django-authority.readthedocs.org`: https://django-authority.readthedocs.io/
.. _in-development version: https://github.com/jazzband/django-authority/archive/master.zip#egg=django-authority-dev
.. _`django-authority.readthedocs.org`: http://django-authority.readthedocs.org/
.. _in-development version: https://github.com/jezdez/django-authority/archive/master.zip#egg=django-authority-dev
Example
=======
To get the example project running do:
- Bootstrap the environment by running in a virtualenv::
- Bootstrap the buildout by running::
pip install Django
pip install -e .
python bootstrap.py
- Get the required packages by running::
bin/buildout
- Sync the database::
python example/manage.py migrate
bin/django-trunk syncdb
- Run the development server and visit the admin at http://127.0.0.1:8000/admin/::
python example/manage.py runserver
bin/django-trunk runserver
Now create a flatage and open it to see some of the templatetags in action.
Don't hesitate to use the admin to edit the permission objects.
Please use https://github.com/jazzband/django-authority/issues/ for issues and bug reports.
Full docs coming soon.
Please use https://github.com/jezdez/django-authority/issues/ for issues and bug reports.
Documentation
=============
@ -58,66 +52,6 @@ html version using the setup.py::
Changelog:
==========
0.15 (unreleased):
------------------
* Moved CI to GitHub Actions.
* Add Django 3.0 and 3.1 support.
* Add Python 3.6 and 3.8 support.
0.14 (2020-02-07):
------------------
* Add Django 2.2 support
* Add Python 3.7 support
* Various fixes around the test harness.
* Use Django's own method of auto-loading permissions modules.
* Fix Django admin incompatibility regarding a method removed years ago.
* Removed unused compatibility code.
* Fix BasePermission.assign for group permissions.
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
* Dropped Support for Django 1.7 and lower
* Remove SQL Migration Files
* Documentation Updates
* Fix linter issues
0.10 (2015-12-14):
------------------
* Fixed a bug with BasePermissionForm and django 1.8
0.9 (2015-11-11):
-----------------
* Added support for Django 1.7 and 1.8
* Dropped support for Django 1.3
0.8 (2013-12-20):
-----------------
@ -153,9 +87,7 @@ Changelog:
* Added ability to override form class in ``add_permission`` view.
* Added easy way to assign permissions via a permission instance, e.g.:
.. code-block:: python
* Added easy way to assign permissions via a permission instance, e.g.::
from django.contrib.auth.models import User
from mysite.articles.permissions import ArticlePermission
@ -183,17 +115,13 @@ Changelog:
* The templatetags have also been refactored to be easier to customize
which required a change in the template tag signature:
Old:
.. code-block:: html+django
Old::
{% permission_form flatpage %}
{% permission_form flatpage "flatpage_permission.top_secret" %}
{% permission_form OBJ PERMISSION_LABEL.CHECK_NAME %}
New:
.. code-block:: html+django
New::
{% permission_form for flatpage %}
{% permission_form for flatpage using "flatpage_permission.top_secret" %}
@ -212,4 +140,4 @@ Changelog:
allows to request permissions, but also add them (only for users with
the 'authority.add_permission' Django permission).
.. _`migrations/`: https://github.com/jazzbands/django-authority/tree/master/migrations
.. _`migrations/`: https://github.com/jezdez/django-authority/tree/master/migrations

View file

@ -1,14 +1,8 @@
from pkg_resources import get_distribution, DistributionNotFound
try:
__version__ = get_distribution("django-authority").version
except DistributionNotFound:
# package is not installed
pass
import sys
from authority.sites import site, get_check, get_choices_for, register, unregister
LOADING = False
def autodiscover():
"""
Goes and imports the permissions submodule of every app in INSTALLED_APPS
@ -19,6 +13,19 @@ def autodiscover():
return
LOADING = True
from django.utils.module_loading import autodiscover_modules
import imp
from django.conf import settings
autodiscover_modules("permissions")
for app in settings.INSTALLED_APPS:
try:
__import__(app)
app_path = sys.modules[app].__path__
except AttributeError:
continue
try:
imp.find_module('permissions', app_path)
except ImportError:
continue
__import__("%s.permissions" % app)
app_path = sys.modules["%s.permissions" % app]
LOADING = False

View file

@ -1,12 +1,12 @@
from django import forms
from django import forms, template
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext, ungettext, ugettext_lazy as _
from django.shortcuts import render
from django.shortcuts import render_to_response
from django.utils.safestring import mark_safe
from django.forms.formsets import all_valid
from django.contrib import admin
from django.contrib.admin import actions, helpers
from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib.admin import helpers
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
@ -15,48 +15,47 @@ try:
except ImportError:
from django.utils.encoding import force_unicode as force_text
try:
from django.contrib.admin import actions
except ImportError:
actions = False
from authority.models import Permission
from authority.widgets import GenericForeignKeyRawIdWidget
from authority.utils import get_choices_for
from authority import get_choices_for
class PermissionInline(GenericTabularInline):
class PermissionInline(generic.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 db_field.formfield(**kwargs)
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):
class ActionErrorList(forms.util.ErrorList):
def __init__(self, inline_formsets):
super(ActionErrorList, self).__init__()
for inline_formset in inline_formsets:
self.extend(inline_formset.non_form_errors())
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
# 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)
@ -67,11 +66,10 @@ def edit_permissions(modeladmin, request, queryset):
prefix = "%s-%s" % (FormSet.get_default_prefix(), obj.pk)
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
)
prefix = "%s-%s-%s" % (prefix, prefixes[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)
@ -84,105 +82,81 @@ def edit_permissions(modeladmin, request, queryset):
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
if request.POST.get("post"):
ordered_objects = opts.get_ordered_objects()
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
),
)
# 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,
"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",
],
)
return render(request, template_name, context)
edit_permissions.short_description = _(
"Edit permissions for selected %(verbose_name_plural)s"
)
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,
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")
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
]
if db_field.name in names:
if db_field.name in [f.fk_field for f in self.model._meta.virtual_fields if f.name in self.generic_fields]:
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()
)
break
return db_field.formfield(
widget=GenericForeignKeyRawIdWidget(
gfk.ct_field, self.admin_site._registry.keys()))
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)
def approve_permissions(self, request, queryset):
for permission in queryset:
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)})
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")
admin.site.register(Permission, PermissionAdmin)
if actions:

View file

@ -2,34 +2,25 @@ import inspect
from django.http import HttpResponseRedirect
from django.utils.http import urlquote
from django.utils.functional import wraps
from django.db.models import Model
from django.apps import apps
from django.db.models import Model, get_model
from django.shortcuts import get_object_or_404
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from authority.utils import get_check
from authority import get_check
from authority.views import permission_denied
try:
basestring
except NameError:
basestring = str
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):
@ -43,19 +34,16 @@ def permission_required(perm, *lookup_variables, **kwargs):
if value is None:
continue
if isinstance(model, basestring):
model_class = apps.get_model(*model.split("."))
model_class = get_model(*model.split("."))
else:
model_class = model
if model_class is None:
raise ValueError(
"The given argument '%s' is not a valid model." % model
)
if inspect.isclass(model_class) and not issubclass(
model_class, Model
):
"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)
@ -67,18 +55,15 @@ 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
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)

View file

@ -1,14 +1,12 @@
class AuthorityException(Exception):
pass
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?")

View file

@ -0,0 +1,20 @@
[
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "jezdez",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2009-11-02 03:06:19",
"groups": [],
"user_permissions": [],
"password": "",
"email": "",
"date_joined": "2009-11-02 03:06:19"
}
}
]

View file

@ -1,20 +1,20 @@
[
{
"pk": 1,
"model": "users.user",
"pk": 1,
"model": "users.user",
"fields": {
"first_name": "Jez",
"last_name": "Dez",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2009-11-02 03:06:19",
"groups": [],
"user_permissions": [],
"password": "",
"email": "jezdez@github.com",
"first_name": "Jez",
"last_name": "Dez",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2009-11-02 03:06:19",
"groups": [],
"user_permissions": [],
"password": "",
"email": "jezdez@github.com",
"date_joined": "2009-11-02 03:06:19",
"greeting_message": "Hello customer user model"
"greeting_message": "Hello customer user model"
}
}
]

View file

@ -1,33 +1,29 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Group
from django.utils.safestring import mark_safe
from authority import permissions
from authority.utils import get_choices_for
from authority import permissions, get_choices_for
from authority.models import Permission
User = get_user_model()
from authority.utils import User
class BasePermissionForm(forms.ModelForm):
codename = forms.CharField(label=_("Permission"))
codename = forms.CharField(label=_('Permission'))
class Meta:
model = Permission
exclude = []
def __init__(self, perm=None, obj=None, approved=False, *args, **kwargs):
self.perm = perm
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):
@ -38,16 +34,15 @@ class BasePermissionForm(forms.ModelForm):
self.instance.approved = self.approved
return super(BasePermissionForm, self).save(commit)
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):
@ -56,41 +51,34 @@ 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"]
@ -98,21 +86,13 @@ 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

View file

@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType
class PermissionManager(models.Manager):
def get_content_type(self, obj):
return ContentType.objects.get_for_model(obj)
@ -11,41 +12,40 @@ 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', 'user__groups', 'creator').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):
"""

View file

@ -1,104 +0,0 @@
# -*- 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",
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"),
),
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name="permission",
unique_together=set(
[("codename", "object_id", "content_type", "user", "group")]
),
),
]

View file

@ -2,13 +2,12 @@ from datetime import datetime
from django.conf import settings
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.auth.models import Group
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User, Group
from django.utils.translation import ugettext_lazy as _
from authority.managers import PermissionManager
USER_MODEL = getattr(settings, "AUTH_USER_MODEL", "auth.User")
from authority.utils import User
class Permission(models.Model):
@ -17,41 +16,19 @@ 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", on_delete=models.CASCADE
)
codename = models.CharField(_('codename'), max_length=100)
content_type = models.ForeignKey(ContentType, related_name="row_permissions")
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
content_object = generic.GenericForeignKey('content_type', 'object_id')
user = models.ForeignKey(
USER_MODEL,
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,
null=True,
blank=True,
related_name="created_permissions",
on_delete=models.CASCADE,
)
user = models.ForeignKey(User, null=True, blank=True, related_name='granted_permissions')
group = models.ForeignKey(Group, null=True, blank=True)
creator = models.ForeignKey(User, 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)
date_requested = models.DateTimeField(_('date requested'), default=datetime.now)
date_approved = models.DateTimeField(_('date approved'), blank=True, null=True)
objects = PermissionManager()
@ -60,12 +37,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):

View file

@ -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,12 +31,11 @@ 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
@ -49,7 +48,10 @@ 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),
)
@ -57,26 +59,22 @@ 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):
@ -85,12 +83,17 @@ 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):
@ -120,7 +123,11 @@ 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.
@ -138,7 +145,11 @@ 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.
@ -156,7 +167,11 @@ 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
@ -179,7 +194,9 @@ class BasePermission(object):
@property
def use_smart_cache(self):
use_smart_cache = getattr(settings, "AUTHORITY_USE_SMART_CACHE", True)
# AUTHORITY_USE_SMART_CACHE defaults to False to maintain backwards
# compatibility.
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):
@ -195,7 +212,12 @@ 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):
@ -207,13 +229,15 @@ 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):
"""
@ -227,17 +251,24 @@ 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):
"""
@ -276,20 +307,25 @@ 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):
@ -311,7 +347,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:
@ -330,11 +366,14 @@ 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,
group=self.group,
codename=codename,
approved=True,
content_type=content_type,
@ -343,7 +382,6 @@ class BasePermission(object):
except Permission.DoesNotExist:
perm = Permission.objects.create(
user=self.user,
group=self.group,
content_object=content_object,
codename=codename,
approved=True,
@ -354,16 +392,21 @@ 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)

View file

@ -1,5 +1,4 @@
from inspect import getmembers, ismethod
from django.apps import apps
from django.db import models
from django.db.models.base import ModelBase
from django.utils.translation import ugettext_lazy as _
@ -7,20 +6,16 @@ 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.
"""
_registry = {}
_choices = {}
@ -34,7 +29,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
@ -54,8 +49,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
@ -69,23 +64,17 @@ 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__
permission_class = type(
"%sPermission" % model.__name__, (permission_class,), options
)
options['__module__'] = __name__
permission_class = type("%sPermission" % model.__name__,
(permission_class,), options)
permission_class.model = model
self.setup(model, permission_class)
@ -96,7 +85,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):
@ -105,12 +94,10 @@ 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",
_("%(object_name)s permission '%(check)s'")
% {"object_name": model._meta.object_name, "check": check_name},
)
func.short_description = getattr(check_func, 'short_description',
_("%(object_name)s permission '%(check)s'") % {
'object_name': model._meta.object_name,
'check': check_name})
setattr(permission, check_name, func)
else:
permission.generic_checks.append(check_name)
@ -119,12 +106,11 @@ 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())
@ -134,19 +120,15 @@ class PermissionSite(object):
if check_func and not granted:
return check_func(self, *args, **kwargs)
return granted
return check
class PermissionDescriptor(object):
def get_content_type(self, obj=None):
ContentType = apps.get_model("contenttypes", "contenttype")
ContentType = models.get_model("contenttypes", "contenttype")
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:
@ -154,7 +136,6 @@ 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

View file

@ -19,7 +19,7 @@
{% if inline_admin_form.form.non_field_errors %}
<tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}">
<tr class="{% cycle row1,row2 %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}">
<td class="original">
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>

View file

@ -2,20 +2,20 @@
{% load i18n admin_modify admin_static %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
<script type="text/javascript" src="../../../jsi18n/"></script>
{{ media }}
{% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}"/>{% endblock %}
{% block coltype %}colM{% endblock %}
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../">{% trans "Home" %}</a> &rsaquo;
<a href="../">{{ app_label|capfirst|escape }}</a> &rsaquo;
<a href="../">{{ app_label|capfirst|escape }}</a> &rsaquo;
<a href="./">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
{% trans "Permissions" %}
</div>
@ -23,7 +23,6 @@
{% block content %}<div id="content-main">
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">
{% csrf_token %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if save_on_top %}{% submit_row %}{% endif %}
@ -45,7 +44,7 @@
{% block after_related_objects %}{% endblock %}
<div class="submit-row">
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save_action" {{ onclick_attrib }}/>
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" {{ onclick_attrib }}/>
</div>
</div>

View file

@ -1,46 +1,39 @@
from django import template
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.core.exceptions import ImproperlyConfigured
from django.urls import reverse
from django.contrib.auth.models import AnonymousUser
from django.core.urlresolvers import reverse
from authority.utils import get_check
from authority import get_check
from authority import permissions
from authority.models import Permission
from authority.forms import UserPermissionForm
from authority.utils import User
User = get_user_model()
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:
@ -53,7 +46,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
@ -62,27 +55,25 @@ 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]
)
end_tag = "endifhasperm"
nodelist_true = parser.parse(("else", end_tag))
raise template.TemplateSyntaxError("'%s' tag takes three, "
"four or five arguments" % bits[0])
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:
nodelist_false = template.NodeList()
if len(bits) == 3: # this tag requires at most 2 objects . None is given
if len(bits) == 3: # this tag requires at most 2 objects . None is given
objs = (None, None)
elif len(bits) == 4: # one is given
elif len(bits) == 4:# one is given
objs = (bits[3], None)
else: # two are given
else: #two are given
objs = (bits[3], bits[4])
return cls(bits[2], bits[1], nodelist_true, nodelist_false, *objs)
@ -111,16 +102,15 @@ 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)
@register.tag
def ifhasperm(parser, token):
"""
@ -134,7 +124,7 @@ def ifhasperm(parser, token):
meh
{% endifhasperm %}
{% ifhasperm "poll_permission.change_poll" request.user %}
{% if hasperm "poll_permission.change_poll" request.user %}
lalala
{% else %}
meh
@ -145,14 +135,16 @@ def ifhasperm(parser, token):
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),
"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)
@ -166,40 +158,33 @@ 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(obj, context) for obj 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(
perm,
obj,
approved=self.approved,
initial=dict(codename=perm, user=request.user.username),
),
'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)),
}
return template.loader.render_to_string(template_name, extra_context, request)
return template.loader.render_to_string(template_name, extra_context,
context_instance=template.RequestContext(request))
@register.tag
def permission_form(parser, token):
@ -215,7 +200,6 @@ def permission_form(parser, token):
"""
return PermissionFormNode.handle_token(parser, token, approved=True)
@register.tag
def permission_request_form(parser, token):
"""
@ -225,23 +209,23 @@ 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)
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)
@ -262,15 +246,14 @@ class PermissionsForObjectNode(ResolverNode):
if isinstance(user, User):
perms = perms.filter(user=user)
context[var_name] = perms
return ""
return ''
@register.tag
def get_permissions(parser, token):
"""
Retrieves all permissions associated with the given obj and user
and assigns the result to a context variable.
Syntax::
{% get_permissions obj %}
@ -282,17 +265,15 @@ 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
def get_permission_requests(parser, token):
"""
Retrieves all permissions requests associated with the given obj and user
and assigns the result to a context variable.
Syntax::
{% get_permission_requests obj %}
@ -304,22 +285,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)
@ -331,7 +312,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)
@ -348,8 +329,7 @@ class PermissionForObjectNode(ResolverNode):
if granted:
break
context[var_name] = granted
return ""
return ''
@register.tag
def get_permission(parser, token):
@ -361,11 +341,9 @@ 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.
{% else %}
@ -373,10 +351,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
def get_permission_request(parser, token):
@ -388,11 +365,9 @@ 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!
{% else %}
@ -400,67 +375,59 @@ 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}),
'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.
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.
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}

View file

@ -1,40 +1,36 @@
from django import VERSION
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission as DjangoPermission
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import MultipleObjectsReturned
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.urls import reverse
from django.contrib.contenttypes.models import ContentType
import authority
from authority import permissions
from authority.models import Permission
from authority.exceptions import NotAModel, UnsavedModelInstance
# Load the form
from authority.forms import UserPermissionForm # noqa
from authority.utils import User
User = get_user_model()
FIXTURES = ["tests_custom.json"]
QUERY = Q(email="jezdez@github.com")
if VERSION >= (1, 5):
FIXTURES = ['tests_custom.json']
QUERY = Q(email="jezdez@github.com")
else:
FIXTURES = ['tests.json']
QUERY = Q(username="jezdez")
class UserPermission(permissions.BasePermission):
checks = ("browse",)
label = "user_permission"
authority.utils.register(User, UserPermission)
checks = ('browse',)
label = 'user_permission'
authority.register(User, UserPermission)
class GroupPermission(permissions.BasePermission):
checks = ("browse",)
label = "group_permission"
authority.utils.register(Group, GroupPermission)
checks = ('browse',)
label = 'group_permission'
authority.register(Group, GroupPermission)
class DjangoPermissionChecksTestCase(TestCase):
@ -49,7 +45,6 @@ class DjangoPermissionChecksTestCase(TestCase):
This permissions are given in the test case and not in the fixture, for
later reference.
"""
fixtures = FIXTURES
def setUp(self):
@ -63,7 +58,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
@ -73,8 +68,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()
@ -90,60 +85,23 @@ 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):
self.user = User.objects.get(QUERY)
self.group1, _ = Group.objects.get_or_create(name="Test Group 1")
self.group2, _ = Group.objects.get_or_create(name="Test Group 2")
self.group3, _ = Group.objects.get_or_create(name="Test Group 2")
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_assign_to_group(self):
result = UserPermission(group=self.group1).assign(
check="delete_user", content_object=self.user
)
self.assertIsInstance(result, list)
self.assertIsInstance(result[0], Permission)
self.assertTrue(UserPermission(group=self.group1).delete_user(self.user))
def test_assign_to_group_does_not_overwrite_other_group_permission(self):
UserPermission(group=self.group1).assign(
check="delete_user", content_object=self.user
)
UserPermission(group=self.group2).assign(
check="delete_user", content_object=self.user
)
self.assertTrue(UserPermission(group=self.group2).delete_user(self.user))
self.assertTrue(UserPermission(group=self.group1).delete_user(self.user))
def test_assign_to_group_does_not_fail_when_two_group_perms_exist(self):
for group in self.group1, self.group2:
perm = Permission(
group=group,
content_object=self.user,
codename="user_permission.delete_user",
approved=True,
)
perm.save()
try:
UserPermission(group=self.group3).assign(
check="delete_user", content_object=self.user
)
except MultipleObjectsReturned:
self.fail("assign() should not have raised this exception")
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())
@ -164,7 +122,6 @@ class GenericAssignBehaviourTest(TestCase):
- permission add (test_add),
- permission delete for him (test_delete),
"""
fixtures = FIXTURES
def setUp(self):
@ -172,14 +129,16 @@ 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))
@ -192,7 +151,6 @@ class AssignExceptionsTest(TestCase):
Tests that exceptions are thrown if assign() was called with inconsistent
arguments.
"""
fixtures = FIXTURES
def setUp(self):
@ -208,7 +166,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()
@ -218,7 +176,6 @@ class SmartCachingTestCase(TestCase):
"""
The base test case for all tests that have to do with smart caching.
"""
fixtures = FIXTURES
def setUp(self):
@ -243,14 +200,21 @@ 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,
)
@ -275,13 +239,20 @@ 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
@ -289,7 +260,10 @@ 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):
@ -301,7 +275,10 @@ 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
@ -312,7 +289,10 @@ 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):
@ -322,7 +302,11 @@ 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.
@ -331,14 +315,18 @@ 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,
)
@ -347,17 +335,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,
)
@ -366,7 +354,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):
@ -381,7 +369,10 @@ 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)
@ -389,7 +380,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,
)
@ -401,7 +392,10 @@ 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)
@ -409,7 +403,9 @@ 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)
@ -420,7 +416,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,
)
@ -433,21 +429,8 @@ 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)
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)

View file

@ -1,40 +1,31 @@
from django.conf.urls import url
from authority.views import (
add_permission,
delete_permission,
approve_permission_request,
delete_permission,
)
try:
from django.conf.urls import *
except ImportError: # django < 1.4
from django.conf.urls.defaults import *
urlpatterns = [
url(
r"^permission/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$",
view=add_permission,
urlpatterns = patterns('authority.views',
url(r'^permission/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$',
view='add_permission',
name="authority-add-permission",
kwargs={"approved": True},
kwargs={'approved': True}
),
url(
r"^permission/delete/(?P<permission_pk>\d+)/$",
view=delete_permission,
url(r'^permission/delete/(?P<permission_pk>\d+)/$',
view='delete_permission',
name="authority-delete-permission",
kwargs={"approved": True},
kwargs={'approved': True}
),
url(
r"^request/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$",
view=add_permission,
url(r'^request/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$',
view='add_permission',
name="authority-add-permission-request",
kwargs={"approved": False},
kwargs={'approved': False}
),
url(
r"^request/approve/(?P<permission_pk>\d+)/$",
view=approve_permission_request,
name="authority-approve-permission-request",
url(r'^request/approve/(?P<permission_pk>\d+)/$',
view='approve_permission_request',
name="authority-approve-permission-request"
),
url(
r"^request/delete/(?P<permission_pk>\d+)/$",
view=delete_permission,
url(r'^request/delete/(?P<permission_pk>\d+)/$',
view='delete_permission',
name="authority-delete-permission-request",
kwargs={"approved": False},
kwargs={'approved': False}
),
]
)

View file

@ -1,7 +1,11 @@
from authority.sites import (
site,
get_check,
get_choices_for,
register,
unregister,
) # noqa
from django.contrib import auth
def get_user_class():
if hasattr(auth, "get_user_model"):
return auth.get_user_model()
else:
return auth.models.User
User = get_user_class()

View file

@ -1,7 +1,10 @@
from django.shortcuts import render, get_object_or_404
from datetime import datetime
from django.shortcuts import render_to_response, get_object_or_404
from django.views.decorators.http import require_POST
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.apps import apps
from django.db.models.loading import get_model
from django.utils.translation import ugettext as _
from django.template.context import RequestContext
from django.template import loader
from django.contrib.auth.decorators import login_required
@ -9,108 +12,85 @@ from authority.models import Permission
from authority.forms import UserPermissionForm
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)
try:
model = apps.get_model(app_label, module_name)
except LookupError:
def add_permission(request, app_label, module_name, pk, approved=False,
template_name = 'authority/permission_form.html',
extra_context={}, form_class=UserPermissionForm):
codename = request.POST.get('codename', None)
model = get_model(app_label, module_name)
if model is None:
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)
permission = 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)
return render(request, template_name, context)
context.update(extra_context)
return render_to_response(template_name, context,
context_instance=RequestContext(request))
@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)
def permission_denied(request, template_name=None, extra_context=None):
def permission_denied(request, template_name=None, extra_context={}):
"""
Default 403 handler.
@ -120,14 +100,10 @@ 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=template_name, context=context, request=request,
)
)
context.update(extra_context)
return HttpResponseForbidden(loader.render_to_string(template_name, context,
context_instance=RequestContext(request)))

View file

@ -4,7 +4,6 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
generic_script = """
<script type="text/javascript">
function showGenericRelatedObjectLookupPopup(ct_select, triggering_link, url_base) {
@ -18,7 +17,6 @@ function showGenericRelatedObjectLookupPopup(ct_select, triggering_link, url_bas
</script>
"""
class GenericForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
def __init__(self, ct_field, cts=[], attrs=None):
self.ct_field = ct_field
@ -28,57 +26,28 @@ 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 = "?" + "&amp;".join(["%s=%s" % (k, v) for k, v in params.iteritems()])
url = '?' + '&amp;'.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
<a href="%(related)s%(url)s"
class="related-lookup"
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,
}
)
output.append(
'<img src="%s/admin/img/selector-search.gif" width="16" height="16" alt="%s" /></a>'
% (settings.STATIC_URL, _("Lookup"))
)
output.append("""%(generic_script)s
<a href="%(related)s%(url)s" class="related-lookup" 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})
output.append('<img src="%s/admin/img/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.STATIC_URL, _('Lookup')))
from django.contrib.contenttypes.models import ContentType
content_types = """
<script type="text/javascript">
var content_types = new Array();
%s
</script>
""" % (
"\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 {}

84
bootstrap.py Normal file
View file

@ -0,0 +1,84 @@
##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
$Id$
"""
import os, shutil, sys, tempfile, urllib2
tmpeggs = tempfile.mkdtemp()
is_jython = sys.platform.startswith('java')
try:
import pkg_resources
except ImportError:
ez = {}
exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
import pkg_resources
if sys.platform == 'win32':
def quote(c):
if ' ' in c:
return '"%s"' % c # work around spawn lamosity on windows
else:
return c
else:
def quote (c):
return c
cmd = 'from setuptools.command.easy_install import main; main()'
ws = pkg_resources.working_set
if len(sys.argv) > 2 and sys.argv[1] == '--version':
VERSION = ' == %s' % sys.argv[2]
args = sys.argv[3:] + ['bootstrap']
else:
VERSION = ''
args = sys.argv[1:] + ['bootstrap']
if is_jython:
import subprocess
assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
quote(tmpeggs), 'zc.buildout' + VERSION],
env=dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse('setuptools')).location
),
).wait() == 0
else:
assert os.spawnle(
os.P_WAIT, sys.executable, quote (sys.executable),
'-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse('setuptools')).location
),
) == 0
ws.add_entry(tmpeggs)
ws.require('zc.buildout' + VERSION)
import zc.buildout.buildout
zc.buildout.buildout.main(args)
shutil.rmtree(tmpeggs)

27
buildout.cfg Normal file
View file

@ -0,0 +1,27 @@
[buildout]
parts =
python
django-1.0.X
django-1.1.X
develop = .
eggs =
django-authority
[python]
recipe = zc.recipe.egg
interpreter = python
eggs = ${buildout:eggs}
[django-1.0.X]
recipe = djangorecipe
version = 1.0.4
projectegg = example
settings = development
eggs = ${buildout:eggs}
[django-1.1.X]
recipe = djangorecipe
version = 1.1.1
projectegg = example
settings = development
eggs = ${buildout:eggs}

View file

@ -28,7 +28,7 @@ Syntax::
Example::
{% ifhasperm "poll_permission.change_poll" request.user %}
{% if hasperm "poll_permission.change_poll" request.user %}
lalala
{% else %}
meh

View file

@ -11,13 +11,13 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
from pkg_resources import get_distribution
import sys, os
# 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
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.append(os.path.abspath('.'))
# sys.path.append(os.path.join(os.path.dirname(__file__), '../src/'))
#sys.path.append(os.path.abspath('.'))
#sys.path.append(os.path.join(os.path.dirname(__file__), '../src/'))
# -- General configuration -----------------------------------------------------
@ -26,117 +26,118 @@ from pkg_resources import get_distribution
extensions = []
# Add any paths that contain templates here, relative to this directory.
# templates_path = ['.templates']
#templates_path = ['.templates']
# The suffix of source filenames.
source_suffix = ".txt"
source_suffix = '.txt'
# The encoding of source files.
# source_encoding = 'utf-8'
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = "index"
master_doc = 'index'
# General information about the project.
project = u"django-authority"
copyright = u"2009-2020, Jannis Leidel"
project = u'django-authority'
copyright = u'2009, the django-authority team'
# 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 full version, including alpha/beta/rc tags.
release = get_distribution("django-authority").version
# The short X.Y version.
version = ".".join(release.split(".")[:2])
version = '0.8'
# The full version, including alpha/beta/rc tags.
release = '0.8dev'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
#today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
# unused_docs = []
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ["build"]
exclude_trees = ['build']
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = "nature"
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [".theme"]
html_theme_path = ['.theme']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
html_logo = ".static/logo.png"
html_logo = '.static/logo.png'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = "favicon.png"
html_favicon = 'favicon.png'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = [".static"]
html_static_path = ['.static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
#html_additional_pages = {}
# If false, no module index is generated.
html_use_modindex = True
@ -145,7 +146,7 @@ html_use_modindex = True
html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
#html_split_index = False
# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False
@ -153,48 +154,43 @@ html_show_sourcelink = False
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = ''
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = "django-authoritydoc"
htmlhelp_basename = 'django-authoritydoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
# latex_paper_size = 'letter'
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
# latex_font_size = '10pt'
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
(
"index",
"django-authority.tex",
u"django-authority Documentation",
u"The django-authority team",
"manual",
),
('index', 'django-authority.tex', u'django-authority Documentation',
u'The django-authority team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
# latex_preamble = ''
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
# latex_appendices = []
#latex_appendices = []
# If false, no module index is generated.
# latex_use_modindex = True
#latex_use_modindex = True

View file

@ -23,12 +23,12 @@ Let's start with an example::
import authority
from authority import permissions
from django.contrib.flatpages.models import FlatPage
from django.contrib.flatpages.models import Flatpage
class FlatpagePermission(permissions.BasePermission):
label = 'flatpage_permission'
authority.register(FlatPage, FlatpagePermission)
authority.register(Flatpage, FlatpagePermission)
Let's have a look at the code above. First of, if you want to create a new
permission you have to subclass it from the BasePermission class::
@ -45,14 +45,14 @@ Next, you need to name this permission using the ``label`` attribute::
And finally you need to register the permission with the pool of all other
permissions::
authority.register(FlatPage, FlatpagePermission)
authority.register(Flatpage, FlatpagePermission)
The syntax of this is simple::
authority.register(<model>, <permission_class>)
While this is not much code, you already wrapped Django's basic permissions
(add_flatpage, change_flatpage, delete_flatpage) for the model ``FlatPage``
(add_flatpage, change_flatpage, delete_flatpage) for the model ``Flatpage``
and you are ready to use it within your templates or code:
.. note:: See `Django's basic permissions`_ how Django creates this permissions for you.

View file

@ -36,7 +36,7 @@ A custom permission is a simple method of the permission class::
Note that we first added the name of your custom permission to the ``checks``
attribute, like in :ref:`create-per-object-permission`::
checks = ('my_custom_check',)
checks = (my_custom_check',)
The permission itself is a simple function that accepts an arbitrary number of
arguments. A permission class should always return a boolean whether the

View file

@ -27,9 +27,9 @@ Development version
===================
The latest development version is located on it's `Github account`_. You
can checkout the package using the Git_ scm::
can checkout the package using the Mercurial_ scm::
git clone https://github.com/jazzband/django-authority
git clone https://github.com/jezdez/django-authority.git
Then install it manually::
@ -39,5 +39,5 @@ Then install it manually::
.. warning:: The development version is not fully tested and may contain
bugs, so we prefer to use the latest package from pypi.
.. _Github account: https://github.com/jazzband/django-authority/
.. _Git: http://gitscm.org/
.. _Github account: https://github.com/jezdez/django-authority/
.. _Mercurial: http://www.selenic.com/mercurial/

View file

@ -14,4 +14,4 @@ For more specific issues and bug reports please use the `issue tracker`_ on
django-authority's Github page.
.. _google group: http://groups.google.com/group/django-authority
.. _issue tracker: https://github.com/jazzband/django-authority/issues/
.. _issue tracker: https://github.com/jezdez/django-authority/issues/

View file

@ -1,4 +1,4 @@
from example.settings import *
DEBUG = True
TEMPLATE_DEBUG = DEBUG
from example.settings import *
DEBUG=True
TEMPLATE_DEBUG=DEBUG

View file

@ -3,6 +3,5 @@ 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())

View file

@ -4,11 +4,10 @@ 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:
@ -41,16 +40,13 @@ 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')
top_secret.short_description = _("Is allowed to see top secret flatpages")
authority.sites.register(FlatPage, FlatPagePermission)
authority.register(FlatPage, FlatPagePermission)

View file

@ -0,0 +1,23 @@
"""
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.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}

View file

@ -8,9 +8,8 @@ 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

View file

@ -7,16 +7,13 @@ 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)

View file

@ -1 +1,2 @@
from example.settings import *

View file

@ -11,87 +11,86 @@ ADMINS = (
MANAGERS = ADMINS
DATABASES = {
"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",},
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(PROJECT_ROOT, 'example.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
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 = (
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
# 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
#'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
)
INTERNAL_IPS = ("127.0.0.1",)
INTERNAL_IPS = ('127.0.0.1',)
TEMPLATE_CONTEXT_PROCESSORS = ()
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',
)
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.messages",
"django.contrib.admin",
"authority",
"example.exampleapp",
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.flatpages',
'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'
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",
],
},
},
]
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"),
)
# Use local_settings.py for things to override privately
try:
from local_settings import * # noqa
except ImportError:
pass

View file

@ -1,44 +1,39 @@
import django.contrib.auth.views
from django.conf.urls import include, handler500, url
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
from django.conf import settings
from django.contrib import admin
import authority
import authority.views
import authority.urls
import example.exampleapp.views
admin.autodiscover()
authority.autodiscover()
handler500 # Pyflakes
from exampleapp.forms import SpecialUserPermissionForm
authority.autodiscover()
handler500 # flake8
urlpatterns = (
url(
r"^authority/permission/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$", # noqa
view=authority.views.add_permission,
urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
#('^admin/', include(admin.site.urls)),
url(r'^authority/permission/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$',
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<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$", # noqa
view=authority.views.add_permission,
url(r'^request/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$',
view='authority.views.add_permission',
name="authority-add-permission-request",
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<url>[\/0-9A-Za-z]+)$",
example.exampleapp.views.top_secret,
{"lala": "oh yeah!"},
kwargs={'approved': False, 'form_class': SpecialUserPermissionForm}
),
(r'^authority/', include('authority.urls')),
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
url(r'^(?P<url>[\/0-9A-Za-z]+)$', 'example.exampleapp.views.top_secret', {'lala': 'oh yeah!'}),
)
if settings.DEBUG:
urlpatterns += (
url(
r"^media/(?P<path>.*)$",
django.views.static.serve,
{"document_root": settings.MEDIA_ROOT,},
),
urlpatterns += patterns('',
(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
'document_root': settings.MEDIA_ROOT,
}),
)

View file

@ -1,6 +1,5 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from example.users.models import User
from example.users.User
admin.site.register(User, UserAdmin)

View file

@ -1,83 +0,0 @@
# -*- 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()),],
),
]

View file

@ -1,14 +1,12 @@
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 = models.CharField(max_length=100)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
@ -16,5 +14,3 @@ 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()

View file

@ -0,0 +1,16 @@
BEGIN;
-- Application: authority
-- Model: Permission
ALTER TABLE `authority_permission`
ADD `date_requested` DATETIME;
ALTER TABLE `authority_permission`
ADD `approved` BOOL;
ALTER TABLE `authority_permission`
ADD `date_approved` DATETIME;
ALTER TABLE `authority_permission`
MODIFY `object_id` INTEGER UNSIGNED;
UPDATE `authority_permission`
SET `approved` = TRUE;
UPDATE `authority_permission`
SET `date_approved` = NOW();
COMMIT;

View file

@ -0,0 +1,14 @@
BEGIN;
-- Application: authority
-- Model: Permission
ALTER TABLE "authority_permission"
ADD "date_requested" timestamp with time zone;
ALTER TABLE "authority_permission"
ADD "approved" boolean;
ALTER TABLE "authority_permission"
ADD "date_approved" timestamp with time zone;
UPDATE "authority_permission"
SET "approved" = True;
UPDATE "authority_permission"
SET "date_approved" = NOW();
COMMIT;

View file

@ -0,0 +1,14 @@
BEGIN;
-- Application: authority
-- Model: Permission
ALTER TABLE "authority_permission"
ADD "date_requested" datetime;
ALTER TABLE "authority_permission"
ADD "approved" bool;
ALTER TABLE "authority_permission"
ADD "date_approved" datetime;
UPDATE "authority_permission"
SET "approved" = 1;
UPDATE "authority_permission"
SET "date_approved" = DATETIME("NOW");
COMMIT;

View file

@ -8,6 +8,3 @@ all_files = 1
[upload_docs]
upload-dir = docs/build/html
[wheel]
universal = 1

View file

@ -5,38 +5,39 @@ from setuptools import setup, find_packages
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setup(
name="django-authority",
use_scm_version=True,
name='django-authority',
version='0.8',
description=(
"A Django app that provides generic per-object-permissions "
"for Django's auth app."
),
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.*")),
long_description=read('README.rst'),
author='Jannis Leidel',
author_email='jannis@leidel.info',
license='BSD',
url='https://github.com/jezdez/django-authority/',
packages=find_packages(),
classifiers=[
"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.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Framework :: Django",
'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.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Framework :: Django',
],
install_requires=["django"],
setup_requires=["setuptools_scm"],
include_package_data=True,
install_requires=['django'],
package_data = {
'authority': [
'fixtures/test.json',
'templates/authority/*.html',
'templates/admin/edit_inline/action_tabular.html',
'templates/admin/permission_change_form.html',
]
},
zip_safe=False,
)

39
tox.ini
View file

@ -1,39 +0,0 @@
[tox]
skipsdist = True
usedevelop = True
minversion = 1.8
envlist =
py27-dj111
py37-dj{111,22}
{py36,py37,py38}-dj{30,31}
py37-check
[testenv]
usedevelop = true
commands =
coverage run -a example/manage.py test authority exampleapp
coverage report
coverage xml
deps =
coverage
dj111: Django>=1.11,<2.0
dj22: Django>=2.2,<2.3
dj30: Django>=3.0,<3.1
dj31: Django>=3.1,<3.2
[testenv:py37-check]
deps =
twine
wheel
commands =
python setup.py sdist bdist_wheel
twine check dist/*
[gh-actions]
python =
2.7: py27
3.6: py36
3.7: py37
3.8: py38