New upstream version 1.4.8
Brian May
6 years ago
19 | 19 | example_project/conf/*.py |
20 | 20 | .idea/ |
21 | 21 | .eggs/ |
22 | .cache/ | |
22 | 23 | |
23 | 24 | # WebDAV remote filesystem |
24 | 25 | .DAV |
0 | 0 | language: python |
1 | 1 | sudo: false |
2 | ||
3 | cache: pip | |
4 | ||
2 | 5 | python: |
3 | 6 | - 2.7 |
4 | - 3.3 | |
5 | 7 | - 3.4 |
6 | 8 | - 3.5 |
9 | - 3.6 | |
7 | 10 | |
8 | 11 | env: |
9 | - DJANGO_VERSION=1.7.10 DATABASE_URL=postgres://postgres@/django_guardian | |
10 | - DJANGO_VERSION=1.8.7 DATABASE_URL=postgres://postgres@/django_guardian | |
11 | - DJANGO_VERSION=1.9.1 DATABASE_URL=postgres://postgres@/django_guardian | |
12 | - DJANGO_VERSION=1.8 DATABASE_URL=postgres://postgres@/django_guardian | |
13 | - DJANGO_VERSION=1.10 DATABASE_URL=postgres://postgres@/django_guardian | |
14 | - DJANGO_VERSION=1.11 DATABASE_URL=postgres://postgres@/django_guardian | |
12 | 15 | - DJANGO_VERSION=master DATABASE_URL=postgres://postgres@/django_guardian |
13 | 16 | |
14 | - DJANGO_VERSION=1.7.10 DATABASE_URL=mysql://root:@localhost/django_guardian | |
15 | - DJANGO_VERSION=1.8.7 DATABASE_URL=mysql://root:@localhost/django_guardian | |
16 | - DJANGO_VERSION=1.9.1 DATABASE_URL=mysql://root:@localhost/django_guardian | |
17 | - DJANGO_VERSION=1.8 DATABASE_URL=mysql://root:@localhost/django_guardian | |
18 | - DJANGO_VERSION=1.10 DATABASE_URL=mysql://root:@localhost/django_guardian | |
19 | - DJANGO_VERSION=1.11 DATABASE_URL=mysql://root:@localhost/django_guardian | |
17 | 20 | - DJANGO_VERSION=master DATABASE_URL=mysql://root:@localhost/django_guardian |
18 | 21 | |
19 | - DJANGO_VERSION=1.7.10 DATABASE_URL=sqlite:// | |
20 | - DJANGO_VERSION=1.8.7 DATABASE_URL=sqlite:// | |
21 | - DJANGO_VERSION=1.9.1 DATABASE_URL=sqlite:// | |
22 | - DJANGO_VERSION=1.8 DATABASE_URL=sqlite:// | |
23 | - DJANGO_VERSION=1.10 DATABASE_URL=sqlite:// | |
24 | - DJANGO_VERSION=1.11 DATABASE_URL=sqlite:// | |
22 | 25 | - DJANGO_VERSION=master DATABASE_URL=sqlite:// |
26 | ||
27 | before_install: | |
28 | - pip install -q -U pytest | |
23 | 29 | |
24 | 30 | install: |
25 | 31 | - travis_retry pip install -q mock==1.0.1 pytest pytest-django pytest-cov django-environ setuptools_scm |
26 | 32 | # Install django master or version |
27 | 33 | - bash -c "if [[ "$DJANGO_VERSION" == 'master' ]]; then pip install 'https://github.com/django/django/archive/master.tar.gz'; else pip install Django==$DJANGO_VERSION; fi; " |
28 | 34 | # Install database drivers |
29 | - bash -c "if [[ $DATABASE_URL = postgres* ]]; then pip install psycopg2==2.6.1; fi; " | |
30 | - bash -c "if [[ $DATABASE_URL = mysql* ]]; then pip install mysqlclient==1.3.7; fi; " | |
35 | - bash -c "if [[ $DATABASE_URL = postgres* ]]; then pip install psycopg2==2.7.1; fi; " | |
36 | - bash -c "if [[ $DATABASE_URL = mysql* ]]; then pip install mysqlclient==1.3.10; fi; " | |
31 | 37 | |
32 | 38 | script: |
33 | 39 | - python ./setup.py --version |
34 | 40 | - py.test --cov=guardian |
41 | - bash -c "if [[ $DJANGO_VERSION = 1.10 || $DJANGO_VERSION = 1.11 || $DJANGO_VERSION = "master" ]]; then pip install .; cd example_project; python manage.py test; fi; " | |
35 | 42 | |
36 | 43 | notifications: |
37 | 44 | irc: "irc.freenode.net#django-guardian" |
39 | 46 | |
40 | 47 | matrix: |
41 | 48 | fast_finish: true |
42 | exclude: | |
43 | # Drop python 3.3 and django 1.9 | |
44 | - python: 3.3 | |
45 | env: DJANGO_VERSION=1.9.1 DATABASE_URL=postgres://postgres@/django_guardian | |
46 | - python: 3.3 | |
47 | env: DJANGO_VERSION=1.9.1 DATABASE_URL=mysql://root:@localhost/django_guardian | |
48 | - python: 3.3 | |
49 | env: DJANGO_VERSION=1.9.1 DATABASE_URL=sqlite:// | |
50 | # Drop python 3.3 and django master | |
51 | - python: 3.3 | |
52 | env: DJANGO_VERSION=master DATABASE_URL=postgres://postgres@/django_guardian | |
53 | - python: 3.3 | |
54 | env: DJANGO_VERSION=master DATABASE_URL=mysql://root:@localhost/django_guardian | |
55 | - python: 3.3 | |
56 | env: DJANGO_VERSION=master DATABASE_URL=sqlite:// | |
57 | # Drop python 3.5 and django 1.7 | |
58 | - python: 3.5 | |
59 | env: DJANGO_VERSION=1.7.10 DATABASE_URL=postgres://postgres@/django_guardian | |
60 | - python: 3.5 | |
61 | env: DJANGO_VERSION=1.7.10 DATABASE_URL=mysql://root:@localhost/django_guardian | |
62 | - python: 3.5 | |
63 | env: DJANGO_VERSION=1.7.10 DATABASE_URL=sqlite:// | |
64 | # Drop python 3.3 with postgres due lack of driver | |
65 | - python: 3.3 | |
66 | env: DJANGO_VERSION=1.7.10 DATABASE_URL=postgres://postgres@/django_guardian | |
67 | - python: 3.3 | |
68 | env: DJANGO_VERSION=1.8.7 DATABASE_URL=postgres://postgres@/django_guardian | |
69 | - python: 3.3 | |
70 | env: DJANGO_VERSION=1.9.1 DATABASE_URL=postgres://postgres@/django_guardian | |
71 | 49 | allow_failures: |
72 | 50 | - env: DJANGO_VERSION=master DATABASE_URL=postgres://postgres@/django_guardian |
73 | 51 | - env: DJANGO_VERSION=master DATABASE_URL=mysql://root:@localhost/django_guardian |
54 | 54 | - Bertrand Svetchine <bertrand.svetchine@gmail.com> |
55 | 55 | - Frank Wickström <frank@bambuser.com> |
56 | 56 | - George Karakostas <gckarakostas@gmail.com> |
57 | - Adam Dobrawy <guardian@jawnosc.tk> |
0 | Release 1.4.8 (Apr 04, 2017) | |
1 | ============================ | |
2 | ||
3 | * Improved performance of `clean_orphan_obj_perms` management command | |
4 | * Use bumpversion for versioning. | |
5 | * Enable Python 3.6 testing | |
6 | * Python 2.7, 3.4, 3.5, 3.6 are only supported Python versions | |
7 | * Django 1.8, 1.10, and 1.11 are only supported Django versions | |
8 | * Added explicity on_delete to all ForeignKeys | |
9 | ||
10 | Release 1.4.6 (Sep 09, 2016) | |
11 | ============================ | |
12 | ||
13 | * Improved performance of get_objects_for_user | |
14 | * Added test-covered and documented guardian.mixins.PermissionListMixin | |
15 | * Allow content type retrieval to be overridden fg. for django-polymorphic support | |
16 | * Added support CreateView-like (no object) view in PermissionRequiredMixin | |
17 | * Added django 1.10 to TravisCI and tox | |
18 | * Run tests for example_project in TravisCI | |
19 | * Require django 1.9+ for example_project (django-guardian core support django 1.7+) | |
20 | * Fix django versions compatibility in example_project | |
21 | * Drop django in install_requires of setuptools | |
22 | ||
23 | Release 1.4.5 (Aug 09, 2016) | |
24 | ============================ | |
25 | ||
26 | * Fix caching issue with prefetch_perms. | |
27 | * Convert readthedocs link for their .org -> .io migration for hosted projects | |
28 | * Added example CRUD CBV project | |
29 | * Added TEMPLATES in example_project settings | |
30 | * Added Queryset support to assign_perm | |
31 | * Added QuerySet support to remove_perm | |
32 | * Updated assign_perm and remove_perm docstrings | |
33 | * Moved queryset support in assign_perms to its own function | |
34 | * Moved queryset support in remove_perms to its own function | |
35 | * Consolidated {User,Group}ObjectPermissionManager, move logic of bulk_*_perm | |
36 | to managers | |
37 | * `assign_perm` and `remove_perm` shortcuts accept `Permission` | |
38 | instance as `perm` and `QuerySet` as `obj` too. | |
39 | * Consolidated bulk_assign_perm to assign_perm and bulk_remove_perm to remove_perm | |
40 | * Upgraded Grappelli templates breadcrumbs block to new Django 1.9 and | |
41 | Grappelli 2.8 standards, including proper URLs and support for | |
42 | preserved_filters. Removed the duplicated field.errors in the field.html | |
43 | template file. | |
44 | * Made UserManage/GroupManage forms overridable | |
45 | * Fixed GuardedModelAdminMixin views render for Django 1.10 | |
46 | ||
47 | ||
0 | 48 | Release 1.4.4 (Apr 04, 2016) |
1 | 49 | ============================ |
2 | 50 |
0 | Copyright (c) 2010-2014 Lukasz Balcerzak <lukaszbalcerzak@gmail.com> | |
0 | Copyright (c) 2010-2016 Lukasz Balcerzak <lukaszbalcerzak@gmail.com> | |
1 | 1 | All rights reserved. |
2 | 2 | |
3 | 3 | Redistribution and use in source and binary forms, with or without |
2 | 2 | include README.rst |
3 | 3 | include MANIFEST.in |
4 | 4 | include *.py |
5 | include *.ini | |
5 | 6 | include run_test_and_report.sh |
6 | 7 | include *.txt |
7 | 8 | include AUTHORS |
8 | include tox.ini | |
9 | 9 | recursive-include guardian *.py |
10 | 10 | recursive-include guardian/locale *.po *.mo |
11 | recursive-include guardian/fixtures *.json | |
12 | recursive-include guardian/templates *.html | |
13 | recursive-include guardian/tests/templates *.html | |
14 | 11 | recursive-include docs *.bat |
15 | recursive-include docs *.conf | |
16 | 12 | recursive-include docs *.css |
17 | 13 | recursive-include docs *.eot |
18 | 14 | recursive-include docs *.html |
19 | 15 | recursive-include docs *.js |
20 | 16 | recursive-include docs *.py |
21 | recursive-include docs *.rb | |
22 | 17 | recursive-include docs *.rst |
23 | recursive-include docs *.sass | |
24 | 18 | recursive-include docs *.sh |
25 | 19 | recursive-include docs *.svg |
26 | 20 | recursive-include docs *.ttf |
31 | 25 | recursive-include example_project *.js |
32 | 26 | recursive-include example_project *.png |
33 | 27 | recursive-include example_project *.txt |
28 | recursive-include example_project *.py | |
34 | 29 | recursive-exclude docs/build * |
35 | 30 | recursive-include guardian *.html |
36 | 31 | recursive-include guardian *.svg |
32 | recursive-include benchmarks *.py |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: django-guardian |
2 | Version: 1.4.4 | |
2 | Version: 1.4.8 | |
3 | 3 | Summary: Implementation of per object permissions for Django. |
4 | 4 | Home-page: http://github.com/django-guardian/django-guardian |
5 | 5 | Author: Lukasz Balcerzak |
7 | 7 | License: BSD |
8 | 8 | Download-URL: https://github.com/django-guardian/django-guardian/tags |
9 | 9 | Description: =============== |
10 | django-guardian | |
11 | =============== | |
12 | ||
13 | .. image:: https://travis-ci.org/django-guardian/django-guardian.svg?branch=devel | |
14 | :target: https://travis-ci.org/django-guardian/django-guardian | |
15 | ||
16 | ``django-guardian`` is an implementation of per object permissions [1]_ on top | |
17 | of Django's authorization backend | |
18 | ||
19 | Documentation | |
20 | ------------- | |
21 | ||
22 | Online documentation is available at https://django-guardian.readthedocs.io/. | |
23 | ||
24 | Requirements | |
25 | ------------ | |
26 | ||
27 | * Python 2.7 or 3.4+ | |
28 | * A supported version of Django (currently 1.8+) | |
29 | ||
30 | Travis CI tests on Django version 1.8, 1.10, and 1.11. | |
31 | ||
32 | Installation | |
33 | ------------ | |
34 | ||
35 | To install ``django-guardian`` simply run:: | |
36 | ||
37 | pip install django-guardian | |
38 | ||
39 | Configuration | |
40 | ------------- | |
41 | ||
42 | We need to hook ``django-guardian`` into our project. | |
43 | ||
44 | 1. Put ``guardian`` into your ``INSTALLED_APPS`` at settings module: | |
45 | ||
46 | .. code:: python | |
47 | ||
48 | INSTALLED_APPS = ( | |
49 | ... | |
50 | 'guardian', | |
51 | ) | |
52 | ||
53 | 2. Add extra authorization backend to your ``settings.py``: | |
54 | ||
55 | .. code:: python | |
56 | ||
57 | AUTHENTICATION_BACKENDS = ( | |
58 | 'django.contrib.auth.backends.ModelBackend', # default | |
59 | 'guardian.backends.ObjectPermissionBackend', | |
60 | ) | |
61 | ||
62 | 3. Create ``guardian`` database tables by running:: | |
63 | ||
64 | python manage.py migrate | |
65 | ||
66 | Usage | |
67 | ----- | |
68 | ||
69 | After installation and project hooks we can finally use object permissions | |
70 | with Django_. | |
71 | ||
72 | Lets start really quickly: | |
73 | ||
74 | .. code:: python | |
75 | ||
76 | >>> from django.contrib.auth.models import User, Group | |
77 | >>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack') | |
78 | >>> admins = Group.objects.create(name='admins') | |
79 | >>> jack.has_perm('change_group', admins) | |
80 | False | |
81 | >>> from guardian.models import UserObjectPermission | |
82 | >>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins) | |
83 | <UserObjectPermission: admins | jack | change_group> | |
84 | >>> jack.has_perm('change_group', admins) | |
85 | True | |
86 | ||
87 | Of course our agent jack here would not be able to *change_group* globally: | |
88 | ||
89 | .. code:: python | |
90 | ||
91 | >>> jack.has_perm('change_group') | |
92 | False | |
93 | ||
94 | Admin integration | |
95 | ----------------- | |
96 | ||
97 | Replace ``admin.ModelAdmin`` with ``GuardedModelAdmin`` for those models | |
98 | which should have object permissions support within admin panel. | |
99 | ||
100 | For example: | |
101 | ||
102 | .. code:: python | |
103 | ||
104 | from django.contrib import admin | |
105 | from myapp.models import Author | |
106 | from guardian.admin import GuardedModelAdmin | |
107 | ||
108 | # Old way: | |
109 | #class AuthorAdmin(admin.ModelAdmin): | |
110 | # pass | |
111 | ||
112 | # With object permissions support | |
113 | class AuthorAdmin(GuardedModelAdmin): | |
114 | pass | |
115 | ||
116 | admin.site.register(Author, AuthorAdmin) | |
117 | ||
118 | ||
119 | .. [1] Great paper about this feature is available at `djangoadvent articles <https://github.com/djangoadvent/djangoadvent-articles/blob/master/1.2/06_object-permissions.rst>`_. | |
120 | ||
121 | .. _Django: http://www.djangoproject.com/ | |
122 | ||
123 | ||
10 | 124 | Platform: UNKNOWN |
11 | 125 | Classifier: Development Status :: 5 - Production/Stable |
12 | 126 | Classifier: Environment :: Web Environment |
17 | 131 | Classifier: Programming Language :: Python |
18 | 132 | Classifier: Topic :: Security |
19 | 133 | Classifier: Programming Language :: Python :: 2.7 |
20 | Classifier: Programming Language :: Python :: 3.3 | |
21 | 134 | Classifier: Programming Language :: Python :: 3.4 |
22 | 135 | Classifier: Programming Language :: Python :: 3.5 |
136 | Classifier: Programming Language :: Python :: 3.6 |
4 | 4 | .. image:: https://travis-ci.org/django-guardian/django-guardian.svg?branch=devel |
5 | 5 | :target: https://travis-ci.org/django-guardian/django-guardian |
6 | 6 | |
7 | ``django-guardian`` is implementation of per object permissions [1]_ as | |
8 | authorization backend which is supported since Django_ 1.5. It won't | |
9 | work with older Django_ releases. | |
7 | ``django-guardian`` is an implementation of per object permissions [1]_ on top | |
8 | of Django's authorization backend | |
10 | 9 | |
11 | 10 | Documentation |
12 | 11 | ------------- |
13 | 12 | |
14 | Online documentation is available at http://django-guardian.rtfd.org/. | |
13 | Online documentation is available at https://django-guardian.readthedocs.io/. | |
14 | ||
15 | Requirements | |
16 | ------------ | |
17 | ||
18 | * Python 2.7 or 3.4+ | |
19 | * A supported version of Django (currently 1.8+) | |
20 | ||
21 | Travis CI tests on Django version 1.8, 1.10, and 1.11. | |
15 | 22 | |
16 | 23 | Installation |
17 | 24 | ------------ |
25 | 32 | |
26 | 33 | We need to hook ``django-guardian`` into our project. |
27 | 34 | |
28 | 1. Put ``guardian`` into your ``INSTALLED_APPS`` at settings module:: | |
35 | 1. Put ``guardian`` into your ``INSTALLED_APPS`` at settings module: | |
29 | 36 | |
30 | INSTALLED_APPS = ( | |
31 | ... | |
32 | 'guardian', | |
33 | ) | |
37 | .. code:: python | |
38 | ||
39 | INSTALLED_APPS = ( | |
40 | ... | |
41 | 'guardian', | |
42 | ) | |
34 | 43 | |
35 | 2. Add extra authorization backend to your `settings.py`:: | |
44 | 2. Add extra authorization backend to your ``settings.py``: | |
36 | 45 | |
37 | AUTHENTICATION_BACKENDS = ( | |
38 | 'django.contrib.auth.backends.ModelBackend', # default | |
39 | 'guardian.backends.ObjectPermissionBackend', | |
40 | ) | |
46 | .. code:: python | |
41 | 47 | |
42 | 4. Create ``guardian`` database tables by running:: | |
48 | AUTHENTICATION_BACKENDS = ( | |
49 | 'django.contrib.auth.backends.ModelBackend', # default | |
50 | 'guardian.backends.ObjectPermissionBackend', | |
51 | ) | |
52 | ||
53 | 3. Create ``guardian`` database tables by running:: | |
43 | 54 | |
44 | 55 | python manage.py migrate |
45 | 56 | |
49 | 60 | After installation and project hooks we can finally use object permissions |
50 | 61 | with Django_. |
51 | 62 | |
52 | Lets start really quickly:: | |
63 | Lets start really quickly: | |
53 | 64 | |
54 | >>> from django.contrib.auth.models import User, Group | |
55 | >>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack') | |
56 | >>> admins = Group.objects.create(name='admins') | |
57 | >>> jack.has_perm('change_group', admins) | |
58 | False | |
59 | >>> from guardian.models import UserObjectPermission | |
60 | >>> UserObjectPermission.objects.assign_perm('change_group', user=jack, obj=admins) | |
61 | <UserObjectPermission: admins | jack | change_group> | |
62 | >>> jack.has_perm('change_group', admins) | |
63 | True | |
65 | .. code:: python | |
64 | 66 | |
65 | Of course our agent jack here would not be able to *change_group* globally:: | |
67 | >>> from django.contrib.auth.models import User, Group | |
68 | >>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack') | |
69 | >>> admins = Group.objects.create(name='admins') | |
70 | >>> jack.has_perm('change_group', admins) | |
71 | False | |
72 | >>> from guardian.models import UserObjectPermission | |
73 | >>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins) | |
74 | <UserObjectPermission: admins | jack | change_group> | |
75 | >>> jack.has_perm('change_group', admins) | |
76 | True | |
77 | ||
78 | Of course our agent jack here would not be able to *change_group* globally: | |
79 | ||
80 | .. code:: python | |
66 | 81 | |
67 | 82 | >>> jack.has_perm('change_group') |
68 | 83 | False |
73 | 88 | Replace ``admin.ModelAdmin`` with ``GuardedModelAdmin`` for those models |
74 | 89 | which should have object permissions support within admin panel. |
75 | 90 | |
76 | For example:: | |
91 | For example: | |
92 | ||
93 | .. code:: python | |
77 | 94 | |
78 | 95 | from django.contrib import admin |
79 | 96 | from myapp.models import Author |
7 | 7 | |
8 | 8 | |
9 | 9 | class DirectUser(UserObjectPermissionBase): |
10 | content_object = models.ForeignKey('TestDirectModel') | |
10 | content_object = models.ForeignKey('TestDirectModel', on_delete=models.CASCADE) | |
11 | 11 | |
12 | 12 | |
13 | 13 | class DirectGroup(GroupObjectPermissionBase): |
14 | content_object = models.ForeignKey('TestDirectModel') | |
14 | content_object = models.ForeignKey('TestDirectModel', on_delete=models.CASCADE) | |
15 | 15 | |
16 | 16 | |
17 | 17 | class TestDirectModel(models.Model): |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: django-guardian |
2 | Version: 1.4.4 | |
2 | Version: 1.4.8 | |
3 | 3 | Summary: Implementation of per object permissions for Django. |
4 | 4 | Home-page: http://github.com/django-guardian/django-guardian |
5 | 5 | Author: Lukasz Balcerzak |
7 | 7 | License: BSD |
8 | 8 | Download-URL: https://github.com/django-guardian/django-guardian/tags |
9 | 9 | Description: =============== |
10 | django-guardian | |
11 | =============== | |
12 | ||
13 | .. image:: https://travis-ci.org/django-guardian/django-guardian.svg?branch=devel | |
14 | :target: https://travis-ci.org/django-guardian/django-guardian | |
15 | ||
16 | ``django-guardian`` is an implementation of per object permissions [1]_ on top | |
17 | of Django's authorization backend | |
18 | ||
19 | Documentation | |
20 | ------------- | |
21 | ||
22 | Online documentation is available at https://django-guardian.readthedocs.io/. | |
23 | ||
24 | Requirements | |
25 | ------------ | |
26 | ||
27 | * Python 2.7 or 3.4+ | |
28 | * A supported version of Django (currently 1.8+) | |
29 | ||
30 | Travis CI tests on Django version 1.8, 1.10, and 1.11. | |
31 | ||
32 | Installation | |
33 | ------------ | |
34 | ||
35 | To install ``django-guardian`` simply run:: | |
36 | ||
37 | pip install django-guardian | |
38 | ||
39 | Configuration | |
40 | ------------- | |
41 | ||
42 | We need to hook ``django-guardian`` into our project. | |
43 | ||
44 | 1. Put ``guardian`` into your ``INSTALLED_APPS`` at settings module: | |
45 | ||
46 | .. code:: python | |
47 | ||
48 | INSTALLED_APPS = ( | |
49 | ... | |
50 | 'guardian', | |
51 | ) | |
52 | ||
53 | 2. Add extra authorization backend to your ``settings.py``: | |
54 | ||
55 | .. code:: python | |
56 | ||
57 | AUTHENTICATION_BACKENDS = ( | |
58 | 'django.contrib.auth.backends.ModelBackend', # default | |
59 | 'guardian.backends.ObjectPermissionBackend', | |
60 | ) | |
61 | ||
62 | 3. Create ``guardian`` database tables by running:: | |
63 | ||
64 | python manage.py migrate | |
65 | ||
66 | Usage | |
67 | ----- | |
68 | ||
69 | After installation and project hooks we can finally use object permissions | |
70 | with Django_. | |
71 | ||
72 | Lets start really quickly: | |
73 | ||
74 | .. code:: python | |
75 | ||
76 | >>> from django.contrib.auth.models import User, Group | |
77 | >>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack') | |
78 | >>> admins = Group.objects.create(name='admins') | |
79 | >>> jack.has_perm('change_group', admins) | |
80 | False | |
81 | >>> from guardian.models import UserObjectPermission | |
82 | >>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins) | |
83 | <UserObjectPermission: admins | jack | change_group> | |
84 | >>> jack.has_perm('change_group', admins) | |
85 | True | |
86 | ||
87 | Of course our agent jack here would not be able to *change_group* globally: | |
88 | ||
89 | .. code:: python | |
90 | ||
91 | >>> jack.has_perm('change_group') | |
92 | False | |
93 | ||
94 | Admin integration | |
95 | ----------------- | |
96 | ||
97 | Replace ``admin.ModelAdmin`` with ``GuardedModelAdmin`` for those models | |
98 | which should have object permissions support within admin panel. | |
99 | ||
100 | For example: | |
101 | ||
102 | .. code:: python | |
103 | ||
104 | from django.contrib import admin | |
105 | from myapp.models import Author | |
106 | from guardian.admin import GuardedModelAdmin | |
107 | ||
108 | # Old way: | |
109 | #class AuthorAdmin(admin.ModelAdmin): | |
110 | # pass | |
111 | ||
112 | # With object permissions support | |
113 | class AuthorAdmin(GuardedModelAdmin): | |
114 | pass | |
115 | ||
116 | admin.site.register(Author, AuthorAdmin) | |
117 | ||
118 | ||
119 | .. [1] Great paper about this feature is available at `djangoadvent articles <https://github.com/djangoadvent/djangoadvent-articles/blob/master/1.2/06_object-permissions.rst>`_. | |
120 | ||
121 | .. _Django: http://www.djangoproject.com/ | |
122 | ||
123 | ||
10 | 124 | Platform: UNKNOWN |
11 | 125 | Classifier: Development Status :: 5 - Production/Stable |
12 | 126 | Classifier: Environment :: Web Environment |
17 | 131 | Classifier: Programming Language :: Python |
18 | 132 | Classifier: Topic :: Security |
19 | 133 | Classifier: Programming Language :: Python :: 2.7 |
20 | Classifier: Programming Language :: Python :: 3.3 | |
21 | 134 | Classifier: Programming Language :: Python :: 3.4 |
22 | 135 | Classifier: Programming Language :: Python :: 3.5 |
136 | Classifier: Programming Language :: Python :: 3.6 |
0 | 0 | .gitignore |
1 | .isort.cfg | |
1 | 2 | .travis.yml |
2 | 3 | AUTHORS |
3 | 4 | CHANGES |
61 | 62 | docs/userguide/performance.rst |
62 | 63 | docs/userguide/remove.rst |
63 | 64 | example_project/__init__.py |
64 | example_project/context_processors.py | |
65 | 65 | example_project/manage.py |
66 | 66 | example_project/requirements.txt |
67 | 67 | example_project/settings.py |
68 | 68 | example_project/urls.py |
69 | example_project/articles/__init__.py | |
70 | example_project/articles/admin.py | |
71 | example_project/articles/apps.py | |
72 | example_project/articles/models.py | |
73 | example_project/articles/tests.py | |
74 | example_project/articles/urls.py | |
75 | example_project/articles/views.py | |
76 | example_project/articles/migrations/0001_initial.py | |
77 | example_project/articles/migrations/0002_auto_20160622_1050.py | |
78 | example_project/articles/migrations/__init__.py | |
79 | example_project/articles/templates/articles/article_confirm_delete.html | |
80 | example_project/articles/templates/articles/article_detail.html | |
81 | example_project/articles/templates/articles/article_form.html | |
82 | example_project/articles/templates/articles/article_list.html | |
69 | 83 | example_project/core/__init__.py |
70 | 84 | example_project/core/admin.py |
85 | example_project/core/context_processors.py | |
71 | 86 | example_project/core/models.py |
72 | 87 | example_project/core/migrations/0001_initial.py |
73 | 88 | example_project/core/migrations/__init__.py |
96 | 111 | guardian/checks.py |
97 | 112 | guardian/compat.py |
98 | 113 | guardian/core.py |
114 | guardian/ctypes.py | |
99 | 115 | guardian/decorators.py |
100 | 116 | guardian/exceptions.py |
101 | 117 | guardian/forms.py |
104 | 120 | guardian/models.py |
105 | 121 | guardian/shortcuts.py |
106 | 122 | guardian/utils.py |
107 | guardian/version.py | |
108 | 123 | guardian/conf/__init__.py |
109 | 124 | guardian/conf/settings.py |
110 | 125 | guardian/locale/es/LC_MESSAGES/django.mo |
142 | 157 | guardian/testapp/testsettings.py |
143 | 158 | guardian/testapp/migrations/0001_initial.py |
144 | 159 | guardian/testapp/migrations/0002_logentrywithgroup.py |
145 | guardian/testapp/migrations/0003_auto_20141124_0729.py | |
146 | guardian/testapp/migrations/0004_auto_20151112_2209.py | |
147 | guardian/testapp/migrations/0005_auto_20151217_2344.py | |
148 | guardian/testapp/migrations/0006_auto_20160221_1054.py | |
149 | guardian/testapp/migrations/0007_auto_20160309_0245.py | |
150 | 160 | guardian/testapp/migrations/__init__.py |
151 | 161 | guardian/testapp/tests/__init__.py |
152 | 162 | guardian/testapp/tests/conf.py |
170 | 180 | guardian/testapp/tests/templates/404.html |
171 | 181 | guardian/testapp/tests/templates/500.html |
172 | 182 | guardian/testapp/tests/templates/blank.html |
173 | guardian/testapp/tests/templates/dummy403.html⏎ | |
183 | guardian/testapp/tests/templates/dummy403.html | |
184 | guardian/testapp/tests/templates/dummy404.html | |
185 | guardian/testapp/tests/templates/list.html⏎ |
25 | 25 | .. autoclass:: guardian.mixins.PermissionRequiredMixin |
26 | 26 | :members: |
27 | 27 | |
28 | PermissionListMixin | |
29 | ----------------------- | |
30 | ||
31 | .. autoclass:: guardian.mixins.PermissionListMixin | |
32 | :members: |
54 | 54 | |
55 | 55 | # General information about the project. |
56 | 56 | project = u'django-guardian' |
57 | copyright = u'2010-2013, Lukasz Balcerzak' | |
57 | copyright = u'Lukasz Balcerzak' | |
58 | 58 | |
59 | 59 | # The version info for the project you're documenting, acts as replacement for |
60 | 60 | # |version| and |release|, also used in various other places throughout the |
61 | 61 | # built documents. |
62 | 62 | # |
63 | 63 | # The short X.Y version. |
64 | from setuptools_scm import get_version | |
65 | version = get_version(root="..") | |
64 | version = guardian.__version__ | |
66 | 65 | # The full version, including alpha/beta/rc tags. |
67 | 66 | release = version |
68 | 67 | |
138 | 137 | # Add any paths that contain custom static files (such as style sheets) here, |
139 | 138 | # relative to this directory. They are copied after the builtin static files, |
140 | 139 | # so a file named "default.css" will overwrite the builtin "default.css". |
141 | html_static_path = ['_static'] | |
140 | # html_static_path = ['_static'] | |
142 | 141 | |
143 | 142 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |
144 | 143 | # using the given strftime format. |
134 | 134 | |
135 | 135 | |
136 | 136 | .. seealso:: :ref:`custom-user-model-anonymous` |
137 | ||
138 | GUARDIAN_GET_CONTENT_TYPE | |
139 | ------------------------- | |
140 | ||
141 | .. versionadded:: 1.5 | |
142 | ||
143 | Guardian allows applications to supply a custom function to retrieve the | |
144 | content type from objects and models. This is useful when a class or class | |
145 | hierarchy uses the ``ContentType`` framework in an non-standard way. Most | |
146 | applications will not have to change this setting. | |
147 | ||
148 | As an example, when using ``django-polymorphic`` it's useful to use a | |
149 | permission on the base model which applies to all child models. In this case, | |
150 | the custom function would return the ``ContentType`` of the base class for | |
151 | polymorphic models and the regular model ``ContentType`` for non-polymorphic | |
152 | classes. | |
153 | ||
154 | Defaults to ``"guardian.ctypes.get_default_content_type"``.⏎ |
28 | 28 | Alternate projects |
29 | 29 | ------------------ |
30 | 30 | |
31 | Django_ 1.2 still has *only* foundation for object permissions [1]_ and | |
31 | Django_ still has *only* foundation for object permissions [1]_ and | |
32 | 32 | ``django-guardian`` make use of new facilities and it is based on them. There |
33 | are some other pluggable applications which does *NOT* require latest 1.2 | |
33 | are some other pluggable applications which does *NOT* require 1.2+ | |
34 | 34 | version of Django_. For instance, there are great `django-authority`_ or |
35 | 35 | `django-permissions`_ available out there. |
36 | 36 |
37 | 37 | def __unicode__(self): |
38 | 38 | return self.title |
39 | 39 | |
40 | @models.permalink | |
41 | 40 | def get_absolute_url(self): |
42 | 41 | return {'post_slug': self.slug} |
43 | 42 |
15 | 15 | class Task(models.Model): |
16 | 16 | summary = models.CharField(max_length=32) |
17 | 17 | content = models.TextField() |
18 | reported_by = models.ForeignKey(User) | |
18 | reported_by = models.ForeignKey(User, on_delete=models.CASCADE) | |
19 | 19 | created_at = models.DateTimeField(auto_now_add=True) |
20 | 20 | |
21 | 21 | ... and we want to be able to set custom permission *view_task*. We let Django |
27 | 27 | class Task(models.Model): |
28 | 28 | summary = models.CharField(max_length=32) |
29 | 29 | content = models.TextField() |
30 | reported_by = models.ForeignKey(User) | |
30 | reported_by = models.ForeignKey(User, on_delete=models.CASCADE) | |
31 | 31 | created_at = models.DateTimeField(auto_now_add=True) |
32 | 32 | |
33 | 33 | class Meta: |
127 | 127 | >>> userA.has_perm('change_company', companyB) |
128 | 128 | False |
129 | 129 | >>> userB = User.objects.create(username="User B") |
130 | >>> userB.groups.add(companyUserGroupB) | |
130 | 131 | >>> userB.has_perm('change_company', companyA) |
131 | 132 | False |
132 | >>> userA.has_perm('change_company', companyB) | |
133 | >>> userB.has_perm('change_company', companyB) | |
133 | 134 | True |
134 | 135 | |
135 | 136 | Assigning Permissions inside Signals |
15 | 15 | of the same app where the custom user model lives. |
16 | 16 | |
17 | 17 | To fix this, it is recommended to add the setting ``GUARDIAN_MONKEY_PATCH = False`` |
18 | in your settings.py and add the ``GuardianUserMixin`` to your custom user model. | |
18 | in your settings.py and subclass ``guardian.mixins.GuardianUserMixin`` in your custom user model. | |
19 | 19 | |
20 | 20 | .. important:: |
21 | 21 | ``django-guardian`` relies **heavily** on the ``auth.User`` model. |
87 | 87 | name = models.CharField(max_length=128, unique=True) |
88 | 88 | |
89 | 89 | class ProjectUserObjectPermission(UserObjectPermissionBase): |
90 | content_object = models.ForeignKey(Project) | |
90 | content_object = models.ForeignKey(Project, on_delete=models.CASCADE) | |
91 | 91 | |
92 | 92 | class ProjectGroupObjectPermission(GroupObjectPermissionBase): |
93 | content_object = models.ForeignKey(Project) | |
93 | content_object = models.ForeignKey(Project, on_delete=models.CASCADE) | |
94 | 94 | |
95 | 95 | |
96 | 96 | .. important:: |
112 | 112 | .. _performance-prefetch: |
113 | 113 | |
114 | 114 | Prefetching permissions |
115 | ------------------- | |
115 | ----------------------- | |
116 | 116 | |
117 | 117 | .. versionadded:: 1.4.3 |
118 | 118 |
0 | from django.contrib import admin | |
1 | from .models import Article | |
2 | ||
3 | ||
4 | class ArticleAdmin(admin.ModelAdmin): | |
5 | list_display = ('title', 'slug', 'created_at') | |
6 | list_filter = ('created_at',) | |
7 | search_fields = ('title',) | |
8 | ||
9 | admin.site.register(Article, ArticleAdmin) |
0 | from __future__ import unicode_literals | |
1 | ||
2 | from django.apps import AppConfig | |
3 | ||
4 | ||
5 | class ArticlesConfig(AppConfig): | |
6 | name = 'articles' |
0 | # -*- coding: utf-8 -*- | |
1 | # Generated by Django 1.9.4 on 2016-06-22 05:23 | |
2 | from __future__ import unicode_literals | |
3 | ||
4 | from django.conf import settings | |
5 | from django.db import migrations, models | |
6 | import django.db.models.deletion | |
7 | ||
8 | ||
9 | class Migration(migrations.Migration): | |
10 | ||
11 | initial = True | |
12 | ||
13 | dependencies = [ | |
14 | ('auth', '0001_initial'), | |
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), | |
16 | ] | |
17 | ||
18 | operations = [ | |
19 | migrations.CreateModel( | |
20 | name='Article', | |
21 | fields=[ | |
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
23 | ('title', models.CharField(max_length=64, verbose_name='title')), | |
24 | ('slug', models.SlugField(max_length=64)), | |
25 | ('content', models.TextField(verbose_name='content')), | |
26 | ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), | |
27 | ], | |
28 | options={ | |
29 | 'get_latest_by': 'created_at', | |
30 | 'permissions': (('view_articlew', 'Can view article'),), | |
31 | }, | |
32 | ), | |
33 | migrations.CreateModel( | |
34 | name='ArticleGroupObjectPermission', | |
35 | fields=[ | |
36 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
37 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), | |
38 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), | |
39 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
40 | ], | |
41 | options={ | |
42 | 'abstract': False, | |
43 | }, | |
44 | ), | |
45 | migrations.CreateModel( | |
46 | name='ArticleUserObjectPermission', | |
47 | fields=[ | |
48 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
49 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), | |
50 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
51 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | |
52 | ], | |
53 | options={ | |
54 | 'abstract': False, | |
55 | }, | |
56 | ), | |
57 | migrations.AlterUniqueTogether( | |
58 | name='articleuserobjectpermission', | |
59 | unique_together=set([('user', 'permission', 'content_object')]), | |
60 | ), | |
61 | migrations.AlterUniqueTogether( | |
62 | name='articlegroupobjectpermission', | |
63 | unique_together=set([('group', 'permission', 'content_object')]), | |
64 | ), | |
65 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | # Generated by Django 1.9.4 on 2016-06-22 10:50 | |
2 | from __future__ import unicode_literals | |
3 | ||
4 | from django.db import migrations | |
5 | ||
6 | ||
7 | class Migration(migrations.Migration): | |
8 | ||
9 | dependencies = [ | |
10 | ('articles', '0001_initial'), | |
11 | ] | |
12 | ||
13 | operations = [ | |
14 | migrations.AlterModelOptions( | |
15 | name='article', | |
16 | options={'get_latest_by': 'created_at', 'permissions': (('view_article', 'Can view article'),)}, | |
17 | ), | |
18 | ] |
0 | from __future__ import unicode_literals | |
1 | ||
2 | from django.db import models | |
3 | from guardian.compat import reverse | |
4 | ||
5 | ||
6 | from guardian.models import GroupObjectPermissionBase, UserObjectPermissionBase | |
7 | ||
8 | ||
9 | class Article(models.Model): | |
10 | title = models.CharField('title', max_length=64) | |
11 | slug = models.SlugField(max_length=64) | |
12 | content = models.TextField('content') | |
13 | created_at = models.DateTimeField(auto_now_add=True, db_index=True) | |
14 | ||
15 | class Meta: | |
16 | permissions = ( | |
17 | ('view_article', 'Can view article'), | |
18 | ) | |
19 | get_latest_by = 'created_at' | |
20 | ||
21 | def __unicode__(self): | |
22 | return self.title | |
23 | ||
24 | def get_absolute_url(self): | |
25 | return reverse('articles:details', kwargs={'slug': self.slug}) | |
26 | ||
27 | ||
28 | class ArticleUserObjectPermission(UserObjectPermissionBase): | |
29 | content_object = models.ForeignKey(Article, on_delete=models.CASCADE) | |
30 | ||
31 | ||
32 | class ArticleGroupObjectPermission(GroupObjectPermissionBase): | |
33 | content_object = models.ForeignKey(Article, on_delete=models.CASCADE) |
0 | {% extends "base.html" %} | |
1 | ||
2 | {% load guardian_tags %} | |
3 | ||
4 | ||
5 | {% block content %} | |
6 | <p>Are you sure do you want to delete "<a href="{{object.get_absolute_url}}">{{object}}</a>"?</p> | |
7 | <form method="POST"> | |
8 | {% csrf_token %} | |
9 | <input type="submit" class="btn btn-primary" value="Delete"> | |
10 | </form> | |
11 | {% endblock %} | |
12 |
0 | {% extends "base.html" %} | |
1 | ||
2 | {% load guardian_tags %} | |
3 | ||
4 | ||
5 | {% block content %} | |
6 | {% get_obj_perms request.user for object as 'object_perms' %} | |
7 | <div class="row"> | |
8 | <div class="span3"> | |
9 | <a href="{% url "articles:list" %}">Back to article list</a> | |
10 | </div> | |
11 | ||
12 | ||
13 | <div class="span9"> | |
14 | {% if "change_article" in object_perms %} | |
15 | <a href="{% url 'articles:update' slug=object.slug%}" class="btn btn-primary">Update</a> | |
16 | {% endif %} | |
17 | {% if "delete_article" in object_perms %} | |
18 | <a href="{% url 'articles:delete' slug=object.slug%}" class="btn btn-primary">Delete</a> | |
19 | {% endif %} | |
20 | <h1 name="{{ object.id }}">{{ object.title }}</h1> | |
21 | <h5>at {{ object.created_at }}</h5> | |
22 | <hr/> | |
23 | {{ object.content|safe }} | |
24 | </div> | |
25 | </div> | |
26 | {% endblock %} | |
27 |
0 | {% extends "base.html" %} | |
1 | ||
2 | {% load guardian_tags %} | |
3 | ||
4 | {% block content %} | |
5 | <div class="row"> | |
6 | <div class="span12"> | |
7 | <h1>{% if object %} | |
8 | <a href="{{ object.get_absolute_url }}" name="{{ object.id }}">Back to "{{ object.title }}"</a> | |
9 | {% else %} | |
10 | <a href="{% url "articles:list" %}">Back to article list</a> | |
11 | {% endif %}</h1> | |
12 | <form method="post">{% csrf_token %} {{ form.as_p }} | |
13 | <input type="submit" value="Save" class="btn btn-primary" /> | |
14 | </form> | |
15 | </div> | |
16 | </div> | |
17 | {% endblock %} |
0 | {% extends "base.html" %} | |
1 | ||
2 | {% block content %} | |
3 | <div class="row"> | |
4 | ||
5 | <div class="span12"> | |
6 | <div class="hero-unit"> | |
7 | <h1>Articles</h1> | |
8 | <a href="{% url "articles:create" %}" class="btn btn-primary">Add article</a> | |
9 | </div> | |
10 | </div> | |
11 | {% for object in object_list %} | |
12 | <div class="span3"> | |
13 | </div> | |
14 | <div class="span9"> | |
15 | <a href="{{object.get_absolute_url}}" name="{{ post.id }}"><h1>{{ object.title }}</h1></a> | |
16 | <h5>at {{ object.created_at }}</h5> | |
17 | <hr/> | |
18 | {{ object.content|safe }} | |
19 | </div> | |
20 | {% endfor %} | |
21 | </div> | |
22 | ||
23 | ||
24 | {% endblock %} |
0 | from django.test import TestCase | |
1 | from django.test.client import RequestFactory | |
2 | from guardian.compat import get_user_model | |
3 | from guardian.shortcuts import assign_perm | |
4 | from articles.models import Article | |
5 | from articles.views import (ArticleCreateView, ArticleDeleteView, ArticleDetailView, | |
6 | ArticleListView, ArticleUpdateView) | |
7 | ||
8 | ||
9 | class ViewTestCase(TestCase): | |
10 | def setUp(self): | |
11 | self.article = Article.objects.create(title='foo-title', | |
12 | slug='foo-slug', | |
13 | content='bar-content') | |
14 | self.factory = RequestFactory() | |
15 | self.user = get_user_model().objects.create_user( | |
16 | 'joe', 'joe@doe.com', 'doe') | |
17 | self.client.login(username='joe', password='doe') | |
18 | ||
19 | def test_list_permitted(self): | |
20 | request = self.factory.get('/') | |
21 | request.user = self.user | |
22 | assign_perm('articles.view_article', self.user, self.article) | |
23 | assign_perm('articles.delete_article', self.user, self.article) | |
24 | view = ArticleListView.as_view() | |
25 | response = view(request) | |
26 | response.render() | |
27 | self.assertContains(response, 'foo-title') | |
28 | ||
29 | def test_list_denied(self): | |
30 | request = self.factory.get('/') | |
31 | request.user = self.user | |
32 | view = ArticleListView.as_view() | |
33 | response = view(request) | |
34 | response.render() | |
35 | self.assertNotContains(response, 'foo-title') | |
36 | ||
37 | def test_create_permitted(self): | |
38 | request = self.factory.get('/~create') | |
39 | request.user = self.user | |
40 | assign_perm('articles.add_article', self.user) | |
41 | view = ArticleCreateView.as_view() | |
42 | response = view(request) | |
43 | self.assertEqual(response.status_code, 200) | |
44 | ||
45 | def test_create_denied(self): | |
46 | request = self.factory.get('/~create') | |
47 | request.user = self.user | |
48 | view = ArticleCreateView.as_view() | |
49 | response = view(request) | |
50 | self.assertEqual(response.status_code, 302) | |
51 | ||
52 | def test_detail_permitted(self): | |
53 | request = self.factory.get('/foo/') | |
54 | request.user = self.user | |
55 | assign_perm('articles.view_article', self.user, self.article) | |
56 | view = ArticleDetailView.as_view() | |
57 | response = view(request, slug='foo-slug') | |
58 | self.assertEqual(response.status_code, 200) | |
59 | ||
60 | def test_detail_denied(self): | |
61 | request = self.factory.get('/foo/') | |
62 | request.user = self.user | |
63 | view = ArticleDetailView.as_view() | |
64 | response = view(request, slug='foo-slug') | |
65 | self.assertEqual(response.status_code, 302) | |
66 | ||
67 | def test_update_permitted(self): | |
68 | request = self.factory.get('/') | |
69 | request.user = self.user | |
70 | assign_perm('articles.view_article', self.user, self.article) | |
71 | assign_perm('articles.change_article', self.user, self.article) | |
72 | view = ArticleUpdateView.as_view() | |
73 | response = view(request, slug='foo-slug') | |
74 | self.assertEqual(response.status_code, 200) | |
75 | ||
76 | def test_update_denied(self): | |
77 | request = self.factory.get('/') | |
78 | request.user = self.user | |
79 | view = ArticleUpdateView.as_view() | |
80 | response = view(request, slug='foo-slug') | |
81 | self.assertEqual(response.status_code, 302) | |
82 | ||
83 | def test_delete_permitted(self): | |
84 | request = self.factory.get('/foo-slug/~delete') | |
85 | request.user = self.user | |
86 | assign_perm('articles.view_article', self.user, self.article) | |
87 | assign_perm('articles.delete_article', self.user, self.article) | |
88 | view = ArticleDeleteView.as_view() | |
89 | response = view(request, slug='foo-slug') | |
90 | self.assertEqual(response.status_code, 200) | |
91 | ||
92 | def test_delete_denied(self): | |
93 | request = self.factory.get('/foo/~delete') | |
94 | request.user = self.user | |
95 | view = ArticleDeleteView.as_view() | |
96 | response = view(request, slug='foo-slug') | |
97 | self.assertEqual(response.status_code, 302) |
0 | # -*- coding: utf-8 -*- | |
1 | from __future__ import unicode_literals | |
2 | ||
3 | from django.conf.urls import url | |
4 | from . import views | |
5 | ||
6 | urlpatterns = [ | |
7 | url(r'^$', views.ArticleListView.as_view(), | |
8 | name="list"), | |
9 | url(r'^~create$', views.ArticleCreateView.as_view(), | |
10 | name="create"), | |
11 | url(r'^(?P<slug>[\w-]+)$', views.ArticleDetailView.as_view(), | |
12 | name="details"), | |
13 | url(r'^(?P<slug>[\w-]+)/~update$', views.ArticleUpdateView.as_view(), | |
14 | name="update"), | |
15 | url(r'^(?P<slug>[\w-]+)/~delete$', views.ArticleDeleteView.as_view(), | |
16 | name="delete"), | |
17 | ] |
0 | from django.views.generic import (CreateView, DeleteView, DetailView, ListView, | |
1 | UpdateView) | |
2 | from guardian.compat import reverse_lazy | |
3 | from guardian.mixins import PermissionRequiredMixin, PermissionListMixin | |
4 | from guardian.shortcuts import assign_perm | |
5 | from articles.models import Article | |
6 | ||
7 | ||
8 | class ArticleListView(PermissionListMixin, ListView): | |
9 | model = Article | |
10 | permission_required = ['view_article', ] | |
11 | ||
12 | ||
13 | class ArticleDetailView(PermissionRequiredMixin, DetailView): | |
14 | model = Article | |
15 | permission_required = ['view_article'] | |
16 | ||
17 | ||
18 | class ArticleCreateView(PermissionRequiredMixin, CreateView): | |
19 | model = Article | |
20 | permission_object = None | |
21 | permission_required = ['articles.add_article'] | |
22 | fields = ['title', 'slug', 'content'] | |
23 | ||
24 | def form_valid(self, *args, **kwargs): | |
25 | resp = super(ArticleCreateView, self).form_valid(*args, **kwargs) | |
26 | assign_perm('view_article', self.request.user, self.object) | |
27 | assign_perm('change_article', self.request.user, self.object) | |
28 | assign_perm('delete_article', self.request.user, self.object) | |
29 | return resp | |
30 | ||
31 | ||
32 | class ArticleUpdateView(PermissionRequiredMixin, UpdateView): | |
33 | model = Article | |
34 | permission_required = ['view_article', 'change_article'] | |
35 | fields = ['title', 'slug', 'content'] | |
36 | ||
37 | ||
38 | class ArticleDeleteView(PermissionRequiredMixin, DeleteView): | |
39 | model = Article | |
40 | success_url = reverse_lazy('articles:list') | |
41 | permission_required = ['view_article', 'delete_article'] |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | from __future__ import unicode_literals |
2 | ||
2 | import django | |
3 | 3 | from django.db import migrations, models |
4 | 4 | import django.utils.timezone |
5 | 5 | import django.core.validators |
6 | 6 | import django.contrib.auth.models |
7 | 7 | |
8 | if django.VERSION >= (1, 8): | |
9 | django_version_depend = {'managers': [ | |
10 | ('objects', django.contrib.auth.models.UserManager()), | |
11 | ]} | |
12 | else: | |
13 | django_version_depend = {} | |
14 | ||
8 | 15 | |
9 | 16 | class Migration(migrations.Migration): |
10 | 17 | |
11 | 18 | dependencies = [ |
12 | ('auth', '0006_require_contenttypes_0002'), | |
19 | ('auth', '0001_initial'), | |
13 | 20 | ] |
14 | 21 | |
15 | 22 | operations = [ |
49 | 56 | 'verbose_name': 'user', |
50 | 57 | 'verbose_name_plural': 'users', |
51 | 58 | }, |
52 | managers=[ | |
53 | ('objects', django.contrib.auth.models.UserManager()), | |
54 | ], | |
59 | **django_version_depend | |
55 | 60 | ), |
56 | 61 | ] |
4 | 4 | |
5 | 5 | if django.VERSION < (1, 5): |
6 | 6 | sys.stderr.write("ERROR: guardian's example project must be run with " |
7 | "Django 1.5 or later!\n") | |
7 | "Django 1.8 or later!\n") | |
8 | 8 | sys.exit(1) |
9 | 9 | |
10 | 10 |
26 | 26 | 'django.contrib.messages', |
27 | 27 | 'guardian', |
28 | 28 | 'posts', |
29 | 'articles', | |
29 | 30 | 'core', |
30 | 31 | 'django.contrib.staticfiles', |
31 | 32 | ) |
59 | 60 | ROOT_URLCONF = 'urls' |
60 | 61 | |
61 | 62 | TEMPLATE_CONTEXT_PROCESSORS = ( |
62 | 'context_processors.version', | |
63 | 'core.context_processors.version', | |
63 | 64 | "django.contrib.auth.context_processors.auth", |
64 | 65 | "django.template.context_processors.debug", |
65 | 66 | "django.template.context_processors.i18n", |
66 | 67 | "django.template.context_processors.media", |
67 | 68 | "django.template.context_processors.static", |
69 | "django.template.context_processors.request", | |
68 | 70 | "django.template.context_processors.tz", |
69 | 71 | "django.contrib.messages.context_processors.messages" |
70 | 72 | ) |
99 | 101 | ) |
100 | 102 | |
101 | 103 | AUTH_USER_MODEL = 'core.CustomUser' |
104 | ||
105 | ||
106 | TEMPLATES = [ | |
107 | { | |
108 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', | |
109 | 'DIRS': TEMPLATE_DIRS, | |
110 | 'OPTIONS': { | |
111 | 'debug': TEMPLATE_DEBUG, | |
112 | 'loaders': TEMPLATE_LOADERS, | |
113 | 'context_processors': TEMPLATE_CONTEXT_PROCESSORS, | |
114 | }, | |
115 | }, | |
116 | ] |
24 | 24 | <div class="nav-collapse collapse"> |
25 | 25 | <ul class="nav"> |
26 | 26 | <li class="active"><a href="{% url "posts_post_list" %}">Posts</a></li> |
27 | <li class="active"><a href="{% url "articles:list" %}">Articles</a></li> | |
27 | 28 | <li><a href="https://github.com/django-guardian/django-guardian" target="_repo">Repository</a></li> |
28 | 29 | <li><a href="https://github.com/django-guardian/django-guardian/issues" target="_issues">Issue tracker</a></li> |
29 | <li><a href="https://django-guardian.readthedocs.org/en/latest/" target="_docs">Documentation</a></li> | |
30 | <li><a href="https://django-guardian.readthedocs.io/en/latest/" target="_docs">Documentation</a></li> | |
30 | 31 | </ul> |
31 | 32 | <p class="navbar-text pull-right"> |
32 | 33 | {% if user.is_authenticated %} |
1 | 1 | from django.conf import settings |
2 | 2 | from django.contrib import admin |
3 | 3 | from django.contrib.auth.views import logout |
4 | from django.conf.urls import url | |
5 | 4 | |
6 | 5 | __all__ = ['handler404', 'handler500'] |
7 | 6 | |
11 | 10 | urlpatterns = [ |
12 | 11 | url(r'^admin/', include(admin.site.urls)), |
13 | 12 | url(r'^logout/$', logout, {'next_page': '/'}, name='logout'), |
13 | url(r'^article/', include('articles.urls', namespace='articles')), | |
14 | 14 | url(r'^', include('posts.urls')), |
15 | 15 | ] |
16 | 16 |
3 | 3 | from __future__ import unicode_literals |
4 | 4 | from . import checks |
5 | 5 | |
6 | default_app_config = 'guardian.apps.GuardianConfig' | |
6 | 7 | |
7 | try: | |
8 | from .version import version as __version__ | |
9 | __version__split__ = __version__.split(".") | |
10 | VERSION = tuple([int(each) for each in __version__split__[:3]] + __version__split__[3:]) | |
8 | # PEP 396: The __version__ attribute's value SHOULD be a string. | |
9 | __version__ = '1.4.8' | |
11 | 10 | |
12 | def get_version(): | |
13 | """ | |
14 | Returns shorter version (digit parts only) as string. | |
15 | """ | |
16 | return '.'.join((str(each) for each in VERSION[:3])) | |
17 | except ImportError: | |
18 | pass | |
11 | # Compatibility to eg. django-rest-framework | |
12 | VERSION = tuple(int(x) for x in __version__.split('.')[:3]) | |
19 | 13 | |
20 | 14 | |
21 | default_app_config = 'guardian.apps.GuardianConfig' | |
15 | def get_version(): | |
16 | return __version__ | |
22 | 17 | |
23 | 18 | |
24 | 19 | def monkey_patch_user(): |
0 | 0 | from __future__ import unicode_literals |
1 | ||
2 | import django | |
3 | 1 | from django import forms |
4 | 2 | from django.conf import settings |
5 | from guardian.compat import url | |
6 | from django.contrib import admin | |
7 | from django.contrib import messages | |
3 | from django.contrib import admin, messages | |
8 | 4 | from django.contrib.admin.widgets import FilteredSelectMultiple |
9 | from django.core.urlresolvers import reverse | |
10 | from django.shortcuts import render_to_response, get_object_or_404, redirect | |
5 | from django.shortcuts import get_object_or_404, redirect, render_to_response, render | |
11 | 6 | from django.template import RequestContext |
12 | from django.utils.translation import ugettext, ugettext_lazy as _ | |
13 | ||
14 | from guardian.compat import OrderedDict, get_user_model, get_model_name | |
15 | from guardian.forms import UserObjectPermissionsForm | |
16 | from guardian.forms import GroupObjectPermissionsForm | |
17 | from guardian.shortcuts import get_user_perms | |
18 | from guardian.shortcuts import get_group_perms | |
19 | from guardian.shortcuts import get_users_with_perms | |
20 | from guardian.shortcuts import get_groups_with_perms | |
21 | from guardian.shortcuts import get_perms_for_model | |
7 | from django.utils.translation import ugettext_lazy as _ | |
8 | from django.utils.translation import ugettext | |
9 | from guardian.compat import get_model_name, get_user_model, OrderedDict, url, reverse | |
10 | from guardian.forms import GroupObjectPermissionsForm, UserObjectPermissionsForm | |
22 | 11 | from guardian.models import Group |
12 | from guardian.shortcuts import (get_group_perms, get_groups_with_perms, get_perms_for_model, get_user_perms, | |
13 | get_users_with_perms) | |
14 | ||
15 | import django | |
23 | 16 | |
24 | 17 | |
25 | 18 | class AdminUserObjectPermissionsForm(UserObjectPermissionsForm): |
177 | 170 | ) |
178 | 171 | |
179 | 172 | if request.method == 'POST' and 'submit_manage_user' in request.POST: |
180 | user_form = UserManage(request.POST) | |
181 | group_form = GroupManage() | |
173 | user_form = self.get_obj_perms_user_select_form(request)(request.POST) | |
174 | group_form = self.get_obj_perms_group_select_form(request)(request.POST) | |
182 | 175 | info = ( |
183 | 176 | self.admin_site.name, |
184 | 177 | self.model._meta.app_label, |
192 | 185 | ) |
193 | 186 | return redirect(url) |
194 | 187 | elif request.method == 'POST' and 'submit_manage_group' in request.POST: |
195 | user_form = UserManage() | |
196 | group_form = GroupManage(request.POST) | |
188 | user_form = self.get_obj_perms_user_select_form(request)(request.POST) | |
189 | group_form = self.get_obj_perms_group_select_form(request)(request.POST) | |
197 | 190 | info = ( |
198 | 191 | self.admin_site.name, |
199 | 192 | self.model._meta.app_label, |
207 | 200 | ) |
208 | 201 | return redirect(url) |
209 | 202 | else: |
210 | user_form = UserManage() | |
211 | group_form = GroupManage() | |
203 | user_form = self.get_obj_perms_user_select_form(request)() | |
204 | group_form = self.get_obj_perms_group_select_form(request)() | |
212 | 205 | |
213 | 206 | context = self.get_obj_perms_base_context(request, obj) |
214 | 207 | context['users_perms'] = users_perms |
219 | 212 | # https://github.com/django/django/commit/cf1f36bb6eb34fafe6c224003ad585a647f6117b |
220 | 213 | request.current_app = self.admin_site.name |
221 | 214 | |
215 | if django.VERSION >= (1, 10): | |
216 | return render(request, self.get_obj_perms_manage_template(), context) | |
217 | ||
222 | 218 | return render_to_response(self.get_obj_perms_manage_template(), context, RequestContext(request)) |
223 | 219 | |
224 | 220 | def get_obj_perms_manage_template(self): |
245 | 241 | |
246 | 242 | user = get_object_or_404(get_user_model(), pk=user_id) |
247 | 243 | obj = get_object_or_404(self.get_queryset(request), pk=object_pk) |
248 | form_class = self.get_obj_perms_manage_user_form() | |
244 | form_class = self.get_obj_perms_manage_user_form(request) | |
249 | 245 | form = form_class(user, obj, request.POST or None) |
250 | 246 | |
251 | 247 | if request.method == 'POST' and form.is_valid(): |
270 | 266 | |
271 | 267 | request.current_app = self.admin_site.name |
272 | 268 | |
269 | if django.VERSION >= (1, 10): | |
270 | return render(request, self.get_obj_perms_manage_user_template(), context) | |
271 | ||
273 | 272 | return render_to_response(self.get_obj_perms_manage_user_template(), context, RequestContext(request)) |
274 | 273 | |
275 | 274 | def get_obj_perms_manage_user_template(self): |
286 | 285 | return 'admin/guardian/contrib/grappelli/obj_perms_manage_user.html' |
287 | 286 | return self.obj_perms_manage_user_template |
288 | 287 | |
289 | def get_obj_perms_manage_user_form(self): | |
288 | def get_obj_perms_user_select_form(self, request): | |
289 | """ | |
290 | Returns form class for selecting a user for permissions management. By | |
291 | default :form:`UserManage` is returned. | |
292 | """ | |
293 | return UserManage | |
294 | ||
295 | def get_obj_perms_group_select_form(self, request): | |
296 | """ | |
297 | Returns form class for selecting a group for permissions management. By | |
298 | default :form:`GroupManage` is returned. | |
299 | """ | |
300 | return GroupManage | |
301 | ||
302 | ||
303 | def get_obj_perms_manage_user_form(self, request): | |
290 | 304 | """ |
291 | 305 | Returns form class for user object permissions management. By default |
292 | 306 | :form:`AdminUserObjectPermissionsForm` is returned. |
303 | 317 | |
304 | 318 | group = get_object_or_404(Group, id=group_id) |
305 | 319 | obj = get_object_or_404(self.get_queryset(request), pk=object_pk) |
306 | form_class = self.get_obj_perms_manage_group_form() | |
320 | form_class = self.get_obj_perms_manage_group_form(request) | |
307 | 321 | form = form_class(group, obj, request.POST or None) |
308 | 322 | |
309 | 323 | if request.method == 'POST' and form.is_valid(): |
328 | 342 | |
329 | 343 | request.current_app = self.admin_site.name |
330 | 344 | |
345 | if django.VERSION >= (1, 10): | |
346 | return render(request, self.get_obj_perms_manage_group_template(), context) | |
347 | ||
331 | 348 | return render_to_response(self.get_obj_perms_manage_group_template(), context, RequestContext(request)) |
332 | 349 | |
333 | 350 | def get_obj_perms_manage_group_template(self): |
344 | 361 | return 'admin/guardian/contrib/grappelli/obj_perms_manage_group.html' |
345 | 362 | return self.obj_perms_manage_group_template |
346 | 363 | |
347 | def get_obj_perms_manage_group_form(self): | |
364 | def get_obj_perms_manage_group_form(self, request): | |
348 | 365 | """ |
349 | 366 | Returns form class for group object permissions management. By default |
350 | 367 | :form:`AdminGroupObjectPermissionsForm` is returned. |
0 | ||
0 | from . import monkey_patch_user | |
1 | 1 | from django.apps import AppConfig |
2 | from . import monkey_patch_user | |
3 | 2 | from guardian.conf import settings |
4 | 3 | |
5 | 4 |
0 | 0 | from __future__ import unicode_literals |
1 | ||
2 | 1 | from django.db import models |
3 | ||
4 | from guardian.compat import get_user_model | |
2 | from guardian.compat import get_user_model, is_authenticated | |
5 | 3 | from guardian.conf import settings |
4 | from guardian.core import ObjectPermissionChecker | |
5 | from guardian.ctypes import get_content_type | |
6 | 6 | from guardian.exceptions import WrongAppError |
7 | from guardian.core import ObjectPermissionChecker | |
8 | 7 | |
9 | 8 | |
10 | 9 | def check_object_support(obj): |
27 | 26 | """ |
28 | 27 | # This is how we support anonymous users - simply try to retrieve User |
29 | 28 | # instance and perform checks for that predefined user |
30 | if not user_obj.is_authenticated(): | |
29 | if not is_authenticated(user_obj): | |
31 | 30 | # If anonymous user permission is disabled then they are always |
32 | 31 | # unauthorized |
33 | 32 | if settings.ANONYMOUS_USER_NAME is None: |
84 | 83 | if '.' in perm: |
85 | 84 | app_label, perm = perm.split('.') |
86 | 85 | if app_label != obj._meta.app_label: |
87 | raise WrongAppError("Passed perm has app label of '%s' and " | |
88 | "given obj has '%s'" % (app_label, obj._meta.app_label)) | |
86 | # Check the content_type app_label when permission | |
87 | # and obj app labels don't match. | |
88 | ctype = get_content_type(obj) | |
89 | if app_label != ctype.app_label: | |
90 | raise WrongAppError("Passed perm has app label of '%s' while " | |
91 | "given obj has app label '%s' and given obj" | |
92 | "content_type has app label '%s'" % | |
93 | (app_label, obj._meta.app_label, ctype.app_label)) | |
89 | 94 | |
90 | 95 | check = ObjectPermissionChecker(user_obj) |
91 | 96 | return check.has_perm(perm, obj) |
0 | ||
0 | from django.conf import settings | |
1 | 1 | from django.core.checks import register, Tags, Warning |
2 | from django.conf import settings | |
3 | 2 | |
4 | 3 | |
5 | 4 | # noinspection PyUnusedLocal |
0 | 0 | from __future__ import unicode_literals |
1 | # OrderedDict only available in Python 2.7. | |
2 | # This will always be the case in Django 1.7 and above, as these versions | |
3 | # no longer support Python 2.6. | |
4 | ||
5 | from collections import OrderedDict | |
6 | from django.conf import settings | |
7 | from django.conf.urls import handler404, handler500, include, url | |
8 | from django.contrib.auth.models import AnonymousUser, Group, Permission | |
9 | from importlib import import_module | |
1 | 10 | |
2 | 11 | import django |
3 | from django.conf import settings | |
4 | from django.contrib.auth.models import Group | |
5 | from django.contrib.auth.models import Permission | |
6 | from django.contrib.auth.models import AnonymousUser | |
7 | 12 | import six |
8 | 13 | import sys |
9 | from importlib import import_module | |
10 | ||
11 | from django.conf.urls import url, include, handler404, handler500 | |
12 | 14 | |
13 | 15 | __all__ = [ |
14 | 16 | 'User', |
100 | 102 | basestring = unicode = str = str |
101 | 103 | |
102 | 104 | |
103 | # OrderedDict only available in Python 2.7. | |
104 | # This will always be the case in Django 1.7 and above, as these versions | |
105 | # no longer support Python 2.6. | |
106 | from collections import OrderedDict | |
107 | ||
108 | ||
109 | 105 | # Django 1.7 compatibility |
110 | 106 | # create_permission API changed: skip the create_models (second |
111 | 107 | # positional argument) if we have django 1.7+ and 2+ positional |
139 | 135 | if hasattr(settings, 'TEMPLATE_DEBUG'): |
140 | 136 | return settings.TEMPLATE_DEBUG |
141 | 137 | return settings.TEMPLATES[0]['OPTIONS'].get('DEBUG', False) |
138 | ||
139 | ||
140 | # Django 1.9 compatibility | |
141 | def remote_field(field): | |
142 | """ | |
143 | https://docs.djangoproject.com/en/1.9/releases/1.9/#field-rel-changes | |
144 | """ | |
145 | if django.VERSION < (1, 9): | |
146 | return field.rel | |
147 | return field.remote_field | |
148 | ||
149 | ||
150 | def remote_model(field): | |
151 | if django.VERSION < (1, 9): | |
152 | return remote_field(field).to | |
153 | return remote_field(field).model | |
154 | ||
155 | ||
156 | # Django 1.10 compatibility | |
157 | def is_authenticated(user): | |
158 | if django.VERSION < (1, 10): | |
159 | return user.is_authenticated() | |
160 | return user.is_authenticated | |
161 | ||
162 | ||
163 | def is_anonymous(user): | |
164 | if django.VERSION < (1, 10): | |
165 | return user.is_anonymous() | |
166 | return user.is_anonymous | |
167 | ||
168 | try: | |
169 | from django.urls import reverse, reverse_lazy | |
170 | except ImportError: | |
171 | from django.core.urlresolvers import reverse, reverse_lazy |
14 | 14 | RENDER_403 = getattr(settings, 'GUARDIAN_RENDER_403', False) |
15 | 15 | TEMPLATE_403 = getattr(settings, 'GUARDIAN_TEMPLATE_403', '403.html') |
16 | 16 | RAISE_403 = getattr(settings, 'GUARDIAN_RAISE_403', False) |
17 | RENDER_404 = getattr(settings, 'GUARDIAN_RENDER_404', False) | |
18 | TEMPLATE_404 = getattr(settings, 'GUARDIAN_TEMPLATE_404', '404.html') | |
19 | RAISE_404 = getattr(settings, 'GUARDIAN_RAISE_404', False) | |
17 | 20 | GET_INIT_ANONYMOUS_USER = getattr(settings, 'GUARDIAN_GET_INIT_ANONYMOUS_USER', |
18 | 21 | 'guardian.management.get_init_anonymous_user') |
19 | 22 | |
20 | 23 | MONKEY_PATCH = getattr(settings, 'GUARDIAN_MONKEY_PATCH', True) |
24 | ||
25 | GET_CONTENT_TYPE = getattr(settings, 'GUARDIAN_GET_CONTENT_TYPE', 'guardian.ctypes.get_default_content_type') | |
21 | 26 | |
22 | 27 | |
23 | 28 | def check_configuration(): |
0 | 0 | from __future__ import unicode_literals |
1 | ||
2 | from itertools import chain | |
3 | ||
4 | 1 | from django.contrib.auth.models import Permission |
5 | from django.contrib.contenttypes.models import ContentType | |
6 | 2 | from django.db.models.query import QuerySet |
7 | 3 | from django.utils.encoding import force_text |
8 | ||
9 | from guardian.utils import get_identity | |
10 | from guardian.utils import get_user_obj_perms_model | |
11 | from guardian.utils import get_group_obj_perms_model | |
12 | 4 | from guardian.compat import get_user_model |
5 | from guardian.ctypes import get_content_type | |
6 | from guardian.utils import get_group_obj_perms_model, get_identity, get_user_obj_perms_model | |
7 | from itertools import chain | |
13 | 8 | |
14 | 9 | |
15 | 10 | def _get_pks_model_and_ctype(objects): |
21 | 16 | if isinstance(objects, QuerySet): |
22 | 17 | model = objects.model |
23 | 18 | pks = [force_text(pk) for pk in objects.values_list('pk', flat=True)] |
24 | ctype = ContentType.objects.get_for_model(model) | |
19 | ctype = get_content_type(model) | |
25 | 20 | else: |
26 | 21 | pks = [] |
27 | 22 | for idx, obj in enumerate(objects): |
28 | 23 | if not idx: |
29 | 24 | model = type(obj) |
30 | ctype = ContentType.objects.get_for_model(model) | |
25 | ctype = get_content_type(model) | |
31 | 26 | pks.append(force_text(obj.pk)) |
32 | 27 | |
33 | 28 | return pks, model, ctype |
71 | 66 | :param obj: Django model instance for which permission should be checked |
72 | 67 | |
73 | 68 | """ |
74 | perm = perm.split('.')[-1] | |
75 | 69 | if self.user and not self.user.is_active: |
76 | 70 | return False |
77 | 71 | elif self.user and self.user.is_superuser: |
78 | 72 | return True |
73 | perm = perm.split('.')[-1] | |
79 | 74 | return perm in self.get_perms(obj) |
80 | 75 | |
81 | 76 | def get_group_filters(self, obj): |
82 | 77 | User = get_user_model() |
83 | ctype = ContentType.objects.get_for_model(obj) | |
78 | ctype = get_content_type(obj) | |
84 | 79 | |
85 | 80 | group_model = get_group_obj_perms_model(obj) |
86 | 81 | group_rel_name = group_model.permission.field.related_query_name() |
103 | 98 | return group_filters |
104 | 99 | |
105 | 100 | def get_user_filters(self, obj): |
106 | ctype = ContentType.objects.get_for_model(obj) | |
101 | ctype = get_content_type(obj) | |
107 | 102 | model = get_user_obj_perms_model(obj) |
108 | 103 | related_name = model.permission.field.related_query_name() |
109 | 104 | |
119 | 114 | return user_filters |
120 | 115 | |
121 | 116 | def get_user_perms(self, obj): |
122 | ctype = ContentType.objects.get_for_model(obj) | |
117 | ctype = get_content_type(obj) | |
123 | 118 | |
124 | 119 | perms_qs = Permission.objects.filter(content_type=ctype) |
125 | 120 | user_filters = self.get_user_filters(obj) |
129 | 124 | return user_perms |
130 | 125 | |
131 | 126 | def get_group_perms(self, obj): |
132 | ctype = ContentType.objects.get_for_model(obj) | |
127 | ctype = get_content_type(obj) | |
133 | 128 | |
134 | 129 | perms_qs = Permission.objects.filter(content_type=ctype) |
135 | 130 | group_filters = self.get_group_filters(obj) |
147 | 142 | """ |
148 | 143 | if self.user and not self.user.is_active: |
149 | 144 | return [] |
150 | ctype = ContentType.objects.get_for_model(obj) | |
145 | ctype = get_content_type(obj) | |
151 | 146 | key = self.get_local_cache_key(obj) |
152 | 147 | if key not in self._obj_perms_cache: |
153 | 148 | if self.user and self.user.is_superuser: |
173 | 168 | """ |
174 | 169 | Returns cache key for ``_obj_perms_cache`` dict. |
175 | 170 | """ |
176 | ctype = ContentType.objects.get_for_model(obj) | |
171 | ctype = get_content_type(obj) | |
177 | 172 | return (ctype.id, force_text(obj.pk)) |
178 | 173 | |
179 | 174 | def prefetch_perms(self, objects): |
203 | 198 | |
204 | 199 | group_model = get_group_obj_perms_model(model) |
205 | 200 | |
206 | group_filters = {} | |
207 | 201 | if self.user: |
208 | 202 | fieldname = 'group__%s' % ( |
209 | 203 | User.groups.field.related_query_name(), |
210 | 204 | ) |
211 | group_filters.update({fieldname: self.user}) | |
205 | group_filters = {fieldname: self.user} | |
212 | 206 | else: |
213 | 207 | group_filters = {'group': self.group} |
214 | 208 | |
248 | 242 | *(group_model.objects.filter(**group_filters).select_related('permission'),) |
249 | 243 | ) |
250 | 244 | |
245 | # initialize entry in '_obj_perms_cache' for all prefetched objects | |
246 | for obj in objects: | |
247 | key = self.get_local_cache_key(obj) | |
248 | if key not in self._obj_perms_cache: | |
249 | self._obj_perms_cache[key] = [] | |
250 | ||
251 | 251 | for perm in perms: |
252 | 252 | if type(perm).objects.is_generic(): |
253 | 253 | key = (ctype.id, perm.object_pk) |
254 | 254 | else: |
255 | 255 | key = (ctype.id, force_text(perm.content_object_id)) |
256 | 256 | |
257 | if key in self._obj_perms_cache: | |
258 | self._obj_perms_cache[key].append(perm.permission.codename) | |
259 | else: | |
260 | self._obj_perms_cache[key] = [perm.permission.codename] | |
257 | self._obj_perms_cache[key].append(perm.permission.codename) | |
261 | 258 | |
262 | 259 | return True |
0 | from __future__ import unicode_literals | |
1 | ||
2 | from django.contrib.contenttypes.models import ContentType | |
3 | ||
4 | from guardian.conf import settings as guardian_settings | |
5 | from guardian.compat import import_string | |
6 | ||
7 | ||
8 | def get_content_type(obj): | |
9 | get_content_type_function = import_string( | |
10 | guardian_settings.GET_CONTENT_TYPE) | |
11 | return get_content_type_function(obj) | |
12 | ||
13 | ||
14 | def get_default_content_type(obj): | |
15 | return ContentType.objects.get_for_model(obj) |
0 | 0 | from __future__ import unicode_literals |
1 | ||
1 | from django.apps import apps | |
2 | 2 | from django.conf import settings |
3 | 3 | from django.contrib.auth import REDIRECT_FIELD_NAME |
4 | from django.utils.functional import wraps | |
5 | 4 | from django.db.models import Model |
6 | from django.apps import apps | |
7 | 5 | from django.db.models.base import ModelBase |
8 | 6 | from django.db.models.query import QuerySet |
9 | 7 | from django.shortcuts import get_object_or_404 |
8 | from django.utils.functional import wraps | |
10 | 9 | from guardian.compat import basestring |
11 | 10 | from guardian.exceptions import GuardianError |
12 | from guardian.utils import get_403_or_None | |
11 | from guardian.utils import get_40x_or_None | |
13 | 12 | |
14 | 13 | |
15 | 14 | def permission_required(perm, lookup_variables=None, **kwargs): |
32 | 31 | login page, response with status code 403 is returned ( |
33 | 32 | ``django.http.HttpResponseForbidden`` instance or rendered template - |
34 | 33 | see :setting:`GUARDIAN_RENDER_403`). Defaults to ``False``. |
34 | :param return_404: if set to ``True`` then instead of redirecting to the | |
35 | login page, response with status code 404 is returned ( | |
36 | ``django.http.HttpResponseNotFound`` instance or rendered template - | |
37 | see :setting:`GUARDIAN_RENDER_404`). Defaults to ``False``. | |
35 | 38 | :param accept_global_perms: if set to ``True``, then *object level |
36 | 39 | permission* would be required **only if user does NOT have global |
37 | 40 | permission** for target *model*. If turned on, makes this decorator |
73 | 76 | redirect_field_name = kwargs.pop( |
74 | 77 | 'redirect_field_name', REDIRECT_FIELD_NAME) |
75 | 78 | return_403 = kwargs.pop('return_403', False) |
79 | return_404 = kwargs.pop('return_404', False) | |
76 | 80 | accept_global_perms = kwargs.pop('accept_global_perms', False) |
77 | 81 | |
78 | 82 | # Check if perm is given as string in order not to decorate |
113 | 117 | lookup_dict[lookup] = kwargs[view_arg] |
114 | 118 | obj = get_object_or_404(model, **lookup_dict) |
115 | 119 | |
116 | response = get_403_or_None(request, perms=[perm], obj=obj, | |
120 | response = get_40x_or_None(request, perms=[perm], obj=obj, | |
117 | 121 | login_url=login_url, redirect_field_name=redirect_field_name, |
118 | return_403=return_403, accept_global_perms=accept_global_perms) | |
122 | return_403=return_403, return_404=return_404, accept_global_perms=accept_global_perms) | |
119 | 123 | if response: |
120 | 124 | return response |
121 | 125 | return view_func(request, *args, **kwargs) |
136 | 140 | """ |
137 | 141 | kwargs['return_403'] = True |
138 | 142 | return permission_required(perm, *args, **kwargs) |
143 | ||
144 | ||
145 | def permission_required_or_404(perm, *args, **kwargs): | |
146 | """ | |
147 | Simple wrapper for permission_required decorator. | |
148 | ||
149 | Standard Django's permission_required decorator redirects user to login page | |
150 | in case permission check failed. This decorator may be used to return | |
151 | HttpResponseNotFound (status 404) instead of redirection. | |
152 | ||
153 | The only difference between ``permission_required`` decorator is that this | |
154 | one always set ``return_404`` parameter to ``True``. | |
155 | """ | |
156 | kwargs['return_404'] = True | |
157 | return permission_required(perm, *args, **kwargs) |
0 | 0 | from __future__ import unicode_literals |
1 | ||
2 | 1 | from django import forms |
3 | 2 | from django.utils.translation import ugettext as _ |
4 | ||
5 | from guardian.shortcuts import assign_perm | |
6 | from guardian.shortcuts import remove_perm | |
7 | from guardian.shortcuts import get_user_perms | |
8 | from guardian.shortcuts import get_group_perms | |
9 | from guardian.shortcuts import get_perms_for_model | |
3 | from guardian.shortcuts import assign_perm, get_group_perms, get_perms_for_model, get_user_perms, remove_perm | |
10 | 4 | |
11 | 5 | |
12 | 6 | class BaseObjectPermissionsForm(forms.Form): |
0 | 0 | from __future__ import unicode_literals |
1 | ||
2 | 1 | from django.db import models |
3 | from django.contrib.contenttypes.models import ContentType | |
4 | ||
2 | from django.db.models import Q | |
3 | from guardian.core import ObjectPermissionChecker | |
4 | from guardian.ctypes import get_content_type | |
5 | 5 | from guardian.exceptions import ObjectNotPersisted |
6 | 6 | from guardian.models import Permission |
7 | ||
7 | 8 | import warnings |
8 | ||
9 | # TODO: consolidate UserObjectPermissionManager and | |
10 | # GroupObjectPermissionManager | |
11 | 9 | |
12 | 10 | |
13 | 11 | class BaseObjectPermissionManager(models.Manager): |
12 | ||
13 | @property | |
14 | def user_or_group_field(self): | |
15 | try: | |
16 | self.model._meta.get_field('user') | |
17 | return 'user' | |
18 | except models.fields.FieldDoesNotExist: | |
19 | return 'group' | |
14 | 20 | |
15 | 21 | def is_generic(self): |
16 | 22 | try: |
19 | 25 | except models.fields.FieldDoesNotExist: |
20 | 26 | return False |
21 | 27 | |
22 | ||
23 | class UserObjectPermissionManager(BaseObjectPermissionManager): | |
24 | ||
25 | def assign_perm(self, perm, user, obj): | |
28 | def assign_perm(self, perm, user_or_group, obj): | |
26 | 29 | """ |
27 | 30 | Assigns permission with given ``perm`` for an instance ``obj`` and |
28 | 31 | ``user``. |
30 | 33 | if getattr(obj, 'pk', None) is None: |
31 | 34 | raise ObjectNotPersisted("Object %s needs to be persisted first" |
32 | 35 | % obj) |
33 | ctype = ContentType.objects.get_for_model(obj) | |
34 | permission = Permission.objects.get(content_type=ctype, codename=perm) | |
36 | ctype = get_content_type(obj) | |
37 | if not isinstance(perm, Permission): | |
38 | permission = Permission.objects.get(content_type=ctype, codename=perm) | |
39 | else: | |
40 | permission = perm | |
35 | 41 | |
36 | kwargs = {'permission': permission, 'user': user} | |
42 | kwargs = {'permission': permission, self.user_or_group_field: user_or_group} | |
37 | 43 | if self.is_generic(): |
38 | 44 | kwargs['content_type'] = ctype |
39 | 45 | kwargs['object_pk'] = obj.pk |
40 | 46 | else: |
41 | 47 | kwargs['content_object'] = obj |
42 | obj_perm, created = self.get_or_create(**kwargs) | |
48 | obj_perm, _ = self.get_or_create(**kwargs) | |
43 | 49 | return obj_perm |
44 | 50 | |
45 | def assign(self, perm, user, obj): | |
51 | def bulk_assign_perm(self, perm, user_or_group, queryset): | |
52 | """ | |
53 | Bulk assigns permissions with given ``perm`` for an objects in ``queryset`` and | |
54 | ``user_or_group``. | |
55 | """ | |
56 | ||
57 | ctype = get_content_type(queryset.model) | |
58 | if not isinstance(perm, Permission): | |
59 | permission = Permission.objects.get(content_type=ctype, codename=perm) | |
60 | else: | |
61 | permission = perm | |
62 | ||
63 | checker = ObjectPermissionChecker(user_or_group) | |
64 | checker.prefetch_perms(queryset) | |
65 | ||
66 | assigned_perms = [] | |
67 | for instance in queryset: | |
68 | if not checker.has_perm(permission.codename, instance): | |
69 | kwargs = {'permission': permission, self.user_or_group_field: user_or_group} | |
70 | if self.is_generic(): | |
71 | kwargs['content_type'] = ctype | |
72 | kwargs['object_pk'] = instance.pk | |
73 | else: | |
74 | kwargs['content_object'] = instance | |
75 | assigned_perms.append(self.model(**kwargs)) | |
76 | self.model.objects.bulk_create(assigned_perms) | |
77 | ||
78 | return assigned_perms | |
79 | ||
80 | def assign(self, perm, user_or_group, obj): | |
46 | 81 | """ Depreciated function name left in for compatibility""" |
47 | 82 | warnings.warn("UserObjectPermissionManager method 'assign' is being renamed to 'assign_perm'. Update your code accordingly as old name will be depreciated in 2.0 version.", DeprecationWarning) |
48 | return self.assign_perm(perm, user, obj) | |
83 | return self.assign_perm(perm, user_or_group, obj) | |
49 | 84 | |
50 | def remove_perm(self, perm, user, obj): | |
85 | def remove_perm(self, perm, user_or_group, obj): | |
51 | 86 | """ |
52 | Removes permission ``perm`` for an instance ``obj`` and given ``user``. | |
87 | Removes permission ``perm`` for an instance ``obj`` and given ``user_or_group``. | |
53 | 88 | |
54 | 89 | Please note that we do NOT fetch object permission from database - we |
55 | 90 | use ``Queryset.delete`` method for removing it. Main implication of this |
58 | 93 | if getattr(obj, 'pk', None) is None: |
59 | 94 | raise ObjectNotPersisted("Object %s needs to be persisted first" |
60 | 95 | % obj) |
61 | filters = { | |
62 | 'permission__codename': perm, | |
63 | 'permission__content_type': ContentType.objects.get_for_model(obj), | |
64 | 'user': user, | |
65 | } | |
96 | ||
97 | filters = Q(**{self.user_or_group_field: user_or_group}) | |
98 | ||
99 | if isinstance(perm, Permission): | |
100 | filters &= Q(permission=perm) | |
101 | else: | |
102 | filters &= Q(permission__codename=perm, | |
103 | permission__content_type=get_content_type(obj)) | |
104 | ||
66 | 105 | if self.is_generic(): |
67 | filters['object_pk'] = obj.pk | |
106 | filters &= Q(object_pk=obj.pk) | |
68 | 107 | else: |
69 | filters['content_object__pk'] = obj.pk | |
70 | self.filter(**filters).delete() | |
108 | filters &= Q(content_object__pk=obj.pk) | |
109 | return self.filter(filters).delete() | |
110 | ||
111 | def bulk_remove_perm(self, perm, user_or_group, queryset): | |
112 | """ | |
113 | Removes permission ``perm`` for a ``queryset`` and given ``user_or_group``. | |
114 | ||
115 | Please note that we do NOT fetch object permission from database - we | |
116 | use ``Queryset.delete`` method for removing it. Main implication of this | |
117 | is that ``post_delete`` signals would NOT be fired. | |
118 | """ | |
119 | filters = Q(**{self.user_or_group_field: user_or_group}) | |
120 | ||
121 | if isinstance(perm, Permission): | |
122 | filters &= Q(permission=perm) | |
123 | else: | |
124 | ctype = get_content_type(queryset.model) | |
125 | filters &= Q(permission__codename=perm, | |
126 | permission__content_type=ctype) | |
127 | ||
128 | if self.is_generic(): | |
129 | filters &= Q(object_pk__in = [str(pk) for pk in queryset.values_list('pk', flat=True)]) | |
130 | else: | |
131 | filters &= Q(content_object__in=queryset) | |
132 | ||
133 | return self.filter(filters).delete() | |
134 | ||
135 | ||
136 | class UserObjectPermissionManager(BaseObjectPermissionManager): | |
137 | pass | |
71 | 138 | |
72 | 139 | |
73 | 140 | class GroupObjectPermissionManager(BaseObjectPermissionManager): |
74 | ||
75 | def assign_perm(self, perm, group, obj): | |
76 | """ | |
77 | Assigns permission with given ``perm`` for an instance ``obj`` and | |
78 | ``group``. | |
79 | """ | |
80 | if getattr(obj, 'pk', None) is None: | |
81 | raise ObjectNotPersisted("Object %s needs to be persisted first" | |
82 | % obj) | |
83 | ctype = ContentType.objects.get_for_model(obj) | |
84 | permission = Permission.objects.get(content_type=ctype, codename=perm) | |
85 | ||
86 | kwargs = {'permission': permission, 'group': group} | |
87 | if self.is_generic(): | |
88 | kwargs['content_type'] = ctype | |
89 | kwargs['object_pk'] = obj.pk | |
90 | else: | |
91 | kwargs['content_object'] = obj | |
92 | obj_perm, created = self.get_or_create(**kwargs) | |
93 | return obj_perm | |
94 | ||
95 | def assign(self, perm, user, obj): | |
96 | """ Depreciated function name left in for compatibility""" | |
97 | warnings.warn("UserObjectPermissionManager method 'assign' is being renamed to 'assign_perm'. Update your code accordingly as old name will be depreciated in 2.0 version.", DeprecationWarning) | |
98 | return self.assign_perm(perm, user, obj) | |
99 | ||
100 | def remove_perm(self, perm, group, obj): | |
101 | """ | |
102 | Removes permission ``perm`` for an instance ``obj`` and given ``group``. | |
103 | """ | |
104 | if getattr(obj, 'pk', None) is None: | |
105 | raise ObjectNotPersisted("Object %s needs to be persisted first" | |
106 | % obj) | |
107 | filters = { | |
108 | 'permission__codename': perm, | |
109 | 'permission__content_type': ContentType.objects.get_for_model(obj), | |
110 | 'group': group, | |
111 | } | |
112 | if self.is_generic(): | |
113 | filters['object_pk'] = obj.pk | |
114 | else: | |
115 | filters['content_object__pk'] = obj.pk | |
116 | ||
117 | self.filter(**filters).delete() | |
141 | pass |
20 | 20 | serialize=False, auto_created=True, verbose_name='ID')), |
21 | 21 | ('object_pk', models.CharField( |
22 | 22 | max_length=255, verbose_name='object ID')), |
23 | ('content_type', models.ForeignKey(to='contenttypes.ContentType')), | |
24 | ('group', models.ForeignKey(to='auth.Group')), | |
25 | ('permission', models.ForeignKey(to='auth.Permission')), | |
23 | ('content_type', models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE)), | |
24 | ('group', models.ForeignKey(to='auth.Group', on_delete=models.CASCADE)), | |
25 | ('permission', models.ForeignKey(to='auth.Permission', on_delete=models.CASCADE)), | |
26 | 26 | ], |
27 | 27 | options={ |
28 | 28 | }, |
35 | 35 | serialize=False, auto_created=True, verbose_name='ID')), |
36 | 36 | ('object_pk', models.CharField( |
37 | 37 | max_length=255, verbose_name='object ID')), |
38 | ('content_type', models.ForeignKey(to='contenttypes.ContentType')), | |
39 | ('permission', models.ForeignKey(to='auth.Permission')), | |
40 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), | |
38 | ('content_type', models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE)), | |
39 | ('permission', models.ForeignKey(to='auth.Permission', on_delete=models.CASCADE)), | |
40 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), | |
41 | 41 | ], |
42 | 42 | options={ |
43 | 43 | }, |
0 | 0 | from __future__ import unicode_literals |
1 | ||
2 | 1 | from collections import Iterable |
3 | 2 | from django.conf import settings |
4 | from django.contrib.auth.decorators import REDIRECT_FIELD_NAME | |
5 | from django.contrib.auth.decorators import login_required | |
6 | from django.core.exceptions import ImproperlyConfigured | |
7 | from django.core.exceptions import PermissionDenied | |
3 | from django.contrib.auth.decorators import login_required, REDIRECT_FIELD_NAME | |
4 | from django.core.exceptions import ImproperlyConfigured, PermissionDenied | |
8 | 5 | from guardian.compat import basestring |
9 | 6 | from guardian.models import UserObjectPermission |
10 | from guardian.utils import get_403_or_None | |
11 | from guardian.utils import get_anonymous_user | |
7 | from guardian.utils import get_40x_or_None, get_anonymous_user | |
8 | from guardian.shortcuts import get_objects_for_user | |
12 | 9 | |
13 | 10 | |
14 | 11 | class LoginRequiredMixin(object): |
105 | 102 | *Default*: ``False``. Returns 403 error page instead of redirecting |
106 | 103 | user. |
107 | 104 | |
105 | ``PermissionRequiredMixin.return_404`` | |
106 | ||
107 | *Default*: ``False``. Returns 404 error page instead of redirecting | |
108 | user. | |
109 | ||
108 | 110 | ``PermissionRequiredMixin.raise_exception`` |
109 | 111 | |
110 | 112 | *Default*: ``False`` |
119 | 121 | proceed to check object level permissions. |
120 | 122 | |
121 | 123 | ``PermissionRequiredMixin.permission_object`` |
122 | *Default*: ``None``, object against which test the permission; if None fallback | |
124 | *Default*: ``(not set)``, object against which test the permission; if not set fallback | |
123 | 125 | to ``self.get_permission_object()`` which return ``self.get_object()`` |
124 | 126 | or ``self.object`` by default. |
125 | 127 | |
129 | 131 | permission_required = None |
130 | 132 | redirect_field_name = REDIRECT_FIELD_NAME |
131 | 133 | return_403 = False |
134 | return_404 = False | |
132 | 135 | raise_exception = False |
133 | 136 | accept_global_perms = False |
134 | permission_object = None | |
135 | 137 | |
136 | 138 | def get_required_permissions(self, request=None): |
137 | 139 | """ |
153 | 155 | return perms |
154 | 156 | |
155 | 157 | def get_permission_object(self): |
156 | if self.permission_object: | |
158 | if hasattr(self, 'permission_object'): | |
157 | 159 | return self.permission_object |
158 | return (hasattr(self, 'get_object') and self.get_object() | |
159 | or getattr(self, 'object', None)) | |
160 | return (hasattr(self, 'get_object') and self.get_object() or | |
161 | getattr(self, 'object', None)) | |
160 | 162 | |
161 | 163 | def check_permissions(self, request): |
162 | 164 | """ |
167 | 169 | """ |
168 | 170 | obj = self.get_permission_object() |
169 | 171 | |
170 | forbidden = get_403_or_None(request, | |
172 | forbidden = get_40x_or_None(request, | |
171 | 173 | perms=self.get_required_permissions( |
172 | 174 | request), |
173 | 175 | obj=obj, |
174 | 176 | login_url=self.login_url, |
175 | 177 | redirect_field_name=self.redirect_field_name, |
176 | 178 | return_403=self.return_403, |
179 | return_404=self.return_404, | |
177 | 180 | accept_global_perms=self.accept_global_perms |
178 | 181 | ) |
179 | 182 | if forbidden: |
215 | 218 | |
216 | 219 | def del_obj_perm(self, perm, obj): |
217 | 220 | return UserObjectPermission.objects.remove_perm(perm, self, obj) |
221 | ||
222 | ||
223 | class PermissionListMixin(object): | |
224 | """ | |
225 | A view mixin that filter object in queryset for the current logged by required permission. | |
226 | ||
227 | **Example Usage**:: | |
228 | ||
229 | class SecureView(PermissionListMixin, ListView): | |
230 | ... | |
231 | permission_required = 'articles.view_article' | |
232 | ... | |
233 | ||
234 | or:: | |
235 | ||
236 | class SecureView(PermissionListMixin, ListView): | |
237 | ... | |
238 | permission_required = 'auth.change_user' | |
239 | get_objects_for_user_extra_kwargs = {'use_groups': False} | |
240 | ... | |
241 | ||
242 | **Class Settings** | |
243 | ||
244 | ``PermissionListMixin.permission_required`` | |
245 | ||
246 | *Default*: ``None``, must be set to either a string or list of strings | |
247 | in format: *<app_label>.<permission_codename>*. | |
248 | ||
249 | ``PermissionListMixin.get_objects_for_user_extra_kwargs`` | |
250 | ||
251 | *Default*: ``{}``, A extra params to pass for ```guardian.shorcuts.get_objects_for_user``` | |
252 | ||
253 | """ | |
254 | permission_required = None | |
255 | get_objects_for_user_extra_kwargs = {} | |
256 | ||
257 | def get_required_permissions(self, request=None): | |
258 | """ | |
259 | Returns list of permissions in format *<app_label>.<codename>* that | |
260 | should be checked against *request.user* and *object*. By default, it | |
261 | returns list from ``permission_required`` attribute. | |
262 | ||
263 | :param request: Original request. | |
264 | """ | |
265 | if isinstance(self.permission_required, basestring): | |
266 | perms = [self.permission_required] | |
267 | elif isinstance(self.permission_required, Iterable): | |
268 | perms = [p for p in self.permission_required] | |
269 | else: | |
270 | raise ImproperlyConfigured("'PermissionRequiredMixin' requires " | |
271 | "'permission_required' attribute to be set to " | |
272 | "'<app_label>.<permission codename>' but is set to '%s' instead" | |
273 | % self.permission_required) | |
274 | return perms | |
275 | ||
276 | def get_get_objects_for_user_kwargs(self, queryset): | |
277 | """ | |
278 | Returns dict of kwargs that should be pass to ```get_objects_for_user```. | |
279 | ||
280 | :param request: Queryset to filter | |
281 | """ | |
282 | return dict(user=self.request.user, | |
283 | perms=self.get_required_permissions(self.request), | |
284 | klass=queryset, | |
285 | **self.get_objects_for_user_extra_kwargs) | |
286 | ||
287 | def get_queryset(self, *args, **kwargs): | |
288 | qs = super(PermissionListMixin, self).get_queryset(*args, **kwargs) | |
289 | return get_objects_for_user(**self.get_get_objects_for_user_kwargs(qs)) |
0 | 0 | from __future__ import unicode_literals |
1 | ||
1 | from django.contrib.auth.models import Group, Permission | |
2 | from django.contrib.contenttypes.models import ContentType | |
3 | from django.core.exceptions import ValidationError | |
2 | 4 | from django.db import models |
3 | from django.core.exceptions import ValidationError | |
4 | from django.contrib.auth.models import Group | |
5 | from django.contrib.auth.models import Permission | |
6 | from django.contrib.contenttypes.models import ContentType | |
5 | from django.utils.translation import ugettext_lazy as _ | |
6 | from guardian.compat import unicode, user_model_label | |
7 | from guardian.ctypes import get_content_type | |
8 | from guardian.managers import GroupObjectPermissionManager, UserObjectPermissionManager | |
7 | 9 | |
8 | 10 | try: |
9 | 11 | from django.contrib.contenttypes.fields import GenericForeignKey |
10 | 12 | except ImportError: |
11 | 13 | from django.contrib.contenttypes.generic import GenericForeignKey |
12 | ||
13 | from django.utils.translation import ugettext_lazy as _ | |
14 | ||
15 | from guardian.compat import user_model_label | |
16 | from guardian.compat import unicode | |
17 | from guardian.managers import GroupObjectPermissionManager | |
18 | from guardian.managers import UserObjectPermissionManager | |
19 | 14 | |
20 | 15 | |
21 | 16 | class BaseObjectPermission(models.Model): |
23 | 18 | Abstract ObjectPermission class. Actual class should additionally define |
24 | 19 | a ``content_object`` field and either ``user`` or ``group`` field. |
25 | 20 | """ |
26 | permission = models.ForeignKey(Permission) | |
21 | permission = models.ForeignKey(Permission, on_delete=models.CASCADE) | |
27 | 22 | |
28 | 23 | class Meta: |
29 | 24 | abstract = True |
35 | 30 | unicode(self.permission.codename)) |
36 | 31 | |
37 | 32 | def save(self, *args, **kwargs): |
38 | content_type = ContentType.objects.get_for_model(self.content_object) | |
33 | content_type = get_content_type(self.content_object) | |
39 | 34 | if content_type != self.permission.content_type: |
40 | 35 | raise ValidationError("Cannot persist permission not designed for " |
41 | 36 | "this class (permission's type is %r and object's type is %r)" |
44 | 39 | |
45 | 40 | |
46 | 41 | class BaseGenericObjectPermission(models.Model): |
47 | content_type = models.ForeignKey(ContentType) | |
42 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) | |
48 | 43 | object_pk = models.CharField(_('object ID'), max_length=255) |
49 | 44 | content_object = GenericForeignKey(fk_field='object_pk') |
50 | 45 | |
56 | 51 | """ |
57 | 52 | **Manager**: :manager:`UserObjectPermissionManager` |
58 | 53 | """ |
59 | user = models.ForeignKey(user_model_label) | |
54 | user = models.ForeignKey(user_model_label, on_delete=models.CASCADE) | |
60 | 55 | |
61 | 56 | objects = UserObjectPermissionManager() |
62 | 57 | |
75 | 70 | """ |
76 | 71 | **Manager**: :manager:`GroupObjectPermissionManager` |
77 | 72 | """ |
78 | group = models.ForeignKey(Group) | |
73 | group = models.ForeignKey(Group, on_delete=models.CASCADE) | |
79 | 74 | |
80 | 75 | objects = GroupObjectPermissionManager() |
81 | 76 |
2 | 2 | """ |
3 | 3 | from __future__ import unicode_literals |
4 | 4 | |
5 | from django.contrib.auth.models import Group | |
6 | from django.contrib.auth.models import Permission | |
5 | import warnings | |
6 | from collections import defaultdict | |
7 | from itertools import groupby | |
8 | ||
9 | from django.apps import apps | |
10 | from django.contrib.auth.models import Group, Permission | |
7 | 11 | from django.contrib.contenttypes.models import ContentType |
8 | from django.db.models import Count, Q | |
9 | from django.apps import apps | |
12 | from django.db.models import Count, Q, QuerySet | |
10 | 13 | from django.shortcuts import _get_queryset |
11 | from itertools import groupby | |
12 | ||
13 | from guardian.compat import basestring | |
14 | from guardian.compat import get_user_model | |
14 | ||
15 | from guardian.compat import basestring, get_user_model, is_anonymous | |
15 | 16 | from guardian.core import ObjectPermissionChecker |
16 | from guardian.exceptions import MixedContentTypeError | |
17 | from guardian.exceptions import WrongAppError | |
18 | from guardian.utils import get_anonymous_user | |
19 | from guardian.utils import get_group_obj_perms_model | |
20 | from guardian.utils import get_identity | |
21 | from guardian.utils import get_user_obj_perms_model | |
22 | import warnings | |
17 | from guardian.ctypes import get_content_type | |
18 | from guardian.exceptions import MixedContentTypeError, WrongAppError | |
19 | from guardian.models import GroupObjectPermission | |
20 | from guardian.utils import get_anonymous_user, get_group_obj_perms_model, get_identity, get_user_obj_perms_model | |
23 | 21 | |
24 | 22 | |
25 | 23 | def assign_perm(perm, user_or_group, obj=None): |
27 | 25 | Assigns permission to user/group and object pair. |
28 | 26 | |
29 | 27 | :param perm: proper permission for given ``obj``, as string (in format: |
30 | ``app_label.codename`` or ``codename``). If ``obj`` is not given, must | |
31 | be in format ``app_label.codename``. | |
28 | ``app_label.codename`` or ``codename``) or ``Permission`` instance. | |
29 | If ``obj`` is not given, must be in format ``app_label.codename`` or | |
30 | ``Permission`` instance. | |
32 | 31 | |
33 | 32 | :param user_or_group: instance of ``User``, ``AnonymousUser`` or ``Group``; |
34 | 33 | passing any other object would raise |
35 | 34 | ``guardian.exceptions.NotUserNorGroup`` exception |
36 | 35 | |
37 | :param obj: persisted Django's ``Model`` instance or ``None`` if assigning | |
38 | global permission. Default is ``None``. | |
36 | :param obj: persisted Django's ``Model`` instance or QuerySet of Django | |
37 | ``Model`` instances or ``None`` if assigning global permission. | |
38 | Default is ``None``. | |
39 | 39 | |
40 | 40 | We can assign permission for ``Model`` instance for specific user: |
41 | 41 | |
72 | 72 | user, group = get_identity(user_or_group) |
73 | 73 | # If obj is None we try to operate on global permissions |
74 | 74 | if obj is None: |
75 | try: | |
76 | app_label, codename = perm.split('.', 1) | |
77 | except ValueError: | |
78 | raise ValueError("For global permissions, first argument must be in" | |
79 | " format: 'app_label.codename' (is %r)" % perm) | |
80 | perm = Permission.objects.get(content_type__app_label=app_label, | |
81 | codename=codename) | |
75 | if not isinstance(perm, Permission): | |
76 | try: | |
77 | app_label, codename = perm.split('.', 1) | |
78 | except ValueError: | |
79 | raise ValueError("For global permissions, first argument must be in" | |
80 | " format: 'app_label.codename' (is %r)" % perm) | |
81 | perm = Permission.objects.get(content_type__app_label=app_label, | |
82 | codename=codename) | |
83 | ||
82 | 84 | if user: |
83 | 85 | user.user_permissions.add(perm) |
84 | 86 | return perm |
85 | 87 | if group: |
86 | 88 | group.permissions.add(perm) |
87 | 89 | return perm |
88 | perm = perm.split('.')[-1] | |
90 | ||
91 | if not isinstance(perm, Permission): | |
92 | perm = perm.split('.')[-1] | |
93 | ||
94 | if isinstance(obj, QuerySet): | |
95 | if user: | |
96 | model = get_user_obj_perms_model(obj.model) | |
97 | return model.objects.bulk_assign_perm(perm, user, obj) | |
98 | if group: | |
99 | model = get_group_obj_perms_model(obj.model) | |
100 | return model.objects.bulk_assign_perm(perm, group, obj) | |
101 | ||
89 | 102 | if user: |
90 | 103 | model = get_user_obj_perms_model(obj) |
91 | 104 | return model.objects.assign_perm(perm, user, obj) |
105 | ||
92 | 106 | if group: |
93 | 107 | model = get_group_obj_perms_model(obj) |
94 | 108 | return model.objects.assign_perm(perm, group, obj) |
96 | 110 | |
97 | 111 | def assign(perm, user_or_group, obj=None): |
98 | 112 | """ Depreciated function name left in for compatibility""" |
99 | warnings.warn("Shortcut function 'assign' is being renamed to 'assign_perm'. Update your code accordingly as old name will be depreciated in 2.0 version.", DeprecationWarning) | |
113 | warnings.warn( | |
114 | "Shortcut function 'assign' is being renamed to 'assign_perm'. Update your code accordingly as old name will be depreciated in 2.0 version.", | |
115 | DeprecationWarning) | |
100 | 116 | return assign_perm(perm, user_or_group, obj) |
101 | 117 | |
102 | 118 | |
112 | 128 | passing any other object would raise |
113 | 129 | ``guardian.exceptions.NotUserNorGroup`` exception |
114 | 130 | |
115 | :param obj: persisted Django's ``Model`` instance or ``None`` if assigning | |
116 | global permission. Default is ``None``. | |
131 | :param obj: persisted Django's ``Model`` instance or QuerySet of Django | |
132 | ``Model`` instances or ``None`` if assigning global permission. | |
133 | Default is ``None``. | |
117 | 134 | |
118 | 135 | """ |
119 | 136 | user, group = get_identity(user_or_group) |
131 | 148 | elif group: |
132 | 149 | group.permissions.remove(perm) |
133 | 150 | return |
134 | perm = perm.split('.')[-1] | |
151 | ||
152 | if not isinstance(perm, Permission): | |
153 | perm = perm.split('.')[-1] | |
154 | ||
155 | if isinstance(obj, QuerySet): | |
156 | if user: | |
157 | model = get_user_obj_perms_model(obj.model) | |
158 | return model.objects.bulk_remove_perm(perm, user, obj) | |
159 | if group: | |
160 | model = get_group_obj_perms_model(obj.model) | |
161 | return model.objects.bulk_remove_perm(perm, group, obj) | |
162 | ||
135 | 163 | if user: |
136 | 164 | model = get_user_obj_perms_model(obj) |
137 | model.objects.remove_perm(perm, user, obj) | |
165 | return model.objects.remove_perm(perm, user, obj) | |
166 | ||
138 | 167 | if group: |
139 | 168 | model = get_group_obj_perms_model(obj) |
140 | model.objects.remove_perm(perm, group, obj) | |
169 | return model.objects.remove_perm(perm, group, obj) | |
141 | 170 | |
142 | 171 | |
143 | 172 | def get_perms(user_or_group, obj): |
177 | 206 | model = apps.get_model(app_label, model_name) |
178 | 207 | else: |
179 | 208 | model = cls |
180 | ctype = ContentType.objects.get_for_model(model) | |
209 | ctype = get_content_type(model) | |
181 | 210 | return Permission.objects.filter(content_type=ctype) |
182 | 211 | |
183 | 212 | |
217 | 246 | {<User: joe>: [u'change_flatpage']} |
218 | 247 | |
219 | 248 | """ |
220 | ctype = ContentType.objects.get_for_model(obj) | |
249 | ctype = get_content_type(obj) | |
221 | 250 | if not attach_perms: |
222 | 251 | # It's much easier without attached perms so we do it first if that is |
223 | 252 | # the case |
289 | 318 | {<Group: admins>: [u'change_flatpage']} |
290 | 319 | |
291 | 320 | """ |
292 | ctype = ContentType.objects.get_for_model(obj) | |
321 | ctype = get_content_type(obj) | |
322 | group_model = get_group_obj_perms_model(obj) | |
323 | ||
293 | 324 | if not attach_perms: |
294 | # It's much easier without attached perms so we do it first if that is | |
295 | # the case | |
296 | group_model = get_group_obj_perms_model(obj) | |
325 | # It's much easier without attached perms so we do it first if that is the case | |
297 | 326 | group_rel_name = group_model.group.field.related_query_name() |
298 | 327 | if group_model.objects.is_generic(): |
299 | 328 | group_filters = { |
302 | 331 | } |
303 | 332 | else: |
304 | 333 | group_filters = {'%s__content_object' % group_rel_name: obj} |
305 | groups = Group.objects.filter(**group_filters).distinct() | |
306 | return groups | |
334 | return Group.objects.filter(**group_filters).distinct() | |
307 | 335 | else: |
308 | # TODO: Do not hit db for each group! | |
309 | groups = {} | |
310 | for group in get_groups_with_perms(obj): | |
311 | if group not in groups: | |
312 | groups[group] = sorted(get_group_perms(group, obj)) | |
313 | return groups | |
336 | group_perms_mapping = defaultdict(list) | |
337 | groups_with_perms = get_groups_with_perms(obj) | |
338 | qs = group_model.objects.filter(group__in=groups_with_perms).prefetch_related('group', 'permission') | |
339 | if group_model is GroupObjectPermission: | |
340 | qs = qs.filter(object_pk=obj.pk) | |
341 | else: | |
342 | qs = qs.filter(content_object_id=obj.pk) | |
343 | ||
344 | for group_perm in qs: | |
345 | group_perms_mapping[group_perm.group].append(group_perm.permission.codename) | |
346 | return dict(group_perms_mapping) | |
314 | 347 | |
315 | 348 | |
316 | 349 | def get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=False, |
318 | 351 | """ |
319 | 352 | Returns queryset of objects for which a given ``user`` has *all* |
320 | 353 | permissions present at ``perms``. |
321 | ||
322 | If ``perms`` is an empty list, then it returns objects for which | |
323 | a given ``user`` has *any* object permission. | |
324 | 354 | |
325 | 355 | :param user: ``User`` or ``AnonymousUser`` instance for which objects would |
326 | 356 | be returned. |
447 | 477 | # Compute queryset and ctype if still missing |
448 | 478 | if ctype is None and klass is not None: |
449 | 479 | queryset = _get_queryset(klass) |
450 | ctype = ContentType.objects.get_for_model(queryset.model) | |
480 | ctype = get_content_type(queryset.model) | |
451 | 481 | elif ctype is not None and klass is None: |
452 | 482 | queryset = _get_queryset(ctype.model_class()) |
453 | 483 | elif klass is None: |
469 | 499 | # Check if the user is anonymous. The |
470 | 500 | # django.contrib.auth.models.AnonymousUser object doesn't work for queries |
471 | 501 | # and it's nice to be able to pass in request.user blindly. |
472 | if user.is_anonymous(): | |
502 | if is_anonymous(user): | |
473 | 503 | user = get_anonymous_user() |
474 | 504 | |
475 | 505 | global_perms = set() |
529 | 559 | group_fields = generic_fields |
530 | 560 | else: |
531 | 561 | group_fields = direct_fields |
532 | if not any_perm and len(codenames) and not has_global_perms: | |
562 | if not any_perm and len(codenames) > 1 and not has_global_perms: | |
533 | 563 | user_obj_perms = user_obj_perms_queryset.values_list(*user_fields) |
534 | 564 | groups_obj_perms = groups_obj_perms_queryset.values_list(*group_fields) |
535 | 565 | data = list(user_obj_perms) + list(groups_obj_perms) |
552 | 582 | |
553 | 583 | values = user_obj_perms_queryset.values_list(user_fields[0], flat=True) |
554 | 584 | if user_model.objects.is_generic(): |
555 | values = list(values) | |
585 | values = set(values) | |
556 | 586 | q = Q(pk__in=values) |
557 | 587 | if use_groups: |
558 | 588 | values = groups_obj_perms_queryset.values_list(group_fields[0], flat=True) |
559 | 589 | if group_model.objects.is_generic(): |
560 | values = list(values) | |
590 | values = set(values) | |
561 | 591 | q |= Q(pk__in=values) |
562 | 592 | |
563 | 593 | return queryset.filter(q) |
650 | 680 | # Compute queryset and ctype if still missing |
651 | 681 | if ctype is None and klass is not None: |
652 | 682 | queryset = _get_queryset(klass) |
653 | ctype = ContentType.objects.get_for_model(queryset.model) | |
683 | ctype = get_content_type(queryset.model) | |
654 | 684 | elif ctype is not None and klass is None: |
655 | 685 | queryset = _get_queryset(ctype.model_class()) |
656 | 686 | elif klass is None: |
10 | 10 | {% endif %} |
11 | 11 | {% if field.help_text %}<p class="grp-help">{{ field.help_text|safe }}</p>{% endif %} |
12 | 12 | {{ field.errors }} |
13 | {% if field.errors %} | |
14 | <ul class="errorlist"> | |
15 | {% for error in field.errors %} | |
16 | <li>{{ error }}</li> | |
17 | {% endfor %} | |
18 | </ul> | |
19 | {% endif %} | |
20 | 13 | </div> |
21 | 14 | </div> |
22 | 15 | </div>⏎ |
1 | 1 | {% load i18n %} |
2 | 2 | {% load guardian_tags %} |
3 | 3 | {% load admin_static %} |
4 | {% load admin_urls %} | |
4 | 5 | |
5 | {% block breadcrumbs %}{% if not is_popup %} | |
6 | {% block breadcrumbs %} | |
6 | 7 | <ul> |
7 | <li><a href="../../../../">{% trans "Home" %}</a></li> | |
8 | <li><a href="../../../">{% trans app_label|capfirst|escape %}</a></li> | |
9 | <li>{% if has_change_permission %}<a href="../../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}</li> | |
10 | <li>{% if has_change_permission %}<a href="../">{{ original|truncatewords:"18" }}</a>{% else %}{{ original|truncatewords:"18" }}{% endif %}</li> | |
11 | <li>{% trans "Object permissions" %}</li> | |
8 | <li><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a></li> | |
9 | <li><a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a></li> | |
10 | {% if has_change_permission %} | |
11 | <li> | |
12 | {% url opts|admin_urlname:'changelist' as changelist_url %} | |
13 | <a href="{% add_preserved_filters changelist_url %}">{{ opts.verbose_name_plural|capfirst }}</a> | |
14 | </li> | |
15 | <li> | |
16 | {% url opts|admin_urlname:'change' object.pk|admin_urlquote as object_url %} | |
17 | <a href="{% add_preserved_filters object_url %}">{{ object|truncatewords:"18" }}</a> | |
18 | </li> | |
19 | {% else %} | |
20 | <li>{{ opts.verbose_name_plural|capfirst }}</li> | |
21 | <li>{{ original|truncatewords:"18" }}</li> | |
22 | {% endif %} | |
23 | <li>{% trans 'Object permissions' %}</li> | |
12 | 24 | </ul> |
13 | {% endif %}{% endblock %} | |
25 | {% endblock %} | |
14 | 26 | |
15 | 27 | {% block content %} |
16 | 28 | <form action="." method="post"> |
135 | 147 | </fieldset> |
136 | 148 | </div> |
137 | 149 | </form> |
138 | {% endblock %} | |
139 | ||
140 | ||
150 | {% endblock %}⏎ |
0 | 0 | {% extends "admin/change_form.html" %} |
1 | 1 | {% load i18n %} |
2 | {% load admin_urls %} | |
2 | 3 | |
3 | 4 | {% block extrahead %}{{ block.super }} |
4 | 5 | {{ form.media }} |
6 | 7 | |
7 | 8 | {% block breadcrumbs %}{% if not is_popup %} |
8 | 9 | <ul> |
9 | <li><a href="../../../../../../">{% trans "Home" %}</a></li> | |
10 | <li><a href="../../../../../">{% trans app_label|capfirst|escape %}</a></li> | |
11 | <li>{% if has_change_permission %}<a href="../../../../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}</li> | |
12 | <li>{% if has_change_permission %}<a href="../../../">{{ original|truncatewords:"18" }}</a>{% else %}{{ original|truncatewords:"18" }}{% endif %}</li> | |
13 | <li>{% if has_change_permission %}<a href="../../">{% trans "Object permissions" %}</a>{% else %}{% trans "Object permissions" %}{% endif %}</li> | |
10 | <li><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a></li> | |
11 | <li><a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a></li> | |
12 | {% if has_change_permission %} | |
13 | <li> | |
14 | {% url opts|admin_urlname:'changelist' as changelist_url %} | |
15 | <a href="{% add_preserved_filters changelist_url %}">{{ opts.verbose_name_plural|capfirst }}</a> | |
16 | </li> | |
17 | <li> | |
18 | {% url opts|admin_urlname:'change' object.pk|admin_urlquote as object_url %} | |
19 | <a href="{% add_preserved_filters object_url %}">{{ object|truncatewords:"18" }}</a> | |
20 | </li> | |
21 | <li> | |
22 | {% url opts|admin_urlname:'permissions' object.pk|admin_urlquote as permissions_url %} | |
23 | <a href="{% add_preserved_filters permissions_url %}">{% trans "Object permissions" %}</a> | |
24 | </li> | |
25 | {% else %} | |
26 | <li>{{ opts.verbose_name_plural|capfirst }}</li> | |
27 | <li>{{ original|truncatewords:"18" }}</li> | |
28 | <li>{% trans "Object permissions" %}</li> | |
29 | {% endif %} | |
14 | 30 | <li>{% trans "Manage group" %}: {{ group_obj|truncatewords:"18" }}</li> |
15 | 31 | </ul> |
16 | 32 | {% endif %}{% endblock %} |
17 | ||
18 | 33 | |
19 | 34 | {% block content %} |
20 | 35 | <form action="." method="post"> |
48 | 63 | </fieldset> |
49 | 64 | </div> |
50 | 65 | </form> |
51 | {% endblock %} | |
66 | {% endblock %}⏎ |
0 | 0 | {% extends "admin/change_form.html" %} |
1 | 1 | {% load i18n %} |
2 | {% load admin_urls %} | |
2 | 3 | |
3 | 4 | {% block extrahead %}{{ block.super }} |
4 | 5 | {{ form.media }} |
6 | 7 | |
7 | 8 | {% block breadcrumbs %}{% if not is_popup %} |
8 | 9 | <ul> |
9 | <li><a href="../../../../../../">{% trans "Home" %}</a></li> | |
10 | <li><a href="../../../../../">{% trans app_label|capfirst|escape %}</a></li> | |
11 | <li>{% if has_change_permission %}<a href="../../../../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}</li> | |
12 | <li>{% if has_change_permission %}<a href="../../../">{{ original|truncatewords:"18" }}</a>{% else %}{{ original|truncatewords:"18" }}{% endif %}</li> | |
13 | <li>{% if has_change_permission %}<a href="../../">{% trans "Object permissions" %}</a>{% else %}{% trans "Object permissions" %}{% endif %}</li> | |
10 | <li><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a></li> | |
11 | <li><a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a></li> | |
12 | {% if has_change_permission %} | |
13 | <li> | |
14 | {% url opts|admin_urlname:'changelist' as changelist_url %} | |
15 | <a href="{% add_preserved_filters changelist_url %}">{{ opts.verbose_name_plural|capfirst }}</a> | |
16 | </li> | |
17 | <li> | |
18 | {% url opts|admin_urlname:'change' object.pk|admin_urlquote as object_url %} | |
19 | <a href="{% add_preserved_filters object_url %}">{{ object|truncatewords:"18" }}</a> | |
20 | </li> | |
21 | <li> | |
22 | {% url opts|admin_urlname:'permissions' object.pk|admin_urlquote as permissions_url %} | |
23 | <a href="{% add_preserved_filters permissions_url %}">{% trans "Object permissions" %}</a> | |
24 | </li> | |
25 | {% else %} | |
26 | <li>{{ opts.verbose_name_plural|capfirst }}</li> | |
27 | <li>{{ original|truncatewords:"18" }}</li> | |
28 | <li>{% trans "Object permissions" %}</li> | |
29 | {% endif %} | |
14 | 30 | <li>{% trans "Manage user" %}: {{ user_obj|truncatewords:"18" }}</li> |
15 | 31 | </ul> |
16 | 32 | {% endif %}{% endblock %} |
17 | ||
18 | 33 | |
19 | 34 | {% block content %} |
20 | 35 | <form action="." method="post"> |
48 | 63 | </fieldset> |
49 | 64 | </div> |
50 | 65 | </form> |
51 | {% endblock %} | |
66 | {% endblock %}⏎ |
5 | 5 | |
6 | 6 | """ |
7 | 7 | from __future__ import unicode_literals |
8 | ||
8 | 9 | from django import template |
9 | from django.contrib.auth.models import Group, AnonymousUser | |
10 | from django.contrib.auth.models import AnonymousUser, Group | |
10 | 11 | |
11 | 12 | from guardian.compat import get_user_model |
13 | from guardian.core import ObjectPermissionChecker | |
12 | 14 | from guardian.exceptions import NotUserNorGroup |
13 | from guardian.core import ObjectPermissionChecker | |
14 | 15 | |
15 | 16 | register = template.Library() |
16 | 17 |
0 | # -*- coding: utf-8 -*- | |
1 | # Generated by Django 1.9.9 on 2016-09-18 17:52 | |
0 | 2 | from __future__ import unicode_literals |
1 | 3 | |
2 | 4 | import datetime |
3 | ||
4 | 5 | from django.conf import settings |
6 | import django.contrib.auth.models | |
5 | 7 | import django.core.validators |
6 | from django.db import models, migrations | |
8 | from django.db import migrations, models | |
9 | import django.db.models.deletion | |
7 | 10 | import django.utils.timezone |
8 | 11 | import guardian.mixins |
9 | 12 | |
10 | 13 | |
11 | 14 | class Migration(migrations.Migration): |
12 | 15 | |
16 | initial = True | |
17 | ||
13 | 18 | dependencies = [ |
14 | 19 | ('auth', '0001_initial'), |
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), | |
16 | 20 | ] |
17 | 21 | |
18 | 22 | operations = [ |
20 | 24 | name='CustomUser', |
21 | 25 | fields=[ |
22 | 26 | ('password', models.CharField(max_length=128, verbose_name='password')), |
23 | ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login', null=True)), | |
27 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | |
24 | 28 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), |
25 | ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@-]+$', 'Enter a valid username.', 'invalid')])), | |
26 | ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), | |
27 | ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), | |
28 | ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)), | |
29 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username')), | |
30 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), | |
31 | ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), | |
32 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), | |
29 | 33 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), |
30 | 34 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), |
31 | 35 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), |
32 | ('custom_id', models.AutoField(serialize=False, primary_key=True)), | |
33 | ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')), | |
34 | ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')), | |
36 | ('custom_id', models.AutoField(primary_key=True, serialize=False)), | |
37 | ('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')), | |
38 | ('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')), | |
35 | 39 | ], |
36 | 40 | options={ |
37 | 41 | 'abstract': False, |
39 | 43 | 'verbose_name_plural': 'users', |
40 | 44 | }, |
41 | 45 | bases=(models.Model, guardian.mixins.GuardianUserMixin), |
46 | managers=[ | |
47 | ('objects', django.contrib.auth.models.UserManager()), | |
48 | ], | |
49 | ), | |
50 | migrations.CreateModel( | |
51 | name='CustomUsernameUser', | |
52 | fields=[ | |
53 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
54 | ('password', models.CharField(max_length=128, verbose_name='password')), | |
55 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | |
56 | ('email', models.EmailField(max_length=100, unique=True)), | |
57 | ], | |
58 | options={ | |
59 | 'abstract': False, | |
60 | }, | |
61 | bases=(models.Model, guardian.mixins.GuardianUserMixin), | |
42 | 62 | ), |
43 | 63 | migrations.CreateModel( |
44 | 64 | name='Mixed', |
45 | 65 | fields=[ |
46 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
47 | ('name', models.CharField(unique=True, max_length=128)), | |
66 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
67 | ('name', models.CharField(max_length=128, unique=True)), | |
48 | 68 | ], |
49 | options={ | |
50 | }, | |
51 | bases=(models.Model,), | |
52 | 69 | ), |
53 | 70 | migrations.CreateModel( |
54 | 71 | name='MixedGroupObjectPermission', |
55 | 72 | fields=[ |
56 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
57 | ('content_object', models.ForeignKey(to='testapp.Mixed')), | |
58 | ('group', models.ForeignKey(to='auth.Group')), | |
59 | ('permission', models.ForeignKey(to='auth.Permission')), | |
73 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
74 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='testapp.Mixed')), | |
75 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), | |
76 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
60 | 77 | ], |
61 | 78 | options={ |
62 | 79 | 'abstract': False, |
63 | 80 | }, |
64 | bases=(models.Model,), | |
81 | ), | |
82 | migrations.CreateModel( | |
83 | name='NonIntPKModel', | |
84 | fields=[ | |
85 | ('char_pk', models.CharField(max_length=128, primary_key=True, serialize=False)), | |
86 | ], | |
87 | ), | |
88 | migrations.CreateModel( | |
89 | name='Post', | |
90 | fields=[ | |
91 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
92 | ('title', models.CharField(max_length=64, verbose_name='title')), | |
93 | ], | |
65 | 94 | ), |
66 | 95 | migrations.CreateModel( |
67 | 96 | name='Project', |
68 | 97 | fields=[ |
69 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
70 | ('name', models.CharField(unique=True, max_length=128)), | |
98 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
99 | ('name', models.CharField(max_length=128, unique=True)), | |
71 | 100 | ('created_at', models.DateTimeField(default=datetime.datetime.now)), |
72 | 101 | ], |
73 | 102 | options={ |
74 | 103 | 'get_latest_by': 'created_at', |
75 | 104 | }, |
76 | bases=(models.Model,), | |
77 | 105 | ), |
78 | 106 | migrations.CreateModel( |
79 | 107 | name='ProjectGroupObjectPermission', |
80 | 108 | fields=[ |
81 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
82 | ('content_object', models.ForeignKey(to='testapp.Project')), | |
83 | ('group', models.ForeignKey(to='auth.Group')), | |
84 | ('permission', models.ForeignKey(to='auth.Permission')), | |
109 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
110 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='testapp.Project')), | |
111 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), | |
112 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
85 | 113 | ], |
86 | 114 | options={ |
87 | 115 | 'abstract': False, |
88 | 116 | }, |
89 | bases=(models.Model,), | |
90 | 117 | ), |
91 | 118 | migrations.CreateModel( |
92 | 119 | name='ProjectUserObjectPermission', |
93 | 120 | fields=[ |
94 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
95 | ('content_object', models.ForeignKey(to='testapp.Project')), | |
96 | ('permission', models.ForeignKey(to='auth.Permission')), | |
97 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), | |
121 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
122 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='testapp.Project')), | |
123 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
124 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | |
98 | 125 | ], |
99 | 126 | options={ |
100 | 127 | 'abstract': False, |
101 | 128 | }, |
102 | bases=(models.Model,), | |
129 | ), | |
130 | migrations.CreateModel( | |
131 | name='ReverseMixed', | |
132 | fields=[ | |
133 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
134 | ('name', models.CharField(max_length=128, unique=True)), | |
135 | ], | |
136 | ), | |
137 | migrations.CreateModel( | |
138 | name='ReverseMixedUserObjectPermission', | |
139 | fields=[ | |
140 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
141 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='testapp.ReverseMixed')), | |
142 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
143 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | |
144 | ], | |
145 | options={ | |
146 | 'abstract': False, | |
147 | }, | |
148 | ), | |
149 | migrations.AlterUniqueTogether( | |
150 | name='reversemixeduserobjectpermission', | |
151 | unique_together=set([('user', 'permission', 'content_object')]), | |
103 | 152 | ), |
104 | 153 | migrations.AlterUniqueTogether( |
105 | 154 | name='projectuserobjectpermission', |
113 | 162 | name='mixedgroupobjectpermission', |
114 | 163 | unique_together=set([('group', 'permission', 'content_object')]), |
115 | 164 | ), |
116 | ]⏎ | |
165 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | # Generated by Django 1.9.9 on 2016-09-18 17:52 | |
0 | 2 | from __future__ import unicode_literals |
1 | 3 | |
2 | from django.db import models, migrations | |
4 | from django.db import migrations, models | |
5 | import django.db.models.deletion | |
3 | 6 | |
4 | 7 | |
5 | 8 | class Migration(migrations.Migration): |
14 | 17 | migrations.CreateModel( |
15 | 18 | name='LogEntryWithGroup', |
16 | 19 | fields=[ |
17 | ('logentry_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='admin.LogEntry')), | |
18 | ('group', models.ForeignKey(blank=True, to='auth.Group', null=True)), | |
20 | ('logentry_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='admin.LogEntry')), | |
21 | ('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), | |
19 | 22 | ], |
20 | options={ | |
21 | }, | |
22 | 23 | bases=('admin.logentry',), |
23 | 24 | ), |
24 | ]⏎ | |
25 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | from __future__ import unicode_literals | |
2 | ||
3 | from django.db import models, migrations | |
4 | import django.core.validators | |
5 | ||
6 | ||
7 | class Migration(migrations.Migration): | |
8 | ||
9 | dependencies = [ | |
10 | ('testapp', '0002_logentrywithgroup'), | |
11 | ] | |
12 | ||
13 | operations = [ | |
14 | migrations.CreateModel( | |
15 | name='NonIntPKModel', | |
16 | fields=[ | |
17 | ('char_pk', models.CharField(max_length=128, serialize=False, primary_key=True)), | |
18 | ], | |
19 | options={ | |
20 | }, | |
21 | bases=(models.Model,), | |
22 | ), | |
23 | migrations.AlterField( | |
24 | model_name='customuser', | |
25 | name='username', | |
26 | field=models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')]), | |
27 | preserve_default=True, | |
28 | ), | |
29 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | from __future__ import unicode_literals | |
2 | ||
3 | from django.db import migrations, models | |
4 | import django.core.validators | |
5 | import django.contrib.auth.models | |
6 | ||
7 | ||
8 | class Migration(migrations.Migration): | |
9 | ||
10 | dependencies = [ | |
11 | ('testapp', '0003_auto_20141124_0729'), | |
12 | ] | |
13 | ||
14 | operations = [ | |
15 | migrations.AlterField( | |
16 | model_name='customuser', | |
17 | name='email', | |
18 | field=models.EmailField(max_length=254, verbose_name='email address', blank=True), | |
19 | ), | |
20 | migrations.AlterField( | |
21 | model_name='customuser', | |
22 | name='groups', | |
23 | field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'), | |
24 | ), | |
25 | migrations.AlterField( | |
26 | model_name='customuser', | |
27 | name='last_login', | |
28 | field=models.DateTimeField(null=True, verbose_name='last login', blank=True), | |
29 | ), | |
30 | migrations.AlterField( | |
31 | model_name='customuser', | |
32 | name='username', | |
33 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'), | |
34 | ), | |
35 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | # Generated by Django 1.9 on 2015-12-17 23:44 | |
2 | from __future__ import unicode_literals | |
3 | ||
4 | from django.db import migrations, models | |
5 | ||
6 | ||
7 | class Migration(migrations.Migration): | |
8 | ||
9 | dependencies = [ | |
10 | ('testapp', '0004_auto_20151112_2209'), | |
11 | ] | |
12 | ||
13 | operations = [ | |
14 | migrations.CreateModel( | |
15 | name='Post', | |
16 | fields=[ | |
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
18 | ('title', models.CharField(max_length=64, verbose_name='title')), | |
19 | ], | |
20 | ), | |
21 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | from __future__ import unicode_literals | |
2 | ||
3 | from django.db import models, migrations | |
4 | import django.utils.timezone | |
5 | import guardian.mixins | |
6 | import django.core.validators | |
7 | ||
8 | ||
9 | class Migration(migrations.Migration): | |
10 | ||
11 | dependencies = [ | |
12 | ('testapp', '0005_auto_20151217_2344'), | |
13 | ] | |
14 | ||
15 | operations = [ | |
16 | migrations.CreateModel( | |
17 | name='CustomUsernameUser', | |
18 | fields=[ | |
19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
20 | ('password', models.CharField(max_length=128, verbose_name='password')), | |
21 | ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')), | |
22 | ('email', models.EmailField(unique=True, max_length=100)), | |
23 | ], | |
24 | options={ | |
25 | 'abstract': False, | |
26 | }, | |
27 | bases=(models.Model, guardian.mixins.GuardianUserMixin), | |
28 | ) | |
29 | ] |
0 | # -*- coding: utf-8 -*- | |
1 | # Generated by Django 1.9.1 on 2016-03-09 02:45 | |
2 | from __future__ import unicode_literals | |
3 | ||
4 | from django.conf import settings | |
5 | import django.contrib.auth.models | |
6 | import django.core.validators | |
7 | from django.db import migrations, models | |
8 | import django.db.models.deletion | |
9 | ||
10 | ||
11 | class Migration(migrations.Migration): | |
12 | ||
13 | dependencies = [ | |
14 | ('testapp', '0006_auto_20160221_1054'), | |
15 | ] | |
16 | ||
17 | operations = [ | |
18 | migrations.CreateModel( | |
19 | name='ReverseMixed', | |
20 | fields=[ | |
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
22 | ('name', models.CharField(max_length=128, unique=True)), | |
23 | ], | |
24 | ), | |
25 | migrations.CreateModel( | |
26 | name='ReverseMixedUserObjectPermission', | |
27 | fields=[ | |
28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
29 | ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='testapp.ReverseMixed')), | |
30 | ('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Permission')), | |
31 | ], | |
32 | options={ | |
33 | 'abstract': False, | |
34 | }, | |
35 | ), | |
36 | migrations.AddField( | |
37 | model_name='reversemixeduserobjectpermission', | |
38 | name='user', | |
39 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), | |
40 | ), | |
41 | migrations.AlterUniqueTogether( | |
42 | name='reversemixeduserobjectpermission', | |
43 | unique_together=set([('user', 'permission', 'content_object')]), | |
44 | ), | |
45 | ] |
3 | 3 | from django.db import models |
4 | 4 | from django.contrib.admin.models import LogEntry |
5 | 5 | from django.contrib.auth.models import AbstractUser, AbstractBaseUser |
6 | from django.utils.encoding import python_2_unicode_compatible | |
6 | 7 | |
7 | 8 | from guardian.mixins import GuardianUserMixin |
8 | 9 | from guardian.models import UserObjectPermissionBase |
9 | 10 | from guardian.models import GroupObjectPermissionBase |
10 | 11 | |
11 | 12 | |
13 | @python_2_unicode_compatible | |
12 | 14 | class Post(models.Model): |
13 | 15 | title = models.CharField('title', max_length=64) |
16 | ||
17 | def __str__(self): | |
18 | return self.title | |
14 | 19 | |
15 | 20 | |
16 | 21 | class DynamicAccessor(object): |
23 | 28 | |
24 | 29 | |
25 | 30 | class ProjectUserObjectPermission(UserObjectPermissionBase): |
26 | content_object = models.ForeignKey('Project') | |
31 | content_object = models.ForeignKey('Project', on_delete=models.CASCADE) | |
27 | 32 | |
28 | 33 | |
29 | 34 | class ProjectGroupObjectPermission(GroupObjectPermissionBase): |
30 | content_object = models.ForeignKey('Project') | |
35 | content_object = models.ForeignKey('Project', on_delete=models.CASCADE) | |
31 | 36 | |
32 | 37 | |
33 | 38 | class Project(models.Model): |
37 | 42 | class Meta: |
38 | 43 | get_latest_by = 'created_at' |
39 | 44 | |
40 | def __unicode__(self): | |
45 | def __str__(self): | |
41 | 46 | return self.name |
42 | 47 | |
43 | 48 | |
45 | 50 | |
46 | 51 | |
47 | 52 | class MixedGroupObjectPermission(GroupObjectPermissionBase): |
48 | content_object = models.ForeignKey('Mixed') | |
53 | content_object = models.ForeignKey('Mixed', on_delete=models.CASCADE) | |
49 | 54 | |
50 | 55 | |
56 | @python_2_unicode_compatible | |
51 | 57 | class Mixed(models.Model): |
52 | 58 | """ |
53 | 59 | Model for tests obj perms checks with generic user object permissions model |
55 | 61 | """ |
56 | 62 | name = models.CharField(max_length=128, unique=True) |
57 | 63 | |
58 | def __unicode__(self): | |
64 | def __str__(self): | |
59 | 65 | return self.name |
60 | 66 | |
61 | 67 | |
62 | 68 | class ReverseMixedUserObjectPermission(UserObjectPermissionBase): |
63 | content_object = models.ForeignKey('ReverseMixed') | |
69 | content_object = models.ForeignKey('ReverseMixed', on_delete=models.CASCADE) | |
64 | 70 | |
65 | 71 | |
72 | @python_2_unicode_compatible | |
66 | 73 | class ReverseMixed(models.Model): |
67 | 74 | """ |
68 | 75 | Model for tests obj perms checks with generic group object permissions model |
70 | 77 | """ |
71 | 78 | name = models.CharField(max_length=128, unique=True) |
72 | 79 | |
73 | def __unicode__(self): | |
80 | def __str__(self): | |
74 | 81 | return self.name |
75 | 82 | |
76 | 83 | |
77 | 84 | class LogEntryWithGroup(LogEntry): |
78 | group = models.ForeignKey('auth.Group', null=True, blank=True) | |
85 | group = models.ForeignKey('auth.Group', null=True, blank=True, on_delete=models.CASCADE) | |
86 | ||
87 | objects = models.Manager() | |
79 | 88 | |
80 | 89 | |
81 | 90 | class NonIntPKModel(models.Model): |
0 | foobar404 |
4 | 4 | from django.conf import settings |
5 | 5 | from django.contrib import admin |
6 | 6 | from django.contrib.contenttypes.models import ContentType |
7 | from django.core.urlresolvers import reverse | |
8 | 7 | from django.http import HttpRequest |
9 | 8 | from django.test import TestCase |
10 | 9 | from django.test.client import Client |
11 | 10 | |
12 | 11 | from guardian.admin import GuardedModelAdmin |
13 | 12 | from guardian.compat import get_user_model, get_model_name |
13 | from guardian.compat import reverse | |
14 | 14 | from guardian.compat import str |
15 | 15 | from guardian.shortcuts import get_perms |
16 | 16 | from guardian.shortcuts import get_perms_for_model |
319 | 319 | def test_obj_perms_manage_user_form_attr(self): |
320 | 320 | attrs = {'obj_perms_manage_user_form': forms.Form} |
321 | 321 | gma = self._get_gma(attrs=attrs) |
322 | self.assertTrue(gma.get_obj_perms_manage_user_form(), forms.Form) | |
322 | self.assertTrue(issubclass(gma.get_obj_perms_manage_user_form(None), forms.Form)) | |
323 | ||
324 | def test_obj_perms_user_select_form_attr(self): | |
325 | attrs = {'obj_perms_user_select_form': forms.Form} | |
326 | gma = self._get_gma(attrs=attrs) | |
327 | self.assertTrue(issubclass(gma.get_obj_perms_user_select_form(None), forms.Form)) | |
323 | 328 | |
324 | 329 | def test_obj_perms_manage_group_template_attr(self): |
325 | 330 | attrs = {'obj_perms_manage_group_template': 'foobar.html'} |
330 | 335 | def test_obj_perms_manage_group_form_attr(self): |
331 | 336 | attrs = {'obj_perms_manage_group_form': forms.Form} |
332 | 337 | gma = self._get_gma(attrs=attrs) |
333 | self.assertTrue(gma.get_obj_perms_manage_group_form(), forms.Form) | |
338 | self.assertTrue(issubclass(gma.get_obj_perms_manage_group_form(None), forms.Form)) | |
339 | ||
340 | def test_obj_perms_group_select_form_attr(self): | |
341 | attrs = {'obj_perms_group_select_form': forms.Form} | |
342 | gma = self._get_gma(attrs=attrs) | |
343 | self.assertTrue(issubclass(gma.get_obj_perms_group_select_form(None), forms.Form)) | |
334 | 344 | |
335 | 345 | def test_user_can_acces_owned_objects_only(self): |
336 | 346 | attrs = { |
2 | 2 | from django.test import TestCase |
3 | 3 | import mock |
4 | 4 | from guardian.conf import settings as guardian_settings |
5 | from guardian.ctypes import get_content_type | |
5 | 6 | |
6 | 7 | |
7 | 8 | class TestConfiguration(TestCase): |
12 | 13 | with mock.patch('guardian.conf.settings.RAISE_403', True): |
13 | 14 | self.assertRaises(ImproperlyConfigured, |
14 | 15 | guardian_settings.check_configuration) |
16 | ||
17 | def test_get_content_type(self): | |
18 | with mock.patch('guardian.conf.settings.GET_CONTENT_TYPE', 'guardian.testapp.tests.test_conf.get_test_content_type'): | |
19 | self.assertEqual(get_content_type(None), 'x') | |
20 | ||
21 | ||
22 | def get_test_content_type(obj): | |
23 | """ Used in TestConfiguration.test_get_content_type().""" | |
24 | return 'x' |
41 | 41 | self.user.groups.add(self.group) |
42 | 42 | self.ctype = ContentType.objects.create( |
43 | 43 | model='bar', app_label='fake-for-guardian-tests') |
44 | self.ctype_qset = ContentType.objects.filter(model='bar', | |
45 | app_label='fake-for-guardian-tests') | |
44 | 46 | self.anonymous_user = User.objects.get( |
45 | 47 | username=guardian_settings.ANONYMOUS_USER_NAME) |
48 | ||
49 | def get_permission(self, codename, app_label=None): | |
50 | qs = Permission.objects | |
51 | if app_label: | |
52 | qs = qs.filter(content_type__app_label=app_label) | |
53 | return Permission.objects.get(codename=codename) | |
46 | 54 | |
47 | 55 | |
48 | 56 | class ObjectPermissionCheckerTest(ObjectPermissionTestCase): |
189 | 197 | from django.db import connection |
190 | 198 | |
191 | 199 | ContentType.objects.clear_cache() |
192 | new_group = Group.objects.create(name='new-group') | |
200 | group1 = Group.objects.create(name='group1') | |
201 | group2 = Group.objects.create(name='group2') | |
193 | 202 | user = User.objects.create(username='active_user', is_active=True) |
194 | 203 | assign_perm("change_group", user, self.group) |
195 | assign_perm("change_group", user, new_group) | |
204 | assign_perm("change_group", user, group1) | |
196 | 205 | checker = ObjectPermissionChecker(user) |
197 | 206 | |
198 | 207 | # Prefetch permissions |
199 | self.assertTrue(checker.prefetch_perms([self.group, new_group])) | |
200 | query_count = len(connection.queries) | |
201 | ||
202 | # Checking cache is filled | |
203 | self.assertEqual(len(checker._obj_perms_cache), 2) | |
208 | prefetched_objects = [self.group, group1, group2] | |
209 | self.assertTrue(checker.prefetch_perms(prefetched_objects)) | |
210 | query_count = len(connection.queries) | |
211 | ||
212 | # Checking cache is filled | |
213 | self.assertEqual( | |
214 | len(checker._obj_perms_cache), | |
215 | len(prefetched_objects) | |
216 | ) | |
204 | 217 | |
205 | 218 | # Checking shouldn't spawn any queries |
206 | 219 | checker.has_perm("change_group", self.group) |
212 | 225 | self.assertEqual(len(connection.queries), query_count) |
213 | 226 | |
214 | 227 | # Checking for same model but other instance shouldn't spawn any queries |
215 | checker.has_perm("change_group", new_group) | |
216 | self.assertEqual(len(connection.queries), query_count) | |
217 | ||
228 | checker.has_perm("change_group", group1) | |
229 | self.assertEqual(len(connection.queries), query_count) | |
230 | ||
231 | # Checking for same model but other instance shouldn't spawn any queries | |
232 | # Even though User doesn't have perms on Group2, we still should | |
233 | # not hit DB | |
234 | self.assertFalse(checker.has_perm("change_group", group2)) | |
235 | self.assertEqual(len(connection.queries), query_count) | |
218 | 236 | finally: |
219 | 237 | settings.DEBUG = False |
220 | 238 | |
224 | 242 | from django.db import connection |
225 | 243 | |
226 | 244 | ContentType.objects.clear_cache() |
227 | new_group = Group.objects.create(name='new-group') | |
245 | group1 = Group.objects.create(name='group1') | |
228 | 246 | user = User.objects.create(username='active_superuser', |
229 | 247 | is_superuser=True, is_active=True) |
230 | 248 | assign_perm("change_group", user, self.group) |
231 | 249 | checker = ObjectPermissionChecker(user) |
232 | 250 | |
233 | 251 | # Prefetch permissions |
234 | self.assertTrue(checker.prefetch_perms([self.group, new_group])) | |
235 | query_count = len(connection.queries) | |
236 | ||
237 | # Checking cache is filled | |
238 | self.assertEqual(len(checker._obj_perms_cache), 2) | |
252 | prefetched_objects = [self.group, group1] | |
253 | self.assertTrue(checker.prefetch_perms(prefetched_objects)) | |
254 | query_count = len(connection.queries) | |
255 | ||
256 | # Checking cache is filled | |
257 | self.assertEqual( | |
258 | len(checker._obj_perms_cache), | |
259 | len(prefetched_objects) | |
260 | ) | |
239 | 261 | |
240 | 262 | # Checking shouldn't spawn any queries |
241 | 263 | checker.has_perm("change_group", self.group) |
247 | 269 | self.assertEqual(len(connection.queries), query_count) |
248 | 270 | |
249 | 271 | # Checking for same model but other instance shouldn't spawn any queries |
250 | checker.has_perm("change_group", new_group) | |
251 | self.assertEqual(len(connection.queries), query_count) | |
252 | ||
272 | checker.has_perm("change_group", group1) | |
273 | self.assertEqual(len(connection.queries), query_count) | |
253 | 274 | finally: |
254 | 275 | settings.DEBUG = False |
255 | 276 | |
259 | 280 | from django.db import connection |
260 | 281 | |
261 | 282 | ContentType.objects.clear_cache() |
262 | new_group = Group.objects.create(name='new-group') | |
263 | assign_perm("change_group", new_group, self.group) | |
264 | assign_perm("change_group", new_group, new_group) | |
265 | checker = ObjectPermissionChecker(new_group) | |
266 | ||
267 | # Prefetch permissions | |
268 | self.assertTrue(checker.prefetch_perms([self.group, new_group])) | |
269 | query_count = len(connection.queries) | |
270 | ||
271 | # Checking cache is filled | |
272 | self.assertEqual(len(checker._obj_perms_cache), 2) | |
283 | group1 = Group.objects.create(name='group1') | |
284 | group2 = Group.objects.create(name='group2') | |
285 | assign_perm("change_group", group1, self.group) | |
286 | assign_perm("change_group", group1, group1) | |
287 | checker = ObjectPermissionChecker(group1) | |
288 | ||
289 | # Prefetch permissions | |
290 | prefetched_objects = [self.group, group1, group2] | |
291 | self.assertTrue(checker.prefetch_perms(prefetched_objects)) | |
292 | ||
293 | query_count = len(connection.queries) | |
294 | ||
295 | # Checking cache is filled | |
296 | self.assertEqual( | |
297 | len(checker._obj_perms_cache), | |
298 | len(prefetched_objects) | |
299 | ) | |
273 | 300 | |
274 | 301 | # Checking shouldn't spawn any queries |
275 | 302 | checker.has_perm("change_group", self.group) |
281 | 308 | self.assertEqual(len(connection.queries), query_count) |
282 | 309 | |
283 | 310 | # Checking for same model but other instance shouldn't spawn any queries |
284 | checker.has_perm("change_group", new_group) | |
285 | self.assertEqual(len(connection.queries), query_count) | |
286 | ||
311 | checker.has_perm("change_group", group1) | |
312 | self.assertEqual(len(connection.queries), query_count) | |
313 | ||
314 | # Checking for same model but other instance shouldn't spawn any queries | |
315 | # Even though User doesn't have perms on Group2, we still should | |
316 | # not hit DB | |
317 | self.assertFalse(checker.has_perm("change_group", group2)) | |
318 | self.assertEqual(len(connection.queries), query_count) | |
287 | 319 | finally: |
288 | 320 | settings.DEBUG = False |
289 | 321 | |
296 | 328 | user = User.objects.create(username='active_user', is_active=True) |
297 | 329 | projects = \ |
298 | 330 | [Project.objects.create(name='Project%s' % i) |
299 | for i in range(2)] | |
300 | for project in projects: | |
301 | assign_perm("change_project", user, project) | |
331 | for i in range(3)] | |
332 | assign_perm("change_project", user, projects[0]) | |
333 | assign_perm("change_project", user, projects[1]) | |
334 | ||
302 | 335 | checker = ObjectPermissionChecker(user) |
303 | 336 | |
304 | 337 | # Prefetch permissions |
306 | 339 | query_count = len(connection.queries) |
307 | 340 | |
308 | 341 | # Checking cache is filled |
309 | self.assertEqual(len(checker._obj_perms_cache), 2) | |
342 | self.assertEqual(len(checker._obj_perms_cache), len(projects)) | |
310 | 343 | |
311 | 344 | # Checking shouldn't spawn any queries |
312 | 345 | checker.has_perm("change_project", projects[0]) |
322 | 355 | checker.has_perm("change_project", projects[1]) |
323 | 356 | self.assertEqual(len(connection.queries), query_count) |
324 | 357 | |
358 | # Checking for same model but other instance shouldn't spawn any queries | |
359 | # Even though User doesn't have perms on projects[2], we still | |
360 | # should not hit DB | |
361 | self.assertFalse(checker.has_perm("change_project", projects[2])) | |
362 | self.assertEqual(len(connection.queries), query_count) | |
325 | 363 | finally: |
326 | 364 | settings.DEBUG = False |
327 | 365 | |
336 | 374 | projects = \ |
337 | 375 | [Project.objects.create(name='Project%s' % i) |
338 | 376 | for i in range(2)] |
339 | for project in projects: | |
340 | assign_perm("change_project", user, project) | |
377 | assign_perm("change_project", user, projects[0]) | |
378 | ||
341 | 379 | checker = ObjectPermissionChecker(user) |
342 | 380 | |
343 | 381 | # Prefetch permissions |
345 | 383 | query_count = len(connection.queries) |
346 | 384 | |
347 | 385 | # Checking cache is filled |
348 | self.assertEqual(len(checker._obj_perms_cache), 2) | |
386 | self.assertEqual(len(checker._obj_perms_cache), len(projects)) | |
349 | 387 | |
350 | 388 | # Checking shouldn't spawn any queries |
351 | 389 | checker.has_perm("change_project", projects[0]) |
360 | 398 | # queries |
361 | 399 | checker.has_perm("change_project", projects[1]) |
362 | 400 | self.assertEqual(len(connection.queries), query_count) |
363 | ||
364 | 401 | finally: |
365 | 402 | settings.DEBUG = False |
366 | 403 | |
370 | 407 | from django.db import connection |
371 | 408 | |
372 | 409 | ContentType.objects.clear_cache() |
373 | new_group = Group.objects.create(name='new-group') | |
410 | group = Group.objects.create(name='new-group') | |
374 | 411 | projects = \ |
375 | 412 | [Project.objects.create(name='Project%s' % i) |
376 | for i in range(2)] | |
377 | for project in projects: | |
378 | assign_perm("change_project", new_group, project) | |
379 | checker = ObjectPermissionChecker(new_group) | |
413 | for i in range(3)] | |
414 | assign_perm("change_project", group, projects[0]) | |
415 | assign_perm("change_project", group, projects[1]) | |
416 | ||
417 | checker = ObjectPermissionChecker(group) | |
380 | 418 | |
381 | 419 | # Prefetch permissions |
382 | 420 | self.assertTrue(checker.prefetch_perms(projects)) |
383 | 421 | query_count = len(connection.queries) |
384 | 422 | |
385 | 423 | # Checking cache is filled |
386 | self.assertEqual(len(checker._obj_perms_cache), 2) | |
424 | self.assertEqual(len(checker._obj_perms_cache), len(projects)) | |
387 | 425 | |
388 | 426 | # Checking shouldn't spawn any queries |
389 | 427 | checker.has_perm("change_project", projects[0]) |
399 | 437 | checker.has_perm("change_project", projects[1]) |
400 | 438 | self.assertEqual(len(connection.queries), query_count) |
401 | 439 | |
402 | finally: | |
403 | settings.DEBUG = False | |
440 | # Checking for same model but other instance shouldn't spawn any queries | |
441 | # Even though User doesn't have perms on projects[2], we still | |
442 | # should not hit DB | |
443 | self.assertFalse(checker.has_perm("change_project", projects[2])) | |
444 | self.assertEqual(len(connection.queries), query_count) | |
445 | finally: | |
446 | settings.DEBUG = False |
7 | 7 | |
8 | 8 | class CustomPKModelTest(TestCase): |
9 | 9 | """ |
10 | Tests agains custom model with primary key other than *standard* | |
10 | Tests against custom model with primary key other than *standard* | |
11 | 11 | ``id`` integer field. |
12 | 12 | """ |
13 | 13 |
0 | 0 | from __future__ import unicode_literals |
1 | 1 | from django.conf import settings, global_settings |
2 | 2 | from django.contrib.auth.models import Group, AnonymousUser |
3 | from django.core.exceptions import PermissionDenied | |
3 | from django.core.exceptions import ObjectDoesNotExist, PermissionDenied | |
4 | 4 | from django.db.models.base import ModelBase |
5 | 5 | from django.http import HttpRequest |
6 | 6 | from django.http import HttpResponse |
7 | 7 | from django.http import HttpResponseForbidden |
8 | from django.http import HttpResponseNotFound | |
8 | 9 | from django.http import HttpResponseRedirect |
9 | 10 | from django.shortcuts import get_object_or_404 |
10 | 11 | from django.template import TemplateDoesNotExist |
14 | 15 | from guardian.compat import get_user_model_path |
15 | 16 | from guardian.compat import get_user_permission_full_codename |
16 | 17 | import mock |
17 | from guardian.decorators import permission_required, permission_required_or_403 | |
18 | from guardian.decorators import permission_required, permission_required_or_403, permission_required_or_404 | |
18 | 19 | from guardian.exceptions import GuardianError |
19 | 20 | from guardian.exceptions import WrongAppError |
20 | 21 | from guardian.shortcuts import assign_perm |
69 | 70 | self.assertEqual(response.content, b'') |
70 | 71 | self.assertTrue(isinstance(response, HttpResponseForbidden)) |
71 | 72 | |
73 | def test_RENDER_404_is_false(self): | |
74 | request = self._get_request(self.anon) | |
75 | ||
76 | @permission_required_or_404('not_installed_app.change_user') | |
77 | def dummy_view(request): | |
78 | return HttpResponse('dummy_view') | |
79 | ||
80 | with mock.patch('guardian.conf.settings.RENDER_404', False): | |
81 | response = dummy_view(request) | |
82 | self.assertEqual(response.content, b'') | |
83 | self.assertTrue(isinstance(response, HttpResponseNotFound)) | |
84 | ||
72 | 85 | @mock.patch('guardian.conf.settings.RENDER_403', True) |
73 | 86 | def test_TEMPLATE_403_setting(self): |
74 | 87 | request = self._get_request(self.anon) |
81 | 94 | response = dummy_view(request) |
82 | 95 | self.assertEqual(response.content, b'foobar403\n') |
83 | 96 | |
97 | @mock.patch('guardian.conf.settings.RENDER_404', True) | |
98 | def test_TEMPLATE_404_setting(self): | |
99 | request = self._get_request(self.anon) | |
100 | ||
101 | @permission_required_or_404('not_installed_app.change_user') | |
102 | def dummy_view(request): | |
103 | return HttpResponse('dummy_view') | |
104 | ||
105 | with mock.patch('guardian.conf.settings.TEMPLATE_404', 'dummy404.html'): | |
106 | response = dummy_view(request) | |
107 | self.assertEqual(response.content, b'foobar404\n') | |
108 | ||
84 | 109 | @mock.patch('guardian.conf.settings.RENDER_403', True) |
85 | 110 | def test_403_response_raises_error(self): |
86 | 111 | request = self._get_request(self.anon) |
92 | 117 | '_non-exisitng-403.html'): |
93 | 118 | self.assertRaises(TemplateDoesNotExist, dummy_view, request) |
94 | 119 | |
120 | @mock.patch('guardian.conf.settings.RENDER_404', True) | |
121 | def test_404_response_raises_error(self): | |
122 | request = self._get_request(self.anon) | |
123 | ||
124 | @permission_required_or_404('not_installed_app.change_user') | |
125 | def dummy_view(request): | |
126 | return HttpResponse('dummy_view') | |
127 | with mock.patch('guardian.conf.settings.TEMPLATE_404', | |
128 | '_non-exisitng-404.html'): | |
129 | self.assertRaises(TemplateDoesNotExist, dummy_view, request) | |
130 | ||
95 | 131 | @mock.patch('guardian.conf.settings.RENDER_403', False) |
96 | 132 | @mock.patch('guardian.conf.settings.RAISE_403', True) |
97 | 133 | def test_RAISE_403_setting_is_true(self): |
102 | 138 | return HttpResponse('dummy_view') |
103 | 139 | |
104 | 140 | self.assertRaises(PermissionDenied, dummy_view, request) |
141 | ||
142 | @mock.patch('guardian.conf.settings.RENDER_404', False) | |
143 | @mock.patch('guardian.conf.settings.RAISE_404', True) | |
144 | def test_RAISE_404_setting_is_true(self): | |
145 | request = self._get_request(self.anon) | |
146 | ||
147 | @permission_required_or_404('not_installed_app.change_user') | |
148 | def dummy_view(request): | |
149 | return HttpResponse('dummy_view') | |
150 | ||
151 | self.assertRaises(ObjectDoesNotExist, dummy_view, request) | |
105 | 152 | |
106 | 153 | def test_anonymous_user_wrong_app(self): |
107 | 154 |
0 | 0 | from __future__ import unicode_literals |
1 | from guardian.testapp.models import Mixed, ReverseMixed | |
2 | from guardian.testapp.models import Project | |
3 | from guardian.testapp.models import ProjectGroupObjectPermission | |
4 | from guardian.testapp.models import ProjectUserObjectPermission | |
1 | ||
5 | 2 | from django.contrib.auth.models import Group, Permission |
6 | 3 | from django.test import TestCase |
4 | ||
7 | 5 | from guardian.compat import get_user_model |
8 | 6 | from guardian.shortcuts import assign_perm |
9 | 7 | from guardian.shortcuts import get_groups_with_perms |
11 | 9 | from guardian.shortcuts import get_objects_for_user |
12 | 10 | from guardian.shortcuts import get_users_with_perms |
13 | 11 | from guardian.shortcuts import remove_perm |
12 | from guardian.testapp.models import Mixed, ReverseMixed | |
13 | from guardian.testapp.models import Project | |
14 | from guardian.testapp.models import ProjectGroupObjectPermission | |
15 | from guardian.testapp.models import ProjectUserObjectPermission | |
14 | 16 | from guardian.testapp.tests.conf import skipUnlessTestApp |
15 | ||
16 | 17 | |
17 | 18 | User = get_user_model() |
18 | 19 | |
19 | 20 | |
20 | 21 | @skipUnlessTestApp |
21 | 22 | class TestDirectUserPermissions(TestCase): |
22 | ||
23 | 23 | def setUp(self): |
24 | 24 | self.joe = User.objects.create_user('joe', 'joe@example.com', 'foobar') |
25 | 25 | self.project = Project.objects.create(name='Foobar') |
72 | 72 | assign_perm('change_project', jane, self.project) |
73 | 73 | self.assertEqual(get_users_with_perms(self.project, attach_perms=True), |
74 | 74 | { |
75 | self.joe: ['add_project', 'change_project'], | |
76 | jane: ['change_project'], | |
77 | }) | |
75 | self.joe: ['add_project', 'change_project'], | |
76 | jane: ['change_project'], | |
77 | }) | |
78 | 78 | |
79 | 79 | def test_get_users_with_perms_plus_groups(self): |
80 | 80 | User.objects.create_user('john', 'john@foobar.com', 'john') |
86 | 86 | assign_perm('change_project', jane, self.project) |
87 | 87 | self.assertEqual(get_users_with_perms(self.project, attach_perms=True), |
88 | 88 | { |
89 | self.joe: ['add_project', 'change_project'], | |
90 | jane: ['change_project'], | |
91 | }) | |
89 | self.joe: ['add_project', 'change_project'], | |
90 | jane: ['change_project'], | |
91 | }) | |
92 | 92 | |
93 | 93 | def test_get_objects_for_user(self): |
94 | 94 | foo = Project.objects.create(name='foo') |
120 | 120 | |
121 | 121 | @skipUnlessTestApp |
122 | 122 | class TestDirectGroupPermissions(TestCase): |
123 | ||
124 | 123 | def setUp(self): |
125 | 124 | self.joe = User.objects.create_user('joe', 'joe@example.com', 'foobar') |
126 | 125 | self.group = Group.objects.create(name='admins') |
175 | 174 | assign_perm('change_project', devs, self.project) |
176 | 175 | self.assertEqual(get_groups_with_perms(self.project, attach_perms=True), |
177 | 176 | { |
178 | self.group: ['add_project', 'change_project'], | |
179 | devs: ['change_project'], | |
180 | }) | |
177 | self.group: ['add_project', 'change_project'], | |
178 | devs: ['change_project'], | |
179 | }) | |
180 | ||
181 | def test_get_groups_with_perms_doesnt_spawn_extra_queries_for_more_groups_with_perms(self): | |
182 | Group.objects.create(name='managers') | |
183 | devs = Group.objects.create(name='devs') | |
184 | devs1 = Group.objects.create(name='devs1') | |
185 | devs2 = Group.objects.create(name='devs2') | |
186 | devs3 = Group.objects.create(name='devs3') | |
187 | devs4 = Group.objects.create(name='devs4') | |
188 | devs5 = Group.objects.create(name='devs5') | |
189 | assign_perm('add_project', self.group, self.project) | |
190 | assign_perm('change_project', self.group, self.project) | |
191 | for group in [devs, devs1, devs2, devs3, devs4, devs5]: | |
192 | assign_perm('add_project', group, self.project) | |
193 | assign_perm('change_project', group, self.project) | |
194 | ||
195 | with self.assertNumQueries(3): | |
196 | result = get_groups_with_perms(self.project, attach_perms=True) | |
197 | ||
198 | self.assertEqual(result, | |
199 | { | |
200 | self.group: ['add_project', 'change_project'], | |
201 | devs: ['add_project', 'change_project'], | |
202 | devs1: ['add_project', 'change_project'], | |
203 | devs2: ['add_project', 'change_project'], | |
204 | devs3: ['add_project', 'change_project'], | |
205 | devs4: ['add_project', 'change_project'], | |
206 | devs5: ['add_project', 'change_project'], | |
207 | }) | |
181 | 208 | |
182 | 209 | def test_get_objects_for_group(self): |
183 | 210 | foo = Project.objects.create(name='foo') |
193 | 220 | |
194 | 221 | @skipUnlessTestApp |
195 | 222 | class TestMixedDirectAndGenericObjectPermission(TestCase): |
196 | ||
197 | 223 | def setUp(self): |
198 | 224 | self.joe = User.objects.create_user('joe', 'joe@example.com', 'foobar') |
199 | 225 | self.group = Group.objects.create(name='admins') |
211 | 237 | assign_perm('change_mixed', jane, self.mixed) |
212 | 238 | self.assertEqual(get_users_with_perms(self.mixed, attach_perms=True), |
213 | 239 | { |
214 | self.joe: ['add_mixed', 'change_mixed'], | |
215 | jane: ['change_mixed'], | |
216 | }) | |
240 | self.joe: ['add_mixed', 'change_mixed'], | |
241 | jane: ['change_mixed'], | |
242 | }) | |
217 | 243 | result = get_objects_for_user(self.joe, 'testapp.add_mixed') |
218 | 244 | self.assertEqual(sorted(p.pk for p in result), |
219 | 245 | sorted([self.mixed.pk])) |
228 | 254 | assign_perm('change_reversemixed', jane, self.reverse_mixed) |
229 | 255 | self.assertEqual(get_users_with_perms(self.reverse_mixed, attach_perms=True), |
230 | 256 | { |
231 | self.joe: ['add_reversemixed', 'change_reversemixed'], | |
232 | jane: ['change_reversemixed'], | |
233 | }) | |
257 | self.joe: ['add_reversemixed', 'change_reversemixed'], | |
258 | jane: ['change_reversemixed'], | |
259 | }) | |
234 | 260 | result = get_objects_for_user(self.joe, 'testapp.add_reversemixed') |
235 | 261 | self.assertEqual(sorted(p.pk for p in result), |
236 | 262 | sorted([self.reverse_mixed.pk])) |
0 | 0 | from __future__ import unicode_literals |
1 | 1 | from django.test import TestCase |
2 | 2 | import mock |
3 | import warnings | |
3 | 4 | from guardian.managers import UserObjectPermissionManager |
4 | 5 | from guardian.managers import GroupObjectPermissionManager |
5 | 6 | |
9 | 10 | def test_user_manager_assign(self): |
10 | 11 | manager = UserObjectPermissionManager() |
11 | 12 | manager.assign_perm = mock.Mock() |
12 | manager.assign('perm', 'user', 'object') | |
13 | ||
14 | with warnings.catch_warnings(record=True) as w: | |
15 | warnings.simplefilter("always") | |
16 | ||
17 | manager.assign('perm', 'user', 'object') | |
18 | ||
13 | 19 | manager.assign_perm.assert_called_once_with('perm', 'user', 'object') |
20 | ||
21 | self.assertTrue(issubclass(w[0].category, DeprecationWarning)) | |
22 | self.assertIn("UserObjectPermissionManager method 'assign' is being renamed to 'assign_perm'.", str(w[0].message)) | |
14 | 23 | |
15 | 24 | def test_group_manager_assign(self): |
16 | 25 | manager = GroupObjectPermissionManager() |
17 | 26 | manager.assign_perm = mock.Mock() |
18 | manager.assign('perm', 'group', 'object') | |
27 | ||
28 | with warnings.catch_warnings(record=True) as w: | |
29 | warnings.simplefilter("always") | |
30 | ||
31 | manager.assign('perm', 'group', 'object') | |
32 | ||
19 | 33 | manager.assign_perm.assert_called_once_with('perm', 'group', 'object') |
34 | ||
35 | self.assertTrue(issubclass(w[0].category, DeprecationWarning)) | |
36 | self.assertIn("UserObjectPermissionManager method 'assign' is being renamed to 'assign_perm'.", str(w[0].message)) |
6 | 6 | from django.test import TestCase |
7 | 7 | from django.test.client import RequestFactory |
8 | 8 | from django.views.generic import View |
9 | ||
9 | from django.views.generic import ListView | |
10 | ||
11 | from guardian.shortcuts import assign_perm | |
10 | 12 | from guardian.compat import get_user_model |
11 | 13 | import mock |
12 | 14 | from guardian.mixins import LoginRequiredMixin |
13 | 15 | from guardian.mixins import PermissionRequiredMixin |
16 | from guardian.mixins import PermissionListMixin | |
14 | 17 | |
15 | 18 | from ..models import Post |
16 | 19 | |
34 | 37 | permission_required = 'testapp.change_post' |
35 | 38 | |
36 | 39 | |
40 | class GlobalNoObjectView(PermissionRequiredMixin, RemoveDatabaseView): | |
41 | permission_required = 'testapp.add_post' | |
42 | accept_global_perms = True | |
43 | ||
44 | ||
45 | class PostPermissionListView(PermissionListMixin, ListView): | |
46 | model = Post | |
47 | permission_required = 'testapp.change_post' | |
48 | template_name = 'list.html' | |
49 | ||
50 | ||
37 | 51 | class TestViewMixins(TestCase): |
38 | 52 | |
39 | 53 | def setUp(self): |
40 | self.post = Post.objects.create(title='foo') | |
54 | self.post = Post.objects.create(title='foo-post-title') | |
41 | 55 | self.factory = RequestFactory() |
42 | 56 | self.user = get_user_model().objects.create_user( |
43 | 57 | 'joe', 'joe@doe.com', 'doe') |
110 | 124 | response = view(request) |
111 | 125 | self.assertEqual(response.status_code, 302) |
112 | 126 | |
127 | def test_permission_required_global_no_object(self): | |
128 | """ | |
129 | This test would fail if permission is checked on a view's | |
130 | object when it not set and **no** global permission | |
131 | """ | |
132 | ||
133 | request = self.factory.get('/') | |
134 | request.user = self.user | |
135 | view = GlobalNoObjectView.as_view() | |
136 | response = view(request) | |
137 | self.assertEqual(response.status_code, 302) | |
138 | ||
139 | def test_permission_granted_global_no_object(self): | |
140 | """ | |
141 | This test would fail if permission is checked on a view's | |
142 | object when it not set and **has** global permission | |
143 | """ | |
144 | ||
145 | request = self.factory.get('/') | |
146 | request.user = self.user | |
147 | assign_perm('testapp.add_post', request.user) | |
148 | view = GlobalNoObjectView.as_view() | |
149 | with self.assertRaises(DatabaseRemovedError): | |
150 | view(request) | |
151 | ||
113 | 152 | def test_permission_required_as_list(self): |
114 | 153 | """ |
115 | 154 | This test would fail if permission is checked **after** view is |
159 | 198 | response = view(request) |
160 | 199 | self.assertEqual(response.status_code, 200) |
161 | 200 | self.assertEqual(response.content, b'secret-view') |
201 | ||
202 | def test_list_permission(self): | |
203 | request = self.factory.get('/some-secret-list/') | |
204 | request.user = AnonymousUser() | |
205 | ||
206 | view = PostPermissionListView.as_view() | |
207 | ||
208 | response = view(request) | |
209 | self.assertNotContains(response, b'foo-post-title') | |
210 | ||
211 | request.user = self.user | |
212 | request.user.add_obj_perm('change_post', self.post) | |
213 | ||
214 | response = view(request) | |
215 | self.assertContains(response, b'foo-post-title') |
68 | 68 | user_or_group=self.user) |
69 | 69 | |
70 | 70 | def test_user_assign_perm(self): |
71 | assign_perm("change_contenttype", self.user, self.ctype) | |
71 | assign_perm("add_contenttype", self.user, self.ctype) | |
72 | 72 | assign_perm("change_contenttype", self.group, self.ctype) |
73 | assign_perm(self.get_permission("delete_contenttype"), self.user, self.ctype) | |
74 | self.assertTrue(self.user.has_perm("add_contenttype", self.ctype)) | |
73 | 75 | self.assertTrue(self.user.has_perm("change_contenttype", self.ctype)) |
76 | self.assertTrue(self.user.has_perm("delete_contenttype", self.ctype)) | |
74 | 77 | |
75 | 78 | def test_group_assign_perm(self): |
79 | assign_perm("add_contenttype", self.group, self.ctype) | |
76 | 80 | assign_perm("change_contenttype", self.group, self.ctype) |
77 | assign_perm("delete_contenttype", self.group, self.ctype) | |
81 | assign_perm(self.get_permission("delete_contenttype"), self.group, self.ctype) | |
78 | 82 | |
79 | 83 | check = ObjectPermissionChecker(self.group) |
84 | self.assertTrue(check.has_perm("add_contenttype", self.ctype)) | |
80 | 85 | self.assertTrue(check.has_perm("change_contenttype", self.ctype)) |
81 | 86 | self.assertTrue(check.has_perm("delete_contenttype", self.ctype)) |
87 | ||
88 | def test_user_assign_perm_queryset(self): | |
89 | assign_perm("add_contenttype", self.user, self.ctype_qset) | |
90 | assign_perm("change_contenttype", self.group, self.ctype_qset) | |
91 | assign_perm(self.get_permission("delete_contenttype"), self.user, self.ctype_qset) | |
92 | for obj in self.ctype_qset: | |
93 | self.assertTrue(self.user.has_perm("add_contenttype", obj)) | |
94 | self.assertTrue(self.user.has_perm("change_contenttype", obj)) | |
95 | self.assertTrue(self.user.has_perm("delete_contenttype", obj)) | |
96 | ||
97 | def test_group_assign_perm_queryset(self): | |
98 | assign_perm("add_contenttype", self.group, self.ctype_qset) | |
99 | assign_perm("change_contenttype", self.group, self.ctype_qset) | |
100 | assign_perm(self.get_permission("delete_contenttype"), self.group, self.ctype_qset) | |
101 | ||
102 | check = ObjectPermissionChecker(self.group) | |
103 | for obj in self.ctype_qset: | |
104 | self.assertTrue(check.has_perm("add_contenttype", obj)) | |
105 | self.assertTrue(check.has_perm("change_contenttype", obj)) | |
106 | self.assertTrue(check.has_perm("delete_contenttype", obj)) | |
82 | 107 | |
83 | 108 | def test_user_assign_perm_global(self): |
84 | 109 | perm = assign_perm("contenttypes.change_contenttype", self.user) |
128 | 153 | |
129 | 154 | check = ObjectPermissionChecker(self.group) |
130 | 155 | self.assertFalse(check.has_perm("change_contenttype", self.ctype)) |
156 | ||
157 | def test_user_remove_perm_queryset(self): | |
158 | assign_perm("change_contenttype", self.user, self.ctype_qset) | |
159 | remove_perm("change_contenttype", self.user, self.ctype_qset) | |
160 | for obj in self.ctype_qset: | |
161 | self.assertFalse(self.user.has_perm("change_contenttype", obj)) | |
162 | ||
163 | def test_user_remove_perm_empty_queryset(self): | |
164 | assign_perm("change_contenttype", self.user, self.ctype_qset) | |
165 | remove_perm("change_contenttype", self.user, self.ctype_qset.none()) | |
166 | ||
167 | self.assertEquals(list(self.ctype_qset.none()), []) | |
168 | for obj in self.ctype_qset: | |
169 | self.assertTrue(self.user.has_perm("change_contenttype", obj)) | |
170 | ||
171 | def test_group_remove_perm_queryset(self): | |
172 | assign_perm("change_contenttype", self.group, self.ctype_qset) | |
173 | remove_perm("change_contenttype", self.group, self.ctype_qset) | |
174 | ||
175 | check = ObjectPermissionChecker(self.group) | |
176 | for obj in self.ctype_qset: | |
177 | self.assertFalse(check.has_perm("change_contenttype", obj)) | |
131 | 178 | |
132 | 179 | def test_user_remove_perm_global(self): |
133 | 180 | # assign perm first |
12 | 12 | permission_required = 'testapp.change_project' |
13 | 13 | |
14 | 14 | urlpatterns = [ |
15 | url(r'^admin/', include(admin.site.urls)), | |
15 | url(r'^admin/', admin.site.urls), | |
16 | 16 | url(r'^accounts/login/', login, {'template_name': 'blank.html'}), |
17 | 17 | url(r'^permission_required/', TestClassRedirectView.as_view()), |
18 | 18 | ] |
1 | 1 | import random |
2 | 2 | import string |
3 | 3 | import environ |
4 | import django | |
4 | 5 | |
5 | 6 | env = environ.Env() |
6 | 7 | |
27 | 28 | 'guardian.backends.ObjectPermissionBackend', |
28 | 29 | ) |
29 | 30 | |
30 | # this fixes warnings in django 1.7 | |
31 | MIDDLEWARE_CLASSES = ( | |
31 | # this fixes warnings in django 1.10 | |
32 | MIDDLEWARE = ( | |
32 | 33 | 'django.middleware.common.CommonMiddleware', |
33 | 34 | 'django.contrib.sessions.middleware.SessionMiddleware', |
34 | 35 | 'django.contrib.auth.middleware.AuthenticationMiddleware', |
35 | 36 | 'django.contrib.messages.middleware.MessageMiddleware', |
36 | 37 | ) |
37 | 38 | |
39 | if django.VERSION < (1, 10): | |
40 | MIDDLEWARE_CLASSES = MIDDLEWARE | |
41 | ||
38 | 42 | TEST_RUNNER = 'django.test.runner.DiscoverRunner' |
39 | 43 | |
40 | 44 | ROOT_URLCONF = 'guardian.testapp.tests.urls' |
41 | 45 | SITE_ID = 1 |
42 | ||
43 | TEMPLATE_DIRS = ( | |
44 | os.path.join(os.path.dirname(__file__), 'tests', 'templates'), | |
45 | ) | |
46 | 46 | |
47 | 47 | SECRET_KEY = ''.join([random.choice(string.ascii_letters) for x in range(40)]) |
48 | 48 | |
54 | 54 | TEMPLATES = [ |
55 | 55 | { |
56 | 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
57 | 'DIRS': TEMPLATE_DIRS, | |
57 | 'DIRS': ( | |
58 | os.path.join(os.path.dirname(__file__), 'tests', 'templates'), | |
59 | ), | |
58 | 60 | 'APP_DIRS': True, |
59 | 61 | 'OPTIONS': { |
60 | 62 | 'context_processors': [ |
69 | 71 | }, |
70 | 72 | }, |
71 | 73 | ] |
74 | ||
75 | if django.VERSION < (1, 8): | |
76 | TEMPLATE_DIRS = TEMPLATES[0]['DIRS'] |
5 | 5 | they actual input parameters/output type may change in future releases. |
6 | 6 | """ |
7 | 7 | from __future__ import unicode_literals |
8 | import os | |
9 | import logging | |
10 | from itertools import chain | |
11 | import django | |
12 | 8 | from django.conf import settings |
13 | 9 | from django.contrib.auth import REDIRECT_FIELD_NAME |
14 | 10 | from django.contrib.auth.models import AnonymousUser, Group |
15 | from django.contrib.contenttypes.models import ContentType | |
16 | from django.core.exceptions import PermissionDenied | |
11 | from django.core.exceptions import ObjectDoesNotExist, PermissionDenied | |
17 | 12 | from django.db.models import Model |
18 | from django.http import HttpResponseForbidden | |
13 | from django.http import HttpResponseForbidden, HttpResponseNotFound | |
19 | 14 | from django.shortcuts import render_to_response |
20 | from django.template import RequestContext, TemplateDoesNotExist | |
15 | from django.template import RequestContext | |
16 | from guardian.compat import get_user_model, remote_model | |
17 | from guardian.conf import settings as guardian_settings | |
18 | from guardian.ctypes import get_content_type | |
19 | from guardian.exceptions import NotUserNorGroup | |
20 | from itertools import chain | |
21 | 21 | |
22 | from guardian.compat import get_user_model | |
23 | from guardian.conf import settings as guardian_settings | |
24 | from guardian.exceptions import NotUserNorGroup | |
25 | ||
26 | from django.contrib.auth.views import redirect_to_login | |
22 | import django | |
23 | import logging | |
24 | import os | |
27 | 25 | |
28 | 26 | logger = logging.getLogger(__name__) |
29 | 27 | abspath = lambda *p: os.path.abspath(os.path.join(*p)) |
82 | 80 | "(got %s)" % identity) |
83 | 81 | |
84 | 82 | |
85 | def get_403_or_None(request, perms, obj=None, login_url=None, | |
86 | redirect_field_name=None, return_403=False, accept_global_perms=False): | |
83 | def get_40x_or_None(request, perms, obj=None, login_url=None, | |
84 | redirect_field_name=None, return_403=False, | |
85 | return_404=False, accept_global_perms=False): | |
87 | 86 | login_url = login_url or settings.LOGIN_URL |
88 | 87 | redirect_field_name = redirect_field_name or REDIRECT_FIELD_NAME |
89 | 88 | |
110 | 109 | elif guardian_settings.RAISE_403: |
111 | 110 | raise PermissionDenied |
112 | 111 | return HttpResponseForbidden() |
112 | if return_404: | |
113 | if guardian_settings.RENDER_404: | |
114 | response = render_to_response( | |
115 | guardian_settings.TEMPLATE_404, {}, | |
116 | RequestContext(request)) | |
117 | response.status_code = 404 | |
118 | return response | |
119 | elif guardian_settings.RAISE_404: | |
120 | raise ObjectDoesNotExist | |
121 | return HttpResponseNotFound() | |
113 | 122 | else: |
123 | from django.contrib.auth.views import redirect_to_login | |
114 | 124 | return redirect_to_login(request.get_full_path(), |
115 | 125 | login_url, |
116 | 126 | redirect_field_name) |
128 | 138 | |
129 | 139 | deleted = 0 |
130 | 140 | # TODO: optimise |
131 | for perm in chain(UserObjectPermission.objects.all(), | |
132 | GroupObjectPermission.objects.all()): | |
141 | for perm in chain(UserObjectPermission.objects.all().iterator(), | |
142 | GroupObjectPermission.objects.all().iterator()): | |
133 | 143 | if perm.content_object is None: |
134 | 144 | logger.debug("Removing %s (pk=%d)" % (perm, perm.pk)) |
135 | 145 | perm.delete() |
145 | 155 | def get_obj_perms_model(obj, base_cls, generic_cls): |
146 | 156 | if isinstance(obj, Model): |
147 | 157 | obj = obj.__class__ |
148 | ctype = ContentType.objects.get_for_model(obj) | |
158 | ctype = get_content_type(obj) | |
149 | 159 | |
150 | 160 | if django.VERSION >= (1, 8): |
151 | 161 | fields = (f for f in obj._meta.get_fields() |
165 | 175 | # make sure that content_object's content_type is same as |
166 | 176 | # the one of given obj |
167 | 177 | fk = model._meta.get_field('content_object') |
168 | if ctype == ContentType.objects.get_for_model(fk.rel.to): | |
178 | if ctype == get_content_type(remote_model(fk)): | |
169 | 179 | return model |
170 | 180 | return generic_cls |
171 | 181 |
0 | # coding: utf-8 | |
1 | # file generated by setuptools_scm | |
2 | # don't change, don't track in version control | |
3 | version = '1.4.4' |
0 | [bumpversion] | |
1 | current_version = 1.4.8 | |
2 | ||
0 | 3 | [build_sphinx] |
1 | 4 | source-dir = docs/ |
2 | 5 | build-dir = docs/build |
6 | 9 | upload-dir = docs/build/html |
7 | 10 | |
8 | 11 | [bdist_rpm] |
9 | requires = Django >= 1.2 | |
12 | requires = Django >= 1.8 | |
10 | 13 | |
11 | 14 | [wheel] |
12 | 15 | universal = 1 |
14 | 17 | [aliases] |
15 | 18 | test = pytest |
16 | 19 | |
20 | [bumpversion:file:setup.py] | |
21 | search = version = '{current_version}' | |
22 | replace = version = '{new_version}' | |
23 | ||
24 | [bumpversion:file:guardian/__init__.py] | |
25 | search = __version__ = '{current_version}' | |
26 | replace = __version__ = '{new_version}' | |
27 | ||
17 | 28 | [egg_info] |
18 | 29 | tag_build = |
19 | 30 | tag_date = 0 |
0 | 0 | import os |
1 | import sys | |
2 | 1 | from setuptools import setup |
3 | 2 | from extras import RunFlakesCommand |
4 | 3 | |
5 | 4 | |
6 | def version_scheme(version): | |
7 | from setuptools_scm.version import guess_next_dev_version | |
8 | version = guess_next_dev_version(version) | |
9 | return version.lstrip("v") | |
5 | version = '1.4.8' | |
10 | 6 | |
11 | 7 | readme_file = os.path.join(os.path.dirname(__file__), 'README.rst') |
12 | 8 | with open(readme_file, 'r') as f: |
13 | long_description = f.readline().strip() | |
9 | long_description = f.read() | |
14 | 10 | |
15 | 11 | setup( |
16 | 12 | name='django-guardian', |
17 | use_scm_version={ | |
18 | 'write_to': "guardian/version.py", | |
19 | 'version_scheme': version_scheme, | |
20 | }, | |
21 | setup_requires=['setuptools_scm', 'pytest-runner'], | |
13 | version=version, | |
14 | setup_requires=['pytest-runner', ], | |
22 | 15 | url='http://github.com/django-guardian/django-guardian', |
23 | 16 | author='Lukasz Balcerzak', |
24 | 17 | author_email='lukaszbalcerzak@gmail.com', |
35 | 28 | include_package_data=True, |
36 | 29 | license='BSD', |
37 | 30 | install_requires=[ |
38 | 'Django >= 1.7', | |
39 | 31 | 'six', |
40 | 32 | ], |
41 | 33 | tests_require=['mock', 'django-environ', 'pytest', 'pytest-django'], |
48 | 40 | 'Programming Language :: Python', |
49 | 41 | 'Topic :: Security', |
50 | 42 | 'Programming Language :: Python :: 2.7', |
51 | 'Programming Language :: Python :: 3.3', | |
52 | 43 | 'Programming Language :: Python :: 3.4', |
53 | 44 | 'Programming Language :: Python :: 3.5', |
45 | 'Programming Language :: Python :: 3.6', | |
54 | 46 | ], |
55 | 47 | test_suite='tests.main', |
56 | 48 | cmdclass={'flakes': RunFlakesCommand}, |
0 | 0 | [tox] |
1 | 1 | downloadcache = {toxworkdir}/cache/ |
2 | 2 | envlist = |
3 | py27-django17, | |
4 | py27-django18, | |
5 | py27-django19, | |
6 | py33-django17, | |
7 | py33-django18, | |
8 | py34-django17, | |
9 | py34-django18, | |
10 | py35-django18, | |
11 | py34-django19, | |
12 | py35-django19, | |
3 | {core,docs}-py27-django18, | |
4 | {core}-py34-django18, | |
5 | {core,docs}-py35-django18, | |
6 | {core,example,docs}-py27-django110, | |
7 | {core,example,docs}-py27-django111, | |
8 | {core}-py34-django110, | |
9 | {core}-py34-django111, | |
10 | {core,example,docs}-py35-django110 | |
11 | {core,example,docs}-py35-django111 | |
12 | {core,example,docs}-py36-django110 | |
13 | {core,example,docs}-py36-django111 | |
13 | 14 | |
14 | 15 | [testenv] |
15 | 16 | passenv = DATABASE_URL |
16 | 17 | basepython = |
17 | py26: python2.6 | |
18 | 18 | py27: python2.7 |
19 | py33: python3.3 | |
20 | 19 | py34: python3.4 |
21 | 20 | py35: python3.5 |
21 | py36: python3.6 | |
22 | changedir = | |
23 | example: example_project | |
24 | docs: docs | |
22 | 25 | commands = |
23 | # python setup.py flakes | |
24 | py.test --cov=guardian | |
25 | sphinx-build -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html | |
26 | core: py.test --cov=guardian | |
27 | docs: sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html | |
28 | example: python manage.py test | |
26 | 29 | deps = |
27 | sphinx | |
28 | mock>=0.7.2 | |
29 | setuptools>=17.1 | |
30 | setuptools_scm | |
31 | sphinx_rtd_theme | |
32 | pyflakes | |
33 | 30 | django-environ |
34 | pytest | |
35 | pytest-django | |
36 | pytest-cov | |
37 | django17: django==1.7.10 | |
38 | django18: django==1.8.7 | |
39 | django19: django==1.9.1 | |
31 | core: mock>=0.7.2 | |
32 | core: setuptools>=17.1 | |
33 | core: pyflakes | |
34 | core: pytest | |
35 | core: pytest-django | |
36 | core: pytest-cov | |
37 | example: . | |
38 | docs: sphinx | |
39 | docs: sphinx_rtd_theme | |
40 | docs: setuptools_scm | |
41 | django18: django==1.8.17 | |
42 | django110: django>=1.10<1.11 | |
43 | django111: django>=1.11<1.12 |