mirror of
https://github.com/jazzband/django-authority.git
synced 2026-03-16 22:20:28 +00:00
Compare commits
96 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f58bbef27e | ||
|
|
bcb3216dd1 | ||
|
|
28b6e4cb59 | ||
|
|
ff95d54a26 | ||
|
|
f0948c08e9 | ||
|
|
19766a7469 | ||
|
|
e3bd439961 | ||
|
|
9dc0516683 | ||
|
|
5c2803c32c | ||
|
|
8f7df22869 | ||
|
|
7494d94b54 | ||
|
|
6c82e3db63 | ||
|
|
5c5236f0d2 | ||
|
|
e94703f675 | ||
|
|
94d6f29a15 | ||
|
|
3677b421fe | ||
|
|
c2289898fb | ||
|
|
b08191e1b8 | ||
|
|
a755cbe456 | ||
|
|
1372cf1f6f | ||
|
|
fcc569b0d9 | ||
|
|
3d07c79272 | ||
|
|
0a6e3bdbcb | ||
|
|
06905e90c4 | ||
|
|
1866f087b2 | ||
|
|
4d5a4ceeab | ||
|
|
c39adbb1bb | ||
|
|
c5be724ec9 | ||
|
|
f793207cb9 | ||
|
|
82d7feb47b | ||
|
|
2dcbffe297 | ||
|
|
07f0935ae2 | ||
|
|
a2cc00e3b5 | ||
|
|
d8542ffb87 | ||
|
|
d891b2753d | ||
|
|
8bdca24ced | ||
|
|
91ddd54a8d | ||
|
|
52dc715454 | ||
|
|
4cf4853232 | ||
|
|
58e08483cd | ||
|
|
e3e4cd713e | ||
|
|
e823da69d9 | ||
|
|
4bbf12c095 | ||
|
|
371c477aa5 | ||
|
|
eebdcaa009 | ||
|
|
97cd73f9d7 | ||
|
|
d0f5667687 | ||
|
|
f58ec2d240 | ||
|
|
ce14ebd6a0 | ||
|
|
85ee004837 | ||
|
|
f1ba811043 | ||
|
|
a07c553918 | ||
|
|
0083e11b8a | ||
|
|
5f47c69f6b | ||
|
|
d0bf6512a9 | ||
|
|
2356491fd7 | ||
|
|
0945016e59 | ||
|
|
5d466ac318 | ||
|
|
9a0d4ab678 | ||
|
|
76a180bcc9 | ||
|
|
72692ad381 | ||
|
|
44595e7066 | ||
|
|
a868d54945 | ||
|
|
c50368eb34 | ||
|
|
bc3e375718 | ||
|
|
14b92a0dcd | ||
|
|
74137a540b | ||
|
|
85bf2e2d2b | ||
|
|
6fad04fc4c | ||
|
|
9e4035118e | ||
|
|
2f7822af83 | ||
|
|
57684b94df | ||
|
|
d4dc955e63 | ||
|
|
8aa4c85942 | ||
|
|
91929a76c9 | ||
|
|
1cef19d7de | ||
|
|
f7c85994b9 | ||
|
|
232ab607fc | ||
|
|
bf57a34af4 | ||
|
|
dd1d840379 | ||
|
|
7d242ebf00 | ||
|
|
9bc6159d32 | ||
|
|
46634c3b46 | ||
|
|
26ed10824d | ||
|
|
c713eb5419 | ||
|
|
bc5118b524 | ||
|
|
8935320f4b | ||
|
|
08359c4880 | ||
|
|
cfe9d670ba | ||
|
|
762c1e9594 | ||
|
|
1bcf8d5685 | ||
|
|
c289790102 | ||
|
|
8f81984939 | ||
|
|
c5c853e12b | ||
|
|
8e33c667f9 | ||
|
|
bf2423b574 |
56 changed files with 1408 additions and 1058 deletions
6
.coveragerc
Normal file
6
.coveragerc
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[run]
|
||||
source = authority
|
||||
branch = 1
|
||||
|
||||
[report]
|
||||
omit = *tests*,*migrations*
|
||||
53
.github/workflows/release.yml
vendored
Normal file
53
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
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
|
||||
48
.github/workflows/test.yml
vendored
Normal file
48
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
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
7
.gitignore
vendored
|
|
@ -2,4 +2,9 @@
|
|||
*.egg-info
|
||||
*.sql
|
||||
docs/build/*
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.tox/
|
||||
dist/
|
||||
build/
|
||||
.coverage
|
||||
.eggs/
|
||||
|
|
|
|||
26
.hgignore
26
.hgignore
|
|
@ -1,26 +0,0 @@
|
|||
syntax: glob
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
||||
*.swp
|
||||
*.orig
|
||||
*.kpf
|
||||
*.egg-info
|
||||
.project
|
||||
.pydevproject
|
||||
.DS_Store
|
||||
MANIFEST
|
||||
dist
|
||||
build
|
||||
dev.db
|
||||
local_settings.py
|
||||
parts/*
|
||||
eggs/*
|
||||
downloads/*
|
||||
.installed.cfg
|
||||
bin/*
|
||||
develop-eggs/*.egg-link
|
||||
example/example.db
|
||||
docs/build
|
||||
TODO
|
||||
example/example.db
|
||||
20
.travis.yml
20
.travis.yml
|
|
@ -1,20 +0,0 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
env:
|
||||
- TOX_ENV=py26-django14
|
||||
- TOX_ENV=py26-django15
|
||||
- TOX_ENV=py26-django16
|
||||
- TOX_ENV=py27-django14
|
||||
- TOX_ENV=py27-django15
|
||||
- TOX_ENV=py27-django16
|
||||
- TOX_ENV=py27-django17
|
||||
- TOX_ENV=py33-django15
|
||||
- TOX_ENV=py33-django16
|
||||
- TOX_ENV=py33-django17
|
||||
install:
|
||||
- pip install tox
|
||||
notifications:
|
||||
email:
|
||||
- jason.louard.ward@gmail.com
|
||||
script: tox -e $TOX_ENV
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# 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/
|
||||
5
CONTRIBUTING.md
Normal file
5
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[](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).
|
||||
4
LICENSE
4
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2009, Jannis Leidel
|
||||
Copyright (c) 2009-2020, 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.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
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
|
||||
|
|
|
|||
99
README.rst
99
README.rst
|
|
@ -2,6 +2,17 @@
|
|||
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.
|
||||
|
||||
|
|
@ -10,36 +21,31 @@ 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`: http://django-authority.readthedocs.org/
|
||||
.. _in-development version: https://github.com/jezdez/django-authority/archive/master.zip#egg=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
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
To get the example project running do:
|
||||
|
||||
- Bootstrap the buildout by running::
|
||||
- Bootstrap the environment by running in a virtualenv::
|
||||
|
||||
python bootstrap.py
|
||||
|
||||
- Get the required packages by running::
|
||||
|
||||
bin/buildout
|
||||
pip install Django
|
||||
pip install -e .
|
||||
|
||||
- Sync the database::
|
||||
|
||||
bin/django-trunk syncdb
|
||||
python example/manage.py migrate
|
||||
|
||||
- Run the development server and visit the admin at http://127.0.0.1:8000/admin/::
|
||||
|
||||
bin/django-trunk runserver
|
||||
python example/manage.py 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.
|
||||
|
||||
Full docs coming soon.
|
||||
|
||||
Please use https://github.com/jezdez/django-authority/issues/ for issues and bug reports.
|
||||
Please use https://github.com/jazzband/django-authority/issues/ for issues and bug reports.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
|
@ -52,6 +58,59 @@ 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):
|
||||
-----------------
|
||||
|
||||
|
|
@ -94,7 +153,9 @@ Changelog:
|
|||
|
||||
* Added ability to override form class in ``add_permission`` view.
|
||||
|
||||
* Added easy way to assign permissions via a permission instance, e.g.::
|
||||
* Added easy way to assign permissions via a permission instance, e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from mysite.articles.permissions import ArticlePermission
|
||||
|
|
@ -122,13 +183,17 @@ Changelog:
|
|||
* The templatetags have also been refactored to be easier to customize
|
||||
which required a change in the template tag signature:
|
||||
|
||||
Old::
|
||||
Old:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% permission_form flatpage %}
|
||||
{% permission_form flatpage "flatpage_permission.top_secret" %}
|
||||
{% permission_form OBJ PERMISSION_LABEL.CHECK_NAME %}
|
||||
|
||||
New::
|
||||
New:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% permission_form for flatpage %}
|
||||
{% permission_form for flatpage using "flatpage_permission.top_secret" %}
|
||||
|
|
@ -147,4 +212,4 @@ Changelog:
|
|||
allows to request permissions, but also add them (only for users with
|
||||
the 'authority.add_permission' Django permission).
|
||||
|
||||
.. _`migrations/`: https://github.com/jezdez/django-authority/tree/master/migrations
|
||||
.. _`migrations/`: https://github.com/jazzbands/django-authority/tree/master/migrations
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import sys
|
||||
from authority.sites import site, get_check, get_choices_for, register, unregister
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
|
||||
try:
|
||||
__version__ = get_distribution("django-authority").version
|
||||
except DistributionNotFound:
|
||||
# package is not installed
|
||||
pass
|
||||
|
||||
LOADING = False
|
||||
|
||||
|
||||
def autodiscover():
|
||||
"""
|
||||
Goes and imports the permissions submodule of every app in INSTALLED_APPS
|
||||
|
|
@ -13,19 +19,6 @@ def autodiscover():
|
|||
return
|
||||
LOADING = True
|
||||
|
||||
import imp
|
||||
from django.conf import settings
|
||||
from django.utils.module_loading import autodiscover_modules
|
||||
|
||||
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
|
||||
autodiscover_modules("permissions")
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import django
|
||||
from django import forms, template
|
||||
from django import forms
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import ugettext, ungettext, ugettext_lazy as _
|
||||
from django.shortcuts import render_to_response
|
||||
from django.shortcuts import render
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.forms.formsets import all_valid
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import helpers
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.contrib.admin import actions, helpers
|
||||
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
|
|
@ -16,51 +15,48 @@ 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 1.7 forward, Django consistenly uses the name "utils",
|
||||
# not "util". We alias for backwards compatibility.
|
||||
if django.VERSION[:2] < (1, 7):
|
||||
forms.utils = forms.util
|
||||
|
||||
from authority.models import Permission
|
||||
from authority.widgets import GenericForeignKeyRawIdWidget
|
||||
from authority import get_choices_for
|
||||
from authority.utils import get_choices_for
|
||||
|
||||
class PermissionInline(generic.GenericTabularInline):
|
||||
|
||||
class PermissionInline(GenericTabularInline):
|
||||
model = Permission
|
||||
raw_id_fields = ('user', 'group', 'creator')
|
||||
raw_id_fields = ("user", "group", "creator")
|
||||
extra = 1
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
if db_field.name == 'codename':
|
||||
if db_field.name == "codename":
|
||||
perm_choices = get_choices_for(self.parent_model)
|
||||
kwargs['label'] = _('permission')
|
||||
kwargs['widget'] = forms.Select(choices=perm_choices)
|
||||
kwargs["label"] = _("permission")
|
||||
kwargs["widget"] = forms.Select(choices=perm_choices)
|
||||
return super(PermissionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
class ActionPermissionInline(PermissionInline):
|
||||
raw_id_fields = ()
|
||||
template = 'admin/edit_inline/action_tabular.html'
|
||||
template = "admin/edit_inline/action_tabular.html"
|
||||
|
||||
|
||||
class ActionErrorList(forms.utils.ErrorList):
|
||||
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)
|
||||
|
|
@ -72,9 +68,10 @@ def edit_permissions(modeladmin, request, queryset):
|
|||
prefixes[prefix] = prefixes.get(prefix, 0) + 1
|
||||
if prefixes[prefix] != 1:
|
||||
prefix = "%s-%s" % (prefix, prefixes[prefix])
|
||||
if request.POST.get('post'):
|
||||
formset = FormSet(data=request.POST, files=request.FILES,
|
||||
instance=obj, prefix=prefix)
|
||||
if request.POST.get("post"):
|
||||
formset = FormSet(
|
||||
data=request.POST, files=request.FILES, instance=obj, prefix=prefix
|
||||
)
|
||||
else:
|
||||
formset = FormSet(instance=obj, prefix=prefix)
|
||||
formsets.append(formset)
|
||||
|
|
@ -87,85 +84,105 @@ def edit_permissions(modeladmin, request, queryset):
|
|||
inline_admin_formsets.append(inline_admin_formset)
|
||||
media = media + inline_admin_formset.media
|
||||
|
||||
ordered_objects = opts.get_ordered_objects()
|
||||
if request.POST.get('post'):
|
||||
if request.POST.get("post"):
|
||||
if all_valid(formsets):
|
||||
for formset in formsets:
|
||||
formset.save()
|
||||
else:
|
||||
modeladmin.message_user(request, '; '.join(
|
||||
err.as_text() for formset in formsets for err in formset.errors
|
||||
))
|
||||
modeladmin.message_user(
|
||||
request,
|
||||
"; ".join(
|
||||
err.as_text() for formset in formsets for err in formset.errors
|
||||
),
|
||||
)
|
||||
# redirect to full request path to make sure we keep filter
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
context = {
|
||||
'errors': ActionErrorList(formsets),
|
||||
'title': ugettext('Permissions for %s') % force_text(opts.verbose_name_plural),
|
||||
'inline_admin_formsets': inline_admin_formsets,
|
||||
'app_label': app_label,
|
||||
'change': True,
|
||||
'ordered_objects': ordered_objects,
|
||||
'form_url': mark_safe(''),
|
||||
'opts': opts,
|
||||
'target_opts': queryset.model._meta,
|
||||
'content_type_id': ContentType.objects.get_for_model(queryset.model).id,
|
||||
'save_as': False,
|
||||
'save_on_top': False,
|
||||
'is_popup': False,
|
||||
'media': mark_safe(media),
|
||||
'show_delete': False,
|
||||
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||
'queryset': queryset,
|
||||
"errors": ActionErrorList(formsets),
|
||||
"title": ugettext("Permissions for %s") % force_text(opts.verbose_name_plural),
|
||||
"inline_admin_formsets": inline_admin_formsets,
|
||||
"app_label": app_label,
|
||||
"change": True,
|
||||
"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_to_response(template_name, context,
|
||||
context_instance=template.RequestContext(request))
|
||||
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(request, template_name, context)
|
||||
|
||||
|
||||
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
|
||||
if db_field.name in [f.fk_field for f in self.model._meta.virtual_fields if f.name in self.generic_fields]:
|
||||
names = [
|
||||
f.fk_field
|
||||
for f in self.model._meta.virtual_fields
|
||||
if f.name in self.generic_fields
|
||||
]
|
||||
if db_field.name in names:
|
||||
for gfk in self.model._meta.virtual_fields:
|
||||
if gfk.fk_field == db_field.name:
|
||||
kwargs['widget'] = GenericForeignKeyRawIdWidget(
|
||||
gfk.ct_field, self.admin_site._registry.keys())
|
||||
kwargs["widget"] = GenericForeignKeyRawIdWidget(
|
||||
gfk.ct_field, self.admin_site._registry.keys()
|
||||
)
|
||||
break
|
||||
return super(PermissionAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
def queryset(self, request):
|
||||
user = request.user
|
||||
if (user.is_superuser or
|
||||
user.has_perm('permissions.change_foreign_permissions')):
|
||||
if user.is_superuser or user.has_perm("permissions.change_foreign_permissions"):
|
||||
return super(PermissionAdmin, self).queryset(request)
|
||||
return super(PermissionAdmin, self).queryset(request).filter(creator=user)
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
# Django 1.5 compatibility utilities, providing support for custom User models.
|
||||
# Since get_user_model() causes a circular import if called when app models are
|
||||
# being loaded, the user_model_label should be used when possible, with calls
|
||||
# to get_user_model deferred to execution time
|
||||
user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
|
||||
|
||||
try:
|
||||
from django.contrib.auth import get_user_model
|
||||
except ImportError:
|
||||
from django.contrib.auth.models import User
|
||||
get_user_model = lambda: User
|
||||
|
|
@ -2,25 +2,34 @@ 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, get_model
|
||||
from django.db.models import Model
|
||||
from django.apps import apps
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
|
||||
from authority import get_check
|
||||
from authority.utils 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):
|
||||
|
|
@ -34,16 +43,19 @@ def permission_required(perm, *lookup_variables, **kwargs):
|
|||
if value is None:
|
||||
continue
|
||||
if isinstance(model, basestring):
|
||||
model_class = get_model(*model.split("."))
|
||||
model_class = apps.get_model(*model.split("."))
|
||||
else:
|
||||
model_class = model
|
||||
if model_class is None:
|
||||
raise ValueError(
|
||||
"The given argument '%s' is not a valid model." % model)
|
||||
if (inspect.isclass(model_class) and
|
||||
not issubclass(model_class, Model)):
|
||||
"The given argument '%s' is not a valid model." % model
|
||||
)
|
||||
if inspect.isclass(model_class) and not issubclass(
|
||||
model_class, Model
|
||||
):
|
||||
raise ValueError(
|
||||
'The argument %s needs to be a model.' % model)
|
||||
"The argument %s needs to be a model." % model
|
||||
)
|
||||
obj = get_object_or_404(model_class, **{lookup: value})
|
||||
params.append(obj)
|
||||
check = get_check(request.user, perm)
|
||||
|
|
@ -55,15 +67,18 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
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?"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from authority import permissions, get_choices_for
|
||||
from authority import permissions
|
||||
from authority.utils import get_choices_for
|
||||
from authority.models import Permission
|
||||
from authority.compat import get_user_model
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
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):
|
||||
|
|
@ -39,14 +40,14 @@ class BasePermissionForm(forms.ModelForm):
|
|||
|
||||
|
||||
class UserPermissionForm(BasePermissionForm):
|
||||
user = forms.CharField(label=_('User'))
|
||||
user = forms.CharField(label=_("User"))
|
||||
|
||||
class Meta(BasePermissionForm.Meta):
|
||||
fields = ('user',)
|
||||
fields = ("user",)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not kwargs.get('approved', False):
|
||||
self.base_fields['user'].widget = forms.HiddenInput()
|
||||
if not kwargs.get("approved", False):
|
||||
self.base_fields["user"].widget = forms.HiddenInput()
|
||||
super(UserPermissionForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_user(self):
|
||||
|
|
@ -55,34 +56,41 @@ class UserPermissionForm(BasePermissionForm):
|
|||
user = User.objects.get(username__iexact=username)
|
||||
except User.DoesNotExist:
|
||||
raise forms.ValidationError(
|
||||
mark_safe(_("A user with that username does not exist.")))
|
||||
mark_safe(_("A user with that username does not exist."))
|
||||
)
|
||||
check = permissions.BasePermission(user=user)
|
||||
error_msg = None
|
||||
if user.is_superuser:
|
||||
error_msg = _("The user %(user)s do not need to request "
|
||||
"access to any permission as it is a super user.")
|
||||
error_msg = _(
|
||||
"The user %(user)s do not need to request "
|
||||
"access to any permission as it is a super user."
|
||||
)
|
||||
elif check.has_perm(self.perm, self.obj):
|
||||
error_msg = _("The user %(user)s already has the permission "
|
||||
"'%(perm)s' for %(object_name)s '%(obj)s'")
|
||||
error_msg = _(
|
||||
"The user %(user)s already has the permission "
|
||||
"'%(perm)s' for %(object_name)s '%(obj)s'"
|
||||
)
|
||||
elif check.requested_perm(self.perm, self.obj):
|
||||
error_msg = _("The user %(user)s already requested the permission"
|
||||
" '%(perm)s' for %(object_name)s '%(obj)s'")
|
||||
error_msg = _(
|
||||
"The user %(user)s already requested the permission"
|
||||
" '%(perm)s' for %(object_name)s '%(obj)s'"
|
||||
)
|
||||
if error_msg:
|
||||
error_msg = error_msg % {
|
||||
'object_name': self.obj._meta.object_name.lower(),
|
||||
'perm': self.perm,
|
||||
'obj': self.obj,
|
||||
'user': user,
|
||||
"object_name": self.obj._meta.object_name.lower(),
|
||||
"perm": self.perm,
|
||||
"obj": self.obj,
|
||||
"user": user,
|
||||
}
|
||||
raise forms.ValidationError(mark_safe(error_msg))
|
||||
return user
|
||||
|
||||
|
||||
class GroupPermissionForm(BasePermissionForm):
|
||||
group = forms.CharField(label=_('Group'))
|
||||
group = forms.CharField(label=_("Group"))
|
||||
|
||||
class Meta(BasePermissionForm.Meta):
|
||||
fields = ('group',)
|
||||
fields = ("group",)
|
||||
|
||||
def clean_group(self):
|
||||
groupname = self.cleaned_data["group"]
|
||||
|
|
@ -90,13 +98,21 @@ class GroupPermissionForm(BasePermissionForm):
|
|||
group = Group.objects.get(name__iexact=groupname)
|
||||
except Group.DoesNotExist:
|
||||
raise forms.ValidationError(
|
||||
mark_safe(_("A group with that name does not exist.")))
|
||||
mark_safe(_("A group with that name does not exist."))
|
||||
)
|
||||
check = permissions.BasePermission(group=group)
|
||||
if check.has_perm(self.perm, self.obj):
|
||||
raise forms.ValidationError(mark_safe(
|
||||
_("This group already has the permission '%(perm)s' for %(object_name)s '%(obj)s'") % {
|
||||
'perm': self.perm,
|
||||
'object_name': self.obj._meta.object_name.lower(),
|
||||
'obj': self.obj,
|
||||
}))
|
||||
raise forms.ValidationError(
|
||||
mark_safe(
|
||||
_(
|
||||
"This group already has the permission '%(perm)s' "
|
||||
"for %(object_name)s '%(obj)s'"
|
||||
)
|
||||
% {
|
||||
"perm": self.perm,
|
||||
"object_name": self.obj._meta.object_name.lower(),
|
||||
"obj": self.obj,
|
||||
}
|
||||
)
|
||||
)
|
||||
return group
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.contrib.contenttypes.models import ContentType
|
|||
|
||||
|
||||
class PermissionManager(models.Manager):
|
||||
|
||||
def get_content_type(self, obj):
|
||||
return ContentType.objects.get_for_model(obj)
|
||||
|
||||
|
|
@ -12,46 +11,41 @@ class PermissionManager(models.Manager):
|
|||
return self.filter(content_type=self.get_content_type(obj))
|
||||
|
||||
def for_object(self, obj, approved=True):
|
||||
return self.get_for_model(obj).select_related(
|
||||
'user', 'creator', 'group', 'content_type'
|
||||
).filter(object_id=obj.id, approved=approved)
|
||||
return (
|
||||
self.get_for_model(obj)
|
||||
.select_related("user", "creator", "group", "content_type")
|
||||
.filter(object_id=obj.id, approved=approved)
|
||||
)
|
||||
|
||||
def for_user(self, user, obj, check_groups=True):
|
||||
perms = self.get_for_model(obj)
|
||||
if not check_groups:
|
||||
return perms.select_related('user', 'creator').filter(user=user)
|
||||
return perms.select_related("user", "creator").filter(user=user)
|
||||
|
||||
# Hacking user to user__pk to workaround deepcopy bug:
|
||||
# http://bugs.python.org/issue2460
|
||||
# Which is triggered by django's deepcopy which backports that fix in
|
||||
# Django 1.2
|
||||
return perms.select_related(
|
||||
'user',
|
||||
'creator'
|
||||
).prefetch_related(
|
||||
'user__groups'
|
||||
).filter(
|
||||
Q(user__pk=user.pk) | Q(group__in=user.groups.all())
|
||||
return (
|
||||
perms.select_related("user", "creator")
|
||||
.prefetch_related("user__groups")
|
||||
.filter(Q(user__pk=user.pk) | Q(group__in=user.groups.all()))
|
||||
)
|
||||
|
||||
def user_permissions(
|
||||
self, user, perm, obj, approved=True, check_groups=True):
|
||||
return self.for_user(
|
||||
user,
|
||||
obj,
|
||||
check_groups,
|
||||
).filter(
|
||||
codename=perm,
|
||||
approved=approved,
|
||||
def user_permissions(self, user, perm, obj, approved=True, check_groups=True):
|
||||
return self.for_user(user, obj, check_groups,).filter(
|
||||
codename=perm, approved=approved,
|
||||
)
|
||||
|
||||
def group_permissions(self, group, perm, obj, approved=True):
|
||||
"""
|
||||
Get objects that have Group perm permission on
|
||||
"""
|
||||
return self.get_for_model(obj).select_related(
|
||||
'user', 'group', 'creator').filter(group=group, codename=perm,
|
||||
approved=approved)
|
||||
return (
|
||||
self.get_for_model(obj)
|
||||
.select_related("user", "group", "creator")
|
||||
.filter(group=group, codename=perm, approved=approved)
|
||||
)
|
||||
|
||||
def delete_objects_permissions(self, obj):
|
||||
"""
|
||||
|
|
|
|||
104
authority/migrations/0001_initial.py
Normal file
104
authority/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# -*- 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")]
|
||||
),
|
||||
),
|
||||
]
|
||||
0
authority/migrations/__init__.py
Normal file
0
authority/migrations/__init__.py
Normal file
|
|
@ -1,13 +1,15 @@
|
|||
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 import generic
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from authority.compat import user_model_label
|
||||
from authority.managers import PermissionManager
|
||||
|
||||
USER_MODEL = getattr(settings, "AUTH_USER_MODEL", "auth.User")
|
||||
|
||||
|
||||
class Permission(models.Model):
|
||||
"""
|
||||
|
|
@ -15,19 +17,41 @@ class Permission(models.Model):
|
|||
This kind of permission is associated with a user/group and an object
|
||||
of any content type.
|
||||
"""
|
||||
codename = models.CharField(_('codename'), max_length=100)
|
||||
content_type = models.ForeignKey(ContentType, related_name="row_permissions")
|
||||
|
||||
codename = models.CharField(_("codename"), max_length=100)
|
||||
content_type = models.ForeignKey(
|
||||
ContentType, related_name="row_permissions", on_delete=models.CASCADE
|
||||
)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
user = models.ForeignKey(user_model_label, null=True, blank=True, related_name='granted_permissions')
|
||||
group = models.ForeignKey(Group, null=True, blank=True)
|
||||
creator = models.ForeignKey(user_model_label, null=True, blank=True, related_name='created_permissions')
|
||||
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,
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
|
|
@ -36,12 +60,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):
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ class PermissionMetaclass(type):
|
|||
Used to generate the default set of permission checks "add", "change" and
|
||||
"delete".
|
||||
"""
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
new_class = super(
|
||||
PermissionMetaclass, cls).__new__(cls, name, bases, attrs)
|
||||
new_class = super(PermissionMetaclass, cls).__new__(cls, name, bases, attrs)
|
||||
if not new_class.label:
|
||||
new_class.label = "%s_permission" % new_class.__name__.lower()
|
||||
new_class.label = slugify(new_class.label)
|
||||
|
|
@ -31,11 +31,12 @@ class BasePermission(object):
|
|||
"""
|
||||
Base Permission class to be used to define app permissions.
|
||||
"""
|
||||
|
||||
__metaclass__ = PermissionMetaclass
|
||||
|
||||
checks = ()
|
||||
label = None
|
||||
generic_checks = ['add', 'browse', 'change', 'delete']
|
||||
generic_checks = ["add", "browse", "change", "delete"]
|
||||
|
||||
def __init__(self, user=None, group=None, *args, **kwargs):
|
||||
self.user = user
|
||||
|
|
@ -48,10 +49,7 @@ class BasePermission(object):
|
|||
"""
|
||||
if not self.user:
|
||||
return {}, {}
|
||||
group_pks = set(self.user.groups.values_list(
|
||||
'pk',
|
||||
flat=True,
|
||||
))
|
||||
group_pks = set(self.user.groups.values_list("pk", flat=True,))
|
||||
perms = Permission.objects.filter(
|
||||
Q(user__pk=self.user.pk) | Q(group__pk__in=group_pks),
|
||||
)
|
||||
|
|
@ -59,22 +57,26 @@ class BasePermission(object):
|
|||
group_permissions = {}
|
||||
for perm in perms:
|
||||
if perm.user_id == self.user.pk:
|
||||
user_permissions[(
|
||||
perm.object_id,
|
||||
perm.content_type_id,
|
||||
perm.codename,
|
||||
perm.approved,
|
||||
)] = True
|
||||
user_permissions[
|
||||
(
|
||||
perm.object_id,
|
||||
perm.content_type_id,
|
||||
perm.codename,
|
||||
perm.approved,
|
||||
)
|
||||
] = True
|
||||
# If the user has the permission do for something, but perm.user !=
|
||||
# self.user then by definition that permission came from the
|
||||
# group.
|
||||
else:
|
||||
group_permissions[(
|
||||
perm.object_id,
|
||||
perm.content_type_id,
|
||||
perm.codename,
|
||||
perm.approved,
|
||||
)] = True
|
||||
group_permissions[
|
||||
(
|
||||
perm.object_id,
|
||||
perm.content_type_id,
|
||||
perm.codename,
|
||||
perm.approved,
|
||||
)
|
||||
] = True
|
||||
return user_permissions, group_permissions
|
||||
|
||||
def _get_group_cached_perms(self):
|
||||
|
|
@ -83,17 +85,12 @@ class BasePermission(object):
|
|||
"""
|
||||
if not self.group:
|
||||
return {}
|
||||
perms = Permission.objects.filter(
|
||||
group=self.group,
|
||||
)
|
||||
perms = Permission.objects.filter(group=self.group,)
|
||||
group_permissions = {}
|
||||
for perm in perms:
|
||||
group_permissions[(
|
||||
perm.object_id,
|
||||
perm.content_type_id,
|
||||
perm.codename,
|
||||
perm.approved,
|
||||
)] = True
|
||||
group_permissions[
|
||||
(perm.object_id, perm.content_type_id, perm.codename, perm.approved,)
|
||||
] = True
|
||||
return group_permissions
|
||||
|
||||
def _prime_user_perm_caches(self):
|
||||
|
|
@ -123,11 +120,7 @@ class BasePermission(object):
|
|||
# Check to see if the cache has been primed.
|
||||
if not self.user:
|
||||
return {}
|
||||
cache_filled = getattr(
|
||||
self.user,
|
||||
'_authority_perm_cache_filled',
|
||||
False,
|
||||
)
|
||||
cache_filled = getattr(self.user, "_authority_perm_cache_filled", False,)
|
||||
if cache_filled:
|
||||
# Don't really like the name for this, but this matches how Django
|
||||
# does it.
|
||||
|
|
@ -145,11 +138,7 @@ class BasePermission(object):
|
|||
# Check to see if the cache has been primed.
|
||||
if not self.group:
|
||||
return {}
|
||||
cache_filled = getattr(
|
||||
self.group,
|
||||
'_authority_perm_cache_filled',
|
||||
False,
|
||||
)
|
||||
cache_filled = getattr(self.group, "_authority_perm_cache_filled", False,)
|
||||
if cache_filled:
|
||||
# Don't really like the name for this, but this matches how Django
|
||||
# does it.
|
||||
|
|
@ -167,11 +156,7 @@ class BasePermission(object):
|
|||
# Check to see if the cache has been primed.
|
||||
if not self.user:
|
||||
return {}
|
||||
cache_filled = getattr(
|
||||
self.user,
|
||||
'_authority_perm_cache_filled',
|
||||
False,
|
||||
)
|
||||
cache_filled = getattr(self.user, "_authority_perm_cache_filled", False,)
|
||||
if cache_filled:
|
||||
return self.user._authority_group_perm_cache
|
||||
|
||||
|
|
@ -194,7 +179,7 @@ class BasePermission(object):
|
|||
|
||||
@property
|
||||
def use_smart_cache(self):
|
||||
use_smart_cache = getattr(settings, 'AUTHORITY_USE_SMART_CACHE', True)
|
||||
use_smart_cache = getattr(settings, "AUTHORITY_USE_SMART_CACHE", True)
|
||||
return (self.user or self.group) and use_smart_cache
|
||||
|
||||
def has_user_perms(self, perm, obj, approved, check_groups=True):
|
||||
|
|
@ -210,12 +195,7 @@ class BasePermission(object):
|
|||
|
||||
def _user_has_perms(cached_perms):
|
||||
# Check to see if the permission is in the cache.
|
||||
return cached_perms.get((
|
||||
obj.pk,
|
||||
content_type_pk,
|
||||
perm,
|
||||
approved,
|
||||
))
|
||||
return cached_perms.get((obj.pk, content_type_pk, perm, approved,))
|
||||
|
||||
# Check to see if the permission is in the cache.
|
||||
if _user_has_perms(self._user_perm_cache):
|
||||
|
|
@ -227,15 +207,13 @@ class BasePermission(object):
|
|||
return False
|
||||
|
||||
# Actually hit the DB, no smart cache used.
|
||||
return Permission.objects.user_permissions(
|
||||
self.user,
|
||||
perm,
|
||||
obj,
|
||||
approved,
|
||||
check_groups,
|
||||
).filter(
|
||||
object_id=obj.pk,
|
||||
).exists()
|
||||
return (
|
||||
Permission.objects.user_permissions(
|
||||
self.user, perm, obj, approved, check_groups,
|
||||
)
|
||||
.filter(object_id=obj.pk,)
|
||||
.exists()
|
||||
)
|
||||
|
||||
def has_group_perms(self, perm, obj, approved):
|
||||
"""
|
||||
|
|
@ -249,24 +227,17 @@ class BasePermission(object):
|
|||
|
||||
def _group_has_perms(cached_perms):
|
||||
# Check to see if the permission is in the cache.
|
||||
return cached_perms.get((
|
||||
obj.pk,
|
||||
content_type_pk,
|
||||
perm,
|
||||
approved,
|
||||
))
|
||||
return cached_perms.get((obj.pk, content_type_pk, perm, approved,))
|
||||
|
||||
# Check to see if the permission is in the cache.
|
||||
return _group_has_perms(self._group_perm_cache)
|
||||
|
||||
# Actually hit the DB, no smart cache used.
|
||||
return Permission.objects.group_permissions(
|
||||
self.group,
|
||||
perm, obj,
|
||||
approved,
|
||||
).filter(
|
||||
object_id=obj.pk,
|
||||
).exists()
|
||||
return (
|
||||
Permission.objects.group_permissions(self.group, perm, obj, approved,)
|
||||
.filter(object_id=obj.pk,)
|
||||
.exists()
|
||||
)
|
||||
|
||||
def has_perm(self, perm, obj, check_groups=True, approved=True):
|
||||
"""
|
||||
|
|
@ -305,25 +276,20 @@ class BasePermission(object):
|
|||
return perms
|
||||
|
||||
def get_django_codename(
|
||||
self, check, model_or_instance, generic=False, without_left=False):
|
||||
self, check, model_or_instance, generic=False, without_left=False
|
||||
):
|
||||
if without_left:
|
||||
perm = check
|
||||
else:
|
||||
perm = '%s.%s' % (model_or_instance._meta.app_label, check.lower())
|
||||
perm = "%s.%s" % (model_or_instance._meta.app_label, check.lower())
|
||||
if generic:
|
||||
perm = '%s_%s' % (
|
||||
perm,
|
||||
model_or_instance._meta.object_name.lower(),
|
||||
)
|
||||
perm = "%s_%s" % (perm, model_or_instance._meta.object_name.lower(),)
|
||||
return perm
|
||||
|
||||
def get_codename(self, check, model_or_instance, generic=False):
|
||||
perm = '%s.%s' % (self.label, check.lower())
|
||||
perm = "%s.%s" % (self.label, check.lower())
|
||||
if generic:
|
||||
perm = '%s_%s' % (
|
||||
perm,
|
||||
model_or_instance._meta.object_name.lower(),
|
||||
)
|
||||
perm = "%s_%s" % (perm, model_or_instance._meta.object_name.lower(),)
|
||||
return perm
|
||||
|
||||
def assign(self, check=None, content_object=None, generic=False):
|
||||
|
|
@ -345,7 +311,7 @@ class BasePermission(object):
|
|||
content_objects = content_object
|
||||
|
||||
if not check:
|
||||
checks = self.generic_checks + getattr(self, 'checks', [])
|
||||
checks = self.generic_checks + getattr(self, "checks", [])
|
||||
elif not isinstance(check, (list, tuple)):
|
||||
checks = (check,)
|
||||
else:
|
||||
|
|
@ -364,14 +330,11 @@ 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,
|
||||
|
|
@ -380,6 +343,7 @@ class BasePermission(object):
|
|||
except Permission.DoesNotExist:
|
||||
perm = Permission.objects.create(
|
||||
user=self.user,
|
||||
group=self.group,
|
||||
content_object=content_object,
|
||||
codename=codename,
|
||||
approved=True,
|
||||
|
|
@ -390,21 +354,16 @@ class BasePermission(object):
|
|||
elif isinstance(content_object, ModelBase):
|
||||
# make a Django permission
|
||||
codename = self.get_django_codename(
|
||||
check,
|
||||
content_object,
|
||||
generic,
|
||||
without_left=True,
|
||||
check, content_object, generic, without_left=True,
|
||||
)
|
||||
try:
|
||||
perm = DjangoPermission.objects.get(codename=codename)
|
||||
except DjangoPermission.DoesNotExist:
|
||||
name = check
|
||||
if '_' in name:
|
||||
name = name[0:name.find('_')]
|
||||
if "_" in name:
|
||||
name = name[0 : name.find("_")]
|
||||
perm = DjangoPermission(
|
||||
name=name,
|
||||
codename=codename,
|
||||
content_type=content_type,
|
||||
name=name, codename=codename, content_type=content_type,
|
||||
)
|
||||
perm.save()
|
||||
self.user.user_permissions.add(perm)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
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 _
|
||||
|
|
@ -6,16 +7,20 @@ 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 = {}
|
||||
|
||||
|
|
@ -29,7 +34,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
|
||||
|
|
@ -49,8 +54,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
|
||||
|
|
@ -64,17 +69,23 @@ class PermissionSite(object):
|
|||
|
||||
if permission_class.label in self.get_labels():
|
||||
raise ImproperlyConfigured(
|
||||
"The name of %s conflicts with %s" % (permission_class,
|
||||
self.get_permission_by_label(permission_class.label)))
|
||||
"The name of %s conflicts with %s"
|
||||
% (
|
||||
permission_class,
|
||||
self.get_permission_by_label(permission_class.label),
|
||||
)
|
||||
)
|
||||
|
||||
for model in model_or_iterable:
|
||||
if model in self._registry:
|
||||
raise AlreadyRegistered(
|
||||
'The model %s is already registered' % model.__name__)
|
||||
"The model %s is already registered" % model.__name__
|
||||
)
|
||||
if options:
|
||||
options['__module__'] = __name__
|
||||
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)
|
||||
|
|
@ -85,7 +96,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):
|
||||
|
|
@ -94,10 +105,12 @@ 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)
|
||||
|
|
@ -106,11 +119,12 @@ class PermissionSite(object):
|
|||
object_name = model._meta.object_name
|
||||
func_name = "%s_%s" % (check_name, object_name.lower())
|
||||
func.short_description = _("Can %(check)s this %(object_name)s") % {
|
||||
'object_name': model._meta.object_name.lower(),
|
||||
'check': check_name}
|
||||
"object_name": model._meta.object_name.lower(),
|
||||
"check": check_name,
|
||||
}
|
||||
func.check_name = check_name
|
||||
if func_name not in permission.checks:
|
||||
permission.checks = (list(permission.checks) + [func_name])
|
||||
permission.checks = list(permission.checks) + [func_name]
|
||||
setattr(permission, func_name, func)
|
||||
setattr(model, "permissions", PermissionDescriptor())
|
||||
|
||||
|
|
@ -120,15 +134,19 @@ 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 = models.get_model("contenttypes", "contenttype")
|
||||
ContentType = apps.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:
|
||||
|
|
@ -136,6 +154,7 @@ class PermissionDescriptor(object):
|
|||
ct = self.get_content_type(instance)
|
||||
return ct.row_permissions.all()
|
||||
|
||||
|
||||
site = PermissionSite()
|
||||
get_check = site.get_check
|
||||
get_choices_for = site.get_choices_for
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@
|
|||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}"/>{% endblock %}
|
||||
|
||||
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
|
||||
{% block coltype %}colM{% 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> ›
|
||||
<a href="../">{{ app_label|capfirst|escape }}</a> ›
|
||||
<a href="../">{{ app_label|capfirst|escape }}</a> ›
|
||||
<a href="./">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
{% trans "Permissions" %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from django import template
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
|
||||
from authority import get_check
|
||||
from authority.utils import get_check
|
||||
from authority import permissions
|
||||
from authority.compat import get_user_model
|
||||
from authority.models import Permission
|
||||
from authority.forms import UserPermissionForm
|
||||
|
||||
|
|
@ -16,27 +16,31 @@ register = template.Library()
|
|||
|
||||
@register.simple_tag
|
||||
def url_for_obj(view_name, obj):
|
||||
return reverse(view_name, kwargs={
|
||||
'app_label': obj._meta.app_label,
|
||||
'module_name': obj._meta.module_name,
|
||||
'pk': obj.pk}
|
||||
return reverse(
|
||||
view_name,
|
||||
kwargs={
|
||||
"app_label": obj._meta.app_label,
|
||||
"module_name": obj._meta.module_name,
|
||||
"pk": obj.pk,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def add_url_for_obj(obj):
|
||||
return url_for_obj('authority-add-permission', obj)
|
||||
return url_for_obj("authority-add-permission", obj)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def request_url_for_obj(obj):
|
||||
return url_for_obj('authority-add-permission-request', obj)
|
||||
return url_for_obj("authority-add-permission-request", obj)
|
||||
|
||||
|
||||
class ResolverNode(template.Node):
|
||||
"""
|
||||
A small wrapper that adds a convenient resolve method.
|
||||
"""
|
||||
|
||||
def resolve(self, var, context):
|
||||
"""Resolves a variable out of context if it's not in quotes"""
|
||||
if var is None:
|
||||
|
|
@ -49,7 +53,7 @@ class ResolverNode(template.Node):
|
|||
@classmethod
|
||||
def next_bit_for(cls, bits, key, if_none=None):
|
||||
try:
|
||||
return bits[bits.index(key)+1]
|
||||
return bits[bits.index(key) + 1]
|
||||
except ValueError:
|
||||
return if_none
|
||||
|
||||
|
|
@ -58,18 +62,18 @@ class PermissionComparisonNode(ResolverNode):
|
|||
"""
|
||||
Implements a node to provide an "if user/group has permission on object"
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def handle_token(cls, parser, token):
|
||||
bits = token.contents.split()
|
||||
if 5 < len(bits) < 3:
|
||||
raise template.TemplateSyntaxError(
|
||||
"'%s' tag takes three, "
|
||||
"four or five arguments" % bits[0]
|
||||
"'%s' tag takes three, " "four or five arguments" % bits[0]
|
||||
)
|
||||
end_tag = 'endifhasperm'
|
||||
nodelist_true = parser.parse(('else', end_tag))
|
||||
end_tag = "endifhasperm"
|
||||
nodelist_true = parser.parse(("else", end_tag))
|
||||
token = parser.next_token()
|
||||
if token.contents == 'else': # there is an 'else' clause in the tag
|
||||
if token.contents == "else": # there is an 'else' clause in the tag
|
||||
nodelist_false = parser.parse((end_tag,))
|
||||
parser.delete_first_token()
|
||||
else:
|
||||
|
|
@ -107,13 +111,13 @@ class PermissionComparisonNode(ResolverNode):
|
|||
return self.nodelist_true.render(context)
|
||||
# If the app couldn't be found
|
||||
except (ImproperlyConfigured, ImportError):
|
||||
return ''
|
||||
return ""
|
||||
# If either variable fails to resolve, return nothing.
|
||||
except template.VariableDoesNotExist:
|
||||
return ''
|
||||
return ""
|
||||
# If the types don't permit comparison, return nothing.
|
||||
except (TypeError, AttributeError):
|
||||
return ''
|
||||
return ""
|
||||
return self.nodelist_false.render(context)
|
||||
|
||||
|
||||
|
|
@ -141,16 +145,14 @@ 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)
|
||||
|
||||
|
|
@ -164,33 +166,40 @@ class PermissionFormNode(ResolverNode):
|
|||
obj = self.resolve(self.obj, context)
|
||||
perm = self.resolve(self.perm, context)
|
||||
if self.template_name:
|
||||
template_name = [self.resolve(obj, context) for obj in self.template_name.split(',')]
|
||||
template_name = [
|
||||
self.resolve(o, context) for o in self.template_name.split(",")
|
||||
]
|
||||
else:
|
||||
template_name = 'authority/permission_form.html'
|
||||
request = context['request']
|
||||
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,
|
||||
context_instance=template.RequestContext(request))
|
||||
return template.loader.render_to_string(template_name, extra_context, request)
|
||||
|
||||
|
||||
@register.tag
|
||||
def permission_form(parser, token):
|
||||
|
|
@ -206,6 +215,7 @@ def permission_form(parser, token):
|
|||
"""
|
||||
return PermissionFormNode.handle_token(parser, token, approved=True)
|
||||
|
||||
|
||||
@register.tag
|
||||
def permission_request_form(parser, token):
|
||||
"""
|
||||
|
|
@ -215,23 +225,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)
|
||||
|
||||
|
|
@ -252,7 +262,8 @@ 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):
|
||||
|
|
@ -271,8 +282,10 @@ 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):
|
||||
|
|
@ -291,22 +304,22 @@ def get_permission_requests(parser, token):
|
|||
{% get_permission_requests obj for request.user as "my_permissions" %}
|
||||
|
||||
"""
|
||||
return PermissionsForObjectNode.handle_token(parser, token,
|
||||
approved=False,
|
||||
name='"permission_requests"')
|
||||
return PermissionsForObjectNode.handle_token(
|
||||
parser, token, approved=False, name='"permission_requests"'
|
||||
)
|
||||
|
||||
|
||||
class PermissionForObjectNode(ResolverNode):
|
||||
|
||||
@classmethod
|
||||
def handle_token(cls, parser, token, approved, name):
|
||||
bits = token.contents.split()
|
||||
tag_name = bits[0]
|
||||
kwargs = {
|
||||
'perm': cls.next_bit_for(bits, tag_name),
|
||||
'user': cls.next_bit_for(bits, 'for'),
|
||||
'objs': cls.next_bit_for(bits, 'and'),
|
||||
'var_name': cls.next_bit_for(bits, 'as', name),
|
||||
'approved': approved,
|
||||
"perm": cls.next_bit_for(bits, tag_name),
|
||||
"user": cls.next_bit_for(bits, "for"),
|
||||
"objs": cls.next_bit_for(bits, "and"),
|
||||
"var_name": cls.next_bit_for(bits, "as", name),
|
||||
"approved": approved,
|
||||
}
|
||||
return cls(**kwargs)
|
||||
|
||||
|
|
@ -318,7 +331,7 @@ class PermissionForObjectNode(ResolverNode):
|
|||
self.approved = approved
|
||||
|
||||
def render(self, context):
|
||||
objs = [self.resolve(obj, context) for obj in self.objs.split(',')]
|
||||
objs = [self.resolve(obj, context) for obj in self.objs.split(",")]
|
||||
var_name = self.resolve(self.var_name, context)
|
||||
perm = self.resolve(self.perm, context)
|
||||
user = self.resolve(self.user, context)
|
||||
|
|
@ -335,7 +348,8 @@ class PermissionForObjectNode(ResolverNode):
|
|||
if granted:
|
||||
break
|
||||
context[var_name] = granted
|
||||
return ''
|
||||
return ""
|
||||
|
||||
|
||||
@register.tag
|
||||
def get_permission(parser, token):
|
||||
|
|
@ -347,8 +361,10 @@ def get_permission(parser, token):
|
|||
|
||||
{% get_permission PERMISSION_LABEL.CHECK_NAME for USER and *OBJS [as VARNAME] %}
|
||||
|
||||
{% get_permission "poll_permission.change_poll" for request.user and poll as "is_allowed" %}
|
||||
{% get_permission "poll_permission.change_poll" for request.user and poll,second_poll as "is_allowed" %}
|
||||
{% get_permission "poll_permission.change_poll"
|
||||
for request.user and poll as "is_allowed" %}
|
||||
{% get_permission "poll_permission.change_poll"
|
||||
for request.user and poll,second_poll as "is_allowed" %}
|
||||
|
||||
{% if is_allowed %}
|
||||
I've got ze power to change ze pollllllzzz. Muahahaa.
|
||||
|
|
@ -357,9 +373,10 @@ 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):
|
||||
|
|
@ -371,8 +388,10 @@ def get_permission_request(parser, token):
|
|||
|
||||
{% get_permission_request PERMISSION_LABEL.CHECK_NAME for USER and *OBJS [as VARNAME] %}
|
||||
|
||||
{% get_permission_request "poll_permission.change_poll" for request.user and poll as "asked_for_permissio" %}
|
||||
{% get_permission_request "poll_permission.change_poll" for request.user and poll,second_poll as "asked_for_permissio" %}
|
||||
{% get_permission_request "poll_permission.change_poll"
|
||||
for request.user and poll as "asked_for_permissio" %}
|
||||
{% get_permission_request "poll_permission.change_poll"
|
||||
for request.user and poll,second_poll as "asked_for_permissio" %}
|
||||
|
||||
{% if asked_for_permissio %}
|
||||
Dude, you already asked for permission!
|
||||
|
|
@ -381,59 +400,67 @@ 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.
|
||||
Returns no content if the request-user has no permission to delete foreign
|
||||
permissions.
|
||||
"""
|
||||
user = context['request'].user
|
||||
if user.is_authenticated():
|
||||
link_kwargs = base_link(context, perm,
|
||||
'authority-delete-permission-request')
|
||||
if user.has_perm('authority.delete_permission'):
|
||||
link_kwargs['is_requestor'] = False
|
||||
user = context["request"].user
|
||||
if user.is_authenticated:
|
||||
link_kwargs = base_link(context, perm, "authority-delete-permission-request")
|
||||
if user.has_perm("authority.delete_permission"):
|
||||
link_kwargs["is_requestor"] = False
|
||||
return link_kwargs
|
||||
if not perm.approved and perm.user == user:
|
||||
link_kwargs['is_requestor'] = True
|
||||
link_kwargs["is_requestor"] = True
|
||||
return link_kwargs
|
||||
return {'url': None}
|
||||
return {"url": None}
|
||||
|
||||
@register.inclusion_tag('authority/permission_request_approve_link.html', takes_context=True)
|
||||
|
||||
@register.inclusion_tag(
|
||||
"authority/permission_request_approve_link.html", takes_context=True
|
||||
)
|
||||
def permission_request_approve_link(context, perm):
|
||||
"""
|
||||
Renders a html link to the approve view of the given permission request.
|
||||
Returns no content if the request-user has no permission to delete foreign
|
||||
permissions.
|
||||
"""
|
||||
user = context['request'].user
|
||||
if user.is_authenticated():
|
||||
if user.has_perm('authority.approve_permission_requests'):
|
||||
return base_link(context, perm,
|
||||
'authority-approve-permission-request')
|
||||
return {'url': None}
|
||||
user = context["request"].user
|
||||
if user.is_authenticated:
|
||||
if user.has_perm("authority.approve_permission_requests"):
|
||||
return base_link(context, perm, "authority-approve-permission-request")
|
||||
return {"url": None}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,40 @@
|
|||
from django import VERSION
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission as DjangoPermission
|
||||
from django.contrib.auth.models import Group
|
||||
from django.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.db.models import Q
|
||||
from django.test import TestCase
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
|
||||
import authority
|
||||
from authority import permissions
|
||||
from authority.models import Permission
|
||||
from authority.exceptions import NotAModel, UnsavedModelInstance
|
||||
from authority.compat import get_user_model
|
||||
|
||||
# Load the form
|
||||
from authority.forms import UserPermissionForm # noqa
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
if VERSION >= (1, 5):
|
||||
FIXTURES = ['tests_custom.json']
|
||||
QUERY = Q(email="jezdez@github.com")
|
||||
else:
|
||||
FIXTURES = ['tests.json']
|
||||
QUERY = Q(username="jezdez")
|
||||
FIXTURES = ["tests_custom.json"]
|
||||
QUERY = Q(email="jezdez@github.com")
|
||||
|
||||
|
||||
class UserPermission(permissions.BasePermission):
|
||||
checks = ('browse',)
|
||||
label = 'user_permission'
|
||||
authority.register(User, UserPermission)
|
||||
checks = ("browse",)
|
||||
label = "user_permission"
|
||||
|
||||
|
||||
authority.utils.register(User, UserPermission)
|
||||
|
||||
|
||||
class GroupPermission(permissions.BasePermission):
|
||||
checks = ('browse',)
|
||||
label = 'group_permission'
|
||||
authority.register(Group, GroupPermission)
|
||||
checks = ("browse",)
|
||||
label = "group_permission"
|
||||
|
||||
|
||||
authority.utils.register(Group, GroupPermission)
|
||||
|
||||
|
||||
class DjangoPermissionChecksTestCase(TestCase):
|
||||
|
|
@ -46,6 +49,7 @@ class DjangoPermissionChecksTestCase(TestCase):
|
|||
This permissions are given in the test case and not in the fixture, for
|
||||
later reference.
|
||||
"""
|
||||
|
||||
fixtures = FIXTURES
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -59,7 +63,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
|
||||
|
|
@ -69,8 +73,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()
|
||||
|
||||
|
|
@ -86,24 +90,61 @@ 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_delete(self):
|
||||
result = self.check.assign(
|
||||
content_object=self.user,
|
||||
check='delete_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",)
|
||||
|
||||
self.assertTrue(isinstance(result[0], Permission))
|
||||
self.assertFalse(self.check.delete_user())
|
||||
self.assertTrue(self.check.delete_user(self.user))
|
||||
|
|
@ -123,6 +164,7 @@ class GenericAssignBehaviourTest(TestCase):
|
|||
- permission add (test_add),
|
||||
- permission delete for him (test_delete),
|
||||
"""
|
||||
|
||||
fixtures = FIXTURES
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -130,16 +172,14 @@ class GenericAssignBehaviourTest(TestCase):
|
|||
self.check = UserPermission(self.user)
|
||||
|
||||
def test_add(self):
|
||||
result = self.check.assign(check='add', generic=True)
|
||||
result = self.check.assign(check="add", generic=True)
|
||||
|
||||
self.assertTrue(isinstance(result[0], DjangoPermission))
|
||||
self.assertTrue(self.check.add_user())
|
||||
|
||||
def test_delete(self):
|
||||
result = self.check.assign(
|
||||
content_object=self.user,
|
||||
check='delete',
|
||||
generic=True,
|
||||
content_object=self.user, check="delete", generic=True,
|
||||
)
|
||||
|
||||
self.assertTrue(isinstance(result[0], Permission))
|
||||
|
|
@ -152,6 +192,7 @@ class AssignExceptionsTest(TestCase):
|
|||
Tests that exceptions are thrown if assign() was called with inconsistent
|
||||
arguments.
|
||||
"""
|
||||
|
||||
fixtures = FIXTURES
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -167,7 +208,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()
|
||||
|
|
@ -177,6 +218,7 @@ class SmartCachingTestCase(TestCase):
|
|||
"""
|
||||
The base test case for all tests that have to do with smart caching.
|
||||
"""
|
||||
|
||||
fixtures = FIXTURES
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -201,21 +243,14 @@ class SmartCachingTestCase(TestCase):
|
|||
# This is what the old, pre-cache system would check to see if a user
|
||||
# had a given permission.
|
||||
return Permission.objects.user_permissions(
|
||||
self.user,
|
||||
'foo',
|
||||
self.user,
|
||||
approved=True,
|
||||
check_groups=True,
|
||||
self.user, "foo", self.user, approved=True, check_groups=True,
|
||||
)
|
||||
|
||||
def _old_group_permission_check(self):
|
||||
# This is what the old, pre-cache system would check to see if a user
|
||||
# had a given permission.
|
||||
return Permission.objects.group_permissions(
|
||||
self.group,
|
||||
'foo',
|
||||
self.group,
|
||||
approved=True,
|
||||
self.group, "foo", self.group, approved=True,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -240,20 +275,13 @@ class PerformanceTest(SmartCachingTestCase):
|
|||
for _ in range(5):
|
||||
# Need to assert it so the query actually gets executed.
|
||||
assert not self.user_check.has_user_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
True,
|
||||
False,
|
||||
"foo", self.user, True, False,
|
||||
)
|
||||
|
||||
def test_group_has_perms(self):
|
||||
with self.assertNumQueries(2):
|
||||
for _ in range(5):
|
||||
assert not self.group_check.has_group_perms(
|
||||
'foo',
|
||||
self.group,
|
||||
True,
|
||||
)
|
||||
assert not self.group_check.has_group_perms("foo", self.group, True,)
|
||||
|
||||
def test_has_user_perms_check_group(self):
|
||||
# Regardless of the number groups permissions, it should only take one
|
||||
|
|
@ -261,10 +289,7 @@ class PerformanceTest(SmartCachingTestCase):
|
|||
# Content type and permissions (2 queries)
|
||||
with self.assertNumQueries(3):
|
||||
self.user_check.has_user_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
approved=True,
|
||||
check_groups=True,
|
||||
"foo", self.user, approved=True, check_groups=True,
|
||||
)
|
||||
|
||||
def test_invalidate_user_permissions_cache(self):
|
||||
|
|
@ -276,10 +301,7 @@ class PerformanceTest(SmartCachingTestCase):
|
|||
with self.assertNumQueries(6):
|
||||
for _ in range(5):
|
||||
assert not self.user_check.has_user_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
True,
|
||||
False,
|
||||
"foo", self.user, True, False,
|
||||
)
|
||||
|
||||
# Invalidate the cache to show that a query will be generated when
|
||||
|
|
@ -290,10 +312,7 @@ class PerformanceTest(SmartCachingTestCase):
|
|||
# One query to re generate the cache.
|
||||
for _ in range(5):
|
||||
assert not self.user_check.has_user_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
True,
|
||||
False,
|
||||
"foo", self.user, True, False,
|
||||
)
|
||||
|
||||
def test_invalidate_group_permissions_cache(self):
|
||||
|
|
@ -303,11 +322,7 @@ class PerformanceTest(SmartCachingTestCase):
|
|||
# will need to do one query to get content type and one to get
|
||||
with self.assertNumQueries(4):
|
||||
for _ in range(5):
|
||||
assert not self.group_check.has_group_perms(
|
||||
'foo',
|
||||
self.group,
|
||||
True,
|
||||
)
|
||||
assert not self.group_check.has_group_perms("foo", self.group, True,)
|
||||
|
||||
# Invalidate the cache to show that a query will be generated when
|
||||
# checking perms again.
|
||||
|
|
@ -316,18 +331,14 @@ class PerformanceTest(SmartCachingTestCase):
|
|||
|
||||
# One query to re generate the cache.
|
||||
for _ in range(5):
|
||||
assert not self.group_check.has_group_perms(
|
||||
'foo',
|
||||
self.group,
|
||||
True,
|
||||
)
|
||||
assert not self.group_check.has_group_perms("foo", self.group, True,)
|
||||
|
||||
def test_has_user_perms_check_group_multiple(self):
|
||||
# Create a permission with just a group.
|
||||
Permission.objects.create(
|
||||
content_type=Permission.objects.get_content_type(User),
|
||||
object_id=self.user.pk,
|
||||
codename='foo',
|
||||
codename="foo",
|
||||
group=self.group,
|
||||
approved=True,
|
||||
)
|
||||
|
|
@ -336,17 +347,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,
|
||||
)
|
||||
|
|
@ -355,7 +366,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):
|
||||
|
|
@ -370,10 +381,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase):
|
|||
# Use the new cached user perms to show that the user does not have the
|
||||
# perms.
|
||||
can_foo_with_group = self.user_check.has_user_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
approved=True,
|
||||
check_groups=True,
|
||||
"foo", self.user, approved=True, check_groups=True,
|
||||
)
|
||||
self.assertFalse(can_foo_with_group)
|
||||
|
||||
|
|
@ -381,7 +389,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,
|
||||
)
|
||||
|
|
@ -393,10 +401,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase):
|
|||
# Invalidate the cache.
|
||||
self.user_check.invalidate_permissions_cache()
|
||||
can_foo_with_group = self.user_check.has_user_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
approved=True,
|
||||
check_groups=True,
|
||||
"foo", self.user, approved=True, check_groups=True,
|
||||
)
|
||||
self.assertTrue(can_foo_with_group)
|
||||
|
||||
|
|
@ -404,9 +409,7 @@ class GroupPermissionCacheTestCase(SmartCachingTestCase):
|
|||
# Make sure calling has_user_perms on a permission that does not have a
|
||||
# user does not throw any errors.
|
||||
can_foo_with_group = self.group_check.has_group_perms(
|
||||
'foo',
|
||||
self.user,
|
||||
approved=True,
|
||||
"foo", self.user, approved=True,
|
||||
)
|
||||
self.assertFalse(can_foo_with_group)
|
||||
|
||||
|
|
@ -417,7 +420,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,
|
||||
)
|
||||
|
|
@ -430,8 +433,21 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,40 @@
|
|||
try:
|
||||
from django.conf.urls import *
|
||||
except ImportError: # django < 1.4
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
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}
|
||||
),
|
||||
url(r'^permission/delete/(?P<permission_pk>\d+)/$',
|
||||
view='delete_permission',
|
||||
name="authority-delete-permission",
|
||||
kwargs={'approved': True}
|
||||
),
|
||||
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}
|
||||
),
|
||||
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',
|
||||
name="authority-delete-permission-request",
|
||||
kwargs={'approved': False}
|
||||
),
|
||||
from django.conf.urls import url
|
||||
from authority.views import (
|
||||
add_permission,
|
||||
delete_permission,
|
||||
approve_permission_request,
|
||||
delete_permission,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
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},
|
||||
),
|
||||
url(
|
||||
r"^permission/delete/(?P<permission_pk>\d+)/$",
|
||||
view=delete_permission,
|
||||
name="authority-delete-permission",
|
||||
kwargs={"approved": True},
|
||||
),
|
||||
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},
|
||||
),
|
||||
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,
|
||||
name="authority-delete-permission-request",
|
||||
kwargs={"approved": False},
|
||||
),
|
||||
]
|
||||
|
|
|
|||
7
authority/utils.py
Normal file
7
authority/utils.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from authority.sites import (
|
||||
site,
|
||||
get_check,
|
||||
get_choices_for,
|
||||
register,
|
||||
unregister,
|
||||
) # noqa
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
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.shortcuts import render, get_object_or_404
|
||||
from django.http import HttpResponseRedirect, HttpResponseForbidden
|
||||
from django.db.models.loading import get_model
|
||||
from django.apps import apps
|
||||
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
|
||||
|
||||
|
|
@ -12,85 +9,107 @@ 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)
|
||||
model = get_model(app_label, module_name)
|
||||
if model is None:
|
||||
def add_permission(
|
||||
request,
|
||||
app_label,
|
||||
module_name,
|
||||
pk,
|
||||
approved=False,
|
||||
template_name="authority/permission_form.html",
|
||||
extra_context=None,
|
||||
form_class=UserPermissionForm,
|
||||
):
|
||||
codename = request.POST.get("codename", None)
|
||||
try:
|
||||
model = apps.get_model(app_label, module_name)
|
||||
except LookupError:
|
||||
return permission_denied(request)
|
||||
obj = get_object_or_404(model, pk=pk)
|
||||
next = get_next(request, obj)
|
||||
if approved:
|
||||
if not request.user.has_perm('authority.add_permission'):
|
||||
if not request.user.has_perm("authority.add_permission"):
|
||||
return HttpResponseRedirect(
|
||||
url_for_obj('authority-add-permission-request', obj))
|
||||
view_name = 'authority-add-permission'
|
||||
url_for_obj("authority-add-permission-request", obj)
|
||||
)
|
||||
view_name = "authority-add-permission"
|
||||
else:
|
||||
view_name = 'authority-add-permission-request'
|
||||
if request.method == 'POST':
|
||||
view_name = "authority-add-permission-request"
|
||||
if request.method == "POST":
|
||||
if codename is None:
|
||||
return HttpResponseForbidden(next)
|
||||
form = form_class(data=request.POST, obj=obj, approved=approved,
|
||||
perm=codename, initial=dict(codename=codename))
|
||||
form = form_class(
|
||||
data=request.POST,
|
||||
obj=obj,
|
||||
approved=approved,
|
||||
perm=codename,
|
||||
initial=dict(codename=codename),
|
||||
)
|
||||
if not approved:
|
||||
# Limit permission request to current user
|
||||
form.data['user'] = request.user
|
||||
form.data["user"] = request.user
|
||||
if form.is_valid():
|
||||
permission = form.save(request)
|
||||
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_to_response(template_name, context,
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, template_name, context)
|
||||
|
||||
|
||||
@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):
|
||||
"""
|
||||
Default 403 handler.
|
||||
|
|
@ -101,11 +120,14 @@ def permission_denied(request, template_name=None, extra_context=None):
|
|||
The path of the requested URL (e.g., '/app/pages/bad_page/')
|
||||
"""
|
||||
if template_name is None:
|
||||
template_name = ('403.html', 'authority/403.html')
|
||||
template_name = ("403.html", "authority/403.html")
|
||||
context = {
|
||||
'request_path': request.path,
|
||||
"request_path": request.path,
|
||||
}
|
||||
if extra_context:
|
||||
context.update(extra_context)
|
||||
return HttpResponseForbidden(loader.render_to_string(template_name, context,
|
||||
context_instance=RequestContext(request)))
|
||||
return HttpResponseForbidden(
|
||||
loader.render_to_string(
|
||||
template_name=template_name, context=context, request=request,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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) {
|
||||
|
|
@ -17,6 +18,7 @@ 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
|
||||
|
|
@ -26,28 +28,57 @@ class GenericForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
|
|||
def render(self, name, value, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
related_url = '../../../'
|
||||
related_url = "../../../"
|
||||
params = self.url_parameters()
|
||||
if params:
|
||||
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.iteritems()])
|
||||
url = "?" + "&".join(["%s=%s" % (k, v) for k, v in params.iteritems()])
|
||||
else:
|
||||
url = ''
|
||||
if 'class' not in attrs:
|
||||
attrs['class'] = 'vForeignKeyRawIdAdminField'
|
||||
url = ""
|
||||
if "class" not in attrs:
|
||||
attrs["class"] = "vForeignKeyRawIdAdminField"
|
||||
output = [forms.TextInput.render(self, name, value, attrs)]
|
||||
output.append("""%(generic_script)s
|
||||
<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
84
bootstrap.py
|
|
@ -1,84 +0,0 @@
|
|||
##############################################################################
|
||||
#
|
||||
# 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
27
buildout.cfg
|
|
@ -1,27 +0,0 @@
|
|||
[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}
|
||||
101
docs/conf.py
101
docs/conf.py
|
|
@ -11,14 +11,13 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pkg_resources import get_distribution
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# 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 -----------------------------------------------------
|
||||
|
||||
|
|
@ -27,118 +26,117 @@ import sys
|
|||
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, the django-authority team'
|
||||
project = u"django-authority"
|
||||
copyright = u"2009-2020, Jannis Leidel"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.9'
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.9dev'
|
||||
release = get_distribution("django-authority").version
|
||||
# The short X.Y version.
|
||||
version = ".".join(release.split(".")[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#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
|
||||
|
|
@ -147,7 +145,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
|
||||
|
|
@ -155,43 +153,48 @@ 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
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ Development version
|
|||
===================
|
||||
|
||||
The latest development version is located on it's `Github account`_. You
|
||||
can checkout the package using the Mercurial_ scm::
|
||||
can checkout the package using the Git_ scm::
|
||||
|
||||
git clone https://github.com/jezdez/django-authority.git
|
||||
git clone https://github.com/jazzband/django-authority
|
||||
|
||||
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/jezdez/django-authority/
|
||||
.. _Mercurial: http://www.selenic.com/mercurial/
|
||||
.. _Github account: https://github.com/jazzband/django-authority/
|
||||
.. _Git: http://gitscm.org/
|
||||
|
|
|
|||
|
|
@ -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/jezdez/django-authority/issues/
|
||||
.. _issue tracker: https://github.com/jazzband/django-authority/issues/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
from example.settings import *
|
||||
DEBUG=True
|
||||
TEMPLATE_DEBUG=DEBUG
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from authority.forms import UserPermissionForm
|
||||
|
||||
|
||||
class SpecialUserPermissionForm(UserPermissionForm):
|
||||
user = forms.CharField(label=_('Special user'), widget=forms.Textarea())
|
||||
user = forms.CharField(label=_("Special user"), widget=forms.Textarea())
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ from django.utils.translation import ugettext_lazy as _
|
|||
import authority
|
||||
from authority.permissions import BasePermission
|
||||
|
||||
|
||||
class FlatPagePermission(BasePermission):
|
||||
"""
|
||||
This class contains a bunch of checks:
|
||||
|
||||
|
||||
1. the default checks 'add_flatpage', 'browse_flatpage',
|
||||
'change_flatpage' and 'delete_flatpage'
|
||||
2. the custom checks:
|
||||
|
|
@ -40,13 +41,16 @@ class FlatPagePermission(BasePermission):
|
|||
{% endifhasperm %}
|
||||
|
||||
"""
|
||||
label = 'flatpage_permission'
|
||||
checks = ('review', 'top_secret')
|
||||
|
||||
label = "flatpage_permission"
|
||||
checks = ("review", "top_secret")
|
||||
|
||||
def top_secret(self, flatpage=None, lala=None):
|
||||
if flatpage and flatpage.registration_required:
|
||||
return self.browse_flatpage(obj=flatpage)
|
||||
return False
|
||||
top_secret.short_description=_('Is allowed to see top secret flatpages')
|
||||
|
||||
authority.register(FlatPage, FlatPagePermission)
|
||||
top_secret.short_description = _("Is allowed to see top secret flatpages")
|
||||
|
||||
|
||||
authority.sites.register(FlatPage, FlatPagePermission)
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
"""
|
||||
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
|
||||
"""}
|
||||
|
||||
|
|
@ -8,8 +8,9 @@ from authority.decorators import permission_required, permission_required_or_403
|
|||
# @permission_required('flatpage_permission.top_secret',
|
||||
# (FlatPage, 'url__contains', 'url'), (FlatPage, 'url__contains', 'lala'))
|
||||
# use this to return a 403 page:
|
||||
@permission_required_or_403('flatpage_permission.top_secret',
|
||||
(FlatPage, 'url__contains', 'url'), 'lala')
|
||||
@permission_required_or_403(
|
||||
"flatpage_permission.top_secret", (FlatPage, "url__contains", "url"), "lala"
|
||||
)
|
||||
def top_secret(request, url, lala=None):
|
||||
"""
|
||||
A wrapping view that performs the permission check given in the decorator
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ from django.core.management import execute_from_command_line
|
|||
try:
|
||||
import settings as settings_mod # Assumed to be in the same directory.
|
||||
except ImportError:
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.stderr.write(
|
||||
"Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n"
|
||||
% __file__
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
sys.path.insert(0, settings_mod.PROJECT_ROOT)
|
||||
sys.path.insert(0, settings_mod.PROJECT_ROOT + '/../')
|
||||
sys.path.insert(0, settings_mod.PROJECT_ROOT + "/../")
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'example.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_from_command_line(sys.argv)
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
|
||||
from example.settings import *
|
||||
|
|
|
|||
|
|
@ -11,90 +11,87 @@ ADMINS = (
|
|||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(PROJECT_ROOT, 'example.db'),
|
||||
'USER': '',
|
||||
'PASSWORD': '',
|
||||
'HOST': '',
|
||||
'PORT': '',
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": os.path.join(PROJECT_ROOT, "example.db"),
|
||||
"USER": "",
|
||||
"PASSWORD": "",
|
||||
"HOST": "",
|
||||
"PORT": "",
|
||||
"TEST": {"NAME": ":memory:", "ENGINE": "django.db.backends.sqlite3",},
|
||||
}
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
}
|
||||
}
|
||||
CACHES = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache",}}
|
||||
|
||||
TIME_ZONE = 'America/Chicago'
|
||||
TIME_ZONE = "America/Chicago"
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
# Absolute path to the directory that holds media.
|
||||
# Example: "/home/media/media.lawrence.com/"
|
||||
MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media')
|
||||
MEDIA_ROOT = os.path.join(PROJECT_ROOT, "media")
|
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash if there is a path component (optional in other cases).
|
||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# Don't share this with anybody.
|
||||
SECRET_KEY = 'ljlv2lb2d&)#by6th=!v=03-c^(o4lop92i@z4b3f1&ve0yx6d'
|
||||
SECRET_KEY = "ljlv2lb2d&)#by6th=!v=03-c^(o4lop92i@z4b3f1&ve0yx6d"
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
MIDDLEWARE = (
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
# 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
|
||||
)
|
||||
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
INTERNAL_IPS = ("127.0.0.1",)
|
||||
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.core.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.request',
|
||||
)
|
||||
TEMPLATE_CONTEXT_PROCESSORS = ()
|
||||
|
||||
ROOT_URLCONF = 'example.urls'
|
||||
ROOT_URLCONF = "example.urls"
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.flatpages',
|
||||
'django.contrib.admin',
|
||||
'authority',
|
||||
'example.exampleapp',
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.flatpages",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.admin",
|
||||
"authority",
|
||||
"example.exampleapp",
|
||||
)
|
||||
|
||||
if VERSION >= (1, 5):
|
||||
INSTALLED_APPS = INSTALLED_APPS + ('example.users',)
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
INSTALLED_APPS = INSTALLED_APPS + ("example.users",)
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.load_template_source',
|
||||
'django.template.loaders.app_directories.load_template_source',
|
||||
)
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(PROJECT_ROOT, "templates"),
|
||||
)
|
||||
|
||||
# Use local_settings.py for things to override privately
|
||||
try:
|
||||
from local_settings import * # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
if VERSION >= (1, 6):
|
||||
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
||||
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",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,39 +1,44 @@
|
|||
try:
|
||||
from django.conf.urls import patterns, include, handler500, url
|
||||
except ImportError: # django < 1.4
|
||||
from django.conf.urls.defaults import patterns, include, handler500, url
|
||||
import django.contrib.auth.views
|
||||
from django.conf.urls import include, handler500, url
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
import authority
|
||||
|
||||
admin.autodiscover()
|
||||
authority.autodiscover()
|
||||
|
||||
handler500 # Pyflakes
|
||||
import authority.views
|
||||
import authority.urls
|
||||
import example.exampleapp.views
|
||||
|
||||
from exampleapp.forms import SpecialUserPermissionForm
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^admin/(.*)', admin.site.root),
|
||||
#('^admin/', include(admin.site.urls)),
|
||||
url(r'^authority/permission/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$',
|
||||
view='authority.views.add_permission',
|
||||
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,
|
||||
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+)/$',
|
||||
view='authority.views.add_permission',
|
||||
url(
|
||||
r"^request/add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$", # noqa
|
||||
view=authority.views.add_permission,
|
||||
name="authority-add-permission-request",
|
||||
kwargs={'approved': False, 'form_class': SpecialUserPermissionForm}
|
||||
kwargs={"approved": False, "form_class": SpecialUserPermissionForm},
|
||||
),
|
||||
url(r"^authority/", include(authority.urls)),
|
||||
url(r"^accounts/login/$", django.contrib.auth.views.LoginView.as_view()),
|
||||
url(
|
||||
r"^(?P<url>[\/0-9A-Za-z]+)$",
|
||||
example.exampleapp.views.top_secret,
|
||||
{"lala": "oh yeah!"},
|
||||
),
|
||||
(r'^authority/', include('authority.urls')),
|
||||
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
||||
url(r'^(?P<url>[\/0-9A-Za-z]+)$', 'example.exampleapp.views.top_secret', {'lala': 'oh yeah!'}),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += patterns('',
|
||||
(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
|
||||
'document_root': settings.MEDIA_ROOT,
|
||||
}),
|
||||
urlpatterns += (
|
||||
url(
|
||||
r"^media/(?P<path>.*)$",
|
||||
django.views.static.serve,
|
||||
{"document_root": settings.MEDIA_ROOT,},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
83
example/users/migrations/0001_initial.py
Normal file
83
example/users/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.22 on 2019-07-12 16:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.contrib.auth.models
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0008_alter_user_username_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
("username", models.CharField(max_length=100)),
|
||||
("first_name", models.CharField(max_length=50)),
|
||||
("last_name", models.CharField(max_length=50)),
|
||||
("email", models.EmailField(max_length=254, unique=True)),
|
||||
("greeting_message", models.TextField()),
|
||||
("is_staff", models.BooleanField(default=False)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"abstract": False,},
|
||||
managers=[("objects", django.contrib.auth.models.UserManager()),],
|
||||
),
|
||||
]
|
||||
0
example/users/migrations/__init__.py
Normal file
0
example/users/migrations/__init__.py
Normal file
|
|
@ -1,12 +1,14 @@
|
|||
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
||||
from django.contrib.auth.models import UserManager
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = ['first_name', 'last_name']
|
||||
USERNAME_FIELD = "email"
|
||||
REQUIRED_FIELDS = ["first_name", "last_name"]
|
||||
|
||||
username = models.CharField(max_length=100)
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
email = models.EmailField(unique=True)
|
||||
|
|
@ -14,3 +16,5 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||
is_staff = models.BooleanField(default=False)
|
||||
is_active = models.BooleanField(default=True)
|
||||
date_joined = models.DateTimeField(default=timezone.now)
|
||||
|
||||
objects = UserManager()
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
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;
|
||||
|
|
@ -8,3 +8,6 @@ all_files = 1
|
|||
|
||||
[upload_docs]
|
||||
upload-dir = docs/build/html
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
|
|
|||
53
setup.py
53
setup.py
|
|
@ -5,39 +5,38 @@ from setuptools import setup, find_packages
|
|||
def read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
|
||||
setup(
|
||||
name='django-authority',
|
||||
version='0.9',
|
||||
name="django-authority",
|
||||
use_scm_version=True,
|
||||
description=(
|
||||
"A Django app that provides generic per-object-permissions "
|
||||
"for Django's auth app."
|
||||
),
|
||||
long_description=read('README.rst'),
|
||||
author='Jannis Leidel',
|
||||
author_email='jannis@leidel.info',
|
||||
license='BSD',
|
||||
url='https://github.com/jezdez/django-authority/',
|
||||
packages=find_packages(),
|
||||
long_description=read("README.rst"),
|
||||
long_description_content_type="text/x-rst",
|
||||
author="Jannis Leidel",
|
||||
author_email="jannis@leidel.info",
|
||||
license="BSD",
|
||||
url="https://github.com/jazzband/django-authority/",
|
||||
packages=find_packages(exclude=("example", "example.*")),
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Framework :: Django',
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Framework :: Django",
|
||||
],
|
||||
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',
|
||||
]
|
||||
},
|
||||
install_requires=["django"],
|
||||
setup_requires=["setuptools_scm"],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
||||
|
|
|
|||
43
tox.ini
43
tox.ini
|
|
@ -1,14 +1,39 @@
|
|||
[tox]
|
||||
skipsdist = True
|
||||
usedevelop = True
|
||||
minversion = 1.8
|
||||
envlist =
|
||||
py26-django{14,15,16},
|
||||
py27-django{14,15,16,17,18},
|
||||
py33-django{15,16,17,18}
|
||||
py27-dj111
|
||||
py37-dj{111,22}
|
||||
{py36,py37,py38}-dj{30,31}
|
||||
py37-check
|
||||
|
||||
[testenv]
|
||||
commands = python example/manage.py test authority
|
||||
usedevelop = true
|
||||
commands =
|
||||
coverage run -a example/manage.py test authority exampleapp
|
||||
coverage report
|
||||
coverage xml
|
||||
deps =
|
||||
django14: Django>=1.4, <1.5
|
||||
django15: Django>=1.5, <1.6
|
||||
django16: Django>=1.6, <1.7
|
||||
django17: Django>=1.7, <1.8
|
||||
django18: Django>=1.8, <1.9
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue