django-guardian-1.4.1/0000700000175000017500000000000012644306605014753 5ustar brianbrian00000000000000django-guardian-1.4.1/extras.py0000600000175000017500000000556612634712306016647 0ustar brianbrian00000000000000import _ast import os import sys from setuptools import Command #from pyflakes.scripts import pyflakes as flakes def check(filename): from pyflakes import reporter as mod_reporter from pyflakes.checker import Checker codeString = open(filename).read() reporter = mod_reporter._makeDefaultReporter() # First, compile into an AST and handle syntax errors. try: tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) except SyntaxError: value = sys.exc_info()[1] msg = value.args[0] (lineno, offset, text) = value.lineno, value.offset, value.text # If there's an encoding problem with the file, the text is None. if text is None: # Avoid using msg, since for the only known case, it contains a # bogus message that claims the encoding the file declared was # unknown. reporter.unexpectedError(filename, 'problem decoding source') else: reporter.syntaxError(filename, msg, lineno, offset, text) return 1 except Exception: reporter.unexpectedError(filename, 'problem decoding source') return 1 else: # Okay, it's syntactically valid. Now check it. lines = codeString.splitlines() warnings = Checker(tree, filename) warnings.messages.sort(key=lambda m: m.lineno) real_messages = [] for m in warnings.messages: line = lines[m.lineno - 1] if 'pyflakes:ignore' in line.rsplit('#', 1)[-1]: # ignore lines with pyflakes:ignore pass else: real_messages.append(m) reporter.flake(m) return len(real_messages) class RunFlakesCommand(Command): """ Runs pyflakes against guardian codebase. """ description = "Check sources with pyflakes" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: import pyflakes # pyflakes:ignore except ImportError: sys.stderr.write("No pyflakes installed!\n") sys.exit(-1) thisdir = os.path.dirname(__file__) guardiandir = os.path.join(thisdir, 'guardian') warns = 0 # Define top-level directories for topdir, dirnames, filenames in os.walk(guardiandir): paths = (os.path.join(topdir, f) for f in filenames if f .endswith('.py')) for path in paths: if path.endswith('tests/__init__.py'): # ignore that module (it should only gather test cases with *) continue warns += check(path) if warns > 0: sys.stderr.write("ERROR: Finished with total %d warnings.\n" % warns) sys.exit(1) else: print("No problems found in source codes.") django-guardian-1.4.1/setup.cfg0000600000175000017500000000035612644306605016602 0ustar brianbrian00000000000000[build_sphinx] source-dir = docs/ build-dir = docs/build all_files = 1 [upload_sphinx] upload-dir = docs/build/html [bdist_rpm] requires = Django >= 1.2 [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-guardian-1.4.1/tox.ini0000600000175000017500000000127112644306223016265 0ustar brianbrian00000000000000[tox] downloadcache = {toxworkdir}/cache/ envlist = py27-django17, py27-django18, py27-django19, py33-django17, py33-django18, py34-django17, py34-django18, py35-django18, py34-django19, py35-django19, [testenv] passenv = DATABASE_URL basepython = py26: python2.6 py27: python2.7 py33: python3.3 py34: python3.4 py35: python3.5 commands = # python setup.py flakes python setup.py test sphinx-build -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html deps = sphinx mock>=0.7.2 setuptools>=17.1 pyflakes django-environ django17: django==1.7.10 django18: django==1.8.7 django19: django==1.9.1 django-guardian-1.4.1/utils.py0000600000175000017500000000162612634712306016472 0ustar brianbrian00000000000000 def show_settings(settings, action): import guardian from django.utils.termcolors import colorize guardian_path = guardian.__path__[0] msg = "django-guardian module's path: %r" % guardian_path print(colorize(msg, fg='magenta')) db_conf = settings.DATABASES['default'] output = [] msg = "Starting %s for db backend: %s" % (action, db_conf['ENGINE']) embracer = '=' * len(msg) output.append(msg) for key in sorted(db_conf.keys()): if key == 'PASSWORD': value = '****************' else: value = db_conf[key] line = ' %s: "%s"' % (key, value) output.append(line) embracer = colorize('=' * len(max(output, key=lambda s: len(s))), fg='green', opts=['bold']) output = [colorize(line, fg='blue') for line in output] output.insert(0, embracer) output.append(embracer) print('\n'.join(output)) django-guardian-1.4.1/requirements.txt0000600000175000017500000000004112644306223020230 0ustar brianbrian00000000000000Django==1.9.1 six django-environ django-guardian-1.4.1/CHANGES0000600000175000017500000002012312644306223015742 0ustar brianbrian00000000000000Release 1.4.1 (Jan 10, 2016) =========================== * Fix broken documentation. * Fix setup.py errors (#387). * Fix tox tests. * Fix travis tests. Release 1.4.0 (Jan 8, 2016) =========================== * Drop support for Django < 1.7 * Drop support for django south migrations. * Remove depreciated code. * Fix many Django depreciated warnings. * Fix tests and example_project. * Work around for postgresql specific Django bug (#366). This is a regression that was introduced in version 1.3.2. * Updates to documentation. * Require can_change permission to change object perms in admin. * Fixes broke admin URLS (#376 and #381). * Tests now work with Mysql and Postgresql as well as sqlite. * Uses django-environ for tests. Release 1.3.2 (Nov 14, 2015) ============================ * Fixes tests for all versions of Django. * Tests pass for Django 1.9b1. * Drops support for Django < 1.5 * Add Russian translation. * Various bug fixes. * Ensure password for anonymous user is set to unusable, not None. Release 1.3.1 (Oct 20, 2015) ============================ * Fixes for 1.8 compat Release 1.3 (Jun 3, 2015) ========================= * Official Django 1.8 support (thanks to multiple contributors) Release 1.2.5 (Dec 28, 2014) ============================ * Official Django 1.7 support (thanks Troy Grosfield and Brian May) * Allow to override ``PermissionRequiredMixin.get_permission_object``, part of ``PermissionRequiredMixin.check_permissions`` method, responsible for retrieving single object (Thanks zauddelig) * French translations (Thanks Morgan Aubert) * Added support for ``User.get_all_permissions`` (thanks Michael Drescher) Release 1.2.4 (Jul 14, 2014) ============================ * Fixed another issue with custom primary keys at admin extensions (Thanks Omer Katz) Release 1.2.3 (Jul 14, 2014) ============================ Unfortunately this was broken release not including any important changes. Release 1.2.2 (Jul 2, 2014) =========================== * Fixed issue with custom primary keys at admin extensions (Thanks Omer Katz) * ``get_403_or_None`` now accepts Python path to the view function, for example ``'django.contrib.auth.views.login'`` (Thanks Warren Volz) * Added ``with_superuser`` flag to ``guardian.shortcuts.get_objects_for_user`` (Thanks Bruno Ribeiro da Silva) * Added possibility to disable monkey patching of the ``User`` model. (Thanks Cezar Jenkins) Release 1.2 (Mar 7, 2014) ========================= * Removed ``get_for_object`` methods from managers (#188) * Extended documentation * GuardedModelAdmin has been splitted into mixins * Faster queries in get_objects_for_user when use_groups=False or any_perm=True (#148) * Improved speed of get_objects_for_user shortcut * Support for custom User model with not default username field * Added GUARDIAN_GET_INIT_ANONYMOUS_USER setting (#179) * Added ``accept_global_perms`` to ``PermissionRequiredMixin`` * Added brazilian portuguese translations * Added polish translations * Added ``wheel`` support * Fixed wrong anonymous user checks * Support for Django 1.6 * Support for Django 1.7 alpha .. important:: In this release we have removed undocumented ``get_for_object`` method from both ``UserObjectPermissionManager`` and ``GroupObjectPermissionManager``. Not deprecated, removed. Those methods were not used within ``django-guardian`` and their odd names could lead to issues if user would believe they would return object level permissions associated with user/group and object passed as the input. If you depend on those methods, you'd need to stick with version 1.1 and make sure you do not misuse them. Release 1.1 (May 26, 2013) ========================== * Support for Django 1.5 (including Python 3 combination) * Support for custom user models (introduced by Django 1.5) * Ability to create permissions using Foreign Keys * Added ``user_can_access_owned_by_group_objects_only`` option to ``GuardedModelAdmin.`` * Minor documentation fixups * Spanish translations * Better support for grappelli_ * Updated examples project * Speed up ``get_perms`` shortcut function Release 1.0.4 (Jul 15, 2012) ============================ * Added ``GUARDIAN_RENDER_403`` and ``GUARDIAN_RAISE_403`` settings (#40) * Updated docstring for ``get_obj_perms`` (#43) * Updated codes to run with newest django-grappelli (#51) * Fixed problem with building a RPM package (#50) * Updated caveats docs related with oprhaned object permissions (#47) * Updated ``permission_required`` docstring (#49) * Added ``accept_global_perms`` for decorators (#49) * Fixed problem with MySQL and booleans (#56) * Added flag to check for *any* permission in ``get_objects_for_user`` and ``get_objects_for_group`` (#65) * Added missing *tag closing* at template (#63) * Added view mixins related with authorization and authentication (#73) * Added tox_ support * Added Travis_ support Release 1.0.3 (Jul 25, 2011) ============================ * Added ``get_objects_for_group`` shortcut (thanks to Rafael Ponieman) * Added ``user_can_access_owned_objects_only`` flag to ``GuardedModelAdmin`` * Updated and fixed issues with example app (thanks to Bojan Mihelac) * Minor typo fixed at documentation * Included ADC theme for documentation Release 1.0.2 (Apr 12, 2011) ============================ * ``get_users_with_perms`` now accepts ``with_group_users`` flag * Fixed ``group_id`` issue at admin templates * Small fix for documentation building process * It's 2011 (updated dates within this file) Release 1.0.1 (Mar 25, 2011) ============================ * ``get_users_with_perms`` now accepts ``with_superusers`` flag * Small fix for documentation building process Release 1.0.0 (Jan 27, 2011) ============================ * A final v1.0 release! Release 1.0.0.beta2 (Jan 14, 2011) ================================== * Added ``get_objects_for_user`` shortcut function * Added few tests * Fixed issues related with ``django.contrib.auth`` tests * Removed example project from source distribution Release 1.0.0.beta1 (Jan 11, 2011) ================================== * Simplified example project * Fixed issues related with test suite * Added ability to clear orphaned object permissions * Added ``clean_orphan_obj_perms`` management command * Documentation cleanup * Added grappelli_ admin templates Release 1.0.0.alpha2 (Dec 2, 2010) ================================== * Added possibility to operate with global permissions for assign and ``remove_perm`` shortcut functions * Added possibility to generate PDF documentation * Fixed some tests Release 1.0.0.alpha1 (Nov 23, 2010) =================================== * Fixed admin templates not included in ``MANIFEST.in`` * Fixed admin integration codes Release 1.0.0.pre (Nov 23, 2010) ================================ * Added admin integration * Added reusable forms for object permissions management Release 0.2.3 (Nov 17, 2010) ============================ * Added ``guardian.shortcuts.get_users_with_perms`` function * Added ``AUTHORS`` file Release 0.2.2 (Oct 19, 2010) ============================ * Fixed migrations order (thanks to Daniel Rech) Release 0.2.1 (Oct 3, 2010) =========================== * Fixed migration (it wasn't actually updating object_pk field) Release 0.2.0 (Oct 3, 2010) =========================== Fixes ~~~~~ * #4: guardian now supports models with not-integer primary keys and they don't need to be called "id". .. important:: For 0.1.X users: it is required to *migrate* guardian in your projects. Add ``south`` to ``INSTALLED_APPS`` and run:: python manage.py syncdb python manage.py migrate guardian 0001 --fake python manage.py migrate guardian Improvements ~~~~~~~~~~~~ * Added South_ migrations support Release 0.1.1 (Sep 27, 2010) ============================ Improvements ~~~~~~~~~~~~ * Added view decorators: ``permission_required`` and ``permission_required_403`` Release 0.1.0 (Jun 6, 2010) =========================== * Initial public release .. _south: http://south.aeracode.org/ .. _grappelli: https://github.com/sehmaschine/django-grappelli .. _tox: http://tox.testrun.org/ .. _travis: http://travis-ci.org/ .. vim: ft=rst django-guardian-1.4.1/manage.py0000700000175000017500000000040712643667664016577 0ustar brianbrian00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "guardian.testapp.testsettings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-guardian-1.4.1/django_guardian.egg-info/0000700000175000017500000000000012644306605021561 5ustar brianbrian00000000000000django-guardian-1.4.1/django_guardian.egg-info/dependency_links.txt0000600000175000017500000000000112644306600025624 0ustar brianbrian00000000000000 django-guardian-1.4.1/django_guardian.egg-info/SOURCES.txt0000600000175000017500000001266212644306605023456 0ustar brianbrian00000000000000.gitignore .travis.yml AUTHORS CHANGES LICENSE MANIFEST.in README.rst VERSION.txt extras.py manage.py requirements.txt run_test_and_report.sh setup.cfg setup.py tests.py tox.ini utils.py django_guardian.egg-info/PKG-INFO django_guardian.egg-info/SOURCES.txt django_guardian.egg-info/dependency_links.txt django_guardian.egg-info/not-zip-safe django_guardian.egg-info/requires.txt django_guardian.egg-info/top_level.txt docs/Makefile docs/conf.py docs/configuration.rst docs/exts.py docs/exts.pyc docs/index.rst docs/installation.rst docs/license.rst docs/make.bat docs/overview.rst docs/watch-docs.sh docs/__pycache__/exts.cpython-33.pyc docs/__pycache__/exts.cpython-34.pyc docs/__pycache__/exts.cpython-35.pyc docs/api/guardian.admin.rst docs/api/guardian.backends.rst docs/api/guardian.core.rst docs/api/guardian.decorators.rst docs/api/guardian.forms.rst docs/api/guardian.management.commands.rst docs/api/guardian.managers.rst docs/api/guardian.mixins.rst docs/api/guardian.models.rst docs/api/guardian.shortcuts.rst docs/api/guardian.templatetags.guardian_tags.rst docs/api/guardian.utils.rst docs/api/index.rst docs/develop/changes.rst docs/develop/index.rst docs/develop/overview.rst docs/develop/supported-versions.rst docs/develop/testing.rst docs/theme/rtd_theme/breadcrumbs.html docs/theme/rtd_theme/footer.html docs/theme/rtd_theme/layout.html docs/theme/rtd_theme/layout_old.html docs/theme/rtd_theme/search.html docs/theme/rtd_theme/searchbox.html docs/theme/rtd_theme/theme.conf docs/theme/rtd_theme/versions.html docs/theme/rtd_theme/sass/_badge.sass docs/theme/rtd_theme/sass/_badge_font_awesome_mini.sass docs/theme/rtd_theme/sass/_breadcrumbs.sass docs/theme/rtd_theme/sass/_nav.sass docs/theme/rtd_theme/sass/badge_only.sass docs/theme/rtd_theme/sass/config.rb docs/theme/rtd_theme/sass/theme.sass docs/theme/rtd_theme/static/badge_only.css docs/theme/rtd_theme/static/theme.css docs/theme/rtd_theme/static/theme.js docs/theme/rtd_theme/static/font/fontawesome_webfont.eot docs/theme/rtd_theme/static/font/fontawesome_webfont.svg docs/theme/rtd_theme/static/font/fontawesome_webfont.ttf docs/theme/rtd_theme/static/font/fontawesome_webfont.woff docs/userguide/admin-integration.rst docs/userguide/assign.rst docs/userguide/caveats.rst docs/userguide/check.rst docs/userguide/custom-user-model.rst docs/userguide/example_project.rst docs/userguide/index.rst docs/userguide/performance.rst docs/userguide/remove.rst guardian/__init__.py guardian/admin.py guardian/apps.py guardian/backends.py guardian/checks.py guardian/compat.py guardian/core.py guardian/decorators.py guardian/exceptions.py guardian/forms.py guardian/managers.py guardian/mixins.py guardian/models.py guardian/shortcuts.py guardian/utils.py guardian/conf/__init__.py guardian/conf/settings.py guardian/locale/es/LC_MESSAGES/django.mo guardian/locale/es/LC_MESSAGES/django.po guardian/locale/fr/LC_MESSAGES/django.mo guardian/locale/fr/LC_MESSAGES/django.po guardian/locale/pl/LC_MESSAGES/django.mo guardian/locale/pl/LC_MESSAGES/django.po guardian/locale/pt_BR/LC_MESSAGES/django.mo guardian/locale/pt_BR/LC_MESSAGES/django.po guardian/locale/ru/LC_MESSAGES/django.mo guardian/locale/ru/LC_MESSAGES/django.po guardian/management/__init__.py guardian/management/commands/__init__.py guardian/management/commands/clean_orphan_obj_perms.py guardian/migrations/0001_initial.py guardian/migrations/__init__.py guardian/static/guardian/img/icon-no.svg guardian/static/guardian/img/icon-yes.svg guardian/templates/admin/guardian/contrib/grappelli/field.html guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage.html guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html guardian/templates/admin/guardian/model/change_form.html guardian/templates/admin/guardian/model/field.html guardian/templates/admin/guardian/model/obj_perms_manage.html guardian/templates/admin/guardian/model/obj_perms_manage_group.html guardian/templates/admin/guardian/model/obj_perms_manage_user.html guardian/templates/admin/guardian/model/obj_perms_no.html guardian/templates/admin/guardian/model/obj_perms_yes.html guardian/templatetags/__init__.py guardian/templatetags/guardian_tags.py guardian/testapp/__init__.py guardian/testapp/models.py guardian/testapp/testsettings.py guardian/testapp/migrations/0001_initial.py guardian/testapp/migrations/0002_logentrywithgroup.py guardian/testapp/migrations/0003_auto_20141124_0729.py guardian/testapp/migrations/0004_auto_20151112_2209.py guardian/testapp/migrations/0005_auto_20151217_2344.py guardian/testapp/migrations/__init__.py guardian/testapp/tests/__init__.py guardian/testapp/tests/conf.py guardian/testapp/tests/test_admin.py guardian/testapp/tests/test_checks.py guardian/testapp/tests/test_conf.py guardian/testapp/tests/test_core.py guardian/testapp/tests/test_custompkmodel.py guardian/testapp/tests/test_decorators.py guardian/testapp/tests/test_direct_rel.py guardian/testapp/tests/test_forms.py guardian/testapp/tests/test_management.py guardian/testapp/tests/test_managers.py guardian/testapp/tests/test_mixins.py guardian/testapp/tests/test_orphans.py guardian/testapp/tests/test_other.py guardian/testapp/tests/test_shortcuts.py guardian/testapp/tests/test_tags.py guardian/testapp/tests/test_utils.py guardian/testapp/tests/urls.py guardian/testapp/tests/templates/404.html guardian/testapp/tests/templates/500.html guardian/testapp/tests/templates/blank.html guardian/testapp/tests/templates/dummy403.htmldjango-guardian-1.4.1/django_guardian.egg-info/PKG-INFO0000600000175000017500000000162112644306600022653 0ustar brianbrian00000000000000Metadata-Version: 1.1 Name: django-guardian Version: 1.4.1 Summary: Implementation of per object permissions for Django. Home-page: http://github.com/django-guardian/django-guardian Author: Lukasz Balcerzak Author-email: lukaszbalcerzak@gmail.com License: BSD Download-URL: https://github.com/django-guardian/django-guardian/tags Description: 1.4.1 Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Security Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 django-guardian-1.4.1/django_guardian.egg-info/not-zip-safe0000600000175000017500000000000112636352750024014 0ustar brianbrian00000000000000 django-guardian-1.4.1/django_guardian.egg-info/requires.txt0000600000175000017500000000002212644306600024150 0ustar brianbrian00000000000000Django >= 1.7 six django-guardian-1.4.1/django_guardian.egg-info/top_level.txt0000600000175000017500000000004412644306600024306 0ustar brianbrian00000000000000benchmarks example_project guardian django-guardian-1.4.1/tests.py0000600000175000017500000000152112644306223016464 0ustar brianbrian00000000000000""" Unit tests runner for ``django-guardian`` based on boundled example project. Tests are independent from this example application but setuptools need instructions how to interpret ``test`` command when we run:: python setup.py test """ import os import sys import contextlib import tempfile import shutil @contextlib.contextmanager def tempdir(): dirpath = tempfile.mkdtemp() prevdir = os.getcwd() try: os.chdir(dirpath) yield dirpath finally: os.chdir(prevdir) shutil.rmtree(dirpath) def main(): os.environ.setdefault( "DJANGO_SETTINGS_MODULE", "guardian.testapp.testsettings") import django from django.core.management import call_command django.setup() with tempdir(): call_command('test') sys.exit(0) if __name__ == '__main__': main() django-guardian-1.4.1/PKG-INFO0000600000175000017500000000162112644306605016052 0ustar brianbrian00000000000000Metadata-Version: 1.1 Name: django-guardian Version: 1.4.1 Summary: Implementation of per object permissions for Django. Home-page: http://github.com/django-guardian/django-guardian Author: Lukasz Balcerzak Author-email: lukaszbalcerzak@gmail.com License: BSD Download-URL: https://github.com/django-guardian/django-guardian/tags Description: 1.4.1 Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Security Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 django-guardian-1.4.1/.travis.yml0000600000175000017500000000440212643667664017104 0ustar brianbrian00000000000000language: python sudo: false python: - 2.7 - 3.3 - 3.4 - 3.5 env: - DJANGO_VERSION=1.7.10 DATABASE_URL=postgres://postgres@/django_guardian - DJANGO_VERSION=1.8.7 DATABASE_URL=postgres://postgres@/django_guardian - DJANGO_VERSION=1.9.1 DATABASE_URL=postgres://postgres@/django_guardian - DJANGO_VERSION=1.7.10 DATABASE_URL=mysql://root:@localhost/django_guardian - DJANGO_VERSION=1.8.7 DATABASE_URL=mysql://root:@localhost/django_guardian - DJANGO_VERSION=1.9.1 DATABASE_URL=mysql://root:@localhost/django_guardian - DJANGO_VERSION=1.7.10 DATABASE_URL=sqlite:// - DJANGO_VERSION=1.8.7 DATABASE_URL=sqlite:// - DJANGO_VERSION=1.9.1 DATABASE_URL=sqlite:// install: - travis_retry pip install -q mock==1.0.1 Django==$DJANGO_VERSION coverage coveralls # Install database drivers - bash -c "if [[ $DATABASE_URL = postgres* ]]; then pip install psycopg2==2.6.1; fi; " - bash -c "if [[ $DATABASE_URL = mysql* ]]; then pip install mysqlclient==1.3.7; fi; " script: - coverage run --source=guardian setup.py test after_success: - coverage report --omit "guardian/compat.py,guardian/testapp/testsettings.py" -m guardian/*.py - coveralls notifications: irc: "irc.freenode.net#django-guardian" matrix: exclude: # Drop python 3.3 and django 1.9 - python: 3.3 env: DJANGO_VERSION=1.9.1 DATABASE_URL=postgres://postgres@/django_guardian - python: 3.3 env: DJANGO_VERSION=1.9.1 DATABASE_URL=mysql://root:@localhost/django_guardian - python: 3.3 env: DJANGO_VERSION=1.9.1 DATABASE_URL=sqlite:// # Drop python 3.5 and django 1.7 - python: 3.5 env: DJANGO_VERSION=1.7.10 DATABASE_URL=postgres://postgres@/django_guardian - python: 3.5 env: DJANGO_VERSION=1.7.10 DATABASE_URL=mysql://root:@localhost/django_guardian - python: 3.5 env: DJANGO_VERSION=1.7.10 DATABASE_URL=sqlite:// # Drop python 3.3 with postgres due lack of driver - python: 3.3 env: DJANGO_VERSION=1.7.10 DATABASE_URL=postgres://postgres@/django_guardian - python: 3.3 env: DJANGO_VERSION=1.8.7 DATABASE_URL=postgres://postgres@/django_guardian - python: 3.3 env: DJANGO_VERSION=1.9.1 DATABASE_URL=postgres://postgres@/django_guardian django-guardian-1.4.1/LICENSE0000600000175000017500000000506012634712306015761 0ustar brianbrian00000000000000Copyright (c) 2010-2014 Lukasz Balcerzak All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The SVG icons in guardian/static/guardian/img are copied from Django. SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG Font-Awesome-SVG-PNG is licensed under the MIT license: The MIT License (MIT) Copyright (c) 2014 Code Charm Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. django-guardian-1.4.1/guardian/0000700000175000017500000000000012644306605016545 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/checks.py0000600000175000017500000000214112634712306020355 0ustar brianbrian00000000000000 from django.core.checks import register, Tags, Warning from django.conf import settings # noinspection PyUnusedLocal @register(Tags.compatibility) def check_settings(app_configs, **kwargs): """ Check that settings are implemented properly :param app_configs: a list of apps to be checks or None for all :param kwargs: keyword arguments :return: a list of errors """ checks = [] if 'guardian.backends.ObjectPermissionBackend' not in settings.AUTHENTICATION_BACKENDS: msg = ("Guardian authentication backend is not hooked. You can add this in settings as eg: " "`AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', " "'guardian.backends.ObjectPermissionBackend')`.") checks.append(Warning(msg, id='guardian.W001')) if not settings.ANONYMOUS_USER_ID: msg = ("No anonymous user ID is defined. Guardian supports anonymous user object permissions, therefore " "this needs to be specified in settings: `ANONYMOUS_USER_ID = -1`.") checks.append(Warning(msg, id='guardian.W002')) return checks django-guardian-1.4.1/guardian/managers.py0000600000175000017500000001052512634712306020717 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.models import ContentType from guardian.exceptions import ObjectNotPersisted from guardian.models import Permission import warnings # TODO: consolidate UserObjectPermissionManager and GroupObjectPermissionManager class BaseObjectPermissionManager(models.Manager): def is_generic(self): try: self.model._meta.get_field('object_pk') return True except models.fields.FieldDoesNotExist: return False class UserObjectPermissionManager(BaseObjectPermissionManager): def assign_perm(self, perm, user, obj): """ Assigns permission with given ``perm`` for an instance ``obj`` and ``user``. """ if getattr(obj, 'pk', None) is None: raise ObjectNotPersisted("Object %s needs to be persisted first" % obj) ctype = ContentType.objects.get_for_model(obj) permission = Permission.objects.get(content_type=ctype, codename=perm) kwargs = {'permission': permission, 'user': user} if self.is_generic(): kwargs['content_type'] = ctype kwargs['object_pk'] = obj.pk else: kwargs['content_object'] = obj obj_perm, created = self.get_or_create(**kwargs) return obj_perm def assign(self, perm, user, obj): """ Depreciated function name left in for compatibility""" 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) return self.assign_perm(perm, user, obj) def remove_perm(self, perm, user, obj): """ Removes permission ``perm`` for an instance ``obj`` and given ``user``. Please note that we do NOT fetch object permission from database - we use ``Queryset.delete`` method for removing it. Main implication of this is that ``post_delete`` signals would NOT be fired. """ if getattr(obj, 'pk', None) is None: raise ObjectNotPersisted("Object %s needs to be persisted first" % obj) filters = { 'permission__codename': perm, 'permission__content_type': ContentType.objects.get_for_model(obj), 'user': user, } if self.is_generic(): filters['object_pk'] = obj.pk else: filters['content_object__pk'] = obj.pk self.filter(**filters).delete() class GroupObjectPermissionManager(BaseObjectPermissionManager): def assign_perm(self, perm, group, obj): """ Assigns permission with given ``perm`` for an instance ``obj`` and ``group``. """ if getattr(obj, 'pk', None) is None: raise ObjectNotPersisted("Object %s needs to be persisted first" % obj) ctype = ContentType.objects.get_for_model(obj) permission = Permission.objects.get(content_type=ctype, codename=perm) kwargs = {'permission': permission, 'group': group} if self.is_generic(): kwargs['content_type'] = ctype kwargs['object_pk'] = obj.pk else: kwargs['content_object'] = obj obj_perm, created = self.get_or_create(**kwargs) return obj_perm def assign(self, perm, user, obj): """ Depreciated function name left in for compatibility""" 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) return self.assign_perm(perm, user, obj) def remove_perm(self, perm, group, obj): """ Removes permission ``perm`` for an instance ``obj`` and given ``group``. """ if getattr(obj, 'pk', None) is None: raise ObjectNotPersisted("Object %s needs to be persisted first" % obj) filters = { 'permission__codename': perm, 'permission__content_type': ContentType.objects.get_for_model(obj), 'group': group, } if self.is_generic(): filters['object_pk'] = obj.pk else: filters['content_object__pk'] = obj.pk self.filter(**filters).delete() django-guardian-1.4.1/guardian/utils.py0000600000175000017500000001513112634712306020260 0ustar brianbrian00000000000000""" django-guardian helper functions. Functions defined within this module should be considered as django-guardian's internal functionality. They are **not** guaranteed to be stable - which means they actual input parameters/output type may change in future releases. """ from __future__ import unicode_literals import os import logging from itertools import chain import django from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import AnonymousUser, Group from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.db.models import Model from django.http import HttpResponseForbidden, HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext, TemplateDoesNotExist from django.utils.http import urlquote from guardian.compat import get_user_model from guardian.conf import settings as guardian_settings from guardian.exceptions import NotUserNorGroup from django.contrib.auth.views import redirect_to_login logger = logging.getLogger(__name__) abspath = lambda *p: os.path.abspath(os.path.join(*p)) def get_anonymous_user(): """ Returns ``User`` instance (not ``AnonymousUser``) depending on ``ANONYMOUS_USER_ID`` configuration. """ return get_user_model().objects.get(pk=guardian_settings.ANONYMOUS_USER_ID) def get_identity(identity): """ Returns (user_obj, None) or (None, group_obj) tuple depending on what is given. Also accepts AnonymousUser instance but would return ``User`` instead - it is convenient and needed for authorization backend to support anonymous users. :param identity: either ``User`` or ``Group`` instance :raises ``NotUserNorGroup``: if cannot return proper identity instance **Examples**:: >>> from django.contrib.auth.models import User >>> user = User.objects.create(username='joe') >>> get_identity(user) (, None) >>> group = Group.objects.create(name='users') >>> get_identity(group) (None, ) >>> anon = AnonymousUser() >>> get_identity(anon) (, None) >>> get_identity("not instance") ... NotUserNorGroup: User/AnonymousUser or Group instance is required (got ) """ if isinstance(identity, AnonymousUser): identity = get_anonymous_user() if isinstance(identity, get_user_model()): return identity, None elif isinstance(identity, Group): return None, identity raise NotUserNorGroup("User/AnonymousUser or Group instance is required " "(got %s)" % identity) def get_403_or_None(request, perms, obj=None, login_url=None, redirect_field_name=None, return_403=False, accept_global_perms=False): login_url = login_url or settings.LOGIN_URL redirect_field_name = redirect_field_name or REDIRECT_FIELD_NAME # Handles both original and with object provided permission check # as ``obj`` defaults to None has_permissions = False # global perms check first (if accept_global_perms) if accept_global_perms: has_permissions = all(request.user.has_perm(perm) for perm in perms) # if still no permission granted, try obj perms if not has_permissions: has_permissions = all(request.user.has_perm(perm, obj) for perm in perms) if not has_permissions: if return_403: if guardian_settings.RENDER_403: try: response = render_to_response( guardian_settings.TEMPLATE_403, {}, RequestContext(request)) response.status_code = 403 return response except TemplateDoesNotExist as e: if settings.DEBUG: raise e elif guardian_settings.RAISE_403: raise PermissionDenied return HttpResponseForbidden() else: return redirect_to_login(request.get_full_path(), login_url, redirect_field_name) def clean_orphan_obj_perms(): """ Seeks and removes all object permissions entries pointing at non-existing targets. Returns number of removed objects. """ from guardian.models import UserObjectPermission from guardian.models import GroupObjectPermission deleted = 0 # TODO: optimise for perm in chain(UserObjectPermission.objects.all(), GroupObjectPermission.objects.all()): if perm.content_object is None: logger.debug("Removing %s (pk=%d)" % (perm, perm.pk)) perm.delete() deleted += 1 logger.info("Total removed orphan object permissions instances: %d" % deleted) return deleted # TODO: should raise error when multiple UserObjectPermission direct relations # are defined def get_obj_perms_model(obj, base_cls, generic_cls): if isinstance(obj, Model): obj = obj.__class__ ctype = ContentType.objects.get_for_model(obj) if django.VERSION >= (1, 8): fields = (f for f in obj._meta.get_fields() if (f.one_to_many or f.one_to_one) and f.auto_created) else: fields = obj._meta.get_all_related_objects() for attr in fields: if django.VERSION < (1, 8): model = getattr(attr, 'model', None) else: model = getattr(attr, 'related_model', None) if (model and issubclass(model, base_cls) and model is not generic_cls): # if model is generic one it would be returned anyway if not model.objects.is_generic(): # make sure that content_object's content_type is same as # the one of given obj fk = model._meta.get_field('content_object') if ctype == ContentType.objects.get_for_model(fk.rel.to): return model return generic_cls def get_user_obj_perms_model(obj): """ Returns model class that connects given ``obj`` and User class. """ from guardian.models import UserObjectPermissionBase from guardian.models import UserObjectPermission return get_obj_perms_model(obj, UserObjectPermissionBase, UserObjectPermission) def get_group_obj_perms_model(obj): """ Returns model class that connects given ``obj`` and Group class. """ from guardian.models import GroupObjectPermissionBase from guardian.models import GroupObjectPermission return get_obj_perms_model(obj, GroupObjectPermissionBase, GroupObjectPermission) django-guardian-1.4.1/guardian/conf/0000700000175000017500000000000012644306605017472 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/conf/settings.py0000600000175000017500000000216612634712306021711 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.conf import settings from django.core.exceptions import ImproperlyConfigured ANONYMOUS_DEFAULT_USERNAME_VALUE = getattr(settings, 'ANONYMOUS_DEFAULT_USERNAME_VALUE', 'AnonymousUser') try: ANONYMOUS_USER_ID = settings.ANONYMOUS_USER_ID except AttributeError: raise ImproperlyConfigured("In order to use django-guardian's " "ObjectPermissionBackend authorization backend you have to configure " "ANONYMOUS_USER_ID at your settings module") RENDER_403 = getattr(settings, 'GUARDIAN_RENDER_403', False) TEMPLATE_403 = getattr(settings, 'GUARDIAN_TEMPLATE_403', '403.html') RAISE_403 = getattr(settings, 'GUARDIAN_RAISE_403', False) GET_INIT_ANONYMOUS_USER = getattr(settings, 'GUARDIAN_GET_INIT_ANONYMOUS_USER', 'guardian.management.get_init_anonymous_user') MONKEY_PATCH = getattr(settings, 'GUARDIAN_MONKEY_PATCH', True) def check_configuration(): if RENDER_403 and RAISE_403: raise ImproperlyConfigured("Cannot use both GUARDIAN_RENDER_403 AND " "GUARDIAN_RAISE_403 - only one of this config may be True") check_configuration() django-guardian-1.4.1/guardian/conf/__init__.py0000600000175000017500000000005112634712306021577 0ustar brianbrian00000000000000from __future__ import unicode_literals django-guardian-1.4.1/guardian/exceptions.py0000600000175000017500000000062412634712306021302 0ustar brianbrian00000000000000""" Exceptions used by django-guardian. All internal and guardian-specific errors should extend GuardianError class. """ from __future__ import unicode_literals class GuardianError(Exception): pass class NotUserNorGroup(GuardianError): pass class ObjectNotPersisted(GuardianError): pass class WrongAppError(GuardianError): pass class MixedContentTypeError(GuardianError): pass django-guardian-1.4.1/guardian/apps.py0000600000175000017500000000036712634712306020070 0ustar brianbrian00000000000000 from django.apps import AppConfig from . import monkey_patch_user from guardian.conf import settings class GuardianConfig(AppConfig): name = 'guardian' def ready(self): if settings.MONKEY_PATCH: monkey_patch_user() django-guardian-1.4.1/guardian/models.py0000600000175000017500000000617512634712306020413 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.db import models from django.core.exceptions import ValidationError from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType try: from django.contrib.contenttypes.fields import GenericForeignKey except ImportError: from django.contrib.contenttypes.generic import GenericForeignKey from django.utils.translation import ugettext_lazy as _ from guardian.compat import user_model_label from guardian.compat import unicode from guardian.conf import settings from guardian.managers import GroupObjectPermissionManager from guardian.managers import UserObjectPermissionManager class BaseObjectPermission(models.Model): """ Abstract ObjectPermission class. Actual class should additionally define a ``content_object`` field and either ``user`` or ``group`` field. """ permission = models.ForeignKey(Permission) class Meta: abstract = True def __unicode__(self): return '%s | %s | %s' % ( unicode(self.content_object), unicode(getattr(self, 'user', False) or self.group), unicode(self.permission.codename)) def save(self, *args, **kwargs): content_type = ContentType.objects.get_for_model(self.content_object) if content_type != self.permission.content_type: raise ValidationError("Cannot persist permission not designed for " "this class (permission's type is %r and object's type is %r)" % (self.permission.content_type, content_type)) return super(BaseObjectPermission, self).save(*args, **kwargs) class BaseGenericObjectPermission(models.Model): content_type = models.ForeignKey(ContentType) object_pk = models.CharField(_('object ID'), max_length=255) content_object = GenericForeignKey(fk_field='object_pk') class Meta: abstract = True class UserObjectPermissionBase(BaseObjectPermission): """ **Manager**: :manager:`UserObjectPermissionManager` """ user = models.ForeignKey(user_model_label) objects = UserObjectPermissionManager() class Meta: abstract = True unique_together = ['user', 'permission', 'content_object'] class UserObjectPermission(UserObjectPermissionBase, BaseGenericObjectPermission): class Meta: unique_together = ['user', 'permission', 'object_pk'] class GroupObjectPermissionBase(BaseObjectPermission): """ **Manager**: :manager:`GroupObjectPermissionManager` """ group = models.ForeignKey(Group) objects = GroupObjectPermissionManager() class Meta: abstract = True unique_together = ['group', 'permission', 'content_object'] class GroupObjectPermission(GroupObjectPermissionBase, BaseGenericObjectPermission): class Meta: unique_together = ['group', 'permission', 'object_pk'] setattr(Group, 'add_obj_perm', lambda self, perm, obj: GroupObjectPermission.objects.assign_perm(perm, self, obj)) setattr(Group, 'del_obj_perm', lambda self, perm, obj: GroupObjectPermission.objects.remove_perm(perm, self, obj)) django-guardian-1.4.1/guardian/decorators.py0000600000175000017500000001461112634712306021267 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.utils.functional import wraps from django.db.models import Model from django.apps import apps from django.db.models.base import ModelBase from django.db.models.query import QuerySet from django.shortcuts import get_object_or_404 from guardian.compat import basestring from guardian.exceptions import GuardianError from guardian.utils import get_403_or_None def permission_required(perm, lookup_variables=None, **kwargs): """ Decorator for views that checks whether a user has a particular permission enabled. Optionally, instances for which check should be made may be passed as an second argument or as a tuple parameters same as those passed to ``get_object_or_404`` but must be provided as pairs of strings. This way decorator can fetch i.e. ``User`` instance based on performed request and check permissions on it (without this, one would need to fetch user instance at view's logic and check permission inside a view). :param login_url: if denied, user would be redirected to location set by this parameter. Defaults to ``django.conf.settings.LOGIN_URL``. :param redirect_field_name: name of the parameter passed if redirected. Defaults to ``django.contrib.auth.REDIRECT_FIELD_NAME``. :param return_403: if set to ``True`` then instead of redirecting to the login page, response with status code 403 is returned ( ``django.http.HttpResponseForbidden`` instance or rendered template - see :setting:`GUARDIAN_RENDER_403`). Defaults to ``False``. :param accept_global_perms: if set to ``True``, then *object level permission* would be required **only if user does NOT have global permission** for target *model*. If turned on, makes this decorator like an extension over standard ``django.contrib.admin.decorators.permission_required`` as it would check for global permissions first. Defaults to ``False``. Examples:: @permission_required('auth.change_user', return_403=True) def my_view(request): return HttpResponse('Hello') @permission_required('auth.change_user', (User, 'username', 'username')) def my_view(request, username): ''' auth.change_user permission would be checked based on given 'username'. If view's parameter would be named ``name``, we would rather use following decorator:: @permission_required('auth.change_user', (User, 'username', 'name')) ''' user = get_object_or_404(User, username=username) return user.get_absolute_url() @permission_required('auth.change_user', (User, 'username', 'username', 'groups__name', 'group_name')) def my_view(request, username, group_name): ''' Similar to the above example, here however we also make sure that one of user's group is named same as request's ``group_name`` param. ''' user = get_object_or_404(User, username=username, group__name=group_name) return user.get_absolute_url() """ login_url = kwargs.pop('login_url', settings.LOGIN_URL) redirect_field_name = kwargs.pop('redirect_field_name', REDIRECT_FIELD_NAME) return_403 = kwargs.pop('return_403', False) accept_global_perms = kwargs.pop('accept_global_perms', False) # Check if perm is given as string in order not to decorate # view function itself which makes debugging harder if not isinstance(perm, basestring): raise GuardianError("First argument must be in format: " "'app_label.codename or a callable which return similar string'") def decorator(view_func): def _wrapped_view(request, *args, **kwargs): # if more than one parameter is passed to the decorator we try to # fetch object for which check would be made obj = None if lookup_variables: model, lookups = lookup_variables[0], lookup_variables[1:] # Parse model if isinstance(model, basestring): splitted = model.split('.') if len(splitted) != 2: raise GuardianError("If model should be looked up from " "string it needs format: 'app_label.ModelClass'") model = apps.get_model(*splitted) elif issubclass(model.__class__, (Model, ModelBase, QuerySet)): pass else: raise GuardianError("First lookup argument must always be " "a model, string pointing at app/model or queryset. " "Given: %s (type: %s)" % (model, type(model))) # Parse lookups if len(lookups) % 2 != 0: raise GuardianError("Lookup variables must be provided " "as pairs of lookup_string and view_arg") lookup_dict = {} for lookup, view_arg in zip(lookups[::2], lookups[1::2]): if view_arg not in kwargs: raise GuardianError("Argument %s was not passed " "into view function" % view_arg) lookup_dict[lookup] = kwargs[view_arg] obj = get_object_or_404(model, **lookup_dict) response = get_403_or_None(request, perms=[perm], obj=obj, login_url=login_url, redirect_field_name=redirect_field_name, return_403=return_403, accept_global_perms=accept_global_perms) if response: return response return view_func(request, *args, **kwargs) return wraps(view_func)(_wrapped_view) return decorator def permission_required_or_403(perm, *args, **kwargs): """ Simple wrapper for permission_required decorator. Standard Django's permission_required decorator redirects user to login page in case permission check failed. This decorator may be used to return HttpResponseForbidden (status 403) instead of redirection. The only difference between ``permission_required`` decorator is that this one always set ``return_403`` parameter to ``True``. """ kwargs['return_403'] = True return permission_required(perm, *args, **kwargs) django-guardian-1.4.1/guardian/backends.py0000600000175000017500000000643112634712306020675 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.db import models from guardian.compat import get_user_model from guardian.conf import settings from guardian.exceptions import WrongAppError from guardian.core import ObjectPermissionChecker def check_object_support(obj): """ Returns ``True`` if given ``obj`` is supported """ # Backend checks only object permissions (isinstance implies that obj # is not None) # Backend checks only permissions for Django models return isinstance(obj, models.Model) def check_user_support(user_obj): """ Returns a tuple of checkresult and ``user_obj`` which should be used for permission checks Checks if the given user is supported. Anonymous users need explicit activation via ANONYMOUS_USER_ID """ # This is how we support anonymous users - simply try to retrieve User # instance and perform checks for that predefined user if not user_obj.is_authenticated(): # If anonymous user permission is disabled then they are always unauthorized if settings.ANONYMOUS_USER_ID is None: return False, user_obj user_obj = get_user_model().objects.get(pk=settings.ANONYMOUS_USER_ID) return True, user_obj def check_support(user_obj, obj): """ Combination of ``check_object_support`` and ``check_user_support`` """ obj_support = check_object_support(obj) user_support, user_obj = check_user_support(user_obj) return obj_support and user_support, user_obj class ObjectPermissionBackend(object): supports_object_permissions = True supports_anonymous_user = True supports_inactive_user = True def authenticate(self, username, password): return None def has_perm(self, user_obj, perm, obj=None): """ Returns ``True`` if given ``user_obj`` has ``perm`` for ``obj``. If no ``obj`` is given, ``False`` is returned. .. note:: Remember, that if user is not *active*, all checks would return ``False``. Main difference between Django's ``ModelBackend`` is that we can pass ``obj`` instance here and ``perm`` doesn't have to contain ``app_label`` as it can be retrieved from given ``obj``. **Inactive user support** If user is authenticated but inactive at the same time, all checks always returns ``False``. """ # check if user_obj and object are supported support, user_obj = check_support(user_obj, obj) if not support: return False if '.' in perm: app_label, perm = perm.split('.') if app_label != obj._meta.app_label: raise WrongAppError("Passed perm has app label of '%s' and " "given obj has '%s'" % (app_label, obj._meta.app_label)) check = ObjectPermissionChecker(user_obj) return check.has_perm(perm, obj) def get_all_permissions(self, user_obj, obj=None): """ Returns a set of permission strings that the given ``user_obj`` has for ``obj`` """ # check if user_obj and object are supported support, user_obj = check_support(user_obj, obj) if not support: return set() check = ObjectPermissionChecker(user_obj) return check.get_perms(obj) django-guardian-1.4.1/guardian/static/0000700000175000017500000000000012644306605020034 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/static/guardian/0000700000175000017500000000000012644306605021626 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/static/guardian/img/0000700000175000017500000000000012644306605022402 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/static/guardian/img/icon-yes.svg0000600000175000017500000000066412634712306024657 0ustar brianbrian00000000000000 django-guardian-1.4.1/guardian/static/guardian/img/icon-no.svg0000600000175000017500000000106012634712306024462 0ustar brianbrian00000000000000 django-guardian-1.4.1/guardian/compat.py0000600000175000017500000001102012634712306020374 0ustar brianbrian00000000000000from __future__ import unicode_literals import django from django.conf import settings from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.auth.models import AnonymousUser import six import sys from importlib import import_module from django.conf.urls import url, patterns, include, handler404, handler500 __all__ = [ 'User', 'Group', 'Permission', 'AnonymousUser', 'get_user_model', 'import_string', 'user_model_label', 'url', 'patterns', 'include', 'handler404', 'handler500', 'mock', 'unittest', ] try: import unittest2 as unittest except ImportError: import unittest # pyflakes:ignore try: from unittest import mock # Since Python 3.3 mock is is in stdlib except ImportError: try: import mock # pyflakes:ignore except ImportError: # mock is used for tests only however it is hard to check if user is # running tests or production code so we fail silently here; mock is # still required for tests at setup.py (See PR #193) pass # Django 1.5 compatibility utilities, providing support for custom User models. # Since get_user_model() causes a circular import if called when app models are # being loaded, the user_model_label should be used when possible, with calls # to get_user_model deferred to execution time user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') try: from django.contrib.auth import get_user_model except ImportError: from django.contrib.auth.models import User get_user_model = lambda: User def get_user_model_path(): """ Returns 'app_label.ModelName' for User model. Basically if ``AUTH_USER_MODEL`` is set at settings it would be returned, otherwise ``auth.User`` is returned. """ return getattr(settings, 'AUTH_USER_MODEL', 'auth.User') def get_user_permission_full_codename(perm): """ Returns 'app_label._'. If standard ``auth.User`` is used, for 'change' perm this would return ``auth.change_user`` and if ``myapp.CustomUser`` is used it would return ``myapp.change_customuser``. """ User = get_user_model() model_name = User._meta.model_name return '%s.%s_%s' % (User._meta.app_label, perm, model_name) def get_user_permission_codename(perm): """ Returns '_'. If standard ``auth.User`` is used, for 'change' perm this would return ``change_user`` and if ``myapp.CustomUser`` is used it would return ``change_customuser``. """ return get_user_permission_full_codename(perm).split('.')[1] def import_string(dotted_path): """ Import a dotted module path and return the attribute/class designated by the last name in the path. Raise ImportError if the import failed. Backported from Django 1.7 """ try: module_path, class_name = dotted_path.rsplit('.', 1) except ValueError: msg = "%s doesn't look like a module path" % dotted_path six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) module = import_module(module_path) try: return getattr(module, class_name) except AttributeError: msg = 'Module "%s" does not define a "%s" attribute/class' % ( dotted_path, class_name) six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) # Python 3 try: unicode = unicode # pyflakes:ignore basestring = basestring # pyflakes:ignore str = str # pyflakes:ignore except NameError: basestring = unicode = str = str # OrderedDict only available in Python 2.7. # This will always be the case in Django 1.7 and above, as these versions # no longer support Python 2.6. from collections import OrderedDict # Django 1.7 compatibility # create_permission API changed: skip the create_models (second # positional argument) if we have django 1.7+ and 2+ positional # arguments with the second one being a list/tuple def create_permissions(*args, **kwargs): from django.contrib.auth.management import create_permissions as original_create_permissions if len(args) > 1 and isinstance(args[1], (list, tuple)): args = args[:1] + args[2:] return original_create_permissions(*args, **kwargs) __all__ = ['User', 'Group', 'Permission', 'AnonymousUser'] def get_model_name(model): """ Returns the name of the model """ # model._meta.module_name is deprecated in django version 1.7 and removed in django version 1.8. # It is replaced by model._meta.model_name return model._meta.model_name django-guardian-1.4.1/guardian/management/0000700000175000017500000000000012644306605020661 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/management/__init__.py0000600000175000017500000000313412634712306022773 0ustar brianbrian00000000000000from __future__ import unicode_literals import django from django.db.models import signals from guardian.conf import settings as guardian_settings from guardian.compat import get_user_model from guardian.compat import import_string def get_init_anonymous_user(User): """ Returns User model instance that would be referenced by guardian when permissions are checked against users that haven't signed into the system. :param User: User model - result of ``django.contrib.auth.get_user_model``. """ kwargs = { User.USERNAME_FIELD: guardian_settings.ANONYMOUS_DEFAULT_USERNAME_VALUE } user = User(**kwargs) user.set_unusable_password() return user def create_anonymous_user(sender, **kwargs): """ Creates anonymous User instance with id and username from settings. """ User = get_user_model() try: User.objects.get(pk=guardian_settings.ANONYMOUS_USER_ID) except User.DoesNotExist: retrieve_anonymous_function = import_string( guardian_settings.GET_INIT_ANONYMOUS_USER) user = retrieve_anonymous_function(User) # Always set pk to the one pointed at settings user.pk = guardian_settings.ANONYMOUS_USER_ID user.save() # Only create an anonymous user if support is enabled. if guardian_settings.ANONYMOUS_USER_ID is not None: # Django 1.7+ uses post_migrate signal from django.apps import apps guardian_app = apps.get_app_config('guardian') signals.post_migrate.connect(create_anonymous_user, sender=guardian_app, dispatch_uid="guardian.management.create_anonymous_user") django-guardian-1.4.1/guardian/management/commands/0000700000175000017500000000000012644306605022462 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/management/commands/__init__.py0000600000175000017500000000005112634712306024567 0ustar brianbrian00000000000000from __future__ import unicode_literals django-guardian-1.4.1/guardian/management/commands/clean_orphan_obj_perms.py0000600000175000017500000000131612634712306027526 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.core.management.base import NoArgsCommand from guardian.utils import clean_orphan_obj_perms class Command(NoArgsCommand): """ clean_orphan_obj_perms command is a tiny wrapper around :func:`guardian.utils.clean_orphan_obj_perms`. Usage:: $ python manage.py clean_orphan_obj_perms Removed 11 object permission entries with no targets """ help = "Removes object permissions with not existing targets" def handle_noargs(self, **options): removed = clean_orphan_obj_perms() if options['verbosity'] > 0: print("Removed %d object permission entries with no targets" % removed) django-guardian-1.4.1/guardian/__init__.py0000600000175000017500000000170712644306223020661 0ustar brianbrian00000000000000""" Implementation of per object permissions for Django. """ from __future__ import unicode_literals from . import checks VERSION = (1, 4, 1) __version__ = '.'.join((str(each) for each in VERSION[:4])) def get_version(): """ Returns shorter version (digit parts only) as string. """ return '.'.join((str(each) for each in VERSION[:4])) default_app_config = 'guardian.apps.GuardianConfig' def monkey_patch_user(): from .compat import get_user_model from .utils import get_anonymous_user from .models import UserObjectPermission User = get_user_model() # Prototype User and Group methods setattr(User, 'get_anonymous', staticmethod(lambda: get_anonymous_user())) setattr(User, 'add_obj_perm', lambda self, perm, obj: UserObjectPermission.objects.assign_perm(perm, self, obj)) setattr(User, 'del_obj_perm', lambda self, perm, obj: UserObjectPermission.objects.remove_perm(perm, self, obj)) django-guardian-1.4.1/guardian/migrations/0000700000175000017500000000000012644306605020721 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/migrations/0001_initial.py0000600000175000017500000000364312634712306023372 0ustar brianbrian00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings class Migration(migrations.Migration): dependencies = [ ('contenttypes', '0001_initial'), ('auth', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='GroupObjectPermission', fields=[ ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), ('object_pk', models.CharField(max_length=255, verbose_name='object ID')), ('content_type', models.ForeignKey(to='contenttypes.ContentType')), ('group', models.ForeignKey(to='auth.Group')), ('permission', models.ForeignKey(to='auth.Permission')), ], options={ }, bases=(models.Model,), ), migrations.CreateModel( name='UserObjectPermission', fields=[ ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), ('object_pk', models.CharField(max_length=255, verbose_name='object ID')), ('content_type', models.ForeignKey(to='contenttypes.ContentType')), ('permission', models.ForeignKey(to='auth.Permission')), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ], options={ }, bases=(models.Model,), ), migrations.AlterUniqueTogether( name='userobjectpermission', unique_together=set([('user', 'permission', 'object_pk')]), ), migrations.AlterUniqueTogether( name='groupobjectpermission', unique_together=set([('group', 'permission', 'object_pk')]), ), ] django-guardian-1.4.1/guardian/migrations/__init__.py0000600000175000017500000000000012616547266023033 0ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templatetags/0000700000175000017500000000000012644306605021237 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templatetags/guardian_tags.py0000600000175000017500000000617012634712306024425 0ustar brianbrian00000000000000""" ``django-guardian`` template tags. To use in a template just put the following *load* tag inside a template:: {% load guardian_tags %} """ from __future__ import unicode_literals from django import template from django.contrib.auth.models import Group, AnonymousUser from guardian.compat import get_user_model from guardian.exceptions import NotUserNorGroup from guardian.core import ObjectPermissionChecker register = template.Library() class ObjectPermissionsNode(template.Node): def __init__(self, for_whom, obj, context_var): self.for_whom = template.Variable(for_whom) self.obj = template.Variable(obj) self.context_var = context_var def render(self, context): for_whom = self.for_whom.resolve(context) if isinstance(for_whom, get_user_model()): self.user = for_whom self.group = None elif isinstance(for_whom, AnonymousUser): self.user = get_user_model().get_anonymous() self.group = None elif isinstance(for_whom, Group): self.user = None self.group = for_whom else: raise NotUserNorGroup("User or Group instance required (got %s)" % for_whom.__class__) obj = self.obj.resolve(context) if not obj: return '' check = ObjectPermissionChecker(for_whom) perms = check.get_perms(obj) context[self.context_var] = perms return '' @register.tag def get_obj_perms(parser, token): """ Returns a list of permissions (as ``codename`` strings) for a given ``user``/``group`` and ``obj`` (Model instance). Parses ``get_obj_perms`` tag which should be in format:: {% get_obj_perms user/group for obj as "context_var" %} .. note:: Make sure that you set and use those permissions in same template block (``{% block %}``). Example of usage (assuming ``flatpage`` and ``perm`` objects are available from *context*):: {% get_obj_perms request.user for flatpage as "flatpage_perms" %} {% if "delete_flatpage" in flatpage_perms %} Remove page {% endif %} .. note:: Please remember that superusers would always get full list of permissions for a given object. .. versionadded:: 1.2 As of v1.2, passing ``None`` as ``obj`` for this template tag won't rise obfuscated exception and would return empty permissions set instead. """ bits = token.split_contents() format = '{% get_obj_perms user/group for obj as "context_var" %}' if len(bits) != 6 or bits[2] != 'for' or bits[4] != 'as': raise template.TemplateSyntaxError("get_obj_perms tag should be in " "format: %s" % format) for_whom = bits[1] obj = bits[3] context_var = bits[5] if context_var[0] != context_var[-1] or context_var[0] not in ('"', "'"): raise template.TemplateSyntaxError("get_obj_perms tag's context_var " "argument should be in quotes") context_var = context_var[1:-1] return ObjectPermissionsNode(for_whom, obj, context_var) django-guardian-1.4.1/guardian/templatetags/__init__.py0000600000175000017500000000005112634712306023344 0ustar brianbrian00000000000000from __future__ import unicode_literals django-guardian-1.4.1/guardian/forms.py0000600000175000017500000001440112634712306020245 0ustar brianbrian00000000000000from __future__ import unicode_literals from django import forms from django.utils.translation import ugettext as _ from guardian.shortcuts import assign_perm from guardian.shortcuts import remove_perm from guardian.shortcuts import get_perms from guardian.shortcuts import get_perms_for_model class BaseObjectPermissionsForm(forms.Form): """ Base form for object permissions management. Needs to be extended for usage with users and/or groups. """ def __init__(self, obj, *args, **kwargs): """ :param obj: Any instance which form would use to manage object permissions" """ self.obj = obj super(BaseObjectPermissionsForm, self).__init__(*args, **kwargs) field_name = self.get_obj_perms_field_name() self.fields[field_name] = self.get_obj_perms_field() def get_obj_perms_field(self): """ Returns field instance for object permissions management. May be replaced entirely. """ field_class = self.get_obj_perms_field_class() field = field_class( label=self.get_obj_perms_field_label(), choices=self.get_obj_perms_field_choices(), initial=self.get_obj_perms_field_initial(), widget=self.get_obj_perms_field_widget(), required=self.are_obj_perms_required(), ) return field def get_obj_perms_field_name(self): """ Returns name of the object permissions management field. Default: ``permission``. """ return 'permissions' def get_obj_perms_field_label(self): """ Returns label of the object permissions management field. Defualt: ``_("Permissions")`` (marked to be translated). """ return _("Permissions") def get_obj_perms_field_choices(self): """ Returns choices for object permissions management field. Default: list of tuples ``(codename, name)`` for each ``Permission`` instance for the managed object. """ choices = [(p.codename, p.name) for p in get_perms_for_model(self.obj)] return choices def get_obj_perms_field_initial(self): """ Returns initial object permissions management field choices. Default: ``[]`` (empty list). """ return [] def get_obj_perms_field_class(self): """ Returns object permissions management field's base class. Default: ``django.forms.MultipleChoiceField``. """ return forms.MultipleChoiceField def get_obj_perms_field_widget(self): """ Returns object permissions management field's widget base class. Default: ``django.forms.SelectMultiple``. """ return forms.SelectMultiple def are_obj_perms_required(self): """ Indicates if at least one object permission should be required. Default: ``False``. """ return False def save_obj_perms(self): """ Must be implemented in concrete form class. This method should store selected object permissions. """ raise NotImplementedError class UserObjectPermissionsForm(BaseObjectPermissionsForm): """ Object level permissions management form for usage with ``User`` instances. Example usage:: from django.shortcuts import get_object_or_404 from myapp.models import Post from guardian.forms import UserObjectPermissionsForm from django.contrib.auth.models import User def my_view(request, post_slug, user_id): user = get_object_or_404(User, id=user_id) post = get_object_or_404(Post, slug=post_slug) form = UserObjectPermissionsForm(user, post, request.POST or None) if request.method == 'POST' and form.is_valid(): form.save_obj_perms() ... """ def __init__(self, user, *args, **kwargs): self.user = user super(UserObjectPermissionsForm, self).__init__(*args, **kwargs) def get_obj_perms_field_initial(self): perms = get_perms(self.user, self.obj) return perms def save_obj_perms(self): """ Saves selected object permissions by creating new ones and removing those which were not selected but already exists. Should be called *after* form is validated. """ perms = self.cleaned_data[self.get_obj_perms_field_name()] model_perms = [c[0] for c in self.get_obj_perms_field_choices()] to_remove = set(model_perms) - set(perms) for perm in to_remove: remove_perm(perm, self.user, self.obj) for perm in perms: assign_perm(perm, self.user, self.obj) class GroupObjectPermissionsForm(BaseObjectPermissionsForm): """ Object level permissions management form for usage with ``Group`` instances. Example usage:: from django.shortcuts import get_object_or_404 from myapp.models import Post from guardian.forms import GroupObjectPermissionsForm from guardian.models import Group def my_view(request, post_slug, group_id): group = get_object_or_404(Group, id=group_id) post = get_object_or_404(Post, slug=post_slug) form = GroupObjectPermissionsForm(group, post, request.POST or None) if request.method == 'POST' and form.is_valid(): form.save_obj_perms() ... """ def __init__(self, group, *args, **kwargs): self.group = group super(GroupObjectPermissionsForm, self).__init__(*args, **kwargs) def get_obj_perms_field_initial(self): perms = get_perms(self.group, self.obj) return perms def save_obj_perms(self): """ Saves selected object permissions by creating new ones and removing those which were not selected but already exists. Should be called *after* form is validated. """ perms = self.cleaned_data[self.get_obj_perms_field_name()] model_perms = [c[0] for c in self.get_obj_perms_field_choices()] to_remove = set(model_perms) - set(perms) for perm in to_remove: remove_perm(perm, self.group, self.obj) for perm in perms: assign_perm(perm, self.group, self.obj) django-guardian-1.4.1/guardian/shortcuts.py0000600000175000017500000006747512643667664021220 0ustar brianbrian00000000000000""" Convenient shortcuts to manage or check object permissions. """ from __future__ import unicode_literals from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.db.models import Count, Q from django.apps import apps from django.shortcuts import _get_queryset from itertools import groupby from guardian.compat import basestring from guardian.compat import get_user_model from guardian.core import ObjectPermissionChecker from guardian.exceptions import MixedContentTypeError from guardian.exceptions import WrongAppError from guardian.utils import get_anonymous_user from guardian.utils import get_group_obj_perms_model from guardian.utils import get_identity from guardian.utils import get_user_obj_perms_model import warnings def assign_perm(perm, user_or_group, obj=None): """ Assigns permission to user/group and object pair. :param perm: proper permission for given ``obj``, as string (in format: ``app_label.codename`` or ``codename``). If ``obj`` is not given, must be in format ``app_label.codename``. :param user_or_group: instance of ``User``, ``AnonymousUser`` or ``Group``; passing any other object would raise ``guardian.exceptions.NotUserNorGroup`` exception :param obj: persisted Django's ``Model`` instance or ``None`` if assigning global permission. Default is ``None``. We can assign permission for ``Model`` instance for specific user: >>> from django.contrib.sites.models import Site >>> from guardian.models import User >>> from guardian.shortcuts import assign_perm >>> site = Site.objects.get_current() >>> user = User.objects.create(username='joe') >>> assign_perm("change_site", user, site) >>> user.has_perm("change_site", site) True ... or we can assign permission for group: >>> group = Group.objects.create(name='joe-group') >>> user.groups.add(group) >>> assign_perm("delete_site", group, site) >>> user.has_perm("delete_site", site) True **Global permissions** This function may also be used to assign standard, *global* permissions if ``obj`` parameter is omitted. Added Permission would be returned in that case: >>> assign_perm("sites.change_site", user) """ user, group = get_identity(user_or_group) # If obj is None we try to operate on global permissions if obj is None: try: app_label, codename = perm.split('.', 1) except ValueError: raise ValueError("For global permissions, first argument must be in" " format: 'app_label.codename' (is %r)" % perm) perm = Permission.objects.get(content_type__app_label=app_label, codename=codename) if user: user.user_permissions.add(perm) return perm if group: group.permissions.add(perm) return perm perm = perm.split('.')[-1] if user: model = get_user_obj_perms_model(obj) return model.objects.assign_perm(perm, user, obj) if group: model = get_group_obj_perms_model(obj) return model.objects.assign_perm(perm, group, obj) def assign(perm, user_or_group, obj=None): """ Depreciated function name left in for compatibility""" 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) return assign_perm(perm, user_or_group, obj) def remove_perm(perm, user_or_group=None, obj=None): """ Removes permission from user/group and object pair. :param perm: proper permission for given ``obj``, as string (in format: ``app_label.codename`` or ``codename``). If ``obj`` is not given, must be in format ``app_label.codename``. :param user_or_group: instance of ``User``, ``AnonymousUser`` or ``Group``; passing any other object would raise ``guardian.exceptions.NotUserNorGroup`` exception :param obj: persisted Django's ``Model`` instance or ``None`` if assigning global permission. Default is ``None``. """ user, group = get_identity(user_or_group) if obj is None: try: app_label, codename = perm.split('.', 1) except ValueError: raise ValueError("For global permissions, first argument must be in" " format: 'app_label.codename' (is %r)" % perm) perm = Permission.objects.get(content_type__app_label=app_label, codename=codename) if user: user.user_permissions.remove(perm) return elif group: group.permissions.remove(perm) return perm = perm.split('.')[-1] if user: model = get_user_obj_perms_model(obj) model.objects.remove_perm(perm, user, obj) if group: model = get_group_obj_perms_model(obj) model.objects.remove_perm(perm, group, obj) def get_perms(user_or_group, obj): """ Returns permissions for given user/group and object pair, as list of strings. """ check = ObjectPermissionChecker(user_or_group) return check.get_perms(obj) def get_perms_for_model(cls): """ Returns queryset of all Permission objects for the given class. It is possible to pass Model as class or instance. """ if isinstance(cls, basestring): app_label, model_name = cls.split('.') model = apps.get_model(app_label, model_name) else: model = cls ctype = ContentType.objects.get_for_model(model) return Permission.objects.filter(content_type=ctype) def get_users_with_perms(obj, attach_perms=False, with_superusers=False, with_group_users=True): """ Returns queryset of all ``User`` objects with *any* object permissions for the given ``obj``. :param obj: persisted Django's ``Model`` instance :param attach_perms: Default: ``False``. If set to ``True`` result would be dictionary of ``User`` instances with permissions' codenames list as values. This would fetch users eagerly! :param with_superusers: Default: ``False``. If set to ``True`` result would contain all superusers. :param with_group_users: Default: ``True``. If set to ``False`` result would **not** contain those users who have only group permissions for given ``obj``. Example:: >>> from django.contrib.flatpages.models import FlatPage >>> from django.contrib.auth.models import User >>> from guardian.shortcuts import assign_perm, get_users_with_perms >>> >>> page = FlatPage.objects.create(title='Some page', path='/some/page/') >>> joe = User.objects.create_user('joe', 'joe@example.com', 'joesecret') >>> assign_perm('change_flatpage', joe, page) >>> >>> get_users_with_perms(page) [] >>> >>> get_users_with_perms(page, attach_perms=True) {: [u'change_flatpage']} """ ctype = ContentType.objects.get_for_model(obj) if not attach_perms: # It's much easier without attached perms so we do it first if that is # the case user_model = get_user_obj_perms_model(obj) related_name = user_model.user.field.related_query_name() if user_model.objects.is_generic(): user_filters = { '%s__content_type' % related_name: ctype, '%s__object_pk' % related_name: obj.pk, } else: user_filters = {'%s__content_object' % related_name: obj} qset = Q(**user_filters) if with_group_users: group_model = get_group_obj_perms_model(obj) group_rel_name = group_model.group.field.related_query_name() if group_model.objects.is_generic(): group_filters = { 'groups__%s__content_type' % group_rel_name: ctype, 'groups__%s__object_pk' % group_rel_name: obj.pk, } else: group_filters = { 'groups__%s__content_object' % group_rel_name: obj, } qset = qset | Q(**group_filters) if with_superusers: qset = qset | Q(is_superuser=True) return get_user_model().objects.filter(qset).distinct() else: # TODO: Do not hit db for each user! users = {} for user in get_users_with_perms(obj, with_group_users=with_group_users): users[user] = sorted(get_perms(user, obj)) return users def get_groups_with_perms(obj, attach_perms=False): """ Returns queryset of all ``Group`` objects with *any* object permissions for the given ``obj``. :param obj: persisted Django's ``Model`` instance :param attach_perms: Default: ``False``. If set to ``True`` result would be dictionary of ``Group`` instances with permissions' codenames list as values. This would fetch groups eagerly! Example:: >>> from django.contrib.flatpages.models import FlatPage >>> from guardian.shortcuts import assign_perm, get_groups_with_perms >>> from guardian.models import Group >>> >>> page = FlatPage.objects.create(title='Some page', path='/some/page/') >>> admins = Group.objects.create(name='Admins') >>> assign_perm('change_flatpage', admins, page) >>> >>> get_groups_with_perms(page) [] >>> >>> get_groups_with_perms(page, attach_perms=True) {: [u'change_flatpage']} """ ctype = ContentType.objects.get_for_model(obj) if not attach_perms: # It's much easier without attached perms so we do it first if that is # the case group_model = get_group_obj_perms_model(obj) group_rel_name = group_model.group.field.related_query_name() if group_model.objects.is_generic(): group_filters = { '%s__content_type' % group_rel_name: ctype, '%s__object_pk' % group_rel_name: obj.pk, } else: group_filters = {'%s__content_object' % group_rel_name: obj} groups = Group.objects.filter(**group_filters).distinct() return groups else: # TODO: Do not hit db for each group! groups = {} for group in get_groups_with_perms(obj): if not group in groups: groups[group] = sorted(get_perms(group, obj)) return groups def get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=False, with_superuser=True, accept_global_perms=True): """ Returns queryset of objects for which a given ``user`` has *all* permissions present at ``perms``. If ``perms`` is an empty list, then it returns objects for which a given ``user`` has *any* object permission. :param user: ``User`` or ``AnonymousUser`` instance for which objects would be returned. :param perms: single permission string, or sequence of permission strings which should be checked. If ``klass`` parameter is not given, those should be full permission names rather than only codenames (i.e. ``auth.change_user``). If more than one permission is present within sequence, their content type **must** be the same or ``MixedContentTypeError`` exception would be raised. :param klass: may be a Model, Manager or QuerySet object. If not given this parameter would be computed based on given ``params``. :param use_groups: if ``False``, wouldn't check user's groups object permissions. Default is ``True``. :param any_perm: if True, any of permission in sequence is accepted. Default is ``False``. :param with_superuser: if ``True`` and if ``user.is_superuser`` is set, returns the entire queryset. Otherwise will only return objects the user has explicit permissions. This must be ``True`` for the accept_global_perms parameter to have any affect. Default is ``True``. :param accept_global_perms: if ``True`` takes global permissions into account. Object based permissions are taken into account if more than one permission is handed in in perms and at least one of these perms is not globally set. If any_perm is set to false then the intersection of matching object is returned. Note, that if with_superuser is False, accept_global_perms will be ignored, which means that only object permissions will be checked! Default is ``True``. :raises MixedContentTypeError: when computed content type for ``perms`` and/or ``klass`` clashes. :raises WrongAppError: if cannot compute app label for given ``perms``/ ``klass``. Example:: >>> from django.contrib.auth.models import User >>> from guardian.shortcuts import get_objects_for_user >>> joe = User.objects.get(username='joe') >>> get_objects_for_user(joe, 'auth.change_group') [] >>> from guardian.shortcuts import assign_perm >>> group = Group.objects.create('some group') >>> assign_perm('auth.change_group', joe, group) >>> get_objects_for_user(joe, 'auth.change_group') [] The permission string can also be an iterable. Continuing with the previous example: >>> get_objects_for_user(joe, ['auth.change_group', 'auth.delete_group']) [] >>> get_objects_for_user(joe, ['auth.change_group', 'auth.delete_group'], any_perm=True) [] >>> assign_perm('auth.delete_group', joe, group) >>> get_objects_for_user(joe, ['auth.change_group', 'auth.delete_group']) [] Take global permissions into account: >>> jack = User.objects.get(username='jack') >>> assign_perm('auth.change_group', jack) # this will set a global permission >>> get_objects_for_user(jack, 'auth.change_group') [] >>> group2 = Group.objects.create('other group') >>> assign_perm('auth.delete_group', jack, group2) >>> get_objects_for_user(jack, ['auth.change_group', 'auth.delete_group']) # this retrieves intersection [] >>> get_objects_for_user(jack, ['auth.change_group', 'auth.delete_group'], any_perm) # this retrieves union [, ] If accept_global_perms is set to ``True``, then all assigned global permissions will also be taken into account. - Scenario 1: a user has view permissions generally defined on the model 'books' but no object based permission on a single book instance: - If accept_global_perms is ``True``: List of all books will be returned. - If accept_global_perms is ``False``: list will be empty. - Scenario 2: a user has view permissions generally defined on the model 'books' and also has an object based permission to view book 'Whatever': - If accept_global_perms is ``True``: List of all books will be returned. - If accept_global_perms is ``False``: list will only contain book 'Whatever'. - Scenario 3: a user only has object based permission on book 'Whatever': - If accept_global_perms is ``True``: List will only contain book 'Whatever'. - If accept_global_perms is ``False``: List will only contain book 'Whatever'. - Scenario 4: a user does not have any permission: - If accept_global_perms is ``True``: Empty list. - If accept_global_perms is ``False``: Empty list. """ if isinstance(perms, basestring): perms = [perms] ctype = None app_label = None codenames = set() # Compute codenames set and ctype if possible for perm in perms: if '.' in perm: new_app_label, codename = perm.split('.', 1) if app_label is not None and app_label != new_app_label: raise MixedContentTypeError("Given perms must have same app " "label (%s != %s)" % (app_label, new_app_label)) else: app_label = new_app_label else: codename = perm codenames.add(codename) if app_label is not None: new_ctype = ContentType.objects.get(app_label=app_label, permission__codename=codename) if ctype is not None and ctype != new_ctype: raise MixedContentTypeError("ContentType was once computed " "to be %s and another one %s" % (ctype, new_ctype)) else: ctype = new_ctype # Compute queryset and ctype if still missing if ctype is None and klass is not None: queryset = _get_queryset(klass) ctype = ContentType.objects.get_for_model(queryset.model) elif ctype is not None and klass is None: queryset = _get_queryset(ctype.model_class()) elif klass is None: raise WrongAppError("Cannot determine content type") else: queryset = _get_queryset(klass) if ctype.model_class() != queryset.model: raise MixedContentTypeError("Content type for given perms and " "klass differs") # At this point, we should have both ctype and queryset and they should # match which means: ctype.model_class() == queryset.model # we should also have ``codenames`` list # First check if user is superuser and if so, return queryset immediately if with_superuser and user.is_superuser: return queryset # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for queries # and it's nice to be able to pass in request.user blindly. if user.is_anonymous(): user = get_anonymous_user() global_perms = set() has_global_perms = False # a superuser has by default assigned global perms for any if accept_global_perms and with_superuser: for code in codenames: if user.has_perm(ctype.app_label + '.' + code): global_perms.add(code) for code in global_perms: codenames.remove(code) ## prerequisite: there must be elements in global_perms otherwise just follow the procedure for # object based permissions only AND # 1. codenames is empty, which means that permissions are ONLY set globally, therefore return the full queryset. # OR # 2. any_perm is True, then the global permission beats the object based permission anyway, # therefore return full queryset if len(global_perms) > 0 and (len(codenames) == 0 or any_perm): return queryset # if we have global perms and still some object based perms differing from global perms and any_perm is set # to false, then we have to flag that global perms exist in order to merge object based permissions by user # and by group correctly. Scenario: global perm change_xx and object based perm delete_xx on object A for user, # and object based permission delete_xx on object B for group, to which user is assigned. # get_objects_for_user(user, [change_xx, delete_xx], use_groups=True, any_perm=False, accept_global_perms=True) # must retrieve object A and B. elif len(global_perms) > 0 and (len(codenames) > 0): has_global_perms = True # Now we should extract list of pk values for which we would filter queryset user_model = get_user_obj_perms_model(queryset.model) user_obj_perms_queryset = (user_model.objects .filter(user=user) .filter(permission__content_type=ctype)) if len(codenames): user_obj_perms_queryset = user_obj_perms_queryset.filter(permission__codename__in=codenames) if user_model.objects.is_generic(): fields = ['object_pk', 'permission__codename'] else: fields = ['content_object__pk', 'permission__codename'] if use_groups: group_model = get_group_obj_perms_model(queryset.model) group_filters = { 'permission__content_type': ctype, 'group__%s' % get_user_model().groups.field.related_query_name(): user, } if len(codenames): group_filters.update({ 'permission__codename__in': codenames, }) groups_obj_perms_queryset = group_model.objects.filter(**group_filters) if group_model.objects.is_generic(): fields = ['object_pk', 'permission__codename'] else: fields = ['content_object__pk', 'permission__codename'] if not any_perm and len(codenames) and not has_global_perms: user_obj_perms = user_obj_perms_queryset.values_list(*fields) groups_obj_perms = groups_obj_perms_queryset.values_list(*fields) data = list(user_obj_perms) + list(groups_obj_perms) keyfunc = lambda t: t[0] # sorting/grouping by pk (first in result tuple) data = sorted(data, key=keyfunc) pk_list = [] for pk, group in groupby(data, keyfunc): obj_codenames = set((e[1] for e in group)) if codenames.issubset(obj_codenames): pk_list.append(pk) objects = queryset.filter(pk__in=pk_list) return objects if not any_perm and len(codenames) > 1: counts = user_obj_perms_queryset.values(fields[0]).annotate(object_pk_count=Count(fields[0])) user_obj_perms_queryset = counts.filter(object_pk_count__gte=len(codenames)) values = user_obj_perms_queryset.values_list(fields[0], flat=True) if user_model.objects.is_generic(): values = list(values) objects = queryset.filter(pk__in=values) if use_groups: values = groups_obj_perms_queryset.values_list(fields[0], flat=True) if group_model.objects.is_generic(): values = list(values) objects |= queryset.filter(pk__in=values) return objects def get_objects_for_group(group, perms, klass=None, any_perm=False, accept_global_perms=True): """ Returns queryset of objects for which a given ``group`` has *all* permissions present at ``perms``. :param group: ``Group`` instance for which objects would be returned. :param perms: single permission string, or sequence of permission strings which should be checked. If ``klass`` parameter is not given, those should be full permission names rather than only codenames (i.e. ``auth.change_user``). If more than one permission is present within sequence, their content type **must** be the same or ``MixedContentTypeError`` exception would be raised. :param klass: may be a Model, Manager or QuerySet object. If not given this parameter would be computed based on given ``params``. :param any_perm: if True, any of permission in sequence is accepted :param accept_global_perms: if ``True`` takes global permissions into account. If any_perm is set to false then the intersection of matching objects based on global and object based permissions is returned. Default is ``True``. :raises MixedContentTypeError: when computed content type for ``perms`` and/or ``klass`` clashes. :raises WrongAppError: if cannot compute app label for given ``perms``/ ``klass``. Example: Let's assume we have a ``Task`` model belonging to the ``tasker`` app with the default add_task, change_task and delete_task permissions provided by Django:: >>> from guardian.shortcuts import get_objects_for_group >>> from tasker import Task >>> group = Group.objects.create('some group') >>> task = Task.objects.create('some task') >>> get_objects_for_group(group, 'tasker.add_task') [] >>> from guardian.shortcuts import assign_perm >>> assign_perm('tasker.add_task', group, task) >>> get_objects_for_group(group, 'tasker.add_task') [] The permission string can also be an iterable. Continuing with the previous example: >>> get_objects_for_group(group, ['tasker.add_task', 'tasker.delete_task']) [] >>> assign_perm('tasker.delete_task', group, task) >>> get_objects_for_group(group, ['tasker.add_task', 'tasker.delete_task']) [] Global permissions assigned to the group are also taken into account. Continuing with previous example: >>> task_other = Task.objects.create('other task') >>> assign_perm('tasker.change_task', group) >>> get_objects_for_group(group, ['tasker.change_task']) [, ] >>> get_objects_for_group(group, ['tasker.change_task'], accept_global_perms=False) [] """ if isinstance(perms, basestring): perms = [perms] ctype = None app_label = None codenames = set() # Compute codenames set and ctype if possible for perm in perms: if '.' in perm: new_app_label, codename = perm.split('.', 1) if app_label is not None and app_label != new_app_label: raise MixedContentTypeError("Given perms must have same app " "label (%s != %s)" % (app_label, new_app_label)) else: app_label = new_app_label else: codename = perm codenames.add(codename) if app_label is not None: new_ctype = ContentType.objects.get(app_label=app_label, permission__codename=codename) if ctype is not None and ctype != new_ctype: raise MixedContentTypeError("ContentType was once computed " "to be %s and another one %s" % (ctype, new_ctype)) else: ctype = new_ctype # Compute queryset and ctype if still missing if ctype is None and klass is None: raise WrongAppError("Cannot determine content type") elif ctype is None and klass is not None: queryset = _get_queryset(klass) ctype = ContentType.objects.get_for_model(queryset.model) elif ctype is not None and klass is None: queryset = _get_queryset(ctype.model_class()) else: queryset = _get_queryset(klass) if ctype.model_class() != queryset.model: raise MixedContentTypeError("Content type for given perms and " "klass differs") # At this point, we should have both ctype and queryset and they should # match which means: ctype.model_class() == queryset.model # we should also have ``codenames`` list global_perms = set() if accept_global_perms: global_perm_set = group.permissions.values_list('codename', flat=True) for code in codenames: if code in global_perm_set: global_perms.add(code) for code in global_perms: codenames.remove(code) if len(global_perms) > 0 and (len(codenames) == 0 or any_perm): return queryset # Now we should extract list of pk values for which we would filter queryset group_model = get_group_obj_perms_model(queryset.model) groups_obj_perms_queryset = (group_model.objects .filter(group=group) .filter(permission__content_type=ctype) .filter(permission__codename__in=codenames)) if group_model.objects.is_generic(): fields = ['object_pk', 'permission__codename'] else: fields = ['content_object__pk', 'permission__codename'] groups_obj_perms = groups_obj_perms_queryset.values_list(*fields) data = list(groups_obj_perms) keyfunc = lambda t: t[0] # sorting/grouping by pk (first in result tuple) data = sorted(data, key=keyfunc) pk_list = [] for pk, group in groupby(data, keyfunc): obj_codenames = set((e[1] for e in group)) if any_perm or codenames.issubset(obj_codenames): pk_list.append(pk) objects = queryset.filter(pk__in=pk_list) return objects django-guardian-1.4.1/guardian/mixins.py0000600000175000017500000001733412634730304020434 0ustar brianbrian00000000000000from __future__ import unicode_literals from collections import Iterable from django.conf import settings from django.contrib.auth.decorators import REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import PermissionDenied from guardian.compat import basestring from guardian.models import UserObjectPermission from guardian.utils import get_403_or_None from guardian.utils import get_anonymous_user class LoginRequiredMixin(object): """ A login required mixin for use with class based views. This Class is a light wrapper around the `login_required` decorator and hence function parameters are just attributes defined on the class. Due to parent class order traversal this mixin must be added as the left most mixin of a view. The mixin has exaclty the same flow as `login_required` decorator: If the user isn't logged in, redirect to ``settings.LOGIN_URL``, passing the current absolute path in the query string. Example: ``/accounts/login/?next=/polls/3/``. If the user is logged in, execute the view normally. The view code is free to assume the user is logged in. **Class Settings** ``LoginRequiredMixin.redirect_field_name`` *Default*: ``'next'`` ``LoginRequiredMixin.login_url`` *Default*: ``settings.LOGIN_URL`` """ redirect_field_name = REDIRECT_FIELD_NAME login_url = settings.LOGIN_URL def dispatch(self, request, *args, **kwargs): return login_required(redirect_field_name=self.redirect_field_name, login_url=self.login_url)( super(LoginRequiredMixin, self).dispatch )(request, *args, **kwargs) class PermissionRequiredMixin(object): """ A view mixin that verifies if the current logged in user has the specified permission by wrapping the ``request.user.has_perm(..)`` method. If a `get_object()` method is defined either manually or by including another mixin (for example ``SingleObjectMixin``) or ``self.object`` is defined then the permission will be tested against that specific instance, alternatively you can specify `get_permission_object()` method if ``self.object`` or `get_object()` does not return the object against you want to test permission .. note: Testing of a permission against a specific object instance requires an authentication backend that supports. Please see ``django-guardian`` to add object level permissions to your project. The mixin does the following: If the user isn't logged in, redirect to settings.LOGIN_URL, passing the current absolute path in the query string. Example: /accounts/login/?next=/polls/3/. If the `raise_exception` is set to True than rather than redirect to login page a `PermissionDenied` (403) is raised. If the user is logged in, and passes the permission check than the view is executed normally. **Example Usage**:: class SecureView(PermissionRequiredMixin, View): ... permission_required = 'auth.change_user' ... **Class Settings** ``PermissionRequiredMixin.permission_required`` *Default*: ``None``, must be set to either a string or list of strings in format: *.*. ``PermissionRequiredMixin.login_url`` *Default*: ``settings.LOGIN_URL`` ``PermissionRequiredMixin.redirect_field_name`` *Default*: ``'next'`` ``PermissionRequiredMixin.return_403`` *Default*: ``False``. Returns 403 error page instead of redirecting user. ``PermissionRequiredMixin.raise_exception`` *Default*: ``False`` `permission_required` - the permission to check of form "." i.e. 'polls.can_vote' for a permission on a model in the polls application. ``PermissionRequiredMixin.accept_global_perms`` *Default*: ``False``, If accept_global_perms would be set to True, then mixing would first check for global perms, if none found, then it will proceed to check object level permissions. ``PermissionRequiredMixin.permission_object`` *Default*: ``None``, object against which test the permission; if None fallback to ``self.get_permission_object()`` which return ``self.get_object()`` or ``self.object`` by default. """ ### default class view settings login_url = settings.LOGIN_URL permission_required = None redirect_field_name = REDIRECT_FIELD_NAME return_403 = False raise_exception = False accept_global_perms = False permission_object = None def get_required_permissions(self, request=None): """ Returns list of permissions in format *.* that should be checked against *request.user* and *object*. By default, it returns list from ``permission_required`` attribute. :param request: Original request. """ if isinstance(self.permission_required, basestring): perms = [self.permission_required] elif isinstance(self.permission_required, Iterable): perms = [p for p in self.permission_required] else: raise ImproperlyConfigured("'PermissionRequiredMixin' requires " "'permission_required' attribute to be set to " "'.' but is set to '%s' instead" % self.permission_required) return perms def get_permission_object(self): if self.permission_object: return self.permission_object return (hasattr(self, 'get_object') and self.get_object() or getattr(self, 'object', None)) def check_permissions(self, request): """ Checks if *request.user* has all permissions returned by *get_required_permissions* method. :param request: Original request. """ obj = self.get_permission_object() forbidden = get_403_or_None(request, perms=self.get_required_permissions(request), obj=obj, login_url=self.login_url, redirect_field_name=self.redirect_field_name, return_403=self.return_403, accept_global_perms=self.accept_global_perms ) if forbidden: self.on_permission_check_fail(request, forbidden, obj=obj) if forbidden and self.raise_exception: raise PermissionDenied() return forbidden def on_permission_check_fail(self, request, response, obj=None): """ Method called upon permission check fail. By default it does nothing and should be overridden, if needed. :param request: Original request :param response: 403 response returned by *check_permissions* method. :param obj: Object that was fetched from the view (using ``get_object`` method or ``object`` attribute, in that order). """ def dispatch(self, request, *args, **kwargs): self.request = request self.args = args self.kwargs = kwargs response = self.check_permissions(request) if response: return response return super(PermissionRequiredMixin, self).dispatch(request, *args, **kwargs) class GuardianUserMixin(object): @staticmethod def get_anonymous(): return get_anonymous_user() def add_obj_perm(self, perm, obj): return UserObjectPermission.objects.assign_perm(perm, self, obj) def del_obj_perm(self, perm, obj): return UserObjectPermission.objects.remove_perm(perm, self, obj) django-guardian-1.4.1/guardian/core.py0000600000175000017500000001203612634712306020051 0ustar brianbrian00000000000000from __future__ import unicode_literals from itertools import chain from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from guardian.utils import get_identity from guardian.utils import get_user_obj_perms_model from guardian.utils import get_group_obj_perms_model from guardian.compat import get_user_model class ObjectPermissionChecker(object): """ Generic object permissions checker class being the heart of ``django-guardian``. .. note:: Once checked for single object, permissions are stored and we don't hit database again if another check is called for this object. This is great for templates, views or other request based checks (assuming we don't have hundreds of permissions on a single object as we fetch all permissions for checked object). On the other hand, if we call ``has_perm`` for perm1/object1, then we change permission state and call ``has_perm`` again for same perm1/object1 on same instance of ObjectPermissionChecker we won't see a difference as permissions are already fetched and stored within cache dictionary. """ def __init__(self, user_or_group=None): """ :param user_or_group: should be an ``User``, ``AnonymousUser`` or ``Group`` instance """ self.user, self.group = get_identity(user_or_group) self._obj_perms_cache = {} def has_perm(self, perm, obj): """ Checks if user/group has given permission for object. :param perm: permission as string, may or may not contain app_label prefix (if not prefixed, we grab app_label from ``obj``) :param obj: Django model instance for which permission should be checked """ perm = perm.split('.')[-1] if self.user and not self.user.is_active: return False elif self.user and self.user.is_superuser: return True return perm in self.get_perms(obj) def get_perms(self, obj): """ Returns list of ``codename``'s of all permissions for given ``obj``. :param obj: Django model instance for which permission should be checked """ if self.user and not self.user.is_active: return [] User = get_user_model() ctype = ContentType.objects.get_for_model(obj) key = self.get_local_cache_key(obj) if not key in self._obj_perms_cache: group_model = get_group_obj_perms_model(obj) group_rel_name = group_model.permission.field.related_query_name() if self.user: fieldname = '%s__group__%s' % ( group_rel_name, User.groups.field.related_query_name(), ) group_filters = {fieldname: self.user} else: group_filters = {'%s__group' % group_rel_name: self.group} if group_model.objects.is_generic(): group_filters.update({ '%s__content_type' % group_rel_name: ctype, '%s__object_pk' % group_rel_name: obj.pk, }) else: group_filters['%s__content_object' % group_rel_name] = obj if self.user and self.user.is_superuser: perms = list(chain(*Permission.objects .filter(content_type=ctype) .values_list("codename"))) elif self.user: model = get_user_obj_perms_model(obj) related_name = model.permission.field.related_query_name() user_filters = {'%s__user' % related_name: self.user} if model.objects.is_generic(): user_filters.update({ '%s__content_type' % related_name: ctype, '%s__object_pk' % related_name: obj.pk, }) else: user_filters['%s__content_object' % related_name] = obj perms_qs = Permission.objects.filter(content_type=ctype) # Query user and group permissions separately and then combine # the results to avoid a slow query user_perms_qs = perms_qs.filter(**user_filters) user_perms = user_perms_qs.values_list("codename", flat=True) group_perms_qs = perms_qs.filter(**group_filters) group_perms = group_perms_qs.values_list("codename", flat=True) perms = list(set(chain(user_perms, group_perms))) else: perms = list(set(chain(*Permission.objects .filter(content_type=ctype) .filter(**group_filters) .values_list("codename")))) self._obj_perms_cache[key] = perms return self._obj_perms_cache[key] def get_local_cache_key(self, obj): """ Returns cache key for ``_obj_perms_cache`` dict. """ ctype = ContentType.objects.get_for_model(obj) return (ctype.id, obj.pk) django-guardian-1.4.1/guardian/templates/0000700000175000017500000000000012644306605020543 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templates/admin/0000700000175000017500000000000012644306605021633 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templates/admin/guardian/0000700000175000017500000000000012644306605023425 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templates/admin/guardian/model/0000700000175000017500000000000012644306605024525 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templates/admin/guardian/model/obj_perms_manage_group.html0000600000175000017500000000307012616547266032132 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% block extrahead %}{{ block.super }} {{ form.media }} {% endblock %} {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
{% csrf_token %}

{% trans "Object permissions" %}

{{ object }}
{{ group_obj }}
{% for field in form %} {% include "admin/guardian/model/field.html" %} {% endfor %}
{% endblock %} django-guardian-1.4.1/guardian/templates/admin/guardian/model/field.html0000600000175000017500000000061212616547266026510 0ustar brianbrian00000000000000
{% if field.errors %}
    {% for error in field.errors %}
  • {{ error }}
  • {% endfor %}
{% endif %}
{{ field.label_tag }} {{ field }} {% if field.help_text %}

{{ field.help_text }}

{% endif %}
django-guardian-1.4.1/guardian/templates/admin/guardian/model/obj_perms_no.html0000600000175000017500000000014512634712306030067 0ustar brianbrian00000000000000{% load guardian_tags %} {% load staticfiles %} django-guardian-1.4.1/guardian/templates/admin/guardian/model/obj_perms_yes.html0000600000175000017500000000014612634712306030254 0ustar brianbrian00000000000000{% load guardian_tags %} {% load staticfiles %} django-guardian-1.4.1/guardian/templates/admin/guardian/model/change_form.html0000600000175000017500000000052012634712306027660 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n admin_urls %} {% block object-tools-items %} {% url opts|admin_urlname:'permissions' original.pk|admin_urlquote as history_url %}
  • {% trans "Object permissions" %}
  • {{ block.super }} {% endblock %} django-guardian-1.4.1/guardian/templates/admin/guardian/model/obj_perms_manage_user.html0000600000175000017500000000303212616547266031752 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% block extrahead %}{{ block.super }} {{ form.media }} {% endblock %} {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
    {% csrf_token %}

    {% trans "Object permissions" %}

    {{ object }}
    {{ user_obj }}
    {{ form }}
    {% endblock %} django-guardian-1.4.1/guardian/templates/admin/guardian/model/obj_perms_manage.html0000600000175000017500000001161512634712306030707 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% load guardian_tags %} {% block extrahead %}{{ block.super }} {% endblock %} {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
    {% csrf_token %} {% if user_form.errors %}

    {% trans "Please correct the errors below." %}

    {% endif %}

    {% trans "Users" %}

    {% for error in user_form.errors %}

    {{ error }}

    {% endfor %}
    {% for perm in model_perms %} {% endfor %} {% for user, user_perms in users_perms.items %} {% for perm in model_perms %} {% endfor %} {% endfor %}
    {% trans "User permissions" %}
    {% trans "User" %}{{ perm.name }}{% trans "Action" %}
    {{ user }} {% if perm.codename in user_perms %} {% include "admin/guardian/model/obj_perms_yes.html" %} {% else %} {% include "admin/guardian/model/obj_perms_no.html" %} {% endif %} {% trans "Edit" %}
    {% for field in user_form %} {% include "admin/guardian/model/field.html" %} {% endfor %}
    {% csrf_token %} {% if group_form.errors %}

    {% trans "Please correct the errors below." %}

    {% endif %}

    {% trans "Groups" %}

    {% for error in group_form.errors %}

    {{ error }}

    {% endfor %}
    {% for perm in model_perms %} {% endfor %} {% for group, group_perms in groups_perms.items %} {% for perm in model_perms %} {% endfor %} {% endfor %}
    {% trans "Group permissions" %}
    {% trans "Group" %}{{ perm.name }}{% trans "Action" %}
    {{ group }} {% if perm.codename in group_perms %} {% include "admin/guardian/model/obj_perms_yes.html" %} {% else %} {% include "admin/guardian/model/obj_perms_no.html" %} {% endif %} {% trans "Edit" %}
    {% for field in group_form %} {% include "admin/guardian/model/field.html" %} {% endfor %}
    {% endblock %} django-guardian-1.4.1/guardian/templates/admin/guardian/contrib/0000700000175000017500000000000012644306605025065 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/templates/admin/guardian/contrib/grappelli/0000700000175000017500000000000012644306605027044 5ustar brianbrian00000000000000././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000django-guardian-1.4.1/guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.htmldjango-guardian-1.4.1/guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.htm0000600000175000017500000000427412634712306034271 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% block extrahead %}{{ block.super }} {{ form.media }} {% endblock %} {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
    {% csrf_token %}

    {% trans "Object permissions" %}

    {{ object }}
    {{ group_obj }}
    {% for field in form %} {% include "admin/guardian/contrib/grappelli/field.html" %} {% endfor %}
     
    {% endblock %} django-guardian-1.4.1/guardian/templates/admin/guardian/contrib/grappelli/field.html0000600000175000017500000000153412634712306031020 0ustar brianbrian00000000000000
    {% if field.is_checkbox %}
     
    {{ field }} {% else %}
    {{ field }} {% endif %} {% if field.help_text %}

    {{ field.help_text|safe }}

    {% endif %} {{ field.errors }} {% if field.errors %}
      {% for error in field.errors %}
    • {{ error }}
    • {% endfor %}
    {% endif %}
    django-guardian-1.4.1/guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html0000600000175000017500000000426612634712306034270 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% block extrahead %}{{ block.super }} {{ form.media }} {% endblock %} {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
    {% csrf_token %}

    {% trans "Object permissions" %}

    {{ object }}
    {{ user_obj }}
    {% for field in form %} {% include "admin/guardian/contrib/grappelli/field.html" %} {% endfor %}
     
    {% endblock %} django-guardian-1.4.1/guardian/templates/admin/guardian/contrib/grappelli/obj_perms_manage.html0000600000175000017500000001455712634712306033236 0ustar brianbrian00000000000000{% extends "admin/change_form.html" %} {% load i18n %} {% load guardian_tags %} {% load admin_static %} {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
    {% csrf_token %}
    {% if user_form.errors %}

    {% blocktrans count user_form.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}

      {% for error in user_form.errors %}
    • {{ error }}
    • {% endfor %}
    {% endif %}

    {% trans "Users" %}

    {% for perm in model_perms %} {% endfor %} {% for user, user_perms in users_perms.items %} {% for perm in model_perms %} {% endfor %} {% endfor %}
    {% trans "User" %}{{ perm.name }}{% trans "Action" %}
    {{ user }} {% if perm.codename in user_perms %} {% else %} {% endif %} {% trans "Edit" %}
    {% for field in user_form %} {% include "admin/guardian/contrib/grappelli/field.html" %} {% endfor %}
     
    {% csrf_token %}
    {% if group_form.errors %}

    {% blocktrans count group_form.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}

      {% for error in group_form.errors %}
    • {{ error }}
    • {% endfor %}
    {% endif %}

    {% trans "Groups" %}

    {% for perm in model_perms %} {% endfor %} {% for group, group_perms in groups_perms.items %} {% for perm in model_perms %} {% endfor %} {% endfor %}
    {% trans "Group" %}{{ perm.name }}{% trans "Action" %}
    {{ group }} {% if perm.codename in group_perms %} {% else %} {% endif %} {% trans "Edit" %}
    {% for field in group_form %} {% include "admin/guardian/contrib/grappelli/field.html" %} {% endfor %}
     
    {% endblock %} django-guardian-1.4.1/guardian/admin.py0000600000175000017500000004307012643667664020233 0ustar brianbrian00000000000000from __future__ import unicode_literals from django import forms from django.conf import settings from guardian.compat import url from django.contrib import admin from django.contrib import messages from django.contrib.admin.widgets import FilteredSelectMultiple from django.core.urlresolvers import reverse from django.shortcuts import render_to_response, get_object_or_404, redirect from django.template import RequestContext from django.utils.translation import ugettext, ugettext_lazy as _ from guardian.compat import OrderedDict, get_user_model, get_model_name from guardian.forms import UserObjectPermissionsForm from guardian.forms import GroupObjectPermissionsForm from guardian.shortcuts import get_perms from guardian.shortcuts import get_users_with_perms from guardian.shortcuts import get_groups_with_perms from guardian.shortcuts import get_perms_for_model from guardian.models import Group class AdminUserObjectPermissionsForm(UserObjectPermissionsForm): """ Extends :form:`UserObjectPermissionsForm`. It only overrides ``get_obj_perms_field_widget`` method so it return ``django.contrib.admin.widgets.FilteredSelectMultiple`` widget. """ def get_obj_perms_field_widget(self): return FilteredSelectMultiple(_("Permissions"), False) class AdminGroupObjectPermissionsForm(GroupObjectPermissionsForm): """ Extends :form:`GroupObjectPermissionsForm`. It only overrides ``get_obj_perms_field_widget`` method so it return ``django.contrib.admin.widgets.FilteredSelectMultiple`` widget. """ def get_obj_perms_field_widget(self): return FilteredSelectMultiple(_("Permissions"), False) class GuardedModelAdminMixin(object): """ Serves as a helper for custom subclassing ``admin.ModelAdmin``. """ change_form_template = \ 'admin/guardian/model/change_form.html' obj_perms_manage_template = \ 'admin/guardian/model/obj_perms_manage.html' obj_perms_manage_user_template = \ 'admin/guardian/model/obj_perms_manage_user.html' obj_perms_manage_group_template = \ 'admin/guardian/model/obj_perms_manage_group.html' user_can_access_owned_objects_only = False user_owned_objects_field = 'user' user_can_access_owned_by_group_objects_only = False group_owned_objects_field = 'group' include_object_permissions_urls = True def get_queryset(self, request): # Prefer the Django >= 1.6 interface but maintain # backward compatibility method = getattr( super(GuardedModelAdminMixin, self), 'get_queryset', getattr(super(GuardedModelAdminMixin, self), 'queryset', None)) qs = method(request) if request.user.is_superuser: return qs if self.user_can_access_owned_objects_only: filters = {self.user_owned_objects_field: request.user} qs = qs.filter(**filters) if self.user_can_access_owned_by_group_objects_only: User = get_user_model() user_rel_name = User.groups.field.related_query_name() qs_key = '%s__%s' % (self.group_owned_objects_field, user_rel_name) filters = {qs_key: request.user} qs = qs.filter(**filters) return qs def get_urls(self): """ Extends standard admin model urls with the following: - ``.../permissions/`` under ``app_mdodel_permissions`` url name (params: object_pk) - ``.../permissions/user-manage//`` under ``app_model_permissions_manage_user`` url name (params: object_pk, user_pk) - ``.../permissions/group-manage//`` under ``app_model_permissions_manage_group`` url name (params: object_pk, group_pk) .. note:: ``...`` above are standard, instance detail url (i.e. ``/admin/flatpages/1/``) """ urls = super(GuardedModelAdminMixin, self).get_urls() if self.include_object_permissions_urls: info = self.model._meta.app_label, get_model_name(self.model) myurls = [ url(r'^(?P.+)/permissions/$', view=self.admin_site.admin_view(self.obj_perms_manage_view), name='%s_%s_permissions' % info), url(r'^(?P.+)/permissions/user-manage/(?P\-?\d+)/$', view=self.admin_site.admin_view(self.obj_perms_manage_user_view), name='%s_%s_permissions_manage_user' % info), url(r'^(?P.+)/permissions/group-manage/(?P\-?\d+)/$', view=self.admin_site.admin_view(self.obj_perms_manage_group_view), name='%s_%s_permissions_manage_group' % info), ] urls = myurls + urls return urls def get_obj_perms_base_context(self, request, obj): """ Returns context dictionary with common admin and object permissions related content. """ context = { 'adminform': {'model_admin': self}, 'media': self.media, 'object': obj, 'app_label': self.model._meta.app_label, 'opts': self.model._meta, 'original': hasattr(obj, '__unicode__') and obj.__unicode__() or str(obj), 'has_change_permission': self.has_change_permission(request, obj), 'model_perms': get_perms_for_model(obj), 'title': _("Object permissions"), } return context def obj_perms_manage_view(self, request, object_pk): """ Main object permissions view. Presents all users and groups with any object permissions for the current model *instance*. Users or groups without object permissions for related *instance* would **not** be shown. In order to add or manage user or group one should use links or forms presented within the page. """ if not self.has_change_permission(request, None): post_url = reverse('admin:index', current_app=self.admin_site.name) return redirect(post_url) try: # django >= 1.7 from django.contrib.admin.utils import unquote except ImportError: # django < 1.7 from django.contrib.admin.util import unquote obj = get_object_or_404(self.get_queryset(request), pk=unquote(object_pk)) users_perms = OrderedDict( sorted( get_users_with_perms(obj, attach_perms=True, with_group_users=False).items(), key=lambda user: getattr(user[0], get_user_model().USERNAME_FIELD) ) ) groups_perms = OrderedDict( sorted( get_groups_with_perms(obj, attach_perms=True).items(), key=lambda group: group[0].name ) ) if request.method == 'POST' and 'submit_manage_user' in request.POST: user_form = UserManage(request.POST) group_form = GroupManage() info = ( self.admin_site.name, self.model._meta.app_label, get_model_name(self.model) ) if user_form.is_valid(): user_id = user_form.cleaned_data['user'].pk url = reverse( '%s:%s_%s_permissions_manage_user' % info, args=[obj.pk, user_id] ) return redirect(url) elif request.method == 'POST' and 'submit_manage_group' in request.POST: user_form = UserManage() group_form = GroupManage(request.POST) info = ( self.admin_site.name, self.model._meta.app_label, get_model_name(self.model) ) if group_form.is_valid(): group_id = group_form.cleaned_data['group'].id url = reverse( '%s:%s_%s_permissions_manage_group' % info, args=[obj.pk, group_id] ) return redirect(url) else: user_form = UserManage() group_form = GroupManage() context = self.get_obj_perms_base_context(request, obj) context['users_perms'] = users_perms context['groups_perms'] = groups_perms context['user_form'] = user_form context['group_form'] = group_form # https://github.com/django/django/commit/cf1f36bb6eb34fafe6c224003ad585a647f6117b request.current_app = self.admin_site.name return render_to_response(self.get_obj_perms_manage_template(), context, RequestContext(request)) def get_obj_perms_manage_template(self): """ Returns main object permissions admin template. May be overridden if need to change it dynamically. .. note:: If ``INSTALLED_APPS`` contains ``grappelli`` this function would return ``"admin/guardian/grappelli/obj_perms_manage.html"``. """ if 'grappelli' in settings.INSTALLED_APPS: return 'admin/guardian/contrib/grappelli/obj_perms_manage.html' return self.obj_perms_manage_template def obj_perms_manage_user_view(self, request, object_pk, user_id): """ Manages selected users' permissions for current object. """ if not self.has_change_permission(request, None): post_url = reverse('admin:index', current_app=self.admin_site.name) return redirect(post_url) user = get_object_or_404(get_user_model(), pk=user_id) obj = get_object_or_404(self.get_queryset(request), pk=object_pk) form_class = self.get_obj_perms_manage_user_form() form = form_class(user, obj, request.POST or None) if request.method == 'POST' and form.is_valid(): form.save_obj_perms() msg = ugettext("Permissions saved.") messages.success(request, msg) info = ( self.admin_site.name, self.model._meta.app_label, get_model_name(self.model) ) url = reverse( '%s:%s_%s_permissions_manage_user' % info, args=[obj.pk, user.pk] ) return redirect(url) context = self.get_obj_perms_base_context(request, obj) context['user_obj'] = user context['user_perms'] = get_perms(user, obj) context['form'] = form request.current_app = self.admin_site.name return render_to_response(self.get_obj_perms_manage_user_template(), context, RequestContext(request)) def get_obj_perms_manage_user_template(self): """ Returns object permissions for user admin template. May be overridden if need to change it dynamically. .. note:: If ``INSTALLED_APPS`` contains ``grappelli`` this function would return ``"admin/guardian/grappelli/obj_perms_manage_user.html"``. """ if 'grappelli' in settings.INSTALLED_APPS: return 'admin/guardian/contrib/grappelli/obj_perms_manage_user.html' return self.obj_perms_manage_user_template def get_obj_perms_manage_user_form(self): """ Returns form class for user object permissions management. By default :form:`AdminUserObjectPermissionsForm` is returned. """ return AdminUserObjectPermissionsForm def obj_perms_manage_group_view(self, request, object_pk, group_id): """ Manages selected groups' permissions for current object. """ if not self.has_change_permission(request, None): post_url = reverse('admin:index', current_app=self.admin_site.name) return redirect(post_url) group = get_object_or_404(Group, id=group_id) obj = get_object_or_404(self.get_queryset(request), pk=object_pk) form_class = self.get_obj_perms_manage_group_form() form = form_class(group, obj, request.POST or None) if request.method == 'POST' and form.is_valid(): form.save_obj_perms() msg = ugettext("Permissions saved.") messages.success(request, msg) info = ( self.admin_site.name, self.model._meta.app_label, get_model_name(self.model) ) url = reverse( '%s:%s_%s_permissions_manage_group' % info, args=[obj.pk, group.id] ) return redirect(url) context = self.get_obj_perms_base_context(request, obj) context['group_obj'] = group context['group_perms'] = get_perms(group, obj) context['form'] = form request.current_app = self.admin_site.name return render_to_response(self.get_obj_perms_manage_group_template(), context, RequestContext(request)) def get_obj_perms_manage_group_template(self): """ Returns object permissions for group admin template. May be overridden if need to change it dynamically. .. note:: If ``INSTALLED_APPS`` contains ``grappelli`` this function would return ``"admin/guardian/grappelli/obj_perms_manage_group.html"``. """ if 'grappelli' in settings.INSTALLED_APPS: return 'admin/guardian/contrib/grappelli/obj_perms_manage_group.html' return self.obj_perms_manage_group_template def get_obj_perms_manage_group_form(self): """ Returns form class for group object permissions management. By default :form:`AdminGroupObjectPermissionsForm` is returned. """ return AdminGroupObjectPermissionsForm class GuardedModelAdmin(GuardedModelAdminMixin, admin.ModelAdmin): """ Extends ``django.contrib.admin.ModelAdmin`` class. Provides some extra views for object permissions management at admin panel. It also changes default ``change_form_template`` option to ``'admin/guardian/model/change_form.html'`` which is required for proper url (object permissions related) being shown at the model pages. **Extra options** ``GuardedModelAdmin.obj_perms_manage_template`` *Default*: ``admin/guardian/model/obj_perms_manage.html`` ``GuardedModelAdmin.obj_perms_manage_user_template`` *Default*: ``admin/guardian/model/obj_perms_manage_user.html`` ``GuardedModelAdmin.obj_perms_manage_group_template`` *Default*: ``admin/guardian/model/obj_perms_manage_group.html`` ``GuardedModelAdmin.user_can_access_owned_objects_only`` *Default*: ``False`` If this would be set to ``True``, ``request.user`` would be used to filter out objects he or she doesn't own (checking ``user`` field of used model - field name may be overridden by ``user_owned_objects_field`` option). .. note:: Please remember that this will **NOT** affect superusers! Admins would still see all items. ``GuardedModelAdmin.user_can_access_owned_by_group_objects_only`` *Default*: ``False`` If this would be set to ``True``, ``request.user`` would be used to filter out objects her or his group doesn't own (checking if any group user belongs to is set as ``group`` field of the object; name of the field can be changed by overriding ``group_owned_objects_field``). .. note:: Please remember that this will **NOT** affect superusers! Admins would still see all items. ``GuardedModelAdmin.group_owned_objects_field`` *Default*: ``group`` ``GuardedModelAdmin.include_object_permissions_urls`` *Default*: ``True`` .. versionadded:: 1.2 Might be set to ``False`` in order **NOT** to include guardian-specific urls. **Usage example** Just use :admin:`GuardedModelAdmin` instead of ``django.contrib.admin.ModelAdmin``. .. code-block:: python from django.contrib import admin from guardian.admin import GuardedModelAdmin from myapp.models import Author class AuthorAdmin(GuardedModelAdmin): pass admin.site.register(Author, AuthorAdmin) """ class UserManage(forms.Form): user = forms.CharField(label=_("User identification"), max_length=200, error_messages={'does_not_exist': _("This user does not exist")}, help_text=_('Enter a value compatible with User.USERNAME_FIELD') ) def clean_user(self): """ Returns ``User`` instance based on the given identification. """ identification = self.cleaned_data['user'] user_model = get_user_model() try: username_field = user_model.USERNAME_FIELD except AttributeError: username_field = 'username' try: user = user_model.objects.get(**{username_field: identification}) return user except user_model.DoesNotExist: raise forms.ValidationError( self.fields['user'].error_messages['does_not_exist']) class GroupManage(forms.Form): group = forms.CharField(max_length=80, error_messages={'does_not_exist': _("This group does not exist")}) def clean_group(self): """ Returns ``Group`` instance based on the given group name. """ name = self.cleaned_data['group'] try: group = Group.objects.get(name=name) return group except Group.DoesNotExist: raise forms.ValidationError( self.fields['group'].error_messages['does_not_exist']) django-guardian-1.4.1/guardian/locale/0000700000175000017500000000000012644306605020004 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/fr/0000700000175000017500000000000012644306605020413 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/fr/LC_MESSAGES/0000700000175000017500000000000012644306605022200 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/fr/LC_MESSAGES/django.po0000600000175000017500000001407312634712306024007 0ustar brianbrian00000000000000# French translation of django-guardian. # This file is distributed under the same license as the PACKAGE package. # Translator: Morgan Aubert , 2014. # msgid "" msgstr "" "Project-Id-Version: django-guardian 1.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-01-12 19:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Morgan Aubert \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: admin.py:32 admin.py:42 forms.py:55 msgid "Permissions" msgstr "Permissions" #: admin.py:127 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:12 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:25 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:25 #: templates/admin/guardian/model/change_form.html:5 #: templates/admin/guardian/model/obj_perms_manage.html:17 #: templates/admin/guardian/model/obj_perms_manage_group.html:14 #: templates/admin/guardian/model/obj_perms_manage_group.html:25 #: templates/admin/guardian/model/obj_perms_manage_user.html:14 #: templates/admin/guardian/model/obj_perms_manage_user.html:25 msgid "Object permissions" msgstr "Permissions de l'objet" #: admin.py:218 admin.py:271 msgid "Permissions saved." msgstr "Permissions sauvegardées." #: admin.py:395 msgid "User identification" msgstr "Identification de l'utilisateur" #: admin.py:397 msgid "This user does not exist" msgstr "Cet utilisateur n'existe pas" #: admin.py:398 msgid "Enter a value compatible with User.USERNAME_FIELD" msgstr "Entrez une valeur compatible avec User.USERNAME_FIELD" #: admin.py:421 msgid "This group does not exist" msgstr "Ce groupe n'existe pas" #: models.py:46 msgid "object ID" msgstr "ID de l'objet" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:8 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:10 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:10 #: templates/admin/guardian/model/obj_perms_manage.html:13 #: templates/admin/guardian/model/obj_perms_manage_group.html:10 #: templates/admin/guardian/model/obj_perms_manage_user.html:10 msgid "Home" msgstr "Accueil" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:21 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:82 msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Veuillez corriger l'erreur ci-dessous." msgstr[1] "Veuillez corriger les erreurs ci-dessous." #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:25 #: templates/admin/guardian/model/obj_perms_manage.html:32 msgid "Users" msgstr "Utilisateurs" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:38 msgid "User permissions" msgstr "Permissions des utilisateurs" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:33 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:41 #: templates/admin/guardian/model/obj_perms_manage_user.html:30 msgid "User" msgstr "Utilisateur" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:37 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:98 #: templates/admin/guardian/model/obj_perms_manage.html:45 #: templates/admin/guardian/model/obj_perms_manage.html:99 msgid "Action" msgstr "Action" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:54 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:115 #: templates/admin/guardian/model/obj_perms_manage.html:62 #: templates/admin/guardian/model/obj_perms_manage.html:116 msgid "Edit" msgstr "Modifier" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:70 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:73 #: templates/admin/guardian/model/obj_perms_manage_user.html:15 msgid "Manage user" msgstr "Gérer l'utilisateur" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:86 #: templates/admin/guardian/model/obj_perms_manage.html:86 msgid "Groups" msgstr "Groupes" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:89 #: templates/admin/guardian/model/obj_perms_manage.html:92 msgid "Group permissions" msgstr "Permissions des groupes" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:94 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:95 #: templates/admin/guardian/model/obj_perms_manage_group.html:30 msgid "Group" msgstr "Groupe" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:132 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:127 #: templates/admin/guardian/model/obj_perms_manage_group.html:15 msgid "Manage group" msgstr "Gérer le groupe" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:28 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:28 #: templates/admin/guardian/model/obj_perms_manage_group.html:27 #: templates/admin/guardian/model/obj_perms_manage_user.html:27 msgid "Object" msgstr "Objet" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:45 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:45 #: templates/admin/guardian/model/obj_perms_manage_group.html:37 #: templates/admin/guardian/model/obj_perms_manage_user.html:37 msgid "Save" msgstr "Sauvegarder" #: templates/admin/guardian/model/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:82 msgid "Please correct the errors below." msgstr "Veuillez corriger les erreurs ci-dessous." django-guardian-1.4.1/guardian/locale/fr/LC_MESSAGES/django.mo0000600000175000017500000000342312634712306024001 0ustar brianbrian00000000000000%0181=ou   @ &GLf [5%[bz P)E o{        ActionEditEnter a value compatible with User.USERNAME_FIELDGroupGroup permissionsGroupsHomeManage groupManage userObjectObject permissionsPermissionsPermissions saved.Please correct the error below.Please correct the errors below.Please correct the errors below.SaveThis group does not existThis user does not existUserUser identificationUser permissionsUsersobject IDProject-Id-Version: django-guardian 1.1.1 Report-Msgid-Bugs-To: POT-Creation-Date: 2014-01-12 19:50+0100 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Morgan Aubert Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); ActionModifierEntrez une valeur compatible avec User.USERNAME_FIELDGroupePermissions des groupesGroupesAccueilGérer le groupeGérer l'utilisateurObjetPermissions de l'objetPermissionsPermissions sauvegardées.Veuillez corriger l'erreur ci-dessous.Veuillez corriger les erreurs ci-dessous.Veuillez corriger les erreurs ci-dessous.SauvegarderCe groupe n'existe pasCet utilisateur n'existe pasUtilisateurIdentification de l'utilisateurPermissions des utilisateursUtilisateursID de l'objetdjango-guardian-1.4.1/guardian/locale/es/0000700000175000017500000000000012644306605020413 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/es/LC_MESSAGES/0000700000175000017500000000000012644306605022200 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/es/LC_MESSAGES/django.po0000600000175000017500000001404412634712306024005 0ustar brianbrian00000000000000# Spanish translation of django-guardian. # This file is distributed under the same license as django-guardian's package. # Translator: Adrián López Calvo , 2013. # msgid "" msgstr "" "Project-Id-Version: django-guardian 1.0.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-05-10 18:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Adrián López Calvo \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: admin.py:32 admin.py:42 forms.py:55 msgid "Permissions" msgstr "Permisos" #: admin.py:189 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:22 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:25 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:25 #: templates/admin/guardian/model/change_form.html:7 #: templates/admin/guardian/model/obj_perms_manage.html:17 #: templates/admin/guardian/model/obj_perms_manage_group.html:14 #: templates/admin/guardian/model/obj_perms_manage_group.html:25 #: templates/admin/guardian/model/obj_perms_manage_user.html:14 #: templates/admin/guardian/model/obj_perms_manage_user.html:25 msgid "Object permissions" msgstr "Permisos de objeto" #: admin.py:278 admin.py:331 msgid "Permissions saved." msgstr "Permisos guardados." #: admin.py:375 msgid "Username" msgstr "Nombre de usuario" #: admin.py:378 msgid "This value may contain only letters, numbers and @/./+/-/_ characters." msgstr "Este valor puede contener sólo letras, números y @/./+/-/_." #: admin.py:380 msgid "This user does not exist" msgstr "Este usuario no existe" #: admin.py:397 msgid "This group does not exist" msgstr "Este grupo no existe" #: models.py:46 msgid "object ID" msgstr "ID del objeto" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:18 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:10 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:10 #: templates/admin/guardian/model/obj_perms_manage.html:13 #: templates/admin/guardian/model/obj_perms_manage_group.html:10 #: templates/admin/guardian/model/obj_perms_manage_user.html:10 msgid "Home" msgstr "Inicio" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:33 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:93 #: templates/admin/guardian/model/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:82 msgid "Please correct the errors below." msgstr "Por favor, corrija los siguientes errores" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:37 #: templates/admin/guardian/model/obj_perms_manage.html:32 msgid "Users" msgstr "Usuarios" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:43 #: templates/admin/guardian/model/obj_perms_manage.html:38 msgid "User permissions" msgstr "Permisos de usuario" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:46 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:36 #: templates/admin/guardian/model/obj_perms_manage.html:41 #: templates/admin/guardian/model/obj_perms_manage_user.html:30 msgid "User" msgstr "Usuario" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:50 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:110 #: templates/admin/guardian/model/obj_perms_manage.html:45 #: templates/admin/guardian/model/obj_perms_manage.html:99 msgid "Action" msgstr "Acción" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:67 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:127 #: templates/admin/guardian/model/obj_perms_manage.html:62 #: templates/admin/guardian/model/obj_perms_manage.html:116 msgid "Edit" msgstr "Editar" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:80 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:73 #: templates/admin/guardian/model/obj_perms_manage_user.html:15 msgid "Manage user" msgstr "Gestionar usuario" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:97 #: templates/admin/guardian/model/obj_perms_manage.html:86 msgid "Groups" msgstr "Grupos" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:103 #: templates/admin/guardian/model/obj_perms_manage.html:92 msgid "Group permissions" msgstr "Permisos de grupo" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:106 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:36 #: templates/admin/guardian/model/obj_perms_manage.html:95 #: templates/admin/guardian/model/obj_perms_manage_group.html:30 msgid "Group" msgstr "Grupo" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:140 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:127 #: templates/admin/guardian/model/obj_perms_manage_group.html:15 msgid "Manage group" msgstr "Gestionar grupo" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:28 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:28 #: templates/admin/guardian/model/obj_perms_manage_group.html:27 #: templates/admin/guardian/model/obj_perms_manage_user.html:27 msgid "Object" msgstr "Objeto" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:48 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:48 #: templates/admin/guardian/model/obj_perms_manage_group.html:37 #: templates/admin/guardian/model/obj_perms_manage_user.html:37 msgid "Save" msgstr "Guardar" #: templates/admin/guardian/model/change_form.html:8 msgid "History" msgstr "Historial" #: templates/admin/guardian/model/change_form.html:9 msgid "View on site" msgstr "Ver en el sitio" django-guardian-1.4.1/guardian/locale/es/LC_MESSAGES/django.mo0000600000175000017500000000323012634712306023775 0ustar brianbrian00000000000000%@AHMSelt y    F$kp  g$6 =GN^pw)=CK_qz      ActionEditGroupGroup permissionsGroupsHistoryHomeManage groupManage userObjectObject permissionsPermissionsPermissions saved.Please correct the errors below.SaveThis group does not existThis user does not existThis value may contain only letters, numbers and @/./+/-/_ characters.UserUser permissionsUsernameUsersView on siteobject IDProject-Id-Version: django-guardian 1.0.4 Report-Msgid-Bugs-To: POT-Creation-Date: 2013-05-10 18:01+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Adrián López Calvo Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); AcciónEditarGrupoPermisos de grupoGruposHistorialInicioGestionar grupoGestionar usuarioObjetoPermisos de objetoPermisosPermisos guardados.Por favor, corrija los siguientes erroresGuardarEste grupo no existeEste usuario no existeEste valor puede contener sólo letras, números y @/./+/-/_.UsuarioPermisos de usuarioNombre de usuarioUsuariosVer en el sitioID del objetodjango-guardian-1.4.1/guardian/locale/pl/0000700000175000017500000000000012644306605020417 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/pl/LC_MESSAGES/0000700000175000017500000000000012644306605022204 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/pl/LC_MESSAGES/django.po0000600000175000017500000001416512634712306024015 0ustar brianbrian00000000000000# Polish translation of django-guardian. # This file is distributed under the same license as django-guardian's package. # Translator: Tomasz Wsuł <2nickers@gmail.com>, 2013. # msgid "" msgstr "" "Project-Id-Version: django-guardian 1.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-12-29 00:14+0100\n" "PO-Revision-Date: 2013-12-29 17:29-0600\n" "Last-Translator: <2nickers@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Translated-Using: django-rosetta 0.7.3\n" #: admin.py:32 admin.py:42 forms.py:55 msgid "Permissions" msgstr "Uprawnienia" #: admin.py:189 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:12 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:25 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:25 #: templates/admin/guardian/model/change_form.html:5 #: templates/admin/guardian/model/obj_perms_manage.html:17 #: templates/admin/guardian/model/obj_perms_manage_group.html:14 #: templates/admin/guardian/model/obj_perms_manage_group.html:25 #: templates/admin/guardian/model/obj_perms_manage_user.html:14 #: templates/admin/guardian/model/obj_perms_manage_user.html:25 msgid "Object permissions" msgstr "Uprawnienia do obiektu" #: admin.py:278 admin.py:331 msgid "Permissions saved." msgstr "Zapisano uprawnienia." #: admin.py:375 msgid "Username" msgstr "Użytkownik" #: admin.py:378 msgid "This value may contain only letters, numbers and @/./+/-/_ characters." msgstr "Ta wartość może zawierać tylko litery, cyfry oraz symbole @/./+/-/_" #: admin.py:380 msgid "This user does not exist" msgstr "Ten użytkownik nie istnieje" #: admin.py:397 msgid "This group does not exist" msgstr "Ta grupa nie istnieje" #: models.py:46 msgid "object ID" msgstr "ID obiektu" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:8 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:10 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:10 #: templates/admin/guardian/model/obj_perms_manage.html:13 #: templates/admin/guardian/model/obj_perms_manage_group.html:10 #: templates/admin/guardian/model/obj_perms_manage_user.html:10 msgid "Home" msgstr "Start" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:21 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:82 msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Proszę poprawić poniższe błędy." msgstr[1] "Proszę poprawić poniższy błąd." #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:25 #: templates/admin/guardian/model/obj_perms_manage.html:32 msgid "Users" msgstr "Użytkownicy" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:38 msgid "User permissions" msgstr "Uprawnienia użytkownika" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:33 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:41 #: templates/admin/guardian/model/obj_perms_manage_user.html:30 msgid "User" msgstr "Użytkownik" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:37 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:98 #: templates/admin/guardian/model/obj_perms_manage.html:45 #: templates/admin/guardian/model/obj_perms_manage.html:99 msgid "Action" msgstr "Akcja" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:54 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:115 #: templates/admin/guardian/model/obj_perms_manage.html:62 #: templates/admin/guardian/model/obj_perms_manage.html:116 msgid "Edit" msgstr "Edytuj" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:70 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:73 #: templates/admin/guardian/model/obj_perms_manage_user.html:15 msgid "Manage user" msgstr "Zarządzaj użytkownikiem" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:86 #: templates/admin/guardian/model/obj_perms_manage.html:86 msgid "Groups" msgstr "Grupy" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:89 #: templates/admin/guardian/model/obj_perms_manage.html:92 msgid "Group permissions" msgstr "Uprawnienia grupy" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:94 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:95 #: templates/admin/guardian/model/obj_perms_manage_group.html:30 msgid "Group" msgstr "Grupa" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:132 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:127 #: templates/admin/guardian/model/obj_perms_manage_group.html:15 msgid "Manage group" msgstr "Zarządzaj grupą" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:28 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:28 #: templates/admin/guardian/model/obj_perms_manage_group.html:27 #: templates/admin/guardian/model/obj_perms_manage_user.html:27 msgid "Object" msgstr "Obiekt" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:45 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:45 #: templates/admin/guardian/model/obj_perms_manage_group.html:37 #: templates/admin/guardian/model/obj_perms_manage_user.html:37 msgid "Save" msgstr "Zapisz" #: templates/admin/guardian/model/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:82 msgid "Please correct the errors below." msgstr "Proszę poprawić poniższe błędy." django-guardian-1.4.1/guardian/locale/pl/LC_MESSAGES/django.mo0000600000175000017500000000324712634712306024011 0ustar brianbrian00000000000000    @ `F %/.5 LXHn$G ^j   ActionEditGroupGroup permissionsGroupsHomeManage groupManage userObjectObject permissionsPermissionsPermissions saved.Please correct the error below.Please correct the errors below.Please correct the errors below.SaveThis group does not existThis user does not existThis value may contain only letters, numbers and @/./+/-/_ characters.UserUser permissionsUsernameUsersobject IDProject-Id-Version: django-guardian 1.2 Report-Msgid-Bugs-To: POT-Creation-Date: 2013-12-29 00:14+0100 PO-Revision-Date: 2013-12-29 17:29-0600 Last-Translator: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Language: pl Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); X-Translated-Using: django-rosetta 0.7.3 AkcjaEdytujGrupaUprawnienia grupyGrupyStartZarządzaj grupąZarządzaj użytkownikiemObiektUprawnienia do obiektuUprawnieniaZapisano uprawnienia.Proszę poprawić poniższe błędy.Proszę poprawić poniższy błąd.Proszę poprawić poniższe błędy.ZapiszTa grupa nie istniejeTen użytkownik nie istniejeTa wartość może zawierać tylko litery, cyfry oraz symbole @/./+/-/_UżytkownikUprawnienia użytkownikaUżytkownikUżytkownicyID obiektudjango-guardian-1.4.1/guardian/locale/ru/0000700000175000017500000000000012644306605020432 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/ru/LC_MESSAGES/0000700000175000017500000000000012644306605022217 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/ru/LC_MESSAGES/django.po0000600000175000017500000001464412634712306024032 0ustar brianbrian00000000000000# Polish translation of django-guardian. # This file is distributed under the same license as django-guardian's package. # Translator: Tomasz Wsuł <2nickers@gmail.com>, 2013. # msgid "" msgstr "" "Project-Id-Version: django-guardian 1.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-12-29 00:14+0100\n" "PO-Revision-Date: 2013-12-29 17:29-0600\n" "Last-Translator: <2nickers@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Translated-Using: django-rosetta 0.7.3\n" #: admin.py:32 admin.py:42 forms.py:55 msgid "Permissions" msgstr "Права" #: admin.py:189 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:12 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:25 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:25 #: templates/admin/guardian/model/change_form.html:5 #: templates/admin/guardian/model/obj_perms_manage.html:17 #: templates/admin/guardian/model/obj_perms_manage_group.html:14 #: templates/admin/guardian/model/obj_perms_manage_group.html:25 #: templates/admin/guardian/model/obj_perms_manage_user.html:14 #: templates/admin/guardian/model/obj_perms_manage_user.html:25 msgid "Object permissions" msgstr "Права на объект" #: admin.py:278 admin.py:331 msgid "Permissions saved." msgstr "Права сохранены." #: admin.py:375 msgid "Username" msgstr "Имя пользователя" #: admin.py:378 msgid "This value may contain only letters, numbers and @/./+/-/_ characters." msgstr "Это значение должно содержать буквы, цифры и символы @/./+/-/_" #: admin.py:380 msgid "This user does not exist" msgstr "Этот пользователь не существует" #: admin.py:397 msgid "This group does not exist" msgstr "Эта группа не существует" #: models.py:46 msgid "object ID" msgstr "ID объекта" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:8 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:10 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:10 #: templates/admin/guardian/model/obj_perms_manage.html:13 #: templates/admin/guardian/model/obj_perms_manage_group.html:10 #: templates/admin/guardian/model/obj_perms_manage_user.html:10 msgid "Home" msgstr "Домой" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:21 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:82 msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Исправьте ошибку ниже." msgstr[1] "Исправьте ошибки ниже." #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:25 #: templates/admin/guardian/model/obj_perms_manage.html:32 msgid "Users" msgstr "Пользователи" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:38 msgid "User permissions" msgstr "Права пользователя" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:33 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:41 #: templates/admin/guardian/model/obj_perms_manage_user.html:30 msgid "User" msgstr "Пользователь" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:37 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:98 #: templates/admin/guardian/model/obj_perms_manage.html:45 #: templates/admin/guardian/model/obj_perms_manage.html:99 msgid "Action" msgstr "Действие" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:54 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:115 #: templates/admin/guardian/model/obj_perms_manage.html:62 #: templates/admin/guardian/model/obj_perms_manage.html:116 msgid "Edit" msgstr "Редактировать" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:70 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:73 #: templates/admin/guardian/model/obj_perms_manage_user.html:15 msgid "Manage user" msgstr "Управление пользователем" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:86 #: templates/admin/guardian/model/obj_perms_manage.html:86 msgid "Groups" msgstr "Группы" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:89 #: templates/admin/guardian/model/obj_perms_manage.html:92 msgid "Group permissions" msgstr "Групповые права" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:94 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:95 #: templates/admin/guardian/model/obj_perms_manage_group.html:30 msgid "Group" msgstr "Группа" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:132 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:127 #: templates/admin/guardian/model/obj_perms_manage_group.html:15 msgid "Manage group" msgstr "Управление группой" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:28 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:28 #: templates/admin/guardian/model/obj_perms_manage_group.html:27 #: templates/admin/guardian/model/obj_perms_manage_user.html:27 msgid "Object" msgstr "Объект" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:45 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:45 #: templates/admin/guardian/model/obj_perms_manage_group.html:37 #: templates/admin/guardian/model/obj_perms_manage_user.html:37 msgid "Save" msgstr "Сохранить" #: templates/admin/guardian/model/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:82 msgid "Please correct the errors below." msgstr "Исправьте ошибки ниже." django-guardian-1.4.1/guardian/locale/ru/LC_MESSAGES/django.mo0000600000175000017500000000423512634712306024022 0ustar brianbrian00000000000000%018=CU\ a nz @ 4FM    #/* Zg T)--@;nj#.Rr     ActionEditGroupGroup permissionsGroupsHomeManage groupManage userObjectObject permissionsPermissionsPermissions saved.Please correct the error below.Please correct the errors below.Please correct the errors below.SaveThis group does not existThis user does not existThis value may contain only letters, numbers and @/./+/-/_ characters.UserUser permissionsUsernameUsersobject IDProject-Id-Version: django-guardian 1.2 Report-Msgid-Bugs-To: POT-Creation-Date: 2013-12-29 00:14+0100 PO-Revision-Date: 2015-09-25 10:30+0300 Last-Translator: 2nickers@gmail.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Language: pl Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); X-Translated-Using: django-rosetta 0.7.3 Language-Team: X-Generator: Poedit 1.8.5 ДействиеРедактироватьГруппаГрупповые праваГруппыДомойУправление группойУправление пользователемОбъектПрава на объектПраваПрава сохранены.Исправьте ошибку ниже.Исправьте ошибки ниже.Исправьте ошибки ниже.СохранитьЭта группа не существуетЭтот пользователь не существуетЭто значение должно содержать буквы, цифры и символы @/./+/-/_ПользовательПрава пользователяИмя пользователяПользователиID объектаdjango-guardian-1.4.1/guardian/locale/pt_BR/0000700000175000017500000000000012644306605021012 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/pt_BR/LC_MESSAGES/0000700000175000017500000000000012644306605022577 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/locale/pt_BR/LC_MESSAGES/django.po0000600000175000017500000001415012634712306024402 0ustar brianbrian00000000000000# django-guardian # Copyright (c) 2010-2013 Lukasz Balcerzak # This file is distributed under the same license as the PACKAGE package. # Fabio C. Barrionuevo da Luz , 2013. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: 1.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-12-14 02:39-0300\n" "PO-Revision-Date: 2013-12-14 02:39-0300\n" "Last-Translator: Fabio C. Barrionuevo da Luz \n" "Language-Team: pt_BR \n" "Language: Brazilian Portuguese\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: admin.py:32 admin.py:42 forms.py:55 msgid "Permissions" msgstr "Permissões" #: admin.py:189 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:12 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:25 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:14 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:25 #: templates/admin/guardian/model/change_form.html:5 #: templates/admin/guardian/model/obj_perms_manage.html:17 #: templates/admin/guardian/model/obj_perms_manage_group.html:14 #: templates/admin/guardian/model/obj_perms_manage_group.html:25 #: templates/admin/guardian/model/obj_perms_manage_user.html:14 #: templates/admin/guardian/model/obj_perms_manage_user.html:25 msgid "Object permissions" msgstr "Permissões para Objetos" #: admin.py:280 admin.py:333 msgid "Permissions saved." msgstr "Permissões salvas" #: admin.py:377 msgid "User identification" msgstr "Identificação de Usuário" #: admin.py:379 msgid "This user does not exist" msgstr "Este usuário não existe" #: admin.py:380 msgid "Enter a value compatible with User.USERNAME_FIELD" msgstr "Insira um valor compatível com User.USERNAME_FIELD" #: admin.py:403 msgid "This group does not exist" msgstr "Este grupo não existe" #: models.py:46 msgid "object ID" msgstr "id do objeto" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:8 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:10 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:10 #: templates/admin/guardian/model/obj_perms_manage.html:13 #: templates/admin/guardian/model/obj_perms_manage_group.html:10 #: templates/admin/guardian/model/obj_perms_manage_user.html:10 msgid "Home" msgstr "Home" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:21 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:82 msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Por favor, corrija o erro abaixo." msgstr[1] "Por favor, corrija os erros abaixo." #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:25 #: templates/admin/guardian/model/obj_perms_manage.html:32 msgid "Users" msgstr "Usuários" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:38 msgid "User permissions" msgstr "Permissões de usuários" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:33 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:41 #: templates/admin/guardian/model/obj_perms_manage_user.html:30 msgid "User" msgstr "Usuário" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:37 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:98 #: templates/admin/guardian/model/obj_perms_manage.html:45 #: templates/admin/guardian/model/obj_perms_manage.html:99 msgid "Action" msgstr "Ação" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:54 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:115 #: templates/admin/guardian/model/obj_perms_manage.html:62 #: templates/admin/guardian/model/obj_perms_manage.html:116 msgid "Edit" msgstr "Editar" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:70 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:73 #: templates/admin/guardian/model/obj_perms_manage_user.html:15 msgid "Manage user" msgstr "Gerênciar usuário" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:86 #: templates/admin/guardian/model/obj_perms_manage.html:86 msgid "Groups" msgstr "Grupos" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:89 #: templates/admin/guardian/model/obj_perms_manage.html:92 msgid "Group permissions" msgstr "Grupos de permissões" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:94 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:34 #: templates/admin/guardian/model/obj_perms_manage.html:95 #: templates/admin/guardian/model/obj_perms_manage_group.html:30 msgid "Group" msgstr "Grupo" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage.html:132 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:15 #: templates/admin/guardian/model/obj_perms_manage.html:127 #: templates/admin/guardian/model/obj_perms_manage_group.html:15 msgid "Manage group" msgstr "Gerenciar grupo" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:28 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:28 #: templates/admin/guardian/model/obj_perms_manage_group.html:27 #: templates/admin/guardian/model/obj_perms_manage_user.html:27 msgid "Object" msgstr "Objeto" #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_group.html:45 #: templates/admin/guardian/contrib/grappelli/obj_perms_manage_user.html:45 #: templates/admin/guardian/model/obj_perms_manage_group.html:37 #: templates/admin/guardian/model/obj_perms_manage_user.html:37 msgid "Save" msgstr "Salvar" #: templates/admin/guardian/model/obj_perms_manage.html:28 #: templates/admin/guardian/model/obj_perms_manage.html:82 msgid "Please correct the errors below." msgstr "Por favor, corrija os erros abaixo." django-guardian-1.4.1/guardian/locale/pt_BR/LC_MESSAGES/django.mo0000600000175000017500000000340012634712306024373 0ustar brianbrian00000000000000%0181=ou   @ &GLf <C3J~ E #Osz       ActionEditEnter a value compatible with User.USERNAME_FIELDGroupGroup permissionsGroupsHomeManage groupManage userObjectObject permissionsPermissionsPermissions saved.Please correct the error below.Please correct the errors below.Please correct the errors below.SaveThis group does not existThis user does not existUserUser identificationUser permissionsUsersobject IDProject-Id-Version: 1.2.0 Report-Msgid-Bugs-To: POT-Creation-Date: 2013-12-14 02:39-0300 PO-Revision-Date: 2013-12-14 02:39-0300 Last-Translator: Fabio C. Barrionuevo da Luz Language-Team: pt_BR Language: Brazilian Portuguese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); AçãoEditarInsira um valor compatível com User.USERNAME_FIELDGrupoGrupos de permissõesGruposHomeGerenciar grupoGerênciar usuárioObjetoPermissões para ObjetosPermissõesPermissões salvasPor favor, corrija o erro abaixo.Por favor, corrija os erros abaixo.Por favor, corrija os erros abaixo.SalvarEste grupo não existeEste usuário não existeUsuárioIdentificação de UsuárioPermissões de usuáriosUsuáriosid do objetodjango-guardian-1.4.1/guardian/testapp/0000700000175000017500000000000012644306605020225 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/testsettings.py0000600000175000017500000000226112643667664023360 0ustar brianbrian00000000000000import os import random import string import environ env = environ.Env() DEBUG = False ANONYMOUS_USER_ID = -1 AUTH_USER_MODEL = "testapp.CustomUser" GUARDIAN_MONKEY_PATCH = False INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'django.contrib.messages', 'guardian', 'guardian.testapp', ) AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend', ) # this fixes warnings in django 1.7 MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) TEST_RUNNER = 'django.test.runner.DiscoverRunner' ROOT_URLCONF = 'guardian.testapp.tests.urls' SITE_ID = 1 TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'tests', 'templates'), ) SECRET_KEY = ''.join([random.choice(string.ascii_letters) for x in range(40)]) # Database specific DATABASES = {'default': env.db(default="sqlite:///")} django-guardian-1.4.1/guardian/testapp/models.py0000600000175000017500000000364612643667664022113 0ustar brianbrian00000000000000from __future__ import unicode_literals from datetime import datetime from django.db import models from django.contrib.admin.models import LogEntry from guardian.mixins import GuardianUserMixin from guardian.models import UserObjectPermissionBase from guardian.models import GroupObjectPermissionBase class Post(models.Model): title = models.CharField('title', max_length=64) class DynamicAccessor(object): def __init__(self): pass def __getattr__(self, key): return DynamicAccessor() class ProjectUserObjectPermission(UserObjectPermissionBase): content_object = models.ForeignKey('Project') class ProjectGroupObjectPermission(GroupObjectPermissionBase): content_object = models.ForeignKey('Project') class Project(models.Model): name = models.CharField(max_length=128, unique=True) created_at = models.DateTimeField(default=datetime.now) class Meta: get_latest_by = 'created_at' def __unicode__(self): return self.name Project.not_a_relation_descriptor = DynamicAccessor() class MixedGroupObjectPermission(GroupObjectPermissionBase): content_object = models.ForeignKey('Mixed') class Mixed(models.Model): """ Model for tests obj perms checks with generic user object permissions model and direct group object permissions model. """ name = models.CharField(max_length=128, unique=True) def __unicode__(self): return self.name class LogEntryWithGroup(LogEntry): group = models.ForeignKey('auth.Group', null=True, blank=True) class NonIntPKModel(models.Model): """ Model for testing whether get_objects_for_user will work when the objects to be returned have non-integer primary keys. """ char_pk = models.CharField(primary_key=True, max_length=128) from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser, GuardianUserMixin): custom_id = models.AutoField(primary_key=True) django-guardian-1.4.1/guardian/testapp/__init__.py0000600000175000017500000000005112634712306022332 0ustar brianbrian00000000000000from __future__ import unicode_literals django-guardian-1.4.1/guardian/testapp/migrations/0000700000175000017500000000000012644306605022401 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/migrations/0002_logentrywithgroup.py0000600000175000017500000000130612634712306027230 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.db import models, migrations class Migration(migrations.Migration): dependencies = [ ('auth', '0001_initial'), ('admin', '0001_initial'), ('testapp', '0001_initial'), ] operations = [ migrations.CreateModel( name='LogEntryWithGroup', fields=[ ('logentry_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='admin.LogEntry')), ('group', models.ForeignKey(blank=True, to='auth.Group', null=True)), ], options={ }, bases=('admin.logentry',), ), ]django-guardian-1.4.1/guardian/testapp/migrations/0003_auto_20141124_0729.py0000600000175000017500000000172612634712306026032 0ustar brianbrian00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations import django.core.validators class Migration(migrations.Migration): dependencies = [ ('testapp', '0002_logentrywithgroup'), ] operations = [ migrations.CreateModel( name='NonIntPKModel', fields=[ ('char_pk', models.CharField(max_length=128, serialize=False, primary_key=True)), ], options={ }, bases=(models.Model,), ), migrations.AlterField( model_name='customuser', name='username', 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')]), preserve_default=True, ), ] django-guardian-1.4.1/guardian/testapp/migrations/0001_initial.py0000600000175000017500000001343612634712306025053 0ustar brianbrian00000000000000from __future__ import unicode_literals import datetime from django.conf import settings import django.core.validators from django.db import models, migrations import django.utils.timezone import guardian.mixins class Migration(migrations.Migration): dependencies = [ ('auth', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='CustomUser', fields=[ ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login', null=True)), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('username', models.CharField(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')])), ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('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')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('custom_id', models.AutoField(serialize=False, primary_key=True)), ('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')), ('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')), ], options={ 'abstract': False, 'verbose_name': 'user', 'verbose_name_plural': 'users', }, bases=(models.Model, guardian.mixins.GuardianUserMixin), ), migrations.CreateModel( name='Mixed', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(unique=True, max_length=128)), ], options={ }, bases=(models.Model,), ), migrations.CreateModel( name='MixedGroupObjectPermission', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('content_object', models.ForeignKey(to='testapp.Mixed')), ('group', models.ForeignKey(to='auth.Group')), ('permission', models.ForeignKey(to='auth.Permission')), ], options={ 'abstract': False, }, bases=(models.Model,), ), migrations.CreateModel( name='Project', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(unique=True, max_length=128)), ('created_at', models.DateTimeField(default=datetime.datetime.now)), ], options={ 'get_latest_by': 'created_at', }, bases=(models.Model,), ), migrations.CreateModel( name='ProjectGroupObjectPermission', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('content_object', models.ForeignKey(to='testapp.Project')), ('group', models.ForeignKey(to='auth.Group')), ('permission', models.ForeignKey(to='auth.Permission')), ], options={ 'abstract': False, }, bases=(models.Model,), ), migrations.CreateModel( name='ProjectUserObjectPermission', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('content_object', models.ForeignKey(to='testapp.Project')), ('permission', models.ForeignKey(to='auth.Permission')), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ], options={ 'abstract': False, }, bases=(models.Model,), ), migrations.AlterUniqueTogether( name='projectuserobjectpermission', unique_together=set([('user', 'permission', 'content_object')]), ), migrations.AlterUniqueTogether( name='projectgroupobjectpermission', unique_together=set([('group', 'permission', 'content_object')]), ), migrations.AlterUniqueTogether( name='mixedgroupobjectpermission', unique_together=set([('group', 'permission', 'content_object')]), ), ]django-guardian-1.4.1/guardian/testapp/migrations/0005_auto_20151217_2344.py0000600000175000017500000000112412643667664026043 0ustar brianbrian00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9 on 2015-12-17 23:44 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('testapp', '0004_auto_20151112_2209'), ] operations = [ migrations.CreateModel( name='Post', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=64, verbose_name='title')), ], ), ] django-guardian-1.4.1/guardian/testapp/migrations/__init__.py0000600000175000017500000000000012634712306024500 0ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/migrations/0004_auto_20151112_2209.py0000600000175000017500000000310512634712306026015 0ustar brianbrian00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models import django.core.validators import django.contrib.auth.models class Migration(migrations.Migration): dependencies = [ ('testapp', '0003_auto_20141124_0729'), ] operations = [ migrations.AlterField( model_name='customuser', name='email', field=models.EmailField(max_length=254, verbose_name='email address', blank=True), ), migrations.AlterField( model_name='customuser', name='groups', 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'), ), migrations.AlterField( model_name='customuser', name='last_login', field=models.DateTimeField(null=True, verbose_name='last login', blank=True), ), migrations.AlterField( model_name='customuser', name='username', 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'), ), ] django-guardian-1.4.1/guardian/testapp/tests/0000700000175000017500000000000012644306605021367 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/tests/urls.py0000600000175000017500000000125512643667664022751 0ustar brianbrian00000000000000from __future__ import unicode_literals # handler404 and handler500 are needed for admin tests from guardian.compat import include, url, handler404, handler500 # pyflakes:ignore from guardian.mixins import PermissionRequiredMixin from django.contrib import admin from django.contrib.auth.views import login from django.views.generic import View admin.autodiscover() class TestClassRedirectView(PermissionRequiredMixin, View): permission_required = 'testapp.change_project' urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^accounts/login/', login, {'template_name': 'blank.html'}), url(r'^permission_required/', TestClassRedirectView.as_view()), ] django-guardian-1.4.1/guardian/testapp/tests/test_utils.py0000600000175000017500000001017612634712306024145 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.test import TestCase from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import Group, AnonymousUser from django.db import models from guardian.compat import get_user_model from guardian.testapp.tests.conf import skipUnlessTestApp from guardian.testapp.tests.test_core import ObjectPermissionTestCase from guardian.testapp.models import Project from guardian.testapp.models import ProjectUserObjectPermission from guardian.testapp.models import ProjectGroupObjectPermission from guardian.models import UserObjectPermission from guardian.models import UserObjectPermissionBase from guardian.models import GroupObjectPermission from guardian.utils import get_anonymous_user from guardian.utils import get_identity from guardian.utils import get_user_obj_perms_model from guardian.utils import get_group_obj_perms_model from guardian.utils import get_obj_perms_model from guardian.exceptions import NotUserNorGroup User = get_user_model() class GetAnonymousUserTest(TestCase): def test(self): anon = get_anonymous_user() self.assertTrue(isinstance(anon, User)) class GetIdentityTest(ObjectPermissionTestCase): def test_user(self): user, group = get_identity(self.user) self.assertTrue(isinstance(user, User)) self.assertEqual(group, None) def test_anonymous_user(self): anon = AnonymousUser() user, group = get_identity(anon) self.assertTrue(isinstance(user, User)) self.assertEqual(group, None) def test_group(self): user, group = get_identity(self.group) self.assertTrue(isinstance(group, Group)) self.assertEqual(user, None) def test_not_user_nor_group(self): self.assertRaises(NotUserNorGroup, get_identity, 1) self.assertRaises(NotUserNorGroup, get_identity, "User") self.assertRaises(NotUserNorGroup, get_identity, User) @skipUnlessTestApp class GetUserObjPermsModelTest(TestCase): def test_for_instance(self): project = Project(name='Foobar') self.assertEqual(get_user_obj_perms_model(project), ProjectUserObjectPermission) def test_for_class(self): self.assertEqual(get_user_obj_perms_model(Project), ProjectUserObjectPermission) def test_default(self): self.assertEqual(get_user_obj_perms_model(ContentType), UserObjectPermission) def test_user_model(self): # this test assumes that there were no direct obj perms model to User # model defined (i.e. while testing guardian app in some custom project) self.assertEqual(get_user_obj_perms_model(User), UserObjectPermission) @skipUnlessTestApp class GetGroupObjPermsModelTest(TestCase): def test_for_instance(self): project = Project(name='Foobar') self.assertEqual(get_group_obj_perms_model(project), ProjectGroupObjectPermission) def test_for_class(self): self.assertEqual(get_group_obj_perms_model(Project), ProjectGroupObjectPermission) def test_default(self): self.assertEqual(get_group_obj_perms_model(ContentType), GroupObjectPermission) def test_group_model(self): # this test assumes that there were no direct obj perms model to Group # model defined (i.e. while testing guardian app in some custom project) self.assertEqual(get_group_obj_perms_model(Group), GroupObjectPermission) class GetObjPermsModelTest(TestCase): def test_image_field(self): class SomeModel(models.Model): image = models.FileField(upload_to='images/') obj = SomeModel() perm_model = get_obj_perms_model(obj, UserObjectPermissionBase, UserObjectPermission) self.assertEqual(perm_model, UserObjectPermission) def test_file_field(self): class SomeModel2(models.Model): file = models.FileField(upload_to='images/') obj = SomeModel2() perm_model = get_obj_perms_model(obj, UserObjectPermissionBase, UserObjectPermission) self.assertEqual(perm_model, UserObjectPermission) django-guardian-1.4.1/guardian/testapp/tests/test_other.py0000600000175000017500000003056412643667664024151 0ustar brianbrian00000000000000from __future__ import unicode_literals import mock import unittest from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.test import TestCase import guardian from guardian.backends import ObjectPermissionBackend from guardian.compat import get_user_model from guardian.compat import get_user_model_path from guardian.compat import get_user_permission_codename from guardian.compat import basestring from guardian.compat import unicode from guardian.exceptions import GuardianError from guardian.exceptions import NotUserNorGroup from guardian.exceptions import ObjectNotPersisted from guardian.exceptions import WrongAppError from guardian.models import GroupObjectPermission from guardian.models import UserObjectPermission from guardian.testapp.tests.conf import TestDataMixin User = get_user_model() user_model_path = get_user_model_path() class UserPermissionTests(TestDataMixin, TestCase): def setUp(self): super(UserPermissionTests, self).setUp() self.user = User.objects.get(username='jack') self.ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') self.obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') self.obj2 = ContentType.objects.create(model='bar', app_label='guardian-tests') def test_assignement(self): self.assertFalse(self.user.has_perm('change_contenttype', self.ctype)) UserObjectPermission.objects.assign_perm('change_contenttype', self.user, self.ctype) self.assertTrue(self.user.has_perm('change_contenttype', self.ctype)) self.assertTrue(self.user.has_perm('contenttypes.change_contenttype', self.ctype)) def test_assignement_and_remove(self): UserObjectPermission.objects.assign_perm('change_contenttype', self.user, self.ctype) self.assertTrue(self.user.has_perm('change_contenttype', self.ctype)) UserObjectPermission.objects.remove_perm('change_contenttype', self.user, self.ctype) self.assertFalse(self.user.has_perm('change_contenttype', self.ctype)) def test_ctypes(self): UserObjectPermission.objects.assign_perm('change_contenttype', self.user, self.obj1) self.assertTrue(self.user.has_perm('change_contenttype', self.obj1)) self.assertFalse(self.user.has_perm('change_contenttype', self.obj2)) UserObjectPermission.objects.remove_perm('change_contenttype', self.user, self.obj1) UserObjectPermission.objects.assign_perm('change_contenttype', self.user, self.obj2) self.assertTrue(self.user.has_perm('change_contenttype', self.obj2)) self.assertFalse(self.user.has_perm('change_contenttype', self.obj1)) UserObjectPermission.objects.assign_perm('change_contenttype', self.user, self.obj1) UserObjectPermission.objects.assign_perm('change_contenttype', self.user, self.obj2) self.assertTrue(self.user.has_perm('change_contenttype', self.obj2)) self.assertTrue(self.user.has_perm('change_contenttype', self.obj1)) UserObjectPermission.objects.remove_perm('change_contenttype', self.user, self.obj1) UserObjectPermission.objects.remove_perm('change_contenttype', self.user, self.obj2) self.assertFalse(self.user.has_perm('change_contenttype', self.obj2)) self.assertFalse(self.user.has_perm('change_contenttype', self.obj1)) def test_assign_perm_validation(self): self.assertRaises(Permission.DoesNotExist, UserObjectPermission.objects.assign_perm, 'change_group', self.user, self.user) group = Group.objects.create(name='test_group_assign_perm_validation') ctype = ContentType.objects.get_for_model(group) user_ctype = ContentType.objects.get_for_model(self.user) codename = get_user_permission_codename('change') perm = Permission.objects.get(codename=codename, content_type=user_ctype) create_info = dict( permission = perm, user = self.user, content_type = ctype, object_pk = group.pk ) self.assertRaises(ValidationError, UserObjectPermission.objects.create, **create_info) def test_unicode(self): codename = get_user_permission_codename('change') obj_perm = UserObjectPermission.objects.assign_perm(codename, self.user, self.user) self.assertTrue(isinstance(obj_perm.__unicode__(), unicode)) def test_errors(self): not_saved_user = User(username='not_saved_user') codename = get_user_permission_codename('change') self.assertRaises(ObjectNotPersisted, UserObjectPermission.objects.assign_perm, codename, self.user, not_saved_user) self.assertRaises(ObjectNotPersisted, UserObjectPermission.objects.remove_perm, codename, self.user, not_saved_user) class GroupPermissionTests(TestDataMixin, TestCase): def setUp(self): super(GroupPermissionTests, self).setUp() self.user = User.objects.get(username='jack') self.group, created = Group.objects.get_or_create(name='jackGroup') self.user.groups.add(self.group) self.ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') self.obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') self.obj2 = ContentType.objects.create(model='bar', app_label='guardian-tests') def test_assignement(self): self.assertFalse(self.user.has_perm('change_contenttype', self.ctype)) self.assertFalse(self.user.has_perm('contenttypes.change_contenttype', self.ctype)) GroupObjectPermission.objects.assign_perm('change_contenttype', self.group, self.ctype) self.assertTrue(self.user.has_perm('change_contenttype', self.ctype)) self.assertTrue(self.user.has_perm('contenttypes.change_contenttype', self.ctype)) def test_assignement_and_remove(self): GroupObjectPermission.objects.assign_perm('change_contenttype', self.group, self.ctype) self.assertTrue(self.user.has_perm('change_contenttype', self.ctype)) GroupObjectPermission.objects.remove_perm('change_contenttype', self.group, self.ctype) self.assertFalse(self.user.has_perm('change_contenttype', self.ctype)) def test_ctypes(self): GroupObjectPermission.objects.assign_perm('change_contenttype', self.group, self.obj1) self.assertTrue(self.user.has_perm('change_contenttype', self.obj1)) self.assertFalse(self.user.has_perm('change_contenttype', self.obj2)) GroupObjectPermission.objects.remove_perm('change_contenttype', self.group, self.obj1) GroupObjectPermission.objects.assign_perm('change_contenttype', self.group, self.obj2) self.assertTrue(self.user.has_perm('change_contenttype', self.obj2)) self.assertFalse(self.user.has_perm('change_contenttype', self.obj1)) GroupObjectPermission.objects.assign_perm('change_contenttype', self.group, self.obj1) GroupObjectPermission.objects.assign_perm('change_contenttype', self.group, self.obj2) self.assertTrue(self.user.has_perm('change_contenttype', self.obj2)) self.assertTrue(self.user.has_perm('change_contenttype', self.obj1)) GroupObjectPermission.objects.remove_perm('change_contenttype', self.group, self.obj1) GroupObjectPermission.objects.remove_perm('change_contenttype', self.group, self.obj2) self.assertFalse(self.user.has_perm('change_contenttype', self.obj2)) self.assertFalse(self.user.has_perm('change_contenttype', self.obj1)) def test_assign_perm_validation(self): self.assertRaises(Permission.DoesNotExist, GroupObjectPermission.objects.assign_perm, 'change_user', self.group, self.group) user = User.objects.create(username='testuser') ctype = ContentType.objects.get_for_model(user) perm = Permission.objects.get(codename='change_group') create_info = dict( permission = perm, group = self.group, content_type = ctype, object_pk = user.pk ) self.assertRaises(ValidationError, GroupObjectPermission.objects.create, **create_info) def test_unicode(self): obj_perm = GroupObjectPermission.objects.assign_perm("change_group", self.group, self.group) self.assertTrue(isinstance(obj_perm.__unicode__(), unicode)) def test_errors(self): not_saved_group = Group(name='not_saved_group') self.assertRaises(ObjectNotPersisted, GroupObjectPermission.objects.assign_perm, "change_group", self.group, not_saved_group) self.assertRaises(ObjectNotPersisted, GroupObjectPermission.objects.remove_perm, "change_group", self.group, not_saved_group) class ObjectPermissionBackendTests(TestCase): def setUp(self): self.user = User.objects.create(username='jack') self.backend = ObjectPermissionBackend() def test_attrs(self): self.assertTrue(self.backend.supports_anonymous_user) self.assertTrue(self.backend.supports_object_permissions) self.assertTrue(self.backend.supports_inactive_user) def test_authenticate(self): self.assertEqual(self.backend.authenticate( self.user.username, self.user.password), None) def test_has_perm_noobj(self): result = self.backend.has_perm(self.user, "change_contenttype") self.assertFalse(result) def test_has_perm_notauthed(self): user = AnonymousUser() self.assertFalse(self.backend.has_perm(user, "change_user", self.user)) def test_has_perm_wrong_app(self): self.assertRaises(WrongAppError, self.backend.has_perm, self.user, "no_app.change_user", self.user) def test_obj_is_not_model(self): for obj in (Group, 666, "String", [2, 1, 5, 7], {}): self.assertFalse(self.backend.has_perm(self.user, "any perm", obj)) def test_not_active_user(self): user = User.objects.create(username='non active user') ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') perm = 'change_contenttype' UserObjectPermission.objects.assign_perm(perm, user, ctype) self.assertTrue(self.backend.has_perm(user, perm, ctype)) user.is_active = False user.save() self.assertFalse(self.backend.has_perm(user, perm, ctype)) class GuardianBaseTests(TestCase): def has_attrs(self): self.assertTrue(hasattr(guardian, '__version__')) def test_version(self): for x in guardian.VERSION: self.assertTrue(isinstance(x, (int, basestring))) def test_get_version(self): self.assertTrue(isinstance(guardian.get_version(), basestring)) class TestExceptions(TestCase): def _test_error_class(self, exc_cls): self.assertTrue(isinstance(exc_cls, GuardianError)) def test_error_classes(self): self.assertTrue(isinstance(GuardianError(), Exception)) guardian_errors = [NotUserNorGroup] for err in guardian_errors: self._test_error_class(err()) @unittest.skip("test is broken") class TestMonkeyPatch(TestCase): @mock.patch('guardian.compat.get_user_model') def test_monkey_patch(self, mocked_get_user_model): # Import AbstractUser here as it is only available since Django 1.5 from django.contrib.auth.models import AbstractUser class CustomUserTestClass(AbstractUser): pass mocked_get_user_model.return_value = CustomUserTestClass self.assertFalse(getattr(CustomUserTestClass, 'get_anonymous', False)) self.assertFalse(getattr(CustomUserTestClass, 'add_obj_perm', False)) self.assertFalse(getattr(CustomUserTestClass, 'del_obj_perm', False)) # Monkey Patch guardian.monkey_patch_user() self.assertTrue(getattr(CustomUserTestClass, 'get_anonymous', False)) self.assertTrue(getattr(CustomUserTestClass, 'add_obj_perm', False)) self.assertTrue(getattr(CustomUserTestClass, 'del_obj_perm', False)) django-guardian-1.4.1/guardian/testapp/tests/test_managers.py0000600000175000017500000000141012634712306024571 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.test import TestCase from guardian.compat import mock from guardian.managers import UserObjectPermissionManager from guardian.managers import GroupObjectPermissionManager class TestManagers(TestCase): def test_user_manager_assign(self): manager = UserObjectPermissionManager() manager.assign_perm = mock.Mock() manager.assign('perm', 'user', 'object') manager.assign_perm.assert_called_once_with('perm', 'user', 'object') def test_group_manager_assign(self): manager = GroupObjectPermissionManager() manager.assign_perm = mock.Mock() manager.assign('perm', 'group', 'object') manager.assign_perm.assert_called_once_with('perm', 'group', 'object') django-guardian-1.4.1/guardian/testapp/tests/test_orphans.py0000600000175000017500000000674012643667664024501 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.apps import apps as django_apps auth_app = django_apps.get_app_config("auth") from django.contrib.contenttypes.models import ContentType from django.core.management import call_command from django.test import TestCase from guardian.compat import get_user_model, create_permissions, get_model_name from guardian.utils import clean_orphan_obj_perms from guardian.shortcuts import assign_perm from guardian.models import Group from guardian.testapp.tests.conf import skipUnlessTestApp User = get_user_model() user_module_name = get_model_name(User) @skipUnlessTestApp class OrphanedObjectPermissionsTest(TestCase): def setUp(self): # Create objects for which we would assing obj perms self.target_user1 = User.objects.create(username='user1') self.target_group1 = Group.objects.create(name='group1') self.target_obj1 = ContentType.objects.create(model='foo', app_label='fake-for-guardian-tests') self.target_obj2 = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') # Required if MySQL backend is used :/ create_permissions(auth_app, [], 1) self.user = User.objects.create(username='user') self.group = Group.objects.create(name='group') def test_clean_perms(self): # assign obj perms target_perms = { self.target_user1: ["change_%s" % user_module_name], self.target_group1: ["delete_group"], self.target_obj1: ["change_contenttype", "delete_contenttype"], self.target_obj2: ["change_contenttype"], } obj_perms_count = sum([len(val) for key, val in target_perms.items()]) for target, perms in target_perms.items(): target.__old_pk = target.pk # Store pkeys for perm in perms: assign_perm(perm, self.user, target) # Remove targets for target, perms in target_perms.items(): target.delete() # Clean orphans removed = clean_orphan_obj_perms() self.assertEqual(removed, obj_perms_count) # Recreate targets and check if user has no permissions for target, perms in target_perms.items(): target.pk = target.__old_pk target.save() for perm in perms: self.assertFalse(self.user.has_perm(perm, target)) def test_clean_perms_command(self): """ Same test as the one above but rather function directly, we call management command instead. """ # assign obj perms target_perms = { self.target_user1: ["change_%s" % user_module_name], self.target_group1: ["delete_group"], self.target_obj1: ["change_contenttype", "delete_contenttype"], self.target_obj2: ["change_contenttype"], } for target, perms in target_perms.items(): target.__old_pk = target.pk # Store pkeys for perm in perms: assign_perm(perm, self.user, target) # Remove targets for target, perms in target_perms.items(): target.delete() # Clean orphans call_command("clean_orphan_obj_perms", verbosity=0) # Recreate targets and check if user has no permissions for target, perms in target_perms.items(): target.pk = target.__old_pk target.save() for perm in perms: self.assertFalse(self.user.has_perm(perm, target)) django-guardian-1.4.1/guardian/testapp/tests/test_direct_rel.py0000600000175000017500000002116012634712306025114 0ustar brianbrian00000000000000from __future__ import unicode_literals from guardian.testapp.models import Mixed from guardian.testapp.models import Project from guardian.testapp.models import ProjectGroupObjectPermission from guardian.testapp.models import ProjectUserObjectPermission from django.contrib.auth.models import Group, Permission from django.test import TestCase from guardian.compat import get_user_model from guardian.shortcuts import assign_perm from guardian.shortcuts import get_groups_with_perms from guardian.shortcuts import get_objects_for_group from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_users_with_perms from guardian.shortcuts import remove_perm from guardian.testapp.tests.conf import skipUnlessTestApp User = get_user_model() @skipUnlessTestApp class TestDirectUserPermissions(TestCase): def setUp(self): self.joe = User.objects.create_user('joe', 'joe@example.com', 'foobar') self.project = Project.objects.create(name='Foobar') def get_perm(self, codename): filters = {'content_type__app_label': 'testapp', 'codename': codename} return Permission.objects.get(**filters) def test_after_perm_is_created_without_shortcut(self): perm = self.get_perm('add_project') # we should not use assign here - if generic user obj perms model is # used then everything could go fine if using assign shortcut and we # would not be able to see any problem ProjectUserObjectPermission.objects.create( user=self.joe, permission=perm, content_object=self.project, ) self.assertTrue(self.joe.has_perm('add_project', self.project)) def test_assign_perm(self): assign_perm('add_project', self.joe, self.project) filters = { 'content_object': self.project, 'permission__codename': 'add_project', 'user': self.joe, } result = ProjectUserObjectPermission.objects.filter(**filters).count() self.assertEqual(result, 1) def test_remove_perm(self): assign_perm('add_project', self.joe, self.project) filters = { 'content_object': self.project, 'permission__codename': 'add_project', 'user': self.joe, } result = ProjectUserObjectPermission.objects.filter(**filters).count() self.assertEqual(result, 1) remove_perm('add_project', self.joe, self.project) result = ProjectUserObjectPermission.objects.filter(**filters).count() self.assertEqual(result, 0) def test_get_users_with_perms(self): User.objects.create_user('john', 'john@foobar.com', 'john') jane = User.objects.create_user('jane', 'jane@foobar.com', 'jane') assign_perm('add_project', self.joe, self.project) assign_perm('change_project', self.joe, self.project) assign_perm('change_project', jane, self.project) self.assertEqual(get_users_with_perms(self.project, attach_perms=True), { self.joe: ['add_project', 'change_project'], jane: ['change_project'], }) def test_get_users_with_perms_plus_groups(self): User.objects.create_user('john', 'john@foobar.com', 'john') jane = User.objects.create_user('jane', 'jane@foobar.com', 'jane') group = Group.objects.create(name='devs') self.joe.groups.add(group) assign_perm('add_project', self.joe, self.project) assign_perm('change_project', group, self.project) assign_perm('change_project', jane, self.project) self.assertEqual(get_users_with_perms(self.project, attach_perms=True), { self.joe: ['add_project', 'change_project'], jane: ['change_project'], }) def test_get_objects_for_user(self): foo = Project.objects.create(name='foo') bar = Project.objects.create(name='bar') assign_perm('add_project', self.joe, foo) assign_perm('add_project', self.joe, bar) assign_perm('change_project', self.joe, bar) result = get_objects_for_user(self.joe, 'testapp.add_project') self.assertEqual(sorted(p.pk for p in result), sorted([foo.pk, bar.pk])) def test_get_all_permissions(self): foo = Project.objects.create(name='foo') assign_perm('add_project', self.joe, foo) assign_perm('change_project', self.joe, foo) result = self.joe.get_all_permissions(foo) self.assertEqual(result, set(('add_project', 'change_project'))) def test_get_all_permissions_no_object(self): foo = Project.objects.create(name='foo') assign_perm('add_project', self.joe, foo) assign_perm('change_project', self.joe, foo) result = self.joe.get_all_permissions() self.assertEqual(result, set()) @skipUnlessTestApp class TestDirectGroupPermissions(TestCase): def setUp(self): self.joe = User.objects.create_user('joe', 'joe@example.com', 'foobar') self.group = Group.objects.create(name='admins') self.joe.groups.add(self.group) self.project = Project.objects.create(name='Foobar') def get_perm(self, codename): filters = {'content_type__app_label': 'testapp', 'codename': codename} return Permission.objects.get(**filters) def test_after_perm_is_created_without_shortcut(self): perm = self.get_perm('add_project') # we should not use assign here - if generic user obj perms model is # used then everything could go fine if using assign shortcut and we # would not be able to see any problem ProjectGroupObjectPermission.objects.create( group=self.group, permission=perm, content_object=self.project, ) self.assertTrue(self.joe.has_perm('add_project', self.project)) def test_assign_perm(self): assign_perm('add_project', self.group, self.project) filters = { 'content_object': self.project, 'permission__codename': 'add_project', 'group': self.group, } result = ProjectGroupObjectPermission.objects.filter(**filters).count() self.assertEqual(result, 1) def test_remove_perm(self): assign_perm('add_project', self.group, self.project) filters = { 'content_object': self.project, 'permission__codename': 'add_project', 'group': self.group, } result = ProjectGroupObjectPermission.objects.filter(**filters).count() self.assertEqual(result, 1) remove_perm('add_project', self.group, self.project) result = ProjectGroupObjectPermission.objects.filter(**filters).count() self.assertEqual(result, 0) def test_get_groups_with_perms(self): Group.objects.create(name='managers') devs = Group.objects.create(name='devs') assign_perm('add_project', self.group, self.project) assign_perm('change_project', self.group, self.project) assign_perm('change_project', devs, self.project) self.assertEqual(get_groups_with_perms(self.project, attach_perms=True), { self.group: ['add_project', 'change_project'], devs: ['change_project'], }) def test_get_objects_for_group(self): foo = Project.objects.create(name='foo') bar = Project.objects.create(name='bar') assign_perm('add_project', self.group, foo) assign_perm('add_project', self.group, bar) assign_perm('change_project', self.group, bar) result = get_objects_for_group(self.group, 'testapp.add_project') self.assertEqual(sorted(p.pk for p in result), sorted([foo.pk, bar.pk])) @skipUnlessTestApp class TestMixedDirectAndGenericObjectPermission(TestCase): def setUp(self): self.joe = User.objects.create_user('joe', 'joe@example.com', 'foobar') self.group = Group.objects.create(name='admins') self.joe.groups.add(self.group) self.mixed = Mixed.objects.create(name='Foobar') def test_get_users_with_perms_plus_groups(self): User.objects.create_user('john', 'john@foobar.com', 'john') jane = User.objects.create_user('jane', 'jane@foobar.com', 'jane') group = Group.objects.create(name='devs') self.joe.groups.add(group) assign_perm('add_mixed', self.joe, self.mixed) assign_perm('change_mixed', group, self.mixed) assign_perm('change_mixed', jane, self.mixed) self.assertEqual(get_users_with_perms(self.mixed, attach_perms=True), { self.joe: ['add_mixed', 'change_mixed'], jane: ['change_mixed'], }) django-guardian-1.4.1/guardian/testapp/tests/test_forms.py0000600000175000017500000000212012643667664024141 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.test import TestCase from guardian.compat import get_user_model from guardian.forms import BaseObjectPermissionsForm class BaseObjectPermissionsFormTests(TestCase): def setUp(self): self.user = get_user_model().objects.create_user( 'joe', 'joe@example.com', 'joe') self.obj = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') def test_not_implemented(self): class MyUserObjectPermissionsForm(BaseObjectPermissionsForm): def __init__(formself, user, *args, **kwargs): self.user = user super(MyUserObjectPermissionsForm, formself).__init__(*args, **kwargs) form = MyUserObjectPermissionsForm(self.user, self.obj, {}) self.assertRaises(NotImplementedError, form.save_obj_perms) field_name = form.get_obj_perms_field_name() self.assertTrue(form.is_valid()) self.assertEqual(len(form.cleaned_data[field_name]), 0) django-guardian-1.4.1/guardian/testapp/tests/test_checks.py0000600000175000017500000000076112634712306024244 0ustar brianbrian00000000000000 from django.test import TestCase from guardian.checks import check_settings class SystemCheckTestCase(TestCase): def test_checks(self): """ Test custom system checks :return: None """ self.assertFalse(check_settings(None)) with self.settings( AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.ModelBackend',), ANONYMOUS_USER_ID=None, ): self.assertEqual(len(check_settings(None)), 2) django-guardian-1.4.1/guardian/testapp/tests/test_shortcuts.py0000600000175000017500000012017012643667664025057 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.contrib.auth.models import AnonymousUser from django.contrib.contenttypes.models import ContentType from django.db.models.query import QuerySet from django.test import TestCase from guardian.shortcuts import get_perms_for_model from guardian.core import ObjectPermissionChecker from guardian.compat import get_user_model from guardian.compat import get_user_permission_full_codename, get_model_name from guardian.shortcuts import assign from guardian.shortcuts import assign_perm from guardian.shortcuts import remove_perm from guardian.shortcuts import get_perms from guardian.shortcuts import get_users_with_perms from guardian.shortcuts import get_groups_with_perms from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_group from guardian.exceptions import MixedContentTypeError from guardian.exceptions import NotUserNorGroup from guardian.exceptions import WrongAppError from guardian.testapp.models import NonIntPKModel from guardian.testapp.tests.test_core import ObjectPermissionTestCase from guardian.models import Group, Permission import warnings User = get_user_model() user_app_label = User._meta.app_label user_module_name = get_model_name(User) class ShortcutsTests(ObjectPermissionTestCase): def test_get_perms_for_model(self): self.assertEqual(get_perms_for_model(self.user).count(), 3) self.assertTrue(list(get_perms_for_model(self.user)) == list(get_perms_for_model(User))) self.assertEqual(get_perms_for_model(Permission).count(), 3) model_str = 'contenttypes.ContentType' self.assertEqual( sorted(get_perms_for_model(model_str).values_list()), sorted(get_perms_for_model(ContentType).values_list())) obj = ContentType() self.assertEqual( sorted(get_perms_for_model(model_str).values_list()), sorted(get_perms_for_model(obj).values_list())) class AssignPermTest(ObjectPermissionTestCase): """ Tests permission assigning for user/group and object. """ def test_not_model(self): self.assertRaises(NotUserNorGroup, assign_perm, perm="change_object", user_or_group="Not a Model", obj=self.ctype) def test_global_wrong_perm(self): self.assertRaises(ValueError, assign_perm, perm="change_site", # for global permissions must provide app_label user_or_group=self.user) def test_user_assign_perm(self): assign_perm("change_contenttype", self.user, self.ctype) assign_perm("change_contenttype", self.group, self.ctype) self.assertTrue(self.user.has_perm("change_contenttype", self.ctype)) def test_group_assign_perm(self): assign_perm("change_contenttype", self.group, self.ctype) assign_perm("delete_contenttype", self.group, self.ctype) check = ObjectPermissionChecker(self.group) self.assertTrue(check.has_perm("change_contenttype", self.ctype)) self.assertTrue(check.has_perm("delete_contenttype", self.ctype)) def test_user_assign_perm_global(self): perm = assign_perm("contenttypes.change_contenttype", self.user) self.assertTrue(self.user.has_perm("contenttypes.change_contenttype")) self.assertTrue(isinstance(perm, Permission)) def test_group_assign_perm_global(self): perm = assign_perm("contenttypes.change_contenttype", self.group) self.assertTrue(self.user.has_perm("contenttypes.change_contenttype")) self.assertTrue(isinstance(perm, Permission)) def test_deprecation_warning(self): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter('always') assign("contenttypes.change_contenttype", self.group) self.assertEqual(len(warns), 1) self.assertTrue(isinstance(warns[0].message, DeprecationWarning)) class RemovePermTest(ObjectPermissionTestCase): """ Tests object permissions removal. """ def test_not_model(self): self.assertRaises(NotUserNorGroup, remove_perm, perm="change_object", user_or_group="Not a Model", obj=self.ctype) def test_global_wrong_perm(self): self.assertRaises(ValueError, remove_perm, perm="change_site", # for global permissions must provide app_label user_or_group=self.user) def test_user_remove_perm(self): # assign perm first assign_perm("change_contenttype", self.user, self.ctype) remove_perm("change_contenttype", self.user, self.ctype) self.assertFalse(self.user.has_perm("change_contenttype", self.ctype)) def test_group_remove_perm(self): # assign perm first assign_perm("change_contenttype", self.group, self.ctype) remove_perm("change_contenttype", self.group, self.ctype) check = ObjectPermissionChecker(self.group) self.assertFalse(check.has_perm("change_contenttype", self.ctype)) def test_user_remove_perm_global(self): # assign perm first perm = "contenttypes.change_contenttype" assign_perm(perm, self.user) remove_perm(perm, self.user) self.assertFalse(self.user.has_perm(perm)) def test_group_remove_perm_global(self): # assign perm first perm = "contenttypes.change_contenttype" assign_perm(perm, self.group) remove_perm(perm, self.group) app_label, codename = perm.split('.') perm_obj = Permission.objects.get(codename=codename, content_type__app_label=app_label) self.assertFalse(perm_obj in self.group.permissions.all()) class GetPermsTest(ObjectPermissionTestCase): """ Tests get_perms function (already done at core tests but left here as a placeholder). """ def test_not_model(self): self.assertRaises(NotUserNorGroup, get_perms, user_or_group=None, obj=self.ctype) def test_user(self): perms_to_assign = ("change_contenttype",) for perm in perms_to_assign: assign_perm("change_contenttype", self.user, self.ctype) perms = get_perms(self.user, self.ctype) for perm in perms_to_assign: self.assertTrue(perm in perms) class GetUsersWithPermsTest(TestCase): """ Tests get_users_with_perms function. """ def setUp(self): self.obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') self.obj2 = ContentType.objects.create(model='bar', app_label='guardian-tests') self.user1 = User.objects.create(username='user1') self.user2 = User.objects.create(username='user2') self.user3 = User.objects.create(username='user3') self.group1 = Group.objects.create(name='group1') self.group2 = Group.objects.create(name='group2') self.group3 = Group.objects.create(name='group3') def test_empty(self): result = get_users_with_perms(self.obj1) self.assertTrue(isinstance(result, QuerySet)) self.assertEqual(list(result), []) result = get_users_with_perms(self.obj1, attach_perms=True) self.assertTrue(isinstance(result, dict)) self.assertFalse(bool(result)) def test_simple(self): assign_perm("change_contenttype", self.user1, self.obj1) assign_perm("delete_contenttype", self.user2, self.obj1) assign_perm("delete_contenttype", self.user3, self.obj2) result = get_users_with_perms(self.obj1) result_vals = result.values_list('username', flat=True) self.assertEqual( set(result_vals), set([user.username for user in (self.user1, self.user2)]), ) def test_users_groups_perms(self): self.user1.groups.add(self.group1) self.user2.groups.add(self.group2) self.user3.groups.add(self.group3) assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.group2, self.obj1) assign_perm("delete_contenttype", self.group3, self.obj2) result = get_users_with_perms(self.obj1).values_list('pk', flat=True) self.assertEqual( set(result), set([u.pk for u in (self.user1, self.user2)]) ) def test_users_groups_after_removal(self): self.test_users_groups_perms() remove_perm("change_contenttype", self.group1, self.obj1) result = get_users_with_perms(self.obj1).values_list('pk', flat=True) self.assertEqual( set(result), set([self.user2.pk]), ) def test_attach_perms(self): self.user1.groups.add(self.group1) self.user2.groups.add(self.group2) self.user3.groups.add(self.group3) assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.group2, self.obj1) assign_perm("delete_contenttype", self.group3, self.obj2) assign_perm("delete_contenttype", self.user2, self.obj1) assign_perm("change_contenttype", self.user3, self.obj2) # Check contenttype1 result = get_users_with_perms(self.obj1, attach_perms=True) expected = { self.user1: ["change_contenttype"], self.user2: ["change_contenttype", "delete_contenttype"], } self.assertEqual(result.keys(), expected.keys()) for key, perms in result.items(): self.assertEqual(set(perms), set(expected[key])) # Check contenttype2 result = get_users_with_perms(self.obj2, attach_perms=True) expected = { self.user3: ["change_contenttype", "delete_contenttype"], } self.assertEqual(result.keys(), expected.keys()) for key, perms in result.items(): self.assertEqual(set(perms), set(expected[key])) def test_attach_groups_only_has_perms(self): self.user1.groups.add(self.group1) assign_perm("change_contenttype", self.group1, self.obj1) result = get_users_with_perms(self.obj1, attach_perms=True) expected = {self.user1: ["change_contenttype"]} self.assertEqual(result, expected) def test_mixed(self): self.user1.groups.add(self.group1) assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.user2, self.obj1) assign_perm("delete_contenttype", self.user2, self.obj1) assign_perm("delete_contenttype", self.user2, self.obj2) assign_perm("change_contenttype", self.user3, self.obj2) assign_perm("change_%s" % user_module_name, self.user3, self.user1) result = get_users_with_perms(self.obj1) self.assertEqual( set(result), set([self.user1, self.user2]), ) def test_with_superusers(self): admin = User.objects.create(username='admin', is_superuser=True) assign_perm("change_contenttype", self.user1, self.obj1) result = get_users_with_perms(self.obj1, with_superusers=True) self.assertEqual( set(result), set([self.user1, admin]), ) def test_without_group_users(self): self.user1.groups.add(self.group1) self.user2.groups.add(self.group2) assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.user2, self.obj1) assign_perm("change_contenttype", self.group2, self.obj1) result = get_users_with_perms(self.obj1, with_group_users=False) expected = set([self.user2]) self.assertEqual(set(result), expected) def test_without_group_users_but_perms_attached(self): self.user1.groups.add(self.group1) self.user2.groups.add(self.group2) assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.user2, self.obj1) assign_perm("change_contenttype", self.group2, self.obj1) result = get_users_with_perms(self.obj1, with_group_users=False, attach_perms=True) expected = {self.user2: ["change_contenttype"]} self.assertEqual(result, expected) def test_without_group_users_no_result(self): self.user1.groups.add(self.group1) assign_perm("change_contenttype", self.group1, self.obj1) result = get_users_with_perms(self.obj1, attach_perms=True, with_group_users=False) expected = {} self.assertEqual(result, expected) def test_without_group_users_no_result_but_with_superusers(self): admin = User.objects.create(username='admin', is_superuser=True) self.user1.groups.add(self.group1) assign_perm("change_contenttype", self.group1, self.obj1) result = get_users_with_perms(self.obj1, with_group_users=False, with_superusers=True) expected = [admin] self.assertEqual(set(result), set(expected)) class GetGroupsWithPerms(TestCase): """ Tests get_groups_with_perms function. """ def setUp(self): self.obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') self.obj2 = ContentType.objects.create(model='bar', app_label='guardian-tests') self.user1 = User.objects.create(username='user1') self.user2 = User.objects.create(username='user2') self.user3 = User.objects.create(username='user3') self.group1 = Group.objects.create(name='group1') self.group2 = Group.objects.create(name='group2') self.group3 = Group.objects.create(name='group3') def test_empty(self): result = get_groups_with_perms(self.obj1) self.assertTrue(isinstance(result, QuerySet)) self.assertFalse(bool(result)) result = get_groups_with_perms(self.obj1, attach_perms=True) self.assertTrue(isinstance(result, dict)) self.assertFalse(bool(result)) def test_simple(self): assign_perm("change_contenttype", self.group1, self.obj1) result = get_groups_with_perms(self.obj1) self.assertEqual(len(result), 1) self.assertEqual(result[0], self.group1) def test_simple_after_removal(self): self.test_simple() remove_perm("change_contenttype", self.group1, self.obj1) result = get_groups_with_perms(self.obj1) self.assertEqual(len(result), 0) def test_simple_attach_perms(self): assign_perm("change_contenttype", self.group1, self.obj1) result = get_groups_with_perms(self.obj1, attach_perms=True) expected = {self.group1: ["change_contenttype"]} self.assertEqual(result, expected) def test_simple_attach_perms_after_removal(self): self.test_simple_attach_perms() remove_perm("change_contenttype", self.group1, self.obj1) result = get_groups_with_perms(self.obj1, attach_perms=True) self.assertEqual(len(result), 0) def test_mixed(self): assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.group1, self.obj2) assign_perm("change_%s" % user_module_name, self.group1, self.user3) assign_perm("change_contenttype", self.group2, self.obj2) assign_perm("change_contenttype", self.group2, self.obj1) assign_perm("delete_contenttype", self.group2, self.obj1) assign_perm("change_%s" % user_module_name, self.group3, self.user1) result = get_groups_with_perms(self.obj1) self.assertEqual(set(result), set([self.group1, self.group2])) def test_mixed_attach_perms(self): assign_perm("change_contenttype", self.group1, self.obj1) assign_perm("change_contenttype", self.group1, self.obj2) assign_perm("change_group", self.group1, self.group3) assign_perm("change_contenttype", self.group2, self.obj2) assign_perm("change_contenttype", self.group2, self.obj1) assign_perm("delete_contenttype", self.group2, self.obj1) assign_perm("change_group", self.group3, self.group1) result = get_groups_with_perms(self.obj1, attach_perms=True) expected = { self.group1: ["change_contenttype"], self.group2: ["change_contenttype", "delete_contenttype"], } self.assertEqual(result.keys(), expected.keys()) for key, perms in result.items(): self.assertEqual(set(perms), set(expected[key])) class GetObjectsForUser(TestCase): def setUp(self): self.user = User.objects.create(username='joe') self.group = Group.objects.create(name='group') self.ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') def test_superuser(self): self.user.is_superuser = True ctypes = ContentType.objects.all() objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype'], ctypes) self.assertEqual(set(ctypes), set(objects)) def test_with_superuser_true(self): self.user.is_superuser = True ctypes = ContentType.objects.all() objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype'], ctypes, with_superuser=True) self.assertEqual(set(ctypes), set(objects)) def test_with_superuser_false(self): self.user.is_superuser = True ctypes = ContentType.objects.all() obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') assign_perm('change_contenttype', self.user, obj1) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype'], ctypes, with_superuser=False) self.assertEqual(set([obj1]), set(objects)) def test_anonymous(self): self.user = AnonymousUser() ctypes = ContentType.objects.all() objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype'], ctypes) obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') assign_perm('change_contenttype', self.user, obj1) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype'], ctypes) self.assertEqual(set([obj1]), set(objects)) def test_mixed_perms(self): codenames = [ get_user_permission_full_codename('change'), 'auth.change_permission', ] self.assertRaises(MixedContentTypeError, get_objects_for_user, self.user, codenames) def test_perms_with_mixed_apps(self): codenames = [ get_user_permission_full_codename('change'), 'contenttypes.change_contenttype', ] self.assertRaises(MixedContentTypeError, get_objects_for_user, self.user, codenames) def test_mixed_perms_and_klass(self): self.assertRaises(MixedContentTypeError, get_objects_for_user, self.user, ['auth.change_group'], User) def test_no_app_label_nor_klass(self): self.assertRaises(WrongAppError, get_objects_for_user, self.user, ['change_group']) def test_empty_perms_sequence(self): objects = get_objects_for_user(self.user, [], Group.objects.all()) self.assertEqual( set(objects), set() ) def test_perms_single(self): perm = 'auth.change_group' assign_perm(perm, self.user, self.group) self.assertEqual( set(get_objects_for_user(self.user, perm)), set(get_objects_for_user(self.user, [perm]))) def test_klass_as_model(self): assign_perm('contenttypes.change_contenttype', self.user, self.ctype) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype'], ContentType) self.assertEqual([obj.name for obj in objects], [self.ctype.name]) def test_klass_as_manager(self): assign_perm('auth.change_group', self.user, self.group) objects = get_objects_for_user(self.user, ['auth.change_group'], Group.objects) self.assertEqual([obj.name for obj in objects], [self.group.name]) def test_klass_as_queryset(self): assign_perm('auth.change_group', self.user, self.group) objects = get_objects_for_user(self.user, ['auth.change_group'], Group.objects.all()) self.assertEqual([obj.name for obj in objects], [self.group.name]) def test_ensure_returns_queryset(self): objects = get_objects_for_user(self.user, ['auth.change_group']) self.assertTrue(isinstance(objects, QuerySet)) def test_simple(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] for group in groups: assign_perm('change_group', self.user, group) objects = get_objects_for_user(self.user, ['auth.change_group']) self.assertEqual(len(objects), len(groups)) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects), set(groups)) def test_multiple_perms_to_check(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] for group in groups: assign_perm('auth.change_group', self.user, group) assign_perm('auth.delete_group', self.user, groups[1]) objects = get_objects_for_user(self.user, ['auth.change_group', 'auth.delete_group']) self.assertEqual(len(objects), 1) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects.values_list('name', flat=True)), set([groups[1].name])) def test_multiple_perms_to_check_no_groups(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] for group in groups: assign_perm('auth.change_group', self.user, group) assign_perm('auth.delete_group', self.user, groups[1]) objects = get_objects_for_user(self.user, ['auth.change_group', 'auth.delete_group'], use_groups=False) self.assertEqual(len(objects), 1) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects.values_list('name', flat=True)), set([groups[1].name])) def test_any_of_multiple_perms_to_check(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] assign_perm('auth.change_group', self.user, groups[0]) assign_perm('auth.delete_group', self.user, groups[2]) objects = get_objects_for_user(self.user, ['auth.change_group', 'auth.delete_group'], any_perm=True) self.assertEqual(len(objects), 2) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects.values_list('name', flat=True)), set([groups[0].name, groups[2].name])) def test_groups_perms(self): group1 = Group.objects.create(name='group1') group2 = Group.objects.create(name='group2') group3 = Group.objects.create(name='group3') groups = [group1, group2, group3] for group in groups: self.user.groups.add(group) # Objects to operate on ctypes = list(ContentType.objects.all().order_by('id')) assign_perm('auth.change_group', self.user) assign_perm('change_contenttype', self.user, ctypes[0]) assign_perm('change_contenttype', self.user, ctypes[1]) assign_perm('delete_contenttype', self.user, ctypes[1]) assign_perm('delete_contenttype', self.user, ctypes[2]) assign_perm('change_contenttype', groups[0], ctypes[3]) assign_perm('change_contenttype', groups[1], ctypes[3]) assign_perm('change_contenttype', groups[2], ctypes[4]) assign_perm('delete_contenttype', groups[0], ctypes[0]) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype']) self.assertEqual( set(objects.values_list('id', flat=True)), set(ctypes[i].id for i in [0, 1, 3, 4])) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype']) self.assertEqual( set(objects.values_list('id', flat=True)), set(ctypes[i].id for i in [0, 1])) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype']) self.assertEqual( set(objects.values_list('id', flat=True)), set(ctypes[i].id for i in [0, 1, 3, 4])) def test_has_global_permission_only(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] #global permission to change any group perm = 'auth.change_group' assign_perm(perm, self.user) objects = get_objects_for_user(self.user, perm) remove_perm(perm, self.user) self.assertEqual(set(objects), set(Group.objects.all())) def test_has_global_permission_and_object_based_permission(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] #global permission to change any group perm_global = 'auth.change_group' perm_obj = 'delete_group' assign_perm(perm_global, self.user) assign_perm(perm_obj, self.user, groups[0]) objects = get_objects_for_user(self.user, [perm_global, perm_obj]) remove_perm(perm_global, self.user) self.assertEqual(set(objects.values_list('name', flat=True)), set([groups[0].name])) def test_has_global_permission_and_object_based_permission_any_perm(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] #global permission to change any group perm_global = 'auth.change_group' #object based permission to change only a specific group perm_obj = 'auth.delete_group' assign_perm(perm_global, self.user) assign_perm(perm_obj, self.user, groups[0]) objects = get_objects_for_user(self.user, [perm_global, perm_obj], any_perm=True, accept_global_perms=True) remove_perm(perm_global, self.user) self.assertEqual(set(objects), set(Group.objects.all())) def test_object_based_permission_without_global_permission(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] #global permission to delete any group perm_global = 'auth.delete_group' perm_obj = 'auth.delete_group' assign_perm(perm_global, self.user) assign_perm(perm_obj, self.user, groups[0]) objects = get_objects_for_user(self.user, [perm_obj], accept_global_perms=False) remove_perm(perm_global, self.user) self.assertEqual(set(objects.values_list('name', flat=True)), set([groups[0].name])) def test_object_based_permission_with_groups_2perms(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] for group in groups: self.user.groups.add(group) # Objects to operate on ctypes = list(ContentType.objects.all().order_by('id')) assign_perm('contenttypes.change_contenttype', self.user) assign_perm('change_contenttype', self.user, ctypes[0]) assign_perm('change_contenttype', self.user, ctypes[1]) assign_perm('delete_contenttype', self.user, ctypes[1]) assign_perm('delete_contenttype', self.user, ctypes[2]) assign_perm('change_contenttype', groups[0], ctypes[3]) assign_perm('change_contenttype', groups[1], ctypes[3]) assign_perm('change_contenttype', groups[2], ctypes[4]) assign_perm('delete_contenttype', groups[0], ctypes[0]) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype'], accept_global_perms=True) self.assertEqual( set(objects.values_list('id', flat=True)), set([ctypes[0].id, ctypes[1].id, ctypes[2].id])) def test_object_based_permission_with_groups_3perms(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] for group in groups: self.user.groups.add(group) # Objects to operate on ctypes = list(ContentType.objects.all().order_by('id')) assign_perm('contenttypes.change_contenttype', self.user) assign_perm('change_contenttype', self.user, ctypes[0]) assign_perm('change_contenttype', self.user, ctypes[1]) assign_perm('delete_contenttype', self.user, ctypes[1]) assign_perm('delete_contenttype', self.user, ctypes[2]) # add_contenttype does not make sense, here just for testing purposes, to also cover one if branch in function. assign_perm('add_contenttype', self.user, ctypes[1]) assign_perm('change_contenttype', groups[0], ctypes[3]) assign_perm('change_contenttype', groups[1], ctypes[3]) assign_perm('change_contenttype', groups[2], ctypes[4]) assign_perm('delete_contenttype', groups[0], ctypes[0]) assign_perm('add_contenttype', groups[0], ctypes[0]) objects = get_objects_for_user(self.user, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype', 'contenttypes.add_contenttype'], accept_global_perms=True) self.assertEqual( set(objects.values_list('id', flat=True)), set([ctypes[0].id, ctypes[1].id])) def test_non_integer_primary_key(self): """ Verify that the function works when the objects that should be returned have non-integer primary keys. """ obj_with_char_pk = NonIntPKModel.objects.create(char_pk='testprimarykey') assign_perm('add_nonintpkmodel', self.user, obj_with_char_pk) objects = get_objects_for_user(self.user, 'testapp.add_nonintpkmodel') self.assertEqual(len(objects), 1) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects.values_list('pk', flat=True)), set([obj_with_char_pk.pk])) def test_non_integer_primary_key_with_any_perm(self): """ Verify that the function works with any_perm set to True when the objects that should be returned have non-integer primary keys. """ obj_with_char_pk = NonIntPKModel.objects.create(char_pk='testprimarykey') assign_perm('add_nonintpkmodel', self.user, obj_with_char_pk) objects = get_objects_for_user( self.user, ['testapp.add_nonintpkmodel', 'testapp.change_nonintpkmodel'], any_perm=True) self.assertEqual(len(objects), 1) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects.values_list('pk', flat=True)), set([obj_with_char_pk.pk])) def test_non_integer_primary_key_with_group_values(self): """ Verify that the function works when the objects that should be returned have non-integer primary keys, and those objects are due to the user's groups. """ obj_with_char_pk = NonIntPKModel.objects.create(char_pk='testprimarykey') assign_perm('add_nonintpkmodel', self.group, obj_with_char_pk) self.user.groups.add(self.group) objects = get_objects_for_user( self.user, ['testapp.add_nonintpkmodel', 'testapp.change_nonintpkmodel'], any_perm=True) self.assertEqual(len(objects), 1) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects.values_list('pk', flat=True)), set([obj_with_char_pk.pk])) def test_exception_different_ctypes(self): self.assertRaises(MixedContentTypeError, get_objects_for_user, self.user, ['auth.change_permission', 'auth.change_group']) def test_has_any_permissions(self): group_names = ['group1', 'group2', 'group3'] groups = [Group.objects.create(name=name) for name in group_names] for group in groups: assign_perm('change_group', self.user, group) objects = get_objects_for_user(self.user, [], Group) self.assertEqual(len(objects), len(groups)) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects), set(groups)) def test_short_codenames_with_klass(self): assign_perm('contenttypes.change_contenttype', self.user, self.ctype) objects = get_objects_for_user(self.user, ['change_contenttype'], ContentType) self.assertEqual([obj.name for obj in objects], [self.ctype.name]) class GetObjectsForGroup(TestCase): """ Tests get_objects_for_group function. """ def setUp(self): self.obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') self.obj2 = ContentType.objects.create(model='bar', app_label='guardian-tests') self.obj3 = ContentType.objects.create(model='baz', app_label='guardian-tests') self.user1 = User.objects.create(username='user1') self.user2 = User.objects.create(username='user2') self.user3 = User.objects.create(username='user3') self.group1 = Group.objects.create(name='group1') self.group2 = Group.objects.create(name='group2') self.group3 = Group.objects.create(name='group3') def test_mixed_perms(self): codenames = [ get_user_permission_full_codename('change'), 'auth.change_permission', ] self.assertRaises(MixedContentTypeError, get_objects_for_group, self.group1, codenames) def test_perms_with_mixed_apps(self): codenames = [ get_user_permission_full_codename('change'), 'contenttypes.contenttypes.change_contenttype', ] self.assertRaises(MixedContentTypeError, get_objects_for_group, self.group1, codenames) def test_mixed_perms_and_klass(self): self.assertRaises(MixedContentTypeError, get_objects_for_group, self.group1, ['auth.change_group'], User) def test_no_app_label_nor_klass(self): self.assertRaises(WrongAppError, get_objects_for_group, self.group1, ['change_contenttype']) def test_empty_perms_sequence(self): self.assertEqual( set(get_objects_for_group(self.group1, [], ContentType)), set() ) def test_perms_single(self): perm = 'contenttypes.change_contenttype' assign_perm(perm, self.group1, self.obj1) self.assertEqual( set(get_objects_for_group(self.group1, perm)), set(get_objects_for_group(self.group1, [perm])) ) def test_klass_as_model(self): assign_perm('contenttypes.change_contenttype', self.group1, self.obj1) objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype'], ContentType) self.assertEqual([obj.name for obj in objects], [self.obj1.name]) def test_klass_as_manager(self): assign_perm('contenttypes.change_contenttype', self.group1, self.obj1) objects = get_objects_for_group(self.group1, ['change_contenttype'], ContentType.objects) self.assertEqual(list(objects), [self.obj1]) def test_klass_as_queryset(self): assign_perm('contenttypes.change_contenttype', self.group1, self.obj1) objects = get_objects_for_group(self.group1, ['change_contenttype'], ContentType.objects.all()) self.assertEqual(list(objects), [self.obj1]) def test_ensure_returns_queryset(self): objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype']) self.assertTrue(isinstance(objects, QuerySet)) def test_simple(self): assign_perm('change_contenttype', self.group1, self.obj1) assign_perm('change_contenttype', self.group1, self.obj2) objects = get_objects_for_group(self.group1, 'contenttypes.change_contenttype') self.assertEqual(len(objects), 2) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual( set(objects), set([self.obj1, self.obj2])) def test_simple_after_removal(self): self.test_simple() remove_perm('change_contenttype', self.group1, self.obj1) objects = get_objects_for_group(self.group1, 'contenttypes.change_contenttype') self.assertEqual(len(objects), 1) self.assertEqual(objects[0], self.obj2) def test_multiple_perms_to_check(self): assign_perm('change_contenttype', self.group1, self.obj1) assign_perm('delete_contenttype', self.group1, self.obj1) assign_perm('change_contenttype', self.group1, self.obj2) objects = get_objects_for_group(self.group1, [ 'contenttypes.change_contenttype', 'contenttypes.delete_contenttype']) self.assertEqual(len(objects), 1) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual(objects[0], self.obj1) def test_any_of_multiple_perms_to_check(self): assign_perm('change_contenttype', self.group1, self.obj1) assign_perm('delete_contenttype', self.group1, self.obj1) assign_perm('add_contenttype', self.group1, self.obj2) assign_perm('delete_contenttype', self.group1, self.obj3) objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype'], any_perm=True) self.assertTrue(isinstance(objects, QuerySet)) self.assertEqual([obj for obj in objects.order_by('app_label')], [self.obj1, self.obj3]) def test_results_for_different_groups_are_correct(self): assign_perm('change_contenttype', self.group1, self.obj1) assign_perm('delete_contenttype', self.group2, self.obj2) self.assertEqual(set(get_objects_for_group(self.group1, 'contenttypes.change_contenttype')), set([self.obj1])) self.assertEqual(set(get_objects_for_group(self.group2, 'contenttypes.change_contenttype')), set()) self.assertEqual(set(get_objects_for_group(self.group2, 'contenttypes.delete_contenttype')), set([self.obj2])) def test_has_global_permission(self): assign_perm('contenttypes.change_contenttype', self.group1) objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype']) self.assertEquals(set(objects), set(ContentType.objects.all())) def test_has_global_permission_and_object_based_permission(self): assign_perm('contenttypes.change_contenttype', self.group1) assign_perm('contenttypes.delete_contenttype', self.group1, self.obj1) objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype'], any_perm=False) self.assertEquals(set(objects), set([self.obj1])) def test_has_global_permission_and_object_based_permission_any_perm(self): assign_perm('contenttypes.change_contenttype', self.group1) assign_perm('contenttypes.delete_contenttype', self.group1, self.obj1) objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype'], any_perm=True) self.assertEquals(set(objects), set(ContentType.objects.all())) def test_has_global_permission_and_object_based_permission_3perms(self): assign_perm('contenttypes.change_contenttype', self.group1) assign_perm('contenttypes.delete_contenttype', self.group1, self.obj1) assign_perm('contenttypes.add_contenttype', self.group1, self.obj2) objects = get_objects_for_group(self.group1, ['contenttypes.change_contenttype', 'contenttypes.delete_contenttype', 'contenttypes.add_contenttype'], any_perm=False) self.assertEquals(set(objects), set()) def test_exception_different_ctypes(self): self.assertRaises(MixedContentTypeError, get_objects_for_group, self.group1, ['auth.change_permission', 'auth.change_group']) django-guardian-1.4.1/guardian/testapp/tests/test_decorators.py0000600000175000017500000003306212643667664025171 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.conf import settings, global_settings from django.contrib.auth.models import Group, AnonymousUser from django.core.exceptions import PermissionDenied from django.db.models.base import ModelBase from django.http import HttpRequest from django.http import HttpResponse from django.http import HttpResponseForbidden from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.template import TemplateDoesNotExist from django.test import TestCase from guardian.compat import get_user_model from guardian.compat import get_user_model_path from guardian.compat import get_user_permission_full_codename from guardian.compat import mock from guardian.decorators import permission_required, permission_required_or_403 from guardian.exceptions import GuardianError from guardian.exceptions import WrongAppError from guardian.shortcuts import assign_perm from guardian.testapp.tests.conf import TEST_SETTINGS from guardian.testapp.tests.conf import TestDataMixin from guardian.testapp.tests.conf import override_settings from guardian.testapp.tests.conf import skipUnlessTestApp from django import get_version as django_get_version User = get_user_model() user_model_path = get_user_model_path() @override_settings(**TEST_SETTINGS) @skipUnlessTestApp class PermissionRequiredTest(TestDataMixin, TestCase): def setUp(self): super(PermissionRequiredTest, self).setUp() self.anon = AnonymousUser() self.user = User.objects.get_or_create(username='jack')[0] self.group = Group.objects.get_or_create(name='jackGroup')[0] def _get_request(self, user=None): if user is None: user = AnonymousUser() request = HttpRequest() request.user = user return request def test_no_args(self): try: @permission_required def dummy_view(request): return HttpResponse('dummy_view') except GuardianError: pass else: self.fail("Trying to decorate using permission_required without " "permission as first argument should raise exception") def test_RENDER_403_is_false(self): request = self._get_request(self.anon) @permission_required_or_403('not_installed_app.change_user') def dummy_view(request): return HttpResponse('dummy_view') with mock.patch('guardian.conf.settings.RENDER_403', False): response = dummy_view(request) self.assertEqual(response.content, b'') self.assertTrue(isinstance(response, HttpResponseForbidden)) @mock.patch('guardian.conf.settings.RENDER_403', True) def test_TEMPLATE_403_setting(self): request = self._get_request(self.anon) @permission_required_or_403('not_installed_app.change_user') def dummy_view(request): return HttpResponse('dummy_view') with mock.patch('guardian.conf.settings.TEMPLATE_403', 'dummy403.html'): response = dummy_view(request) self.assertEqual(response.content, b'foobar403\n') @mock.patch('guardian.conf.settings.RENDER_403', True) def test_403_response_is_empty_if_template_cannot_be_found(self): request = self._get_request(self.anon) @permission_required_or_403('not_installed_app.change_user') def dummy_view(request): return HttpResponse('dummy_view') with mock.patch('guardian.conf.settings.TEMPLATE_403', '_non-exisitng-403.html'): response = dummy_view(request) self.assertEqual(response.status_code, 403) self.assertEqual(response.content, b'') @mock.patch('guardian.conf.settings.RENDER_403', True) def test_403_response_raises_error_if_debug_is_turned_on(self): org_DEBUG = settings.DEBUG settings.DEBUG = True request = self._get_request(self.anon) @permission_required_or_403('not_installed_app.change_user') def dummy_view(request): return HttpResponse('dummy_view') with mock.patch('guardian.conf.settings.TEMPLATE_403', '_non-exisitng-403.html'): self.assertRaises(TemplateDoesNotExist, dummy_view, request) settings.DEBUG = org_DEBUG @mock.patch('guardian.conf.settings.RENDER_403', False) @mock.patch('guardian.conf.settings.RAISE_403', True) def test_RAISE_403_setting_is_true(self): request = self._get_request(self.anon) @permission_required_or_403('not_installed_app.change_user') def dummy_view(request): return HttpResponse('dummy_view') self.assertRaises(PermissionDenied, dummy_view, request) def test_anonymous_user_wrong_app(self): request = self._get_request(self.anon) @permission_required_or_403('not_installed_app.change_user') def dummy_view(request): return HttpResponse('dummy_view') self.assertEqual(dummy_view(request).status_code, 403) def test_anonymous_user_wrong_codename(self): request = self._get_request() @permission_required_or_403('auth.wrong_codename') def dummy_view(request): return HttpResponse('dummy_view') self.assertEqual(dummy_view(request).status_code, 403) def test_anonymous_user(self): request = self._get_request() @permission_required_or_403('auth.change_user') def dummy_view(request): return HttpResponse('dummy_view') self.assertEqual(dummy_view(request).status_code, 403) def test_wrong_lookup_variables_number(self): request = self._get_request() try: @permission_required_or_403('auth.change_user', (User, 'username')) def dummy_view(request, username): pass dummy_view(request, username='jack') except GuardianError: pass else: self.fail("If lookup variables are passed they must be tuple of: " "(ModelClass/app_label.ModelClass/queryset, " ")\n" "Otherwise GuardianError should be raised") def test_wrong_lookup_variables(self): request = self._get_request() args = ( (2010, 'username', 'username'), ('User', 'username', 'username'), (User, 'username', 'no_arg'), ) for tup in args: try: @permission_required_or_403('auth.change_user', tup) def show_user(request, username): user = get_object_or_404(User, username=username) return HttpResponse("It's %s here!" % user.username) show_user(request, 'jack') except GuardianError: pass else: self.fail("Wrong arguments given but GuardianError not raised") def test_user_has_no_access(self): request = self._get_request() @permission_required_or_403('auth.change_user') def dummy_view(request): return HttpResponse('dummy_view') self.assertEqual(dummy_view(request).status_code, 403) def test_user_has_access(self): perm = get_user_permission_full_codename('change') joe, created = User.objects.get_or_create(username='joe') assign_perm(perm, self.user, obj=joe) request = self._get_request(self.user) @permission_required_or_403(perm, ( user_model_path, 'username', 'username')) def dummy_view(request, username): return HttpResponse('dummy_view') response = dummy_view(request, username='joe') self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'dummy_view') def test_user_has_access_on_model_with_metaclass(self): """ Test to the fix issues of comparaison made via type() in the decorator. In the case of a `Model` implementing a custom metaclass, the decorator fail because type doesn't return `ModelBase` """ perm = get_user_permission_full_codename('change') class TestMeta(ModelBase): pass class ProxyUser(User): class Meta: proxy = True app_label = User._meta.app_label __metaclass__ = TestMeta joe, created = ProxyUser.objects.get_or_create(username='joe') assign_perm(perm, self.user, obj=joe) request = self._get_request(self.user) @permission_required_or_403(perm, ( ProxyUser, 'username', 'username')) def dummy_view(request, username): return HttpResponse('dummy_view') response = dummy_view(request, username='joe') self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'dummy_view') def test_user_has_obj_access_even_if_we_also_check_for_global(self): perm = get_user_permission_full_codename('change') joe, created = User.objects.get_or_create(username='joe') assign_perm(perm, self.user, obj=joe) request = self._get_request(self.user) @permission_required_or_403(perm, ( user_model_path, 'username', 'username'), accept_global_perms=True) def dummy_view(request, username): return HttpResponse('dummy_view') response = dummy_view(request, username='joe') self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'dummy_view') def test_user_has_no_obj_perm_access(self): perm = get_user_permission_full_codename('change') joe, created = User.objects.get_or_create(username='joe') request = self._get_request(self.user) @permission_required_or_403(perm, ( user_model_path, 'username', 'username')) def dummy_view(request, username): return HttpResponse('dummy_view') response = dummy_view(request, username='joe') self.assertEqual(response.status_code, 403) def test_user_has_global_perm_access_but_flag_not_set(self): perm = get_user_permission_full_codename('change') joe, created = User.objects.get_or_create(username='joe') assign_perm(perm, self.user) request = self._get_request(self.user) @permission_required_or_403(perm, ( user_model_path, 'username', 'username')) def dummy_view(request, username): return HttpResponse('dummy_view') response = dummy_view(request, username='joe') self.assertEqual(response.status_code, 403) def test_user_has_global_perm_access(self): perm = get_user_permission_full_codename('change') joe, created = User.objects.get_or_create(username='joe') assign_perm(perm, self.user) request = self._get_request(self.user) @permission_required_or_403(perm, ( user_model_path, 'username', 'username'), accept_global_perms=True) def dummy_view(request, username): return HttpResponse('dummy_view') response = dummy_view(request, username='joe') self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'dummy_view') def test_model_lookup(self): request = self._get_request(self.user) perm = get_user_permission_full_codename('change') joe, created = User.objects.get_or_create(username='joe') assign_perm(perm, self.user, obj=joe) models = ( user_model_path, User, User.objects.filter(is_active=True), ) for model in models: @permission_required_or_403(perm, (model, 'username', 'username')) def dummy_view(request, username): get_object_or_404(User, username=username) return HttpResponse('hello') response = dummy_view(request, username=joe.username) self.assertEqual(response.content, b'hello') def test_redirection_raises_wrong_app_error(self): from guardian.testapp.models import Project request = self._get_request(self.user) User.objects.create(username='foo') Project.objects.create(name='foobar') @permission_required('auth.change_group', (Project, 'name', 'group_name'), login_url='/foobar/') def dummy_view(request, project_name): pass # 'auth.change_group' is wrong permission codename (should be one # related with User self.assertRaises(WrongAppError, dummy_view, request, group_name='foobar') def test_redirection(self): from guardian.testapp.models import Project request = self._get_request(self.user) User.objects.create(username='foo') Project.objects.create(name='foobar') @permission_required('testapp.change_project', (Project, 'name', 'project_name'), login_url='/foobar/') def dummy_view(request, project_name): pass response = dummy_view(request, project_name='foobar') self.assertTrue(isinstance(response, HttpResponseRedirect)) self.assertTrue(response._headers['location'][1].startswith( '/foobar/')) @override_settings(LOGIN_URL='django.contrib.auth.views.login') def test_redirection_class(self): view_url = '/permission_required/' if django_get_version() < "1.5": # skip this test for django versions < 1.5 return response = self.client.get(view_url) # this should be '/account/login' self.assertRedirects(response, global_settings.LOGIN_URL + "?next=" + view_url) django-guardian-1.4.1/guardian/testapp/tests/__init__.py0000600000175000017500000000005012634712306023473 0ustar brianbrian00000000000000from __future__ import unicode_literals django-guardian-1.4.1/guardian/testapp/tests/test_mixins.py0000600000175000017500000001252312643667664024332 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.contrib.auth.models import AnonymousUser from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import PermissionDenied from django.http import HttpResponse from django.test import TestCase from django.test.client import RequestFactory from django.views.generic import View from guardian.compat import get_user_model from guardian.compat import mock from guardian.mixins import LoginRequiredMixin from guardian.mixins import PermissionRequiredMixin from ..models import Post class DatabaseRemovedError(Exception): pass class RemoveDatabaseView(View): def get(self, request, *args, **kwargs): raise DatabaseRemovedError("You've just allowed db to be removed!") class TestView(PermissionRequiredMixin, RemoveDatabaseView): permission_required = 'testapp.change_post' object = None # should be set at each tests explicitly class NoObjectView(PermissionRequiredMixin, RemoveDatabaseView): permission_required = 'testapp.change_post' class TestViewMixins(TestCase): def setUp(self): self.post = Post.objects.create(title='foo') self.factory = RequestFactory() self.user = get_user_model().objects.create_user( 'joe', 'joe@doe.com', 'doe') self.client.login(username='joe', password='doe') def test_permission_is_checked_before_view_is_computed(self): """ This test would fail if permission is checked **after** view is actually resolved. """ request = self.factory.get('/') request.user = self.user # View.object is set view = TestView.as_view(object=self.post) response = view(request) self.assertEqual(response.status_code, 302) # View.get_object returns object TestView.get_object = lambda instance: self.post view = TestView.as_view() response = view(request) self.assertEqual(response.status_code, 302) del TestView.get_object def test_permission_is_checked_before_view_is_computed_perm_denied_raised(self): """ This test would fail if permission is checked **after** view is actually resolved. """ request = self.factory.get('/') request.user = self.user view = TestView.as_view(raise_exception=True, object=self.post) with self.assertRaises(PermissionDenied): view(request) def test_permission_required_view_configured_wrongly(self): """ This test would fail if permission is checked **after** view is actually resolved. """ request = self.factory.get('/') request.user = self.user request.user.add_obj_perm('change_post', self.post) view = TestView.as_view(permission_required=None, object=self.post) with self.assertRaises(ImproperlyConfigured): view(request) def test_permission_required(self): """ This test would fail if permission is checked **after** view is actually resolved. """ request = self.factory.get('/') request.user = self.user request.user.add_obj_perm('change_post', self.post) view = TestView.as_view(object=self.post) with self.assertRaises(DatabaseRemovedError): view(request) def test_permission_required_no_object(self): """ This test would fail if permission is checked on a view's object when it has none """ request = self.factory.get('/') request.user = self.user request.user.add_obj_perm('change_post', self.post) view = NoObjectView.as_view() response = view(request) self.assertEqual(response.status_code, 302) def test_permission_required_as_list(self): """ This test would fail if permission is checked **after** view is actually resolved. """ global TestView class SecretView(TestView): on_permission_check_fail = mock.Mock() request = self.factory.get('/') request.user = self.user request.user.add_obj_perm('change_post', self.post) SecretView.permission_required = ['testapp.change_post', 'testapp.add_post'] view = SecretView.as_view(object=self.post) response = view(request) self.assertEqual(response.status_code, 302) SecretView.on_permission_check_fail.assert_called_once_with(request, response, obj=self.post) request.user.add_obj_perm('add_post', self.post) with self.assertRaises(DatabaseRemovedError): view(request) def test_login_required_mixin(self): class SecretView(LoginRequiredMixin, View): redirect_field_name = 'foobar' login_url = '/let-me-in/' def get(self, request): return HttpResponse('secret-view') request = self.factory.get('/some-secret-page/') request.user = AnonymousUser() view = SecretView.as_view() response = view(request) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], '/let-me-in/?foobar=/some-secret-page/') request.user = self.user response = view(request) self.assertEqual(response.status_code, 200) self.assertEqual(response.content, b'secret-view') django-guardian-1.4.1/guardian/testapp/tests/conf.py0000600000175000017500000000566612634712306022703 0ustar brianbrian00000000000000from __future__ import unicode_literals import os from guardian.compat import unittest from guardian.utils import abspath from django.conf import settings from django.conf import UserSettingsHolder from django.utils.functional import wraps THIS = abspath(os.path.dirname(__file__)) TEST_TEMPLATES_DIR = abspath(THIS, 'templates') TEST_SETTINGS = dict( TEMPLATE_DIRS=[TEST_TEMPLATES_DIR], ) def skipUnlessTestApp(obj): app = 'guardian.testapp' return unittest.skipUnless(app in settings.INSTALLED_APPS, 'app %r must be installed to run this test' % app)(obj) class TestDataMixin(object): def setUp(self): super(TestDataMixin, self).setUp() from django.contrib.auth.models import Group try: from django.contrib.auth import get_user_model User = get_user_model() except ImportError: from django.contrib.auth.models import User Group.objects.create(pk=1, name='admins') jack_group = Group.objects.create(pk=2, name='jackGroup') User.objects.get_or_create(pk=settings.ANONYMOUS_USER_ID) jack = User.objects.create(pk=1, username='jack', is_active=True, is_superuser=False, is_staff=False) jack.groups.add(jack_group) class override_settings(object): """ Acts as either a decorator, or a context manager. If it's a decorator it takes a function and returns a wrapped function. If it's a contextmanager it's used with the ``with`` statement. In either event entering/exiting are called before and after, respectively, the function/block is executed. """ def __init__(self, **kwargs): self.options = kwargs self.wrapped = settings._wrapped def __enter__(self): self.enable() def __exit__(self, exc_type, exc_value, traceback): self.disable() def __call__(self, test_func): from django.test import TransactionTestCase if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase): original_pre_setup = test_func._pre_setup original_post_teardown = test_func._post_teardown def _pre_setup(innerself): self.enable() original_pre_setup(innerself) def _post_teardown(innerself): original_post_teardown(innerself) self.disable() test_func._pre_setup = _pre_setup test_func._post_teardown = _post_teardown return test_func else: @wraps(test_func) def inner(*args, **kwargs): with self: return test_func(*args, **kwargs) return inner def enable(self): override = UserSettingsHolder(settings._wrapped) for key, new_value in self.options.items(): setattr(override, key, new_value) settings._wrapped = override def disable(self): settings._wrapped = self.wrapped django-guardian-1.4.1/guardian/testapp/tests/test_management.py0000600000175000017500000000231512634712306025115 0ustar brianbrian00000000000000from __future__ import absolute_import from __future__ import unicode_literals from guardian.compat import get_user_model from guardian.compat import mock from guardian.compat import unittest from guardian.management import create_anonymous_user from guardian.utils import get_anonymous_user mocked_get_init_anon = mock.Mock() class TestGetAnonymousUser(unittest.TestCase): @mock.patch('guardian.management.guardian_settings') def test_uses_custom_function(self, guardian_settings): mocked_get_init_anon.reset_mock() path = 'guardian.testapp.tests.test_management.mocked_get_init_anon' guardian_settings.GET_INIT_ANONYMOUS_USER = path guardian_settings.ANONYMOUS_USER_ID = 219 User = get_user_model() anon = mocked_get_init_anon.return_value = mock.Mock() create_anonymous_user('sender') mocked_get_init_anon.assert_called_once_with(User) self.assertEqual(anon.pk, 219) anon.save.assert_called_once_with() def test_get_anonymous_user(self): anon = get_anonymous_user() self.assertFalse(anon.has_usable_password()) self.assertEqual(anon.username, "AnonymousUser") self.assertEqual(anon.pk, -1) django-guardian-1.4.1/guardian/testapp/tests/templates/0000700000175000017500000000000012644306605023365 5ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/tests/templates/blank.html0000600000175000017500000000000112634712306025331 0ustar brianbrian00000000000000 django-guardian-1.4.1/guardian/testapp/tests/templates/dummy403.html0000600000175000017500000000001212634712306025626 0ustar brianbrian00000000000000foobar403 django-guardian-1.4.1/guardian/testapp/tests/templates/500.html0000600000175000017500000000000012634712306024545 0ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/tests/templates/404.html0000600000175000017500000000000012634712306024550 0ustar brianbrian00000000000000django-guardian-1.4.1/guardian/testapp/tests/test_tags.py0000600000175000017500000001255412643667664023765 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.conf import settings from django.contrib.auth.models import Group, AnonymousUser from django.contrib.contenttypes.models import ContentType from django.template import Template, Context, TemplateSyntaxError from django.test import TestCase from guardian.compat import get_user_model from guardian.exceptions import NotUserNorGroup from guardian.models import UserObjectPermission, GroupObjectPermission User = get_user_model() def render(template, context): """ Returns rendered ``template`` with ``context``, which are given as string and dict respectively. """ t = Template(template) return t.render(Context(context)) class GetObjPermsTagTest(TestCase): def setUp(self): self.ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') self.group = Group.objects.create(name='jackGroup') self.user = User.objects.create(username='jack') self.user.groups.add(self.group) def test_wrong_formats(self): wrong_formats = ( '{% get_obj_perms user for contenttype as obj_perms %}', # no quotes '{% get_obj_perms user for contenttype as \'obj_perms" %}', # wrong quotes '{% get_obj_perms user for contenttype as \'obj_perms" %}', # wrong quotes '{% get_obj_perms user for contenttype as obj_perms" %}', # wrong quotes '{% get_obj_perms user for contenttype as obj_perms\' %}', # wrong quotes '{% get_obj_perms user for contenttype as %}', # no context_var '{% get_obj_perms for contenttype as "obj_perms" %}', # no user/group '{% get_obj_perms user contenttype as "obj_perms" %}', # no "for" bit '{% get_obj_perms user for contenttype "obj_perms" %}', # no "as" bit '{% get_obj_perms user for as "obj_perms" %}', # no object ) context = {'user': User.get_anonymous(), 'contenttype': self.ctype} for wrong in wrong_formats: fullwrong = '{% load guardian_tags %}' + wrong try: render(fullwrong, context) self.fail("Used wrong get_obj_perms tag format: \n\n\t%s\n\n " "but TemplateSyntaxError have not been raised" % wrong) except TemplateSyntaxError: pass def test_obj_none(self): template = ''.join(( '{% load guardian_tags %}', '{% get_obj_perms user for object as "obj_perms" %}{{ perms }}', )) context = {'user': User.get_anonymous(), 'object': None} output = render(template, context) self.assertEqual(output, '') def test_anonymous_user(self): template = ''.join(( '{% load guardian_tags %}', '{% get_obj_perms user for contenttype as "obj_perms" %}{{ perms }}', )) context = {'user': AnonymousUser(), 'contenttype': self.ctype} anon_output = render(template, context) context = {'user': User.get_anonymous(), 'contenttype': self.ctype} real_anon_user_output = render(template, context) self.assertEqual(anon_output, real_anon_user_output) def test_wrong_user_or_group(self): template = ''.join(( '{% load guardian_tags %}', '{% get_obj_perms some_obj for contenttype as "obj_perms" %}', )) context = {'some_obj': ContentType(), 'contenttype': self.ctype} # This test would raise TemplateSyntaxError instead of NotUserNorGroup # if TEMPLATE_DEBUG is set to True during tests tmp = settings.TEMPLATE_DEBUG settings.TEMPLATE_DEBUG = False self.assertRaises(NotUserNorGroup, render, template, context) settings.TEMPLATE_DEBUG = tmp def test_superuser(self): user = User.objects.create(username='superuser', is_superuser=True) template = ''.join(( '{% load guardian_tags %}', '{% get_obj_perms user for contenttype as "obj_perms" %}', '{{ obj_perms|join:" " }}', )) context = {'user': user, 'contenttype': self.ctype} output = render(template, context) for perm in ('add_contenttype', 'change_contenttype', 'delete_contenttype'): self.assertTrue(perm in output) def test_user(self): UserObjectPermission.objects.assign_perm("change_contenttype", self.user, self.ctype) GroupObjectPermission.objects.assign_perm("delete_contenttype", self.group, self.ctype) template = ''.join(( '{% load guardian_tags %}', '{% get_obj_perms user for contenttype as "obj_perms" %}', '{{ obj_perms|join:" " }}', )) context = {'user': self.user, 'contenttype': self.ctype} output = render(template, context) self.assertEqual( set(output.split(' ')), set('change_contenttype delete_contenttype'.split(' '))) def test_group(self): GroupObjectPermission.objects.assign_perm("delete_contenttype", self.group, self.ctype) template = ''.join(( '{% load guardian_tags %}', '{% get_obj_perms group for contenttype as "obj_perms" %}', '{{ obj_perms|join:" " }}', )) context = {'group': self.group, 'contenttype': self.ctype} output = render(template, context) self.assertEqual(output, 'delete_contenttype') django-guardian-1.4.1/guardian/testapp/tests/test_core.py0000600000175000017500000001707212643667664023757 0ustar brianbrian00000000000000from __future__ import unicode_literals from itertools import chain from django.conf import settings # Try the new app settings (Django 1.7) and fall back to the old system try: from django.apps import apps as django_apps auth_app = django_apps.get_app_config("auth") except ImportError: from django.contrib.auth import models as auth_app from django.contrib.auth.models import Group, Permission, AnonymousUser from django.contrib.contenttypes.models import ContentType from django.test import TestCase from guardian.core import ObjectPermissionChecker from guardian.compat import get_user_model, create_permissions from guardian.exceptions import NotUserNorGroup from guardian.models import UserObjectPermission, GroupObjectPermission from guardian.shortcuts import assign_perm from guardian.management import create_anonymous_user User = get_user_model() class CustomUserTests(TestCase): def test_create_anonymous_user(self): create_anonymous_user(object()) self.assertEqual(1, User.objects.all().count()) anonymous = User.objects.all()[0] self.assertEqual(anonymous.pk, settings.ANONYMOUS_USER_ID) class ObjectPermissionTestCase(TestCase): def setUp(self): self.group, created = Group.objects.get_or_create(name='jackGroup') self.user, created = User.objects.get_or_create(username='jack') self.user.groups.add(self.group) self.ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') try: self.anonymous_user = User.objects.get(pk=settings.ANONYMOUS_USER_ID) except User.DoesNotExist: self.anonymous_user = User( id=settings.ANONYMOUS_USER_ID, username='AnonymousUser', ) self.anonymous_user.save() class ObjectPermissionCheckerTest(ObjectPermissionTestCase): def setUp(self): super(ObjectPermissionCheckerTest, self).setUp() # Required if MySQL backend is used :/ create_permissions(auth_app, [], 1) def test_cache_for_queries_count(self): settings.DEBUG = True try: from django.db import connection ContentType.objects.clear_cache() checker = ObjectPermissionChecker(self.user) # has_perm on Checker should spawn only two queries plus one extra # for fetching the content type first time we check for specific # model and two more content types as there are additional checks # at get_user_obj_perms_model and get_group_obj_perms_model query_count = len(connection.queries) res = checker.has_perm("change_group", self.group) if 'guardian.testapp' in settings.INSTALLED_APPS: expected = 5 else: # TODO: This is strange, need to investigate; totally not sure # why there are more queries if testapp is not included expected = 11 self.assertEqual(len(connection.queries), query_count + expected) # Checking again shouldn't spawn any queries query_count = len(connection.queries) res_new = checker.has_perm("change_group", self.group) self.assertEqual(res, res_new) self.assertEqual(len(connection.queries), query_count) # Checking for other permission but for Group object again # shouldn't spawn any query too query_count = len(connection.queries) checker.has_perm("delete_group", self.group) self.assertEqual(len(connection.queries), query_count) # Checking for same model but other instance should spawn 2 queries new_group = Group.objects.create(name='new-group') query_count = len(connection.queries) checker.has_perm("change_group", new_group) self.assertEqual(len(connection.queries), query_count + 2) # Checking for permission for other model should spawn 3 queries # (again: content type and actual permissions for the object... query_count = len(connection.queries) checker.has_perm("change_user", self.user) self.assertEqual(len(connection.queries), query_count + 3) finally: settings.DEBUG = False def test_init(self): self.assertRaises(NotUserNorGroup, ObjectPermissionChecker, user_or_group=ContentType()) self.assertRaises(NotUserNorGroup, ObjectPermissionChecker) def test_anonymous_user(self): user = AnonymousUser() check = ObjectPermissionChecker(user) # assert anonymous user has no object permissions at all for obj self.assertTrue( [] == list(check.get_perms(self.ctype)) ) def test_superuser(self): user = User.objects.create(username='superuser', is_superuser=True) check = ObjectPermissionChecker(user) ctype = ContentType.objects.get_for_model(self.ctype) perms = sorted(chain(*Permission.objects .filter(content_type=ctype) .values_list('codename'))) self.assertEqual(perms, check.get_perms(self.ctype)) for perm in perms: self.assertTrue(check.has_perm(perm, self.ctype)) def test_not_active_superuser(self): user = User.objects.create(username='not_active_superuser', is_superuser=True, is_active=False) check = ObjectPermissionChecker(user) ctype = ContentType.objects.get_for_model(self.ctype) perms = sorted(chain(*Permission.objects .filter(content_type=ctype) .values_list('codename'))) self.assertEqual(check.get_perms(self.ctype), []) for perm in perms: self.assertFalse(check.has_perm(perm, self.ctype)) def test_not_active_user(self): user = User.objects.create(username='notactive') assign_perm("change_contenttype", user, self.ctype) # new ObjectPermissionChecker is created for each User.has_perm call self.assertTrue(user.has_perm("change_contenttype", self.ctype)) user.is_active = False self.assertFalse(user.has_perm("change_contenttype", self.ctype)) # use on one checker only (as user's is_active attr should be checked # before try to use cache user = User.objects.create(username='notactive-cache') assign_perm("change_contenttype", user, self.ctype) check = ObjectPermissionChecker(user) self.assertTrue(check.has_perm("change_contenttype", self.ctype)) user.is_active = False self.assertFalse(check.has_perm("change_contenttype", self.ctype)) def test_get_perms(self): group = Group.objects.create(name='group') obj1 = ContentType.objects.create(model='foo', app_label='guardian-tests') obj2 = ContentType.objects.create(model='bar', app_label='guardian-tests') assign_perms = { group: ('change_group', 'delete_group'), obj1: ('change_contenttype', 'delete_contenttype'), obj2: ('delete_contenttype',), } check = ObjectPermissionChecker(self.user) for obj, perms in assign_perms.items(): for perm in perms: UserObjectPermission.objects.assign_perm(perm, self.user, obj) self.assertEqual(sorted(perms), sorted(check.get_perms(obj))) check = ObjectPermissionChecker(self.group) for obj, perms in assign_perms.items(): for perm in perms: GroupObjectPermission.objects.assign_perm(perm, self.group, obj) self.assertEqual(sorted(perms), sorted(check.get_perms(obj))) django-guardian-1.4.1/guardian/testapp/tests/test_conf.py0000600000175000017500000000105012634712306023721 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from guardian.compat import mock from guardian.conf import settings as guardian_settings class TestConfiguration(TestCase): def test_check_configuration(self): with mock.patch('guardian.conf.settings.RENDER_403', True): with mock.patch('guardian.conf.settings.RAISE_403', True): self.assertRaises(ImproperlyConfigured, guardian_settings.check_configuration) django-guardian-1.4.1/guardian/testapp/tests/test_admin.py0000600000175000017500000004715712643667664024126 0ustar brianbrian00000000000000from __future__ import unicode_literals import copy from django import forms from django.conf import settings from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.http import HttpRequest from django.test import TestCase from django.test.client import Client from guardian.admin import GuardedModelAdmin from guardian.compat import get_user_model, get_model_name from guardian.compat import str from guardian.shortcuts import get_perms from guardian.shortcuts import get_perms_for_model from guardian.testapp.tests.conf import TEST_SETTINGS from guardian.testapp.tests.conf import override_settings from guardian.models import Group from guardian.testapp.tests.conf import skipUnlessTestApp from guardian.testapp.models import LogEntryWithGroup as LogEntry User = get_user_model() class ContentTypeGuardedAdmin(GuardedModelAdmin): pass try: admin.site.unregister(ContentType) except admin.sites.NotRegistered: pass admin.site.register(ContentType, ContentTypeGuardedAdmin) @override_settings(**TEST_SETTINGS) class AdminTests(TestCase): def setUp(self): self.admin = User.objects.create_superuser('admin', 'admin@example.com', 'admin') self.user = User.objects.create_user('joe', 'joe@example.com', 'joe') self.group = Group.objects.create(name='group') self.client = Client() self.obj = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') self.obj_info = self.obj._meta.app_label, get_model_name(self.obj) def tearDown(self): self.client.logout() def _login_superuser(self): self.client.login(username='admin', password='admin') def test_view_manage_wrong_obj(self): self._login_superuser() url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info, kwargs={'object_pk': -10, 'user_id': self.user.pk}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_view(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['object'], self.obj) def test_view_manage_wrong_user(self): self._login_superuser() url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info, kwargs={'object_pk': self.obj.pk, 'user_id': -10}) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_view_manage_user_form(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'user': self.user.username, 'submit_manage_user': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) redirect_url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info, kwargs={'object_pk': self.obj.pk, 'user_id': self.user.pk}) self.assertEqual(response.request['PATH_INFO'], redirect_url) def test_view_manage_negative_user_form(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) self.user = User.objects.create(username='negative_id_user', pk=-2010) data = {'user': self.user.username, 'submit_manage_user': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) redirect_url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info, args=[self.obj.pk, self.user.pk]) self.assertEqual(response.request['PATH_INFO'], redirect_url) def test_view_manage_user_form_wrong_user(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'user': 'wrong-user', 'submit_manage_user': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 0) self.assertEqual(response.status_code, 200) self.assertTrue('user' in response.context['user_form'].errors) def test_view_manage_user_form_wrong_field(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'user': '', 'submit_manage_user': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 0) self.assertEqual(response.status_code, 200) self.assertTrue('user' in response.context['user_form'].errors) def test_view_manage_user_form_empty_user(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'user': '', 'submit_manage_user': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 0) self.assertEqual(response.status_code, 200) self.assertTrue('user' in response.context['user_form'].errors) def test_view_manage_user_wrong_perms(self): self._login_superuser() url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info, args=[self.obj.pk, self.user.pk]) perms = ['change_user'] # This is not self.obj related permission data = {'permissions': perms} response = self.client.post(url, data, follow=True) self.assertEqual(response.status_code, 200) self.assertTrue('permissions' in response.context['form'].errors) def test_view_manage_user(self): self._login_superuser() url = reverse('admin:%s_%s_permissions_manage_user' % self.obj_info, args=[self.obj.pk, self.user.pk]) response = self.client.get(url) self.assertEqual(response.status_code, 200) choices = set([c[0] for c in response.context['form'].fields['permissions'].choices]) self.assertEqual( set([ p.codename for p in get_perms_for_model(self.obj)]), choices, ) # Add some perms and check if changes were persisted perms = ['change_%s' % self.obj_info[1], 'delete_%s' % self.obj_info[1]] data = {'permissions': perms} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) self.assertEqual( set(get_perms(self.user, self.obj)), set(perms), ) # Remove perm and check if change was persisted perms = ['change_%s' % self.obj_info[1]] data = {'permissions': perms} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) self.assertEqual( set(get_perms(self.user, self.obj)), set(perms), ) def test_view_manage_group_form(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'group': self.group.name, 'submit_manage_group': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) redirect_url = reverse('admin:%s_%s_permissions_manage_group' % self.obj_info, args=[self.obj.pk, self.group.id]) self.assertEqual(response.request['PATH_INFO'], redirect_url) def test_view_manage_negative_group_form(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) self.group = Group.objects.create(name='neagive_id_group', id=-2010) data = {'group': self.group.name, 'submit_manage_group': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) redirect_url = reverse('admin:%s_%s_permissions_manage_group' % self.obj_info, args=[self.obj.pk, self.group.id]) self.assertEqual(response.request['PATH_INFO'], redirect_url) def test_view_manage_group_form_wrong_group(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'group': 'wrong-group', 'submit_manage_group': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 0) self.assertEqual(response.status_code, 200) self.assertTrue('group' in response.context['group_form'].errors) def test_view_manage_group_form_wrong_field(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'group': '', 'submit_manage_group': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 0) self.assertEqual(response.status_code, 200) self.assertTrue('group' in response.context['group_form'].errors) def test_view_manage_group_form_empty_group(self): self._login_superuser() url = reverse('admin:%s_%s_permissions' % self.obj_info, args=[self.obj.pk]) data = {'group': '', 'submit_manage_group': 'submit'} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 0) self.assertEqual(response.status_code, 200) self.assertTrue('group' in response.context['group_form'].errors) def test_view_manage_group_wrong_perms(self): self._login_superuser() url = reverse('admin:%s_%s_permissions_manage_group' % self.obj_info, args=[self.obj.pk, self.group.id]) perms = ['change_user'] # This is not self.obj related permission data = {'permissions': perms} response = self.client.post(url, data, follow=True) self.assertEqual(response.status_code, 200) self.assertTrue('permissions' in response.context['form'].errors) def test_view_manage_group(self): self._login_superuser() url = reverse('admin:%s_%s_permissions_manage_group' % self.obj_info, args=[self.obj.pk, self.group.id]) response = self.client.get(url) self.assertEqual(response.status_code, 200) choices = set([c[0] for c in response.context['form'].fields['permissions'].choices]) self.assertEqual( set([ p.codename for p in get_perms_for_model(self.obj)]), choices, ) # Add some perms and check if changes were persisted perms = ['change_%s' % self.obj_info[1], 'delete_%s' % self.obj_info[1]] data = {'permissions': perms} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) self.assertEqual( set(get_perms(self.group, self.obj)), set(perms), ) # Remove perm and check if change was persisted perms = ['delete_%s' % self.obj_info[1]] data = {'permissions': perms} response = self.client.post(url, data, follow=True) self.assertEqual(len(response.redirect_chain), 1) self.assertEqual(response.redirect_chain[0][1], 302) self.assertEqual( set(get_perms(self.group, self.obj)), set(perms), ) if 'django.contrib.admin' not in settings.INSTALLED_APPS: # Skip admin tests if admin app is not registered # we simpy clean up AdminTests class ... # TODO: use @unittest.skipUnless('django.contrib.admin' in settings.INSTALLED_APPS) # if possible (requires Python 2.7, though) AdminTests = type('AdminTests', (TestCase,), {}) # pyflakes:ignore @skipUnlessTestApp class GuardedModelAdminTests(TestCase): def _get_gma(self, attrs=None, name=None, model=None): """ Returns ``GuardedModelAdmin`` instance. """ attrs = attrs or {} name = str(name or 'GMA') model = model or User GMA = type(name, (GuardedModelAdmin,), attrs) gma = GMA(model, admin.site) return gma def test_obj_perms_manage_template_attr(self): attrs = {'obj_perms_manage_template': 'foobar.html'} gma = self._get_gma(attrs=attrs) self.assertTrue(gma.get_obj_perms_manage_template(), 'foobar.html') def test_obj_perms_manage_user_template_attr(self): attrs = {'obj_perms_manage_user_template': 'foobar.html'} gma = self._get_gma(attrs=attrs) self.assertTrue(gma.get_obj_perms_manage_user_template(), 'foobar.html') def test_obj_perms_manage_user_form_attr(self): attrs = {'obj_perms_manage_user_form': forms.Form} gma = self._get_gma(attrs=attrs) self.assertTrue(gma.get_obj_perms_manage_user_form(), forms.Form) def test_obj_perms_manage_group_template_attr(self): attrs = {'obj_perms_manage_group_template': 'foobar.html'} gma = self._get_gma(attrs=attrs) self.assertTrue(gma.get_obj_perms_manage_group_template(), 'foobar.html') def test_obj_perms_manage_group_form_attr(self): attrs = {'obj_perms_manage_group_form': forms.Form} gma = self._get_gma(attrs=attrs) self.assertTrue(gma.get_obj_perms_manage_group_form(), forms.Form) def test_user_can_acces_owned_objects_only(self): attrs = { 'user_can_access_owned_objects_only': True, 'user_owned_objects_field': 'user', } gma = self._get_gma(attrs=attrs, model=LogEntry) joe = User.objects.create_user('joe', 'joe@example.com', 'joe') jane = User.objects.create_user('jane', 'jane@example.com', 'jane') ctype = ContentType.objects.get_for_model(User) joe_entry = LogEntry.objects.create(user=joe, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo') LogEntry.objects.create(user=jane, content_type=ctype, object_id=jane.pk, action_flag=1, change_message='bar') request = HttpRequest() request.user = joe qs = gma.get_queryset(request) self.assertEqual([e.pk for e in qs], [joe_entry.pk]) def test_user_can_acces_owned_objects_only_unless_superuser(self): attrs = { 'user_can_access_owned_objects_only': True, 'user_owned_objects_field': 'user', } gma = self._get_gma(attrs=attrs, model=LogEntry) joe = User.objects.create_superuser('joe', 'joe@example.com', 'joe') jane = User.objects.create_user('jane', 'jane@example.com', 'jane') ctype = ContentType.objects.get_for_model(User) joe_entry = LogEntry.objects.create(user=joe, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo') jane_entry = LogEntry.objects.create(user=jane, content_type=ctype, object_id=jane.pk, action_flag=1, change_message='bar') request = HttpRequest() request.user = joe qs = gma.get_queryset(request) self.assertEqual(sorted([e.pk for e in qs]), sorted([joe_entry.pk, jane_entry.pk])) def test_user_can_access_owned_by_group_objects_only(self): attrs = { 'user_can_access_owned_by_group_objects_only': True, 'group_owned_objects_field': 'group', } gma = self._get_gma(attrs=attrs, model=LogEntry) joe = User.objects.create_user('joe', 'joe@example.com', 'joe') joe_group = Group.objects.create(name='joe-group') joe.groups.add(joe_group) jane = User.objects.create_user('jane', 'jane@example.com', 'jane') jane_group = Group.objects.create(name='jane-group') jane.groups.add(jane_group) ctype = ContentType.objects.get_for_model(User) LogEntry.objects.create(user=joe, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo') LogEntry.objects.create(user=jane, content_type=ctype, object_id=jane.pk, action_flag=1, change_message='bar') joe_entry_group = LogEntry.objects.create(user=jane, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo', group=joe_group) request = HttpRequest() request.user = joe qs = gma.get_queryset(request) self.assertEqual([e.pk for e in qs], [joe_entry_group.pk]) def test_user_can_access_owned_by_group_objects_only_unless_superuser(self): attrs = { 'user_can_access_owned_by_group_objects_only': True, 'group_owned_objects_field': 'group', } gma = self._get_gma(attrs=attrs, model=LogEntry) joe = User.objects.create_superuser('joe', 'joe@example.com', 'joe') joe_group = Group.objects.create(name='joe-group') joe.groups.add(joe_group) jane = User.objects.create_user('jane', 'jane@example.com', 'jane') jane_group = Group.objects.create(name='jane-group') jane.groups.add(jane_group) ctype = ContentType.objects.get_for_model(User) LogEntry.objects.create(user=joe, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo') LogEntry.objects.create(user=jane, content_type=ctype, object_id=jane.pk, action_flag=1, change_message='bar') LogEntry.objects.create(user=jane, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo', group=joe_group) LogEntry.objects.create(user=joe, content_type=ctype, object_id=joe.pk, action_flag=1, change_message='foo', group=jane_group) request = HttpRequest() request.user = joe qs = gma.get_queryset(request) self.assertEqual(sorted(e.pk for e in qs), sorted(LogEntry.objects.values_list('pk', flat=True))) class GrappelliGuardedModelAdminTests(TestCase): org_installed_apps = copy.copy(settings.INSTALLED_APPS) def _get_gma(self, attrs=None, name=None, model=None): """ Returns ``GuardedModelAdmin`` instance. """ attrs = attrs or {} name = str(name or 'GMA') model = model or User GMA = type(name, (GuardedModelAdmin,), attrs) gma = GMA(model, admin.site) return gma def setUp(self): settings.INSTALLED_APPS = ['grappelli'] + list(settings.INSTALLED_APPS) def tearDown(self): settings.INSTALLED_APPS = self.org_installed_apps def test_get_obj_perms_manage_template(self): gma = self._get_gma() self.assertEqual(gma.get_obj_perms_manage_template(), 'admin/guardian/contrib/grappelli/obj_perms_manage.html') def test_get_obj_perms_manage_user_template(self): gma = self._get_gma() self.assertEqual(gma.get_obj_perms_manage_user_template(), 'admin/guardian/contrib/grappelli/obj_perms_manage_user.html') def test_get_obj_perms_manage_group_template(self): gma = self._get_gma() self.assertEqual(gma.get_obj_perms_manage_group_template(), 'admin/guardian/contrib/grappelli/obj_perms_manage_group.html') django-guardian-1.4.1/guardian/testapp/tests/test_custompkmodel.py0000600000175000017500000000223312643667664025706 0ustar brianbrian00000000000000from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.test import TestCase from guardian.compat import get_user_model from guardian.shortcuts import assign_perm, remove_perm class CustomPKModelTest(TestCase): """ Tests agains custom model with primary key other than *standard* ``id`` integer field. """ def setUp(self): self.user = get_user_model().objects.create(username='joe') self.ctype = ContentType.objects.create(model='bar', app_label='fake-for-guardian-tests') def test_assign_perm(self): assign_perm('contenttypes.change_contenttype', self.user, self.ctype) self.assertTrue(self.user.has_perm('contenttypes.change_contenttype', self.ctype)) def test_remove_perm(self): assign_perm('contenttypes.change_contenttype', self.user, self.ctype) self.assertTrue(self.user.has_perm('contenttypes.change_contenttype', self.ctype)) remove_perm('contenttypes.change_contenttype', self.user, self.ctype) self.assertFalse(self.user.has_perm('contenttypes.change_contenttype', self.ctype)) django-guardian-1.4.1/README.rst0000600000175000017500000000524212634712306016445 0ustar brianbrian00000000000000=============== django-guardian =============== .. image:: https://travis-ci.org/django-guardian/django-guardian.svg?branch=devel :target: https://travis-ci.org/django-guardian/django-guardian ``django-guardian`` is implementation of per object permissions [1]_ as authorization backend which is supported since Django_ 1.5. It won't work with older Django_ releases. Documentation ------------- Online documentation is available at http://django-guardian.rtfd.org/. Installation ------------ To install ``django-guardian`` simply run:: pip install django-guardian Configuration ------------- We need to hook ``django-guardian`` into our project. 1. Put ``guardian`` into your ``INSTALLED_APPS`` at settings module:: INSTALLED_APPS = ( ... 'guardian', ) 2. Add extra authorization backend to your `settings.py`:: AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', # default 'guardian.backends.ObjectPermissionBackend', ) 3. Configure anonymous user ID in your `settings.py`:: ANONYMOUS_USER_ID = -1 4. Create ``guardian`` database tables by running:: python manage.py migrate Usage ----- After installation and project hooks we can finally use object permissions with Django_. Lets start really quickly:: >>> from django.contrib.auth.models import User, Group >>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack') >>> admins = Group.objects.create(name='admins') >>> jack.has_perm('change_group', admins) False >>> from guardian.models import UserObjectPermission >>> UserObjectPermission.objects.assign_perm('change_group', user=jack, obj=admins) >>> jack.has_perm('change_group', admins) True Of course our agent jack here would not be able to *change_group* globally:: >>> jack.has_perm('change_group') False Admin integration ----------------- Replace ``admin.ModelAdmin`` with ``GuardedModelAdmin`` for those models which should have object permissions support within admin panel. For example:: from django.contrib import admin from myapp.models import Author from guardian.admin import GuardedModelAdmin # Old way: #class AuthorAdmin(admin.ModelAdmin): # pass # With object permissions support class AuthorAdmin(GuardedModelAdmin): pass admin.site.register(Author, AuthorAdmin) .. [1] Great paper about this feature is available at `djangoadvent articles `_. .. _Django: http://www.djangoproject.com/ django-guardian-1.4.1/.gitignore0000600000175000017500000000034712634712306016747 0ustar brianbrian00000000000000*.pyc *.log *.egg *.egg-info *.swp *.bak *.db *.orig build _build dist .DS_Store .coverage .hgignore .tox .ropeproject example_project/media example_project/conf/*.py .idea/ # WebDAV remote filesystem .DAV .project .pydevproject django-guardian-1.4.1/docs/0000700000175000017500000000000012644306605015703 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/theme/0000700000175000017500000000000012644306605017005 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/theme/rtd_theme/0000700000175000017500000000000012644306605020760 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/theme/rtd_theme/sass/0000700000175000017500000000000012644306605021731 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/theme/rtd_theme/sass/_breadcrumbs.sass0000600000175000017500000000072512634712306025260 0ustar brianbrian00000000000000.wy-breadcrumbs li display: inline-block &.wy-breadcrumbs-aside float: right a display: inline-block padding: 5px &:first-child padding-left: 0 .wy-breadcrumbs-extra margin-bottom: 0 color: $text-light font-size: 80% display: inline-block +media($mobile) .wy-breadcrumbs-extra display: none .wy-breadcrumbs li.wy-breadcrumbs-aside display: none @media print .wy-breadcrumbs li.wy-breadcrumbs-aside display: none django-guardian-1.4.1/docs/theme/rtd_theme/sass/config.rb0000600000175000017500000000235612634712306023531 0ustar brianbrian00000000000000# Require any additional compass plugins here. # Set this to the root of your project when deployed: #path = File.dirname(__FILE__) http_path = "/" css_dir = "../static" sass_dir = "" images_dir = "../static" fonts_dir = "../static/font" javascripts_dir = "../static" line_comments = false preferred_syntax = :sass # To enable relative paths to assets via compass helper functions. Uncomment: #relative_assets = true add_import_path "../../bower_components/bourbon/app/assets/stylesheets" add_import_path "../../bower_components/neat/app/assets/stylesheets" add_import_path "../../bower_components/wyrm/sass" # You can select your preferred output style here (can be overridden via the command line): # output_style = :expanded or :nested or :compact or :compressed # To enable relative paths to assets via compass helper functions. Uncomment: # relative_assets = true # To disable debugging comments that display the original location of your selectors. Uncomment: # line_comments = false # If you prefer the indented syntax, you might want to regenerate this # project again passing --syntax sass, or you can uncomment this: # preferred_syntax = :sass # and then run: # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass django-guardian-1.4.1/docs/theme/rtd_theme/sass/badge_only.sass0000600000175000017500000000036612634712306024734 0ustar brianbrian00000000000000// This file just generates the RTD badge for use on docs not using the RTD theme. @import wyrm_core/wy_variables @import bourbon @import neat @import wyrm_core/mixin @import wyrm_core/grid_settings @import badge_font_awesome_mini @import badge django-guardian-1.4.1/docs/theme/rtd_theme/sass/_badge_font_awesome_mini.sass0000600000175000017500000000204612634712306027611 0ustar brianbrian00000000000000// SIMPLIFIED VERSION OF FONT AWESOME, FOUND HERE http://fortawesome.github.com/Font-Awesome/ +font-face(fontawesome-webfont, '#{$font-awesome-dir}fontawesome_webfont') .icon:before display: inline-block font-family: fontawesome-webfont font-style: normal font-weight: normal line-height: 1 text-decoration: inherit +font-smooth a .icon display: inline-block text-decoration: inherit li .icon display: inline-block .icon-large:before, .icon-large:before /* 1.5 increased font size for icon-large * 1.25 width width: 1.5 * 1.25em ul.icons list-style-type: none margin-left: 2em text-indent: -0.8em li .icon width: .8em .icon-large:before, .icon-large:before /* 1.5 increased font size for icon-large * 1.25 width vertical-align: baseline // width: 1.5*1.25em .icon-book:before content: "\f02d" .icon-caret-down:before content: "\f0d7" .icon-caret-up:before content: "\f0d8" .icon-caret-left:before content: "\f0d9" .icon-caret-right:before content: "\f0da" django-guardian-1.4.1/docs/theme/rtd_theme/sass/_badge.sass0000600000175000017500000000344112634712306024027 0ustar brianbrian00000000000000.rst-versions position: fixed bottom: 0 left: 0 width: $nav-desktop-width color: $section-background-color background: darken($menu-background-color, 8%) border-top: solid 10px $menu-background-color font-family: $base-font-family a color: $link_color text-decoration: none .rst-badge-small display: none .rst-current-version padding: $base-line-height / 2 background-color: darken($menu-background-color, 5%) display: block text-align: right font-size: 90% cursor: pointer color: $green z-index: $z-index-tray +clearfix .icon color: $section-background-color .icon-book float: left &.rst-out-of-date background-color: $red color: $white &.shift-up .rst-other-versions display: block .rst-other-versions font-size: 90% padding: $base-line-height / 2 color: $text-medium display: none hr display: block height: 1px border: 0 margin: 20px 0 padding: 0 border-top: solid 1px lighten($menu-background-color, 5%) dd display: inline-block margin: 0 a display: inline-block padding: $base-line-height / 4 color: $section-background-color &.rst-badge width: auto bottom: 20px right: 20px left: auto border: none max-width: $nav-desktop-width .icon-book float: none &.shift-up .rst-current-version text-align: right .icon-book float: left .rst-current-version width: auto height: 30px line-height: 30px padding: 0 $base-line-height / 4 display: block text-align: center +media($tablet) .rst-versions width: 85% display: none &.shift display: block img width: 100% height: auto django-guardian-1.4.1/docs/theme/rtd_theme/sass/theme.sass0000600000175000017500000000253412634712306023732 0ustar brianbrian00000000000000@import wyrm_core/wy_variables // bourbon framework installed from bower @import bourbon // Bourbon neat installed from bower, edit grid settings for media queries @import wyrm_core/grid_settings @import neat // Custom reset @import wyrm_core/reset // Some compass libs on top of Bourbon @import compass/typography/vertical_rhythm @import compass/typography/text/ellipsis // Basic defaults and mixins @import wyrm_core/mixin @import wyrm_core/font_awesome // Wyrm core styles @import wyrm_core/alert // @import wyrm_core/autocomplete @import wyrm_core/button @import wyrm_core/dropdown @import wyrm_core/form @import wyrm_core/generic @import wyrm_core/grid_layout @import wyrm_core/spinner @import wyrm_core/table @import wyrm_core/type // Cms // @import wyrm_addons/cms/dashboard // @import wyrm_addons/cms/layout // @import wyrm_addons/cms/list_like_table // @import wyrm_addons/cms/plugin_store // Elements // @import wyrm_addons/elements/modal // @import wyrm_addons/elements/stars @import wyrm_addons/elements/tooltip // Redactor // @import wyrm_addons/redactor/figures // @import wyrm_addons/redactor/redactor // Restructured text and Sphinx @import wyrm_addons/rst/rst @import wyrm_addons/rst/pygments // @import wyrm_addons/rst/pygments_dark @import wyrm_addons/rst/pygments_light // Theme specific styles @import breadcrumbs @import nav @import badge django-guardian-1.4.1/docs/theme/rtd_theme/sass/_nav.sass0000600000175000017500000001501612634712306023552 0ustar brianbrian00000000000000.wy-affix position: fixed top: $gutter .wy-menu a:hover text-decoration: none .wy-menu-horiz +clearfix ul, li display: inline-block li:hover background: rgba(255,255,255,.1) li &.divide-left border-left: solid 1px hsl(0, 0%, 25%) &.divide-right border-right: solid 1px hsl(0, 0%, 25%) a height: $base-font-size * 2 display: inline-block line-height: $base-font-size * 2 padding: 0 $base-font-size .wy-menu-vertical header height: $base-font-size * 2 display: inline-block line-height: $base-font-size * 2 padding: 0 $gutter display: block font-weight: bold text-transform: uppercase font-size: 80% color: $menu-logo-color white-space: nowrap ul margin-bottom: 0 li &.divide-top border-top: solid 1px hsl(0, 0%, 25%) &.divide-bottom border-bottom: solid 1px hsl(0, 0%, 25%) &.current background: darken($section-background-color, 10%) a color: $text-medium border-right: solid 1px darken($section-background-color, 20%) padding: $gutter / 4 $gutter * 1.5 &:hover background: darken($section-background-color, 15%) .current background: darken($section-background-color, 20%) li.on a, li.current > a color: $text-color padding: $gutter / 4 $gutter font-weight: bold position: relative background: $section-background-color border: none border-bottom: solid 1px darken($section-background-color, 20%) padding-left: $gutter -4px +font-smooth &:hover background: $section-background-color li.current ul display: block li ul margin-bottom: 0 display: none li ul li a margin-bottom: 0 color: $text-light font-weight: normal a display: inline-block line-height: 18px padding: $gutter / 4 $gutter display: block position: relative font-size: 90% color: $text-light &:hover background-color: lighten($menu-background-color, 10%) cursor: pointer &:active background-color: $menu-logo-color cursor: pointer color: $white .wy-side-nav-search z-index: $z-index-popover background-color: $link-color text-align: center padding: $gutter / 2 display: block color: $section-background-color margin-bottom: $gutter / 2 input[type=text] width: 100% border-radius: 50px padding: 6px 12px border-color: darken($link-color, 5%) img display: block margin: auto auto $gutter / 2 auto height: 45px width: 45px background-color: $menu-logo-color padding: 5px border-radius: 100% > a, .wy-dropdown > a color: $section-background-color font-size: 100% font-weight: bold display: inline-block padding: $base-line-height / 6 $base-line-height / 4 margin-bottom: $gutter / 2 +font-smooth &:hover background: rgba(255,255,255,.1) .wy-nav .wy-menu-vertical header color: $link-color a color: $text-light &:hover background-color: $link-color color: $white [data-menu-wrap] +transition(all .2s ease-in) position: absolute opacity: 1 width: 100% opacity: 0 &.move-center left: 0 right: auto opacity: 1 &.move-left right: auto left: -100% opacity: 0 &.move-right right: -100% left: auto opacity: 0 .wy-body-for-nav background: left repeat-y $section-background-color background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC) background-size: $nav-desktop-width 1px .wy-grid-for-nav position: absolute width: 100% height: 100% .wy-nav-side position: absolute top: 0 left: 0 width: $nav-desktop-width overflow: hidden min-height: 100% background: $menu-background-color z-index: $z-index-popover .wy-nav-top display: none background: $link-color color: $white padding: $gutter / 4 $gutter / 2 position: relative line-height: 50px text-align: center font-size: 100% +clearfix a color: $white font-weight: bold +font-smooth img margin-right: $base-line-height / 2 height: 45px width: 45px background-color: $menu-logo-color padding: 5px border-radius: 100% i font-size: 30px float: left cursor: pointer .wy-nav-content-wrap margin-left: $nav-desktop-width background: $section-background-color min-height: 100% .wy-nav-content padding: $gutter $gutter * 2 height: 100% max-width: 800px margin: auto .wy-body-mask position: fixed width: 100% height: 100% background: rgba(0,0,0,.2) display: none z-index: $z-index-modal - 1 &.on display: block footer color: $gray-light +media($tablet) .wy-body-for-nav background: $section-background-color .wy-nav-top display: block .wy-nav-side @if $nav-desktop-position == left left: -$nav-desktop-width @else right: -$nav-desktop-width &.shift width: 85% left: 0 .wy-nav-content-wrap margin-left: 0 .wy-nav-content padding: $gutter &.shift position: fixed min-width: 100% left: 85% top: 0 height: 100% overflow: hidden +media($desktop-wide) .wy-nav-content-wrap background: rgba(0,0,0,.05) .wy-nav-content margin: 0 background: $section-background-color @media print .wy-nav-side display: none .wy-nav-content-wrap margin-left: 0 django-guardian-1.4.1/docs/theme/rtd_theme/breadcrumbs.html0000600000175000017500000000063012634712306024136 0ustar brianbrian00000000000000
    django-guardian-1.4.1/docs/theme/rtd_theme/layout_old.html0000600000175000017500000001625512634712306024032 0ustar brianbrian00000000000000{# basic/layout.html ~~~~~~~~~~~~~~~~~ Master layout template for Sphinx themes. :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- block doctype -%} {%- endblock %} {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and (sidebars != []) %} {%- set url_root = pathto('', 1) %} {# XXX necessary? #} {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} {%- if not embedded and docstitle %} {%- set titlesuffix = " — "|safe + docstitle|e %} {%- else %} {%- set titlesuffix = "" %} {%- endif %} {%- macro relbar() %} {%- endmacro %} {%- macro sidebar() %} {%- if render_sidebar %}
    {%- block sidebarlogo %} {%- if logo %} {%- endif %} {%- endblock %} {%- if sidebars != None %} {#- new style sidebar: explicitly include/exclude templates #} {%- for sidebartemplate in sidebars %} {%- include sidebartemplate %} {%- endfor %} {%- else %} {#- old style sidebars: using blocks -- should be deprecated #} {%- block sidebartoc %} {%- include "localtoc.html" %} {%- endblock %} {%- block sidebarrel %} {%- include "relations.html" %} {%- endblock %} {%- block sidebarsourcelink %} {%- include "sourcelink.html" %} {%- endblock %} {%- if customsidebar %} {%- include customsidebar %} {%- endif %} {%- block sidebarsearch %} {%- include "searchbox.html" %} {%- endblock %} {%- endif %}
    {%- endif %} {%- endmacro %} {%- macro script() %} {%- for scriptfile in script_files %} {%- endfor %} {%- endmacro %} {%- macro css() %} {%- for cssfile in css_files %} {%- endfor %} {%- endmacro %} {{ metatags }} {%- block htmltitle %} {{ title|striptags|e }}{{ titlesuffix }} {%- endblock %} {{ css() }} {%- if not embedded %} {{ script() }} {%- if use_opensearch %} {%- endif %} {%- if favicon %} {%- endif %} {%- endif %} {%- block linktags %} {%- if hasdoc('about') %} {%- endif %} {%- if hasdoc('genindex') %} {%- endif %} {%- if hasdoc('search') %} {%- endif %} {%- if hasdoc('copyright') %} {%- endif %} {%- if parents %} {%- endif %} {%- if next %} {%- endif %} {%- if prev %} {%- endif %} {%- endblock %} {%- block extrahead %} {% endblock %} {%- block header %}{% endblock %} {%- block relbar1 %}{{ relbar() }}{% endblock %} {%- block content %} {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
    {%- block document %}
    {%- if render_sidebar %}
    {%- endif %}
    {% block body %} {% endblock %}
    {%- if render_sidebar %}
    {%- endif %}
    {%- endblock %} {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
    {%- endblock %} {%- block relbar2 %}{{ relbar() }}{% endblock %} {%- block footer %}

    asdf asdf asdf asdf 22

    {%- endblock %} django-guardian-1.4.1/docs/theme/rtd_theme/theme.conf0000600000175000017500000000013712634712306022732 0ustar brianbrian00000000000000[theme] inherit = basic stylesheet = theme.css [options] typekit_id = hiw1hhg analytics_id = django-guardian-1.4.1/docs/theme/rtd_theme/searchbox.html0000600000175000017500000000037112634712306023625 0ustar brianbrian00000000000000
    django-guardian-1.4.1/docs/theme/rtd_theme/layout.html0000600000175000017500000001065212634712306023167 0ustar brianbrian00000000000000{# TEMPLATE VAR SETTINGS #} {%- set url_root = pathto('', 1) %} {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} {%- if not embedded and docstitle %} {%- set titlesuffix = " — "|safe + docstitle|e %} {%- else %} {%- set titlesuffix = "" %} {%- endif %} {% block htmltitle %} {{ title|striptags|e }}{{ titlesuffix }} {% endblock %} {# FAVICON #} {% if favicon %} {% endif %} {# CSS #} {# JS #} {% if not embedded %} {%- for scriptfile in script_files %} {%- endfor %} {% if use_opensearch %} {% endif %} {% endif %} {# RTD hosts these file themselves, so just load on non RTD builds #} {% if not READTHEDOCS %} {% endif %} {% for cssfile in css_files %} {% endfor %} {%- block linktags %} {%- if hasdoc('about') %} {%- endif %} {%- if hasdoc('genindex') %} {%- endif %} {%- if hasdoc('search') %} {%- endif %} {%- if hasdoc('copyright') %} {%- endif %} {%- if parents %} {%- endif %} {%- if next %} {%- endif %} {%- if prev %} {%- endif %} {%- endblock %} {%- block extrahead %} {% endblock %}
    {# SIDE NAV, TOGGLES ON MOBILE #}
    {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #} {# PAGE CONTENT #}
    {% include "breadcrumbs.html" %} {% block body %}{% endblock %} {% include "footer.html" %}
    django-guardian-1.4.1/docs/theme/rtd_theme/footer.html0000600000175000017500000000076112634712306023150 0ustar brianbrian00000000000000

    {%- if show_copyright %} {%- if hasdoc('copyright') %} {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} {%- else %} {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} {%- endif %} {%- endif %} {%- if last_updated %} {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} {%- endif %}
    django-guardian-1.4.1/docs/theme/rtd_theme/static/0000700000175000017500000000000012644306605022247 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/theme/rtd_theme/static/theme.css0000600000175000017500000024652512634712306024101 0ustar brianbrian00000000000000*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:20px 0;padding:0}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content dl dt,.rst-content dl dl dt,.rst-content tt.literal,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.font-smooth,.icon:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-tag-input-group .wy-tag .wy-tag-remove:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-tag-input-group,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:fontawesome-webfont;font-weight:normal;font-style:normal;src:url("font/fontawesome_webfont.eot");src:url("font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("font/fontawesome_webfont.woff") format("woff"),url("font/fontawesome_webfont.ttf") format("truetype"),url("font/fontawesome_webfont.svg#fontawesome-webfont") format("svg")}.icon:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-tag-input-group .wy-tag .wy-tag-remove:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before{display:inline-block;font-family:fontawesome-webfont;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .icon,a .wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-success a .wy-input-context,a .wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-danger a .wy-input-context,a .wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-warning a .wy-input-context,a .wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-info a .wy-input-context,a .wy-tag-input-group .wy-tag .wy-tag-remove,.wy-tag-input-group .wy-tag a .wy-tag-remove,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink{display:inline-block;text-decoration:inherit}.icon-large:before{vertical-align:-10%;font-size:1.33333em}.btn .icon,.btn .wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-success .btn .wy-input-context,.btn .wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .btn .wy-input-context,.btn .wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .btn .wy-input-context,.btn .wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-info .btn .wy-input-context,.btn .wy-tag-input-group .wy-tag .wy-tag-remove,.wy-tag-input-group .wy-tag .btn .wy-tag-remove,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.nav .icon,.nav .wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-success .nav .wy-input-context,.nav .wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .nav .wy-input-context,.nav .wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .nav .wy-input-context,.nav .wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-info .nav .wy-input-context,.nav .wy-tag-input-group .wy-tag .wy-tag-remove,.wy-tag-input-group .wy-tag .nav .wy-tag-remove,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink{display:inline}.btn .icon.icon-large,.btn .wy-inline-validate.wy-inline-validate-success .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-success .btn .icon-large.wy-input-context,.btn .wy-inline-validate.wy-inline-validate-danger .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-danger .btn .icon-large.wy-input-context,.btn .wy-inline-validate.wy-inline-validate-warning .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-warning .btn .icon-large.wy-input-context,.btn .wy-inline-validate.wy-inline-validate-info .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-info .btn .icon-large.wy-input-context,.btn .wy-tag-input-group .wy-tag .icon-large.wy-tag-remove,.wy-tag-input-group .wy-tag .btn .icon-large.wy-tag-remove,.btn .rst-content .icon-large.admonition-title,.rst-content .btn .icon-large.admonition-title,.btn .rst-content h1 .icon-large.headerlink,.rst-content h1 .btn .icon-large.headerlink,.btn .rst-content h2 .icon-large.headerlink,.rst-content h2 .btn .icon-large.headerlink,.btn .rst-content h3 .icon-large.headerlink,.rst-content h3 .btn .icon-large.headerlink,.btn .rst-content h4 .icon-large.headerlink,.rst-content h4 .btn .icon-large.headerlink,.btn .rst-content h5 .icon-large.headerlink,.rst-content h5 .btn .icon-large.headerlink,.btn .rst-content h6 .icon-large.headerlink,.rst-content h6 .btn .icon-large.headerlink,.btn .rst-content dl dt .icon-large.headerlink,.rst-content dl dt .btn .icon-large.headerlink,.nav .icon.icon-large,.nav .wy-inline-validate.wy-inline-validate-success .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-success .nav .icon-large.wy-input-context,.nav .wy-inline-validate.wy-inline-validate-danger .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-danger .nav .icon-large.wy-input-context,.nav .wy-inline-validate.wy-inline-validate-warning .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-warning .nav .icon-large.wy-input-context,.nav .wy-inline-validate.wy-inline-validate-info .icon-large.wy-input-context,.wy-inline-validate.wy-inline-validate-info .nav .icon-large.wy-input-context,.nav .wy-tag-input-group .wy-tag .icon-large.wy-tag-remove,.wy-tag-input-group .wy-tag .nav .icon-large.wy-tag-remove,.nav .rst-content .icon-large.admonition-title,.rst-content .nav .icon-large.admonition-title,.nav .rst-content h1 .icon-large.headerlink,.rst-content h1 .nav .icon-large.headerlink,.nav .rst-content h2 .icon-large.headerlink,.rst-content h2 .nav .icon-large.headerlink,.nav .rst-content h3 .icon-large.headerlink,.rst-content h3 .nav .icon-large.headerlink,.nav .rst-content h4 .icon-large.headerlink,.rst-content h4 .nav .icon-large.headerlink,.nav .rst-content h5 .icon-large.headerlink,.rst-content h5 .nav .icon-large.headerlink,.nav .rst-content h6 .icon-large.headerlink,.rst-content h6 .nav .icon-large.headerlink,.nav .rst-content dl dt .icon-large.headerlink,.rst-content dl dt .nav .icon-large.headerlink{line-height:0.9em}.btn .icon.icon-spin,.btn .wy-inline-validate.wy-inline-validate-success .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-success .btn .icon-spin.wy-input-context,.btn .wy-inline-validate.wy-inline-validate-danger .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-danger .btn .icon-spin.wy-input-context,.btn .wy-inline-validate.wy-inline-validate-warning .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-warning .btn .icon-spin.wy-input-context,.btn .wy-inline-validate.wy-inline-validate-info .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-info .btn .icon-spin.wy-input-context,.btn .wy-tag-input-group .wy-tag .icon-spin.wy-tag-remove,.wy-tag-input-group .wy-tag .btn .icon-spin.wy-tag-remove,.btn .rst-content .icon-spin.admonition-title,.rst-content .btn .icon-spin.admonition-title,.btn .rst-content h1 .icon-spin.headerlink,.rst-content h1 .btn .icon-spin.headerlink,.btn .rst-content h2 .icon-spin.headerlink,.rst-content h2 .btn .icon-spin.headerlink,.btn .rst-content h3 .icon-spin.headerlink,.rst-content h3 .btn .icon-spin.headerlink,.btn .rst-content h4 .icon-spin.headerlink,.rst-content h4 .btn .icon-spin.headerlink,.btn .rst-content h5 .icon-spin.headerlink,.rst-content h5 .btn .icon-spin.headerlink,.btn .rst-content h6 .icon-spin.headerlink,.rst-content h6 .btn .icon-spin.headerlink,.btn .rst-content dl dt .icon-spin.headerlink,.rst-content dl dt .btn .icon-spin.headerlink,.nav .icon.icon-spin,.nav .wy-inline-validate.wy-inline-validate-success .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-success .nav .icon-spin.wy-input-context,.nav .wy-inline-validate.wy-inline-validate-danger .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-danger .nav .icon-spin.wy-input-context,.nav .wy-inline-validate.wy-inline-validate-warning .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-warning .nav .icon-spin.wy-input-context,.nav .wy-inline-validate.wy-inline-validate-info .icon-spin.wy-input-context,.wy-inline-validate.wy-inline-validate-info .nav .icon-spin.wy-input-context,.nav .wy-tag-input-group .wy-tag .icon-spin.wy-tag-remove,.wy-tag-input-group .wy-tag .nav .icon-spin.wy-tag-remove,.nav .rst-content .icon-spin.admonition-title,.rst-content .nav .icon-spin.admonition-title,.nav .rst-content h1 .icon-spin.headerlink,.rst-content h1 .nav .icon-spin.headerlink,.nav .rst-content h2 .icon-spin.headerlink,.rst-content h2 .nav .icon-spin.headerlink,.nav .rst-content h3 .icon-spin.headerlink,.rst-content h3 .nav .icon-spin.headerlink,.nav .rst-content h4 .icon-spin.headerlink,.rst-content h4 .nav .icon-spin.headerlink,.nav .rst-content h5 .icon-spin.headerlink,.rst-content h5 .nav .icon-spin.headerlink,.nav .rst-content h6 .icon-spin.headerlink,.rst-content h6 .nav .icon-spin.headerlink,.nav .rst-content dl dt .icon-spin.headerlink,.rst-content dl dt .nav .icon-spin.headerlink{display:inline-block}.btn.icon:before,.wy-inline-validate.wy-inline-validate-success .btn.wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .btn.wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .btn.wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .btn.wy-input-context:before,.wy-tag-input-group .wy-tag .btn.wy-tag-remove:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.icon:hover:before,.wy-inline-validate.wy-inline-validate-success .btn.wy-input-context:hover:before,.wy-inline-validate.wy-inline-validate-danger .btn.wy-input-context:hover:before,.wy-inline-validate.wy-inline-validate-warning .btn.wy-input-context:hover:before,.wy-inline-validate.wy-inline-validate-info .btn.wy-input-context:hover:before,.wy-tag-input-group .wy-tag .btn.wy-tag-remove:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before{opacity:1}.btn-mini .icon:before,.btn-mini .wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .btn-mini .wy-input-context:before,.btn-mini .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .btn-mini .wy-input-context:before,.btn-mini .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .btn-mini .wy-input-context:before,.btn-mini .wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .btn-mini .wy-input-context:before,.btn-mini .wy-tag-input-group .wy-tag .wy-tag-remove:before,.wy-tag-input-group .wy-tag .btn-mini .wy-tag-remove:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before{font-size:14px;vertical-align:-15%}li .icon,li .wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-success li .wy-input-context,li .wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-danger li .wy-input-context,li .wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-warning li .wy-input-context,li .wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-info li .wy-input-context,li .wy-tag-input-group .wy-tag .wy-tag-remove,.wy-tag-input-group .wy-tag li .wy-tag-remove,li .rst-content .admonition-title,.rst-content li .admonition-title,li .rst-content h1 .headerlink,.rst-content h1 li .headerlink,li .rst-content h2 .headerlink,.rst-content h2 li .headerlink,li .rst-content h3 .headerlink,.rst-content h3 li .headerlink,li .rst-content h4 .headerlink,.rst-content h4 li .headerlink,li .rst-content h5 .headerlink,.rst-content h5 li .headerlink,li .rst-content h6 .headerlink,.rst-content h6 li .headerlink,li .rst-content dl dt .headerlink,.rst-content dl dt li .headerlink{display:inline-block}li .icon-large:before,li .icon-large:before{width:1.875em}ul.icons{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.icons li .icon,ul.icons li .wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-success ul.icons li .wy-input-context,ul.icons li .wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-danger ul.icons li .wy-input-context,ul.icons li .wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-warning ul.icons li .wy-input-context,ul.icons li .wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-info ul.icons li .wy-input-context,ul.icons li .wy-tag-input-group .wy-tag .wy-tag-remove,.wy-tag-input-group .wy-tag ul.icons li .wy-tag-remove,ul.icons li .rst-content .admonition-title,.rst-content ul.icons li .admonition-title,ul.icons li .rst-content h1 .headerlink,.rst-content h1 ul.icons li .headerlink,ul.icons li .rst-content h2 .headerlink,.rst-content h2 ul.icons li .headerlink,ul.icons li .rst-content h3 .headerlink,.rst-content h3 ul.icons li .headerlink,ul.icons li .rst-content h4 .headerlink,.rst-content h4 ul.icons li .headerlink,ul.icons li .rst-content h5 .headerlink,.rst-content h5 ul.icons li .headerlink,ul.icons li .rst-content h6 .headerlink,.rst-content h6 ul.icons li .headerlink,ul.icons li .rst-content dl dt .headerlink,.rst-content dl dt ul.icons li .headerlink{width:0.8em}ul.icons li .icon-large:before,ul.icons li .icon-large:before{vertical-align:baseline}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope-alt:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before,.wy-tag-input-group .wy-tag .wy-tag-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-power-off:before,.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-gear:before,.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file-alt:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-rotate-right:before,.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-mail-forward:before,.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-gears:before,.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up-alt:before{content:"\f087"}.icon-thumbs-down-alt:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-unchecked:before,.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-rotate-left:before,.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-text-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-mail-reply:before,.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"}.icon-expand-alt:before{content:"\f116"}.icon-collapse-alt:before{content:"\f117"}.icon-smile:before{content:"\f118"}.icon-frown:before{content:"\f119"}.icon-meh:before{content:"\f11a"}.icon-gamepad:before{content:"\f11b"}.icon-keyboard:before{content:"\f11c"}.icon-flag-alt:before{content:"\f11d"}.icon-flag-checkered:before{content:"\f11e"}.icon-terminal:before{content:"\f120"}.icon-code:before{content:"\f121"}.icon-reply-all:before{content:"\f122"}.icon-mail-reply-all:before{content:"\f122"}.icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123"}.icon-location-arrow:before{content:"\f124"}.icon-crop:before{content:"\f125"}.icon-code-fork:before{content:"\f126"}.icon-unlink:before{content:"\f127"}.icon-question:before{content:"\f128"}.icon-info:before{content:"\f129"}.icon-exclamation:before{content:"\f12a"}.icon-superscript:before{content:"\f12b"}.icon-subscript:before{content:"\f12c"}.icon-eraser:before{content:"\f12d"}.icon-puzzle-piece:before{content:"\f12e"}.icon-microphone:before{content:"\f130"}.icon-microphone-off:before{content:"\f131"}.icon-shield:before{content:"\f132"}.icon-calendar-empty:before{content:"\f133"}.icon-fire-extinguisher:before{content:"\f134"}.icon-rocket:before{content:"\f135"}.icon-maxcdn:before{content:"\f136"}.icon-chevron-sign-left:before{content:"\f137"}.icon-chevron-sign-right:before{content:"\f138"}.icon-chevron-sign-up:before{content:"\f139"}.icon-chevron-sign-down:before{content:"\f13a"}.icon-html5:before{content:"\f13b"}.icon-css3:before{content:"\f13c"}.icon-anchor:before{content:"\f13d"}.icon-unlock-alt:before{content:"\f13e"}.icon-bullseye:before{content:"\f140"}.icon-ellipsis-horizontal:before{content:"\f141"}.icon-ellipsis-vertical:before{content:"\f142"}.icon-rss-sign:before{content:"\f143"}.icon-play-sign:before{content:"\f144"}.icon-ticket:before{content:"\f145"}.icon-minus-sign-alt:before{content:"\f146"}.icon-check-minus:before{content:"\f147"}.icon-level-up:before{content:"\f148"}.icon-level-down:before{content:"\f149"}.icon-check-sign:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:"\f14a"}.icon-edit-sign:before{content:"\f14b"}.icon-external-link-sign:before{content:"\f14c"}.icon-share-sign:before{content:"\f14d"}.icon-compass:before{content:"\f14e"}.icon-collapse:before{content:"\f150"}.icon-collapse-top:before{content:"\f151"}.icon-expand:before{content:"\f152"}.icon-euro:before,.icon-eur:before{content:"\f153"}.icon-gbp:before{content:"\f154"}.icon-dollar:before,.icon-usd:before{content:"\f155"}.icon-rupee:before,.icon-inr:before{content:"\f156"}.icon-yen:before,.icon-jpy:before{content:"\f157"}.icon-renminbi:before,.icon-cny:before{content:"\f158"}.icon-won:before,.icon-krw:before{content:"\f159"}.icon-bitcoin:before,.icon-btc:before{content:"\f15a"}.icon-file:before{content:"\f15b"}.icon-file-text:before{content:"\f15c"}.icon-sort-by-alphabet:before{content:"\f15d"}.icon-sort-by-alphabet-alt:before{content:"\f15e"}.icon-sort-by-attributes:before{content:"\f160"}.icon-sort-by-attributes-alt:before{content:"\f161"}.icon-sort-by-order:before{content:"\f162"}.icon-sort-by-order-alt:before{content:"\f163"}.icon-thumbs-up:before{content:"\f164"}.icon-thumbs-down:before{content:"\f165"}.icon-youtube-sign:before{content:"\f166"}.icon-youtube:before{content:"\f167"}.icon-xing:before{content:"\f168"}.icon-xing-sign:before{content:"\f169"}.icon-youtube-play:before{content:"\f16a"}.icon-dropbox:before{content:"\f16b"}.icon-stackexchange:before{content:"\f16c"}.icon-instagram:before{content:"\f16d"}.icon-flickr:before{content:"\f16e"}.icon-adn:before{content:"\f170"}.icon-bitbucket:before{content:"\f171"}.icon-bitbucket-sign:before{content:"\f172"}.icon-tumblr:before{content:"\f173"}.icon-tumblr-sign:before{content:"\f174"}.icon-long-arrow-down:before{content:"\f175"}.icon-long-arrow-up:before{content:"\f176"}.icon-long-arrow-left:before{content:"\f177"}.icon-long-arrow-right:before{content:"\f178"}.icon-apple:before{content:"\f179"}.icon-windows:before{content:"\f17a"}.icon-android:before{content:"\f17b"}.icon-linux:before{content:"\f17c"}.icon-dribbble:before{content:"\f17d"}.icon-skype:before{content:"\f17e"}.icon-foursquare:before{content:"\f180"}.icon-trello:before{content:"\f181"}.icon-female:before{content:"\f182"}.icon-male:before{content:"\f183"}.icon-gittip:before{content:"\f184"}.icon-sun:before{content:"\f185"}.icon-moon:before{content:"\f186"}.icon-archive:before{content:"\f187"}.icon-bug:before{content:"\f188"}.icon-vk:before{content:"\f189"}.icon-weibo:before{content:"\f18a"}.icon-renren:before{content:"\f18b"}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning{padding:24px;line-height:24px;margin-bottom:24px;border-left:solid 3px transparent}.wy-alert strong,.rst-content .note strong,.rst-content .attention strong,.rst-content .caution strong,.rst-content .danger strong,.rst-content .error strong,.rst-content .hint strong,.rst-content .important strong,.rst-content .tip strong,.rst-content .warning strong,.wy-alert a,.rst-content .note a,.rst-content .attention a,.rst-content .caution a,.rst-content .danger a,.rst-content .error a,.rst-content .hint a,.rst-content .important a,.rst-content .tip a,.rst-content .warning a{color:#fff}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning{background:#e74c3c;color:#fdf3f2;border-color:#d62c1a}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning{background:#e67e22;color:#fbe9d9;border-color:#bf6516}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-info.warning{background:#2980b9;color:#bedcf0;border-color:#20638f}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.hint,.rst-content .wy-alert-success.important,.rst-content .wy-alert-success.tip,.rst-content .wy-alert-success.warning{background:#27ae60;color:#b3eecc;border-color:#1e8449}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning{background:#f3f6f6;border-color:#e1e4e5}.wy-alert.wy-alert-neutral strong,.rst-content .wy-alert-neutral.note strong,.rst-content .wy-alert-neutral.attention strong,.rst-content .wy-alert-neutral.caution strong,.rst-content .wy-alert-neutral.danger strong,.rst-content .wy-alert-neutral.error strong,.rst-content .wy-alert-neutral.hint strong,.rst-content .wy-alert-neutral.important strong,.rst-content .wy-alert-neutral.tip strong,.rst-content .wy-alert-neutral.warning strong{color:#404040}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a{color:#2980b9}.wy-tray-container{position:fixed;top:-50px;left:0;width:100%;-webkit-transition:top 0.2s ease-in;-moz-transition:top 0.2s ease-in;transition:top 0.2s ease-in}.wy-tray-container.on{top:0}.wy-tray-container li{display:none;width:100%;background:#343131;padding:12px 24px;color:#fff;margin-bottom:6px;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1),0px -1px 2px -1px rgba(255,255,255,0.5) inset}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.btn{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:100%;padding:6px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);border-bottom:solid 3px rgba(0,0,0,0.1);background-color:#27ae60;text-decoration:none;font-weight:500;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear;outline-none:false}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;color:#fff;outline:0}.btn:active{border-top:solid 3px rgba(0,0,0,0.1);border-bottom:solid 1px rgba(0,0,0,0.1);box-shadow:0px 1px 2px -1px rgba(0,0,0,0.5) inset}.btn[disabled]{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-danger{background-color:#e74c3c !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#e67e22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#343131}.btn-invert:hover{background-color:#413d3d !important}.btn-link{background-color:transparent !important;color:#2980b9;border-color:transparent}.btn-link:hover{background-color:transparent !important;color:#409ad5;border-color:transparent}.btn-link:active{background-color:transparent !important;border-color:transparent;border-top:solid 1px transparent;border-bottom:solid 3px transparent}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown:hover .wy-dropdown-menu{display:block}.wy-dropdown .caret:after{font-family:fontawesome-webfont;content:"\f0d7";font-size:70%}.wy-dropdown-menu{position:absolute;top:100%;left:0;display:none;float:left;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:0.5em 1em 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:0.5em}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#999;font-size:90%}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button{-webkit-appearance:button;cursor:pointer;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#2980b9}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#e9322d}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:0.8em;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:0.5em 0;color:#404040 !important;display:block}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{padding:6px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.wy-control-group{margin-bottom:24px;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 2px #e74c3c}.wy-control-group.wy-control-group-error textarea{border:solid 2px #e74c3c}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#ccc;font-size:70%;margin-top:0.3125em;font-style:italic}.wy-tag-input-group{padding:4px 4px 0px 4px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}.wy-tag-input-group .wy-tag{display:inline-block;background-color:rgba(0,0,0,0.1);padding:0.5em 0.625em;border-radius:2px;position:relative;margin-bottom:4px}.wy-tag-input-group .wy-tag .wy-tag-remove{color:#ccc;margin-left:5px}.wy-tag-input-group .wy-tag .wy-tag-remove:hover{color:#e74c3c}.wy-tag-input-group label{margin-left:5px;display:inline-block;margin-bottom:0}.wy-tag-input-group input{border:none;font-size:100%;margin-bottom:4px;box-shadow:none}.wy-form-upload{border:solid 1px #ccc;border-bottom:solid 3px #ccc;background-color:#fff;padding:24px;display:inline-block;text-align:center;cursor:pointer;color:#404040;-webkit-transition:border-color 0.1s ease-in;-moz-transition:border-color 0.1s ease-in;transition:border-color 0.1s ease-in;*zoom:1}.wy-form-upload:before,.wy-form-upload:after{display:table;content:""}.wy-form-upload:after{clear:both}@media screen and (max-width: 480px){.wy-form-upload{width:100%}}.wy-form-upload .image-drop{display:none}.wy-form-upload .image-desktop{display:none}.wy-form-upload .image-loading{display:none}.wy-form-upload .wy-form-upload-icon{display:block;font-size:32px;color:#b3b3b3}.wy-form-upload .image-drop .wy-form-upload-icon{color:#27ae60}.wy-form-upload p{font-size:90%}.wy-form-upload .wy-form-upload-image{float:left;margin-right:24px}@media screen and (max-width: 480px){.wy-form-upload .wy-form-upload-image{width:100%;margin-bottom:24px}}.wy-form-upload img{max-width:125px;max-height:125px;opacity:0.9;-webkit-transition:opacity 0.1s ease-in;-moz-transition:opacity 0.1s ease-in;transition:opacity 0.1s ease-in}.wy-form-upload .wy-form-upload-content{float:left}@media screen and (max-width: 480px){.wy-form-upload .wy-form-upload-content{width:100%}}.wy-form-upload:hover{border-color:#b3b3b3;color:#404040}.wy-form-upload:hover .image-desktop{display:block}.wy-form-upload:hover .image-drag{display:none}.wy-form-upload:hover img{opacity:1}.wy-form-upload:active{border-top:solid 3px #ccc;border-bottom:solid 1px #ccc}.wy-form-upload.wy-form-upload-big{width:100%;text-align:center;padding:72px}.wy-form-upload.wy-form-upload-big .wy-form-upload-content{float:none}.wy-form-upload.wy-form-upload-file p{margin-bottom:0}.wy-form-upload.wy-form-upload-file .wy-form-upload-icon{display:inline-block;font-size:inherit}.wy-form-upload.wy-form-upload-drop{background-color:#ddf7e8}.wy-form-upload.wy-form-upload-drop .image-drop{display:block}.wy-form-upload.wy-form-upload-drop .image-desktop{display:none}.wy-form-upload.wy-form-upload-drop .image-drag{display:none}.wy-form-upload.wy-form-upload-loading .image-drag{display:none}.wy-form-upload.wy-form-upload-loading .image-desktop{display:none}.wy-form-upload.wy-form-upload-loading .image-loading{display:block}.wy-form-upload.wy-form-upload-loading .wy-input-prefix{display:none}.wy-form-upload.wy-form-upload-loading p{margin-bottom:0}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}.wy-form-gallery-manage{margin-left:-12px;margin-right:-12px}.wy-form-gallery-manage li{float:left;padding:12px;width:20%;cursor:pointer}@media screen and (max-width: 768px){.wy-form-gallery-manage li{width:25%}}@media screen and (max-width: 480px){.wy-form-gallery-manage li{width:50%}}.wy-form-gallery-manage li:active{cursor:move}.wy-form-gallery-manage li>a{padding:12px;background-color:#fff;border:solid 1px #e1e4e5;border-bottom:solid 3px #e1e4e5;display:inline-block;-webkit-transition:all 0.1s ease-in;-moz-transition:all 0.1s ease-in;transition:all 0.1s ease-in}.wy-form-gallery-manage li>a:active{border:solid 1px #ccc;border-top:solid 3px #ccc}.wy-form-gallery-manage img{width:100%;-webkit-transition:all 0.05s ease-in;-moz-transition:all 0.05s ease-in;transition:all 0.05s ease-in}li.wy-form-gallery-edit{position:relative;color:#fff;padding:24px;width:100%;display:block;background-color:#343131;border-radius:4px}li.wy-form-gallery-edit .arrow{position:absolute;display:block;top:-50px;left:50%;margin-left:-25px;z-index:500;height:0;width:0;border-color:transparent;border-style:solid;border-width:25px;border-bottom-color:#343131}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-controls{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:0.2em 0 0.8em}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-grid-one-col{*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;max-width:1066px;margin-top:1.618em}.wy-grid-one-col:before,.wy-grid-one-col:after{display:table;content:""}.wy-grid-one-col:after{clear:both}.wy-grid-one-col section{display:block;float:left;margin-right:2.35765%;width:100%;background:#fcfcfc;padding:1.618em;margin-right:0}.wy-grid-one-col section:last-child{margin-right:0}.wy-grid-index-card{*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;max-width:460px;margin-top:1.618em;background:#fcfcfc;padding:1.618em}.wy-grid-index-card:before,.wy-grid-index-card:after{display:table;content:""}.wy-grid-index-card:after{clear:both}.wy-grid-index-card header,.wy-grid-index-card section,.wy-grid-index-card aside{display:block;float:left;margin-right:2.35765%;width:100%}.wy-grid-index-card header:last-child,.wy-grid-index-card section:last-child,.wy-grid-index-card aside:last-child{margin-right:0}.wy-grid-index-card.twocol{max-width:768px}.wy-grid-index-card.twocol section{display:block;float:left;margin-right:2.35765%;width:48.82117%}.wy-grid-index-card.twocol section:last-child{margin-right:0}.wy-grid-index-card.twocol aside{display:block;float:left;margin-right:2.35765%;width:48.82117%}.wy-grid-index-card.twocol aside:last-child{margin-right:0}.wy-grid-search-filter{*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;margin-bottom:24px}.wy-grid-search-filter:before,.wy-grid-search-filter:after{display:table;content:""}.wy-grid-search-filter:after{clear:both}.wy-grid-search-filter .wy-grid-search-filter-input{display:block;float:left;margin-right:2.35765%;width:74.41059%}.wy-grid-search-filter .wy-grid-search-filter-input:last-child{margin-right:0}.wy-grid-search-filter .wy-grid-search-filter-btn{display:block;float:left;margin-right:2.35765%;width:23.23176%}.wy-grid-search-filter .wy-grid-search-filter-btn:last-child{margin-right:0}.wy-spinner{height:30px;width:30px;margin:0 auto;position:relative;-webkit-animation:rotation 0.6s infinite linear;-moz-animation:rotation 0.6s infinite linear;animation:rotation 0.6s infinite linear;border:8px solid #e7f2fa;border-top:8px solid #2980b9;border-radius:100%}.wy-spinner.wy-spinner-margin{margin:24px auto}.btn .wy-spinner{display:inline-block;height:14px;width:14px;border-width:4px;border-color:rgba(0,0,0,0.1);border-top-color:#fff;top:1px}.wy-logo .wy-spinner{border-color:rgba(0,0,0,0.1);border-top-color:#fcfcfc;margin-bottom:0.809em}.wy-spinner.wy-spinner-inline{display:inline-block;margin:none}.wy-spinner.wy-spinner-large{height:45px;width:45px;border-width:12px}.wy-spinner.wy-spinner-small{height:16px;width:16px;border-width:4px}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(359deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0deg)}to{-moz-transform:rotate(359deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0deg)}to{-o-transform:rotate(359deg)}}@keyframes rotation{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.wy-table,.rst-content table.docutils,.rst-content table.docutils.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.docutils.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.docutils.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.docutils.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.docutils.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.docutils.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.docutils.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.docutils.field-list td p{line-height:18px;margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}a{color:#2980b9;text-decoration:none}a:hover{color:#3091d1}.link-danger{color:#e74c3c}.link-danger:hover{color:#d62c1a}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}small{font-size:80%}code,.rst-content dl dt,.rst-content dl dl dt,.rst-content tt.literal{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:80%;padding:0 5px;font-family:"Incosolata","Consolata","Monaco",monospace;color:#e74c3c;overflow-x:auto}code.code-large,.rst-content dl dt.code-large,.rst-content tt.code-large.literal{font-size:90%}.full-width{width:100%}.wy-plain-list-disc,.rst-content .section ul,.rst-content ul.simple,.rst-content ul.stimple ul,.rst-content .toctree-wrapper ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content ul.simple li,.rst-content ul.stimple ul li,.rst-content .toctree-wrapper ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content ul.simple li ul,.rst-content ul.stimple ul li ul,.rst-content .toctree-wrapper ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content ul.simple li li,.rst-content ul.stimple ul li li,.rst-content .toctree-wrapper ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content ul.simple li li li,.rst-content ul.stimple ul li li li,.rst-content .toctree-wrapper ul li li li{list-style:square}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.simple,.rst-content ol.arabic{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.simple li,.rst-content ol.arabic li{list-style:decimal;margin-left:24px}.wy-type-large{font-size:120%}.wy-type-normal{font-size:100%}.wy-type-small{font-size:100%}.wy-type-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980b9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27ae60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#e74c3c !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}.wy-tooltip{position:absolute;z-index:300;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;-webkit-transition:opacity 2s ease-in;-moz-transition:opacity 2s ease-in;transition:opacity 2s ease-in}.wy-tooltip.on{opacity:0.8}.wy-tooltip.top{margin-top:-2px}.wy-tooltip.right{margin-left:2px}.wy-tooltip.bottom{margin-top:2px}.wy-tooltip.left{margin-left:-2px}.wy-tooltip.top .wy-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000}.wy-tooltip.left .wy-tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.wy-tooltip.bottom .wy-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000}.wy-tooltip.right .wy-tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000}.wy-tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.wy-tooltip-arrow{position:absolute;width:0;height:0}.rst-content img{max-width:800px;height:auto !important}.rst-content .section>img{margin-bottom:24px}.rst-content .note p.last,.rst-content .note p.first,.rst-content .attention p.last,.rst-content .attention p.first,.rst-content .caution p.last,.rst-content .caution p.first,.rst-content .danger p.last,.rst-content .danger p.first,.rst-content .error p.last,.rst-content .error p.first,.rst-content .hint p.last,.rst-content .hint p.first,.rst-content .important p.last,.rst-content .important p.first,.rst-content .tip p.last,.rst-content .tip p.first,.rst-content .warning p.last,.rst-content .warning p.first{margin-bottom:0}.rst-content .admonition-title{font-weight:bold}.rst-content .admonition-title:before{margin-right:4px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"\f0c1";font-family:fontawesome-webfont;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content table.docutils.field-list{border:none}.rst-content table.docutils.field-list td{border:none}.rst-content table.docutils.field-list .field-name{padding-right:5px;text-align:left}.rst-content table.docutils.field-list .field-body{text-align:left;padding-left:0}.rst-content dl dt{display:inline-block;margin-bottom:6px;font-size:80%;line-height:normal;background:#ccc;color:#333;border:none;border-left:solid 3px #999;padding:6px}.rst-content dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl dt .descname,.rst-content dl dt .descclassname,.rst-content dl dt big,.rst-content dl dt em{font-size:100% !important;line-height:normal}.rst-content dl dd{margin-left:24px}.rst-content dl dl dt{display:inline-block;margin-bottom:6px;font-size:80%;line-height:normal}.rst-content dl dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content tt{font-family:"Incosolata","Consolata","Monaco",monospace}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content #search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}.rst-content #search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}.rst-content #search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}.rst-content #search-results .context{color:gray;font-size:90%}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9b59b6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;width:100%;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Menlo,"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Consolas,"source-code-pro-1","source-code-pro-2",monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Menlo,"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Consolas,"source-code-pro-1","source-code-pro-2",monospace;font-size:12px;line-height:1.5;display:block;overflow:auto}pre.literal-block{@extends .codeblock;;background:#343131;color:#fff;padding:42px 12px 12px 12px;font-family:Menlo,"Bitstream Vera Sans Mono","DejaVu Sans Mono",Monaco,Consolas,"source-code-pro-1","source-code-pro-2",monospace;font-size:12px;line-height:1.5;position:relative}pre.literal-block:after{position:absolute;content:" Terminal";background:#f3f6f6;color:#404040;top:0;left:0;width:100%;padding:6px 0}@media print{.codeblock,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#f8f8f8;border:1px solid #ccc;padding:1.5px 5px}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#eaf2f5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980b9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li.current a .current{background:#c9c9c9}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-side-nav-search{z-index:200;background-color:#2980b9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;z-index:400;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-success .rst-versions .rst-current-version .wy-input-context,.rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .rst-versions .rst-current-version .wy-input-context,.rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .rst-versions .rst-current-version .wy-input-context,.rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-info .rst-versions .rst-current-version .wy-input-context,.rst-versions .rst-current-version .wy-tag-input-group .wy-tag .wy-tag-remove,.wy-tag-input-group .wy-tag .rst-versions .rst-current-version .wy-tag-remove,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink{color:#fcfcfc}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} django-guardian-1.4.1/docs/theme/rtd_theme/static/font/0000700000175000017500000000000012644306605023215 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/theme/rtd_theme/static/font/fontawesome_webfont.ttf0000700000175000017500000023234412634712306030020 0ustar brianbrian00000000000000`FFTMepaGDEF OS/2z(`cmap5jgaspglyf2head\"6hhea $hmtxlocaq4maxp!D name<e!dpost2$webfRQ4=T0<33sZ3pyrs@ # dHN@ / _!"""`>N^n~.>N^n~ / _!"""`!@P`p 0@P`pd]YTC ߷ݹ   p7!!!@pp p1]!2#!"&463!&54>3!2+@&&&&@+$(($F#+&4&&4&x+#+".4>32".4>32467632DhgZghDDhg-iWDhgZghDDhg-iW&@ (8 2N++NdN+';2N++NdN+'3 8!  #"'#"$&6$ rL46$܏ooo|W%r4L&V|oooܳ%=M%+".'&%&'3!26<.#!";2>767>7#!"&5463!2 %3@m00m@3%    @ :"7..7":6]^B@B^^BB^ $΄+0110+$ (   t1%%1+`B^^B@B^^"'.54632>324 #LoP$$Po>Z$_dC+I@$$@I+"#"'%#"&547&547%62V??V8<8y   b% I))9I  + % %#"'%#"&547&547%62q2ZZ2IzyV)??V8<8)>~>[   2 b% I))9I '%#!"&54>322>32 &6 yy 6Fe= BSSB =eF6 >xx5eud_C(+5++5+(C_due> /?O_o54&+";2654&+";2654&+";264&#!"3!2654&+";2654&+";264&#!"3!2654&+";2654&+";2654&+";267#!"&5463!2&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&^BB^^B@B^@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&B^^B@B^^/?#!"&5463!2#!"&5463!2#!"&5463!2#!"&5463!2L44LL44LL44LL44LL44LL44LL44LL44L4LL44LL4LL44LL4LL44LL4LL44LL /?O_o#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!28((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(88((88(@(8 (88((88(88((88(88((88(88((88(88((88(88((88(88((88(88((88(88((88/?O_#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!28((88(@(88((88(@(88(@(88((88((88(@(88(@(88((88(@(88((8 (88((88(88((88(88((88(88((88(88((88(88((88y"/&4?62 62,PP&PP,jPn#$"' "/&47 &4?62 62 PP&P&&P&P&P&&P&P#+D++"&=#"&=46;546;232  #"'#"$&6$   @    @  rK56$܏ooo|W@    @   rjK&V|oooܳ0#!"&=463!2  #"'#"$&6$   @ rK56$܏ooo|W@  @ rjK&V|oooܳ)5 $&54762>54&'.7>"&5462zz+i *bkQнQkb* j*LhLLhLzzBm +*i JyhQQhyJ i*+ mJ4LL44LL/?O%+"&=46;2%+"&546;2%+"&546;2+"&546;2+"&546;2`r@@r@@n4&"2#"/+"&/&'#"'&'&547>7&/.=46?67&'&547>3267676;27632Ԗ #H  ,/ 1)  ~'H  (C  ,/ 1)  $H ԖԖm 6%2X  % l2 k r6 [21 ..9Q $ k2 k w3 [20/;Cg+"&546;2+"&546;2+"&546;2!3!2>!'&'!+#!"&5#"&=463!7>3!2!2@@@@@@@`0 o`^BB^`5FN(@(NF5 @@@L%%Ju  @LSyuS@%44%f5#!!!"&5465 7#"' '&/&6762546;2&&??>  LL >  X   &&&AJ A J Wh#3!!"&5!!&'&'#!"&5463!2`(8x 8((88((`8(8( 9 h(88(@(8(` ,#!"&=46;46;2.  6 $$ @(r^aa@@`(_^aa2NC5.+";26#!26'.#!"3!"547>3!";26/.#!2W  .@   @.$S   S$@   9I   I6>  >%=$4&"2$4&"2#!"&5463!2?!2"'&763!463!2!2&4&&4&&4&&48(@(88(ч::(8@6@*&&*4&&4&&4&&4& (88(@(8888)@)'&&@$0"'&76;46;232  >& $$ `  (r^aa` @`2(^aa$0++"&5#"&54762  >& $$ ^ ?  @(r^aa` ? (^aa #!.'!!!%#!"&547>3!2<<<_@`&& 5@5 @  &&>=(""='#"'&5476.  6 $$   ! (r^aaJ %%(_^aa3#!"'&?&#"3267672#"$&6$3276&@*hQQhwI mʬzzk)'@&('QнQh_   z8zoe$G!"$'"&5463!23267676;2#!"&4?&#"+"&=!2762@hk4&&&GaF * &@&ɆF * Ak4&nf&&&4BHrd@&&4rd  Moe&/?O_o+"&=46;25+"&=46;25+"&=46;2#!"&=463!25#!"&=463!25#!"&=463!24&#!"3!26#!"&5463!2 @  @  @  @  @  @  @    @    @    @   ^B@B^^BB^`@  @ @  @ @  @ @  @ @  @ @  @ 3@  MB^^B@B^^!54&"#!"&546;54 32@Ԗ@8(@(88( p (8jj(88(@(88@7+"&5&5462#".#"#"&5476763232>32@@ @ @KjKך=}\I&:k~&26]S &H&  &H5KKut,4, & x:;*4*&K#+"&546;227654$ >3546;2+"&="&/&546$ <X@@Gv"DװD"vG@@X<4L41!Sk @ G< _bb_ 4.54632&4&&M4&UF &""""& F&M&&M&%.D.%G-Ik"'!"&5463!62#"&54>4.54632#"&54767>4&'&'&54632#"&547>7676'&'.'&54632&4&&M4&UF &""""& FU &'8JSSJ8'&  &'.${{$.'& &M&&M&%.D.%7;&'66'&;4[&$ [2[ $&[  #/37#5#5!#5!!!!!!!#5!#5!5##!35!!! #'+/37;?3#3#3#3#3#3#3#3#3#3#3#3#3#3#3#3#3???? ^>>~??????~??~??^??^^? ^??4&"2#"'.5463!2KjKKjv%'45%5&5L45&% jKKjK@5%%%%54L5&6'k54&"2#"'.5463!2#"&'654'.#32KjKKjv%'45%5&5L45&%%'4$.%%5&55&% jKKjK@5%%%%54L5&6'45%%%54'&55&6' yTdt#!"&'&74676&7>7>76&7>7>76&7>7>76&7>7>63!2#!"3!2676'3!26?6&#!"3!26?6&#!"g(sAeM ,*$/ !'& JP$G] x6,& `   h `   "9Hv@WkNC<.  &k& ( "$p" . #u&#  %!' pJvwEF#  @   @  2#"' #"'.546763!''!0#GG$/!''! 8""8  X! 8" "8  <)!!#"&=!4&"27+#!"&=#"&546;463!232(8&4&&4 8(@(8 qO@8((`(@Oq8(&4&&4&@` (88( Oq (8(`(q!)2"&42#!"&546;7>3!2  Ijjjj3e55e3gr`Ijjjj1GG1r Q37&'&#7676767;"'&#"4?6764/%2"%ժIM <5:YK5 g'9') //8Pp]`O8:8 /\>KM'B0Q>_O 4h 7f:jCR1'  -! rA@   %e%3267654'&'&#"32654'&#"767676765'&'&'&'&/-72632;2/&+L@%&):SPJ+BUT4N-M.   3T|-)JXg+59-,*@?|Z\2BJIRtT! RHForB^ pKK ,!zb+e^  BW  S  //rAFt/9)ijLU>7H$$ J767676?7>5?5&'&'7327>3"#"'&/&IL( 8  )g ='"B!F76@% ,=&+ @7$ ~)J~U%@ @,Q5(?2&g  9,&kɞ-   i;?!6?2&'.'&'&"#"2#"'&#"#&5'56767676'&64&'&'&#"#&'52"/&6;#"&?62+Q6s%"* ' G+"!  1( 8nMHX0: &n+r  , !~:~!PP!~:~!P5d: +UM6a'.'  -   !& #>q\ 0f!)V%%%%h;?!6?2&'.'&'&"#"52#"'&#"#&5'56767676''&'&'&#"#&'5&=!/&4?6!546Q6s>"* ' g)^!  1( 8nMHR-: &n2  , %%%%5d: +UM6a'4.'  -    !& #(,  0f!)V:~!PP!~:~!PP!/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?%#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&f&&&&f&&&&f&&&&/?O_o%+"&=46;2+"&=46;2+"&=46;2#!"&=463!2+"&=46;2#!"&=463!2#!"&=463!2#!"&=463!2        @     @   @   @   s  s    s    s  s  /?O#"'&47632#!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2     @     @   @  @          s  s  s  /?O#"&54632 #!"&=463!2#!"&=463!2#!"&=463!2#!"&=463!2`      @     @   @  @     @   s  s  s  #"'#!"&5463!2632' mw@www '*wwww."&462!5 !"3!2654&#!"&5463!2pppp@  @ ^BB^^B@B^ppp@@  @    @B^^BB^^k%!7'34#"3276' !7632k[[v  6`%`$65&%[[k `5%&&'4&"2"&'&54 Ԗ!?H?!,,ԖԖmF!&&!Fm,%" $$ ^aa`@^aa-4'.'&"26% 547>7>2"KjKXQqYn 243nYqQ$!+!77!+!$5KK,ԑ ]""]ً 9>H7'3&7#!"&5463!2'&#!"3!26=4?6 !762xtt`  ^Qwww@?6 1B^^B@B^ @(` `\\\P`tt8`  ^Ͼww@w 1^BB^^B~ @` \ \P+Z#!"&5463!12+"3!26=47676#"'&=# #"'.54>;547632www M8 pB^^B@B^ 'sw- 9*##;Noj' #ww@w "^BB^^B  *  "g`81T`PSA:'*4/D#!"&5463!2#"'&#!"3!26=4?632"'&4?62 62www@?6 1 B^^B@B^ @ BRnBBn^ww@w 1 ^BB^^B @ BnnBC"&=!32"'&46;!"'&4762!#"&4762+!54624&&4&&44&&4&&44&&44&&4&&44&&6'&'+"&546;267: &&&& s @  Z&&&&Z +6'&''&'+"&546;267667: : &&&&  s @  :  Z&&&&Z  : z6'&''&47667S::s @  : 4 : | &546h!!0a   $#!"&5463!2#!"&5463!2&&&&&&&&@&&&&&&&&#!"&5463!2&&&&@&&&&&54646&5-:s  :  :4:  +&5464646;2+"&5&5-&&&&:s  :  : &&&& :  &54646;2+"&5-&&&&s  : &&&&  62#!"&!"&5463!24 @ &&&&-:&&&&5 &4762 "t%%%k%K%%%%K%k%%k%%%K%k%&j%K%uK"/&547 &54?62K%t%j%L%%%%L$l$%4'u%%K'45%'45%K&&u%#/54&#!4&+"!"3!;265!26 $$ &&&&&&&&@^aa@&&&&&&&&+^aa54&#!"3!26 $$ &&&&@^aa@&&&&+^aa+74/7654/&#"'&#"32?32?6 $$ }ZZZZ^aaZZZZ^aa#4/&"'&"327> $$ [4h4[j^aa"ZiZJ^aa:F%54&+";264.#"32767632;265467>$ $$ oW  5!"40K(0?i+! ":^aaXRd D4!&.uC$=1/J=^aa.:%54&+4&#!";#"3!2654&+";26 $$ ```^aa^aa/_#"&=46;.'+"&=32+546;2>++"&=.'#"&=46;>7546;232m&&m l&&l m&&m l&&ls&%&&%&&%&&%&&&l m&&m l&&l m&&m ,&%&&%&&%&&%&#/;"/"/&4?'&4?627626.  6 $$ I     ͒(r^aaɒ    (_^aa , "'&4?6262.  6 $$ Z4f44fz(r^aaZ&4ff4(_^aa "4'32>&#" $&6$  WoɒV󇥔 zzz8YW˼[?zz:zz@5K #!#"'&547632!2A4@%&&K%54'u%%&54&K&&4A5K$l$L%%%54'&&J&j&K5K #"/&47!"&=463!&4?632%u'43'K&&%@4AA4&&K&45&%@6%u%%K&j&%K55K&$l$K&&u#5K@!#"'+"&5"/&547632K%K&56$K55K$l$K&&#76%%53'K&&%@4AA4&&K&45&%%u'5K"#"'&54?63246;2632K%u'45%u&&J'45%&L44L&%54'K%5%t%%$65&K%%4LL4@&%%K',"&5#"#"'.'547!34624&bqb>#  5&44& 6Uue7D#  "dž&/#!"&546262"/"/&47'&463!2 &@&&4L  r&4  r L&& 4&&&L rI@& r  L4&& s/"/"/&47'&463!2#!"&546262&4  r L&& &@&&4L  r@@& r  L4&& 4&&&L r##!+"&5!"&=463!46;2!28(`8((8`(88(8((8(8 (8`(88(8((8(88(`8#!"&=463!28(@(88((8 (88((88z5'%+"&5&/&67-.?>46;2%6.@g.L44L.g@. .@g. L44L .g@.g.n.4LL43.n.gg.n.34LL4͙.n.g -  $54&+";264'&+";26/a^    ^aa fm  @ J%55!;263'&#"$4&#"32+#!"&5#"&5463!"&46327632#!2$$8~+(888(+}(`8((8`]]k==k]]8,8e8P88P8`(88(@MMO4&#"327>76$32#"'.#"#".'.54>54&'&54>7>7>32&z&^&./+>*>J> Wm7' '"''? &4&c&^|h_bml/J@L@ #M6:D 35sҟw$ '% ' \t3#!"&=463!2'.54>54''  @ 1O``O1CZZ71O``O1BZZ7@  @ N]SHH[3`)TtbN]SHH[3^)Tt!1&' 547 $4&#"2654632 '&476 ==嘅}(zVl''ٌ@uhyyhu9(}VzD##D# =CU%7.5474&#"2654632%#"'&547.'&476!27632#76$7&'7+NWb=嘧}(zVi\j1  z,X Y[6 $!%'FuJiys?_9ɍ?kyhun(}Vz YF  KA؉La  02-F"@Qsp@_!3%54&+";264'&+";26#!"&'&7>2    #%;"";%#`,@L 5 `   `  L`4LH` `   a 5 L@ #37;?Os!!!!%!!!!%!!!!!!!!%!!4&+";26!!%!!!!74&+";26%#!"&546;546;2!546;232 `@ `@ @@ @ @  @  @  @  @ L44LL4^B@B^^B@B^4L  @@@@    @@   @@    M4LL44L`B^^B``B^^B`L7q.+"&=46;2#"&=".'673!54632#"&=!"+"&=46;2>767>3!546327>7&54>$32dFK1A  0) L.٫C58.H(Ye#3C $=463!22>=463!2#!"&5463!2#!"&5463!2H&&/7#"&463!2!2KjKKjKjKKj &&&%&& &5jKKjKKjKKjK%z 0&4&&3D7&4& %&#!"&5463!2!2\@\\@\\@\\\\ W*#!"&547>3!2!"4&5463!2!2W+B"5P+B@"5^=\@\ \H#t3G#3G:_Ht\\ @+32"'&46;#"&4762&&4&&44&&44&&4@"&=!"'&4762!54624&&44&&44&&4&& /!!!!4&#!"3!26#!"&5463!2  @ ^BB^^B@B^  @ @B^^BB^^0@67&#".'&'#"'#"'32>54'6#!"&5463!28ADAE=\W{O[/5dI kDtpČe1?*w@www (M& B{Wta28r=Ku?RZ^GwT -@www#7#546;5#"#3!#!"&5463!28nw@wwwjm1'ې{@www#'.>4&#"26546326"&462!5!&  !5!!=!!%#!"&5463!2B^8(Ԗ>@|K55KK55K^B(8ԖԖ€>v5KK55KKHG4&"&#"2654'32#".'#"'#"&54$327.54632@pp)*Pppp)*Pb '"+`N*(a;2̓c`." b PTY9ppP*)pppP*)b ".`(*Nͣ2ͣ`+"' b MRZB4&"24&"264&"26#"/+"&/&'#"'&547>7&/.=46?67&'&547>3267676;27632#"&'"'#"'&547&'&=4767&547>32626?2#"&'"'#"'&547&'&=4767&547>32626?2ԖLhLKjKLhLKjK "8w s%(  ")v  >  "8x s"+  ")v  <  3zLLz3 3>8L3)x3 3zLLz3 3>8L3)x3 ԖԖ4LL45KK54LL45KK #)0C wZ l/ Y N,& #)0C vZl. Y L0"qG^^Gqq$ ]G)FqqG^^Gqq$ ]G)Fq%O#"'#"&'&4>7>7.546$ '&'&'# '32$7>54'VZ|$2 $ |E~E<| $ 2$|ZV:(t}X(  &%(Hw쉉xH(%& (XZT\MKG<m$4&"24&#!4654&#+32;254'>4'654&'>7+"&'&#!"&5463!6767>763232&4&&4N2`@`%)7&,$)' %/0Ӄy#5 +1 &<$]`{t5KK5$e:1&+'3TF0h4&&4&3M:;b^v+D2 5#$IIJ 2E=\$YJ!$MCeM-+(K55KK5y*%Au]c=p4&"24&'>54'64&'654&+"+322654&5!267+#"'.'&'&'!"&5463!27>;2&4&&4+ 5#bW0/% ')$,&7)%`@``2Nh0##T3'"( 0;e$5KK5 tip<& 1&4&&4&#\=E2 JIURI$#5 2D+v^b;:M2gc]vDEA%!bSV2MK55K(,,MeCM$!J@#"&547&547%6@?V8 b% I)94.""'." 67"'.54632>32+C`\hxeH>Hexh\`C+ED4 #LoP$$Po>Q|I.3MCCM3.I|Q/Z$_dC+I@$$@I+ (@%#!"&5463!2#!"3!:"&5!"&5463!462 ww@  B^^B  4&@&&&4 `  ww   ^B@B^ 24& && &%573#7.";2634&#"35#347>32#!"&5463!2FtIG9;HIxI<,tԩw@wwwz4DD43EEueB&#1s@www .4&"26#!+"'!"&5463"&463!2#2&S3 Ll&c4LL44LL4c@& &{LhLLhL'?#!"&5463!2#!"3!26546;2"/"/&47'&463!2www@B^^B@B^@&4t  r &&`ww@w@^BB^^B@R&t r  4&&@"&5!"&5463!462 #!"&54&>3!2654&#!*.54&>3!24&@&&&4 sw  @B^^B  @w4& && &3@w   ^BB^    I&5!%5!>732#!"&=4632654&'&'.=463!5463!2!2JJSq*5&=CKuuKC=&5*q͍S8( ^B@B^ (8`N`Ѣ΀GtO6)"M36J[E@@E[J63M")6OtG(8`B^^B`8%-3%'&76'&76''&76'&76'&6#5436&76+".=4'>54'6'&&"."&'./"?+"&5463!2  2  5    z<: Ʃw 49[aA)O%-j'&]]5r,%O)@a[9( 0BA; + >HCwww  5 /)  u    @wa-6OUyU[q ( - q[UyUP6$C +) (  8&/ &ww'?$4&"2$4&"2#!"&5463!3!267!2#!#!"&5!"'&762&4&&4&&4&&48(@(88(c==c(8*&&*6&4&&4&&4&&4& (88(@(88HH88`(@&&('@1d4&'.54654'&#"#"&#"32632327>7#"&#"#"&54654&54>76763232632   N<;+gC8A`1a99gw|98aIe$IVNz<:LQJ  ,-[% 061I()W,$-7,oIX()oζA;=N0 eTZ  (O#".'&'&'&'.54767>3232>32 e^\3@P bMO0# 382W# & 9C9 Lĉ" 82<*9FF(W283 #0OMb P@3\^e FF9*<28 "L 9C9 & #!"3!2654&#!"&5463!2`B^^B@B^^ީwww@w^BB^^B@B^ww@w#!72#"' #"'.546763YY !''!0#GG$/!''!&UUjZ 8""8  X! 8" "8 EU4'./.#"#".'.'.54>54.'.#"32676#!"&5463!2G55 :8 c7 )1)  05.D <90)$9w@wwwW + AB 7c  )$+ -.1 9$)0< D.59@www,T1# '327.'327.=.547&54632676TC_LҬ#+i!+*pDNBN,y[`m`%i]hbEm}a u&,SXK &$f9s? !#!#3546;#"'/8 "# R&=4'>54'6'&&"."&'./"?'&54$ 49[aA)O%-j'&]]5r,%O)@a[9( 0BA; + >HCaaoMa-6OUyU[q ( - q[UyUP6$C +) (  8&/ &fMa%+"&54&"32#!"&5463!54 &@&Ԗ`(88(@(88(r&&jj8((88(@(8#'+2#!"&5463"!54&#265!375!35!B^^BB^^B   `^B@B^^BB^  ` !="&462+"&'&'.=476;+"&'&$'.=476; pppp$!$qr % }#ߺppp!E$ rqܢ# % ֻ!)?"&462"&4624&#!"3!26!.#!"#!"&547>3!2/B//B//B//B @   2^B@B^\77\aB//B//B//B/@    ~B^^B@2^5BB52.42##%&'.67#"&=463! 25KK5L4_u:B&1/&.- zB^^B4LvyKjK4L[!^k'!A3;):2*54&#"+323254'>4'654&'!267+#"'&#!"&5463!2>767>32!2&4&&4N2$YGB (HGEG HQ#5K4Li!<;5KK5 A# ("/?&}vh4&&4&3M95S+C=,@QQ9@@IJ 2E=L5i>9eME;K55K J7R>@#zD<7?s%3#".'.'&'&'.#"!"3!32>$4&"2#!"#"&?&547&'#"&5463!&546323!2` #A<(H(GY$2NL4K5#aWTƾh&4&&4K5;=!ihv}&?/"( #A  5K2*!Q@.'!&=C+S59M34L=E2 JI UR@@&4&&4&5K;ELf9>igR7J K5h4&"24#"."&#"4&#"".#"!54>7#!"&54.'&'.5463246326326&4&&4IJ 2E=L43M95S+C=,@QQ9@@E;K55K J7R>@#zD9eMZ4&&4&<#5K4LN2$YGB (HGEG HV;5KK5 A# ("/?&}vhi!<4<p4.=!32>332653272673264&"2/#"'#"&5#"&54>767>5463!2@@2*! Q@.'!&=C+S59M34L.9E2 JI UR&4&&4&Lf6Aig6Jy#@>R7J K55K;E@TƾH #A<(H(GY$2NL4K#5#a=4&&4&D=ihv}&?/"( #A  5KK5;+54&#!764/&"2?64/!26 $$ & [6[[j6[&^aa@&4[[6[[6&+^aa+4/&"!"3!277$ $$ [6[ &&[6j[ ^aae6[j[6&&4[j[^aa+4''&"2?;2652?$ $$ [6[[6&&4[^aaf6j[[6[ &&[^aa+4/&"4&+"'&"2? $$ [6&&4[j[6[j^aad6[&& [6[[j^aa   $2>767676&67>?&'4&'.'.'."#&6'&6&'3.'.&'&'&&'&6'&>567>#7>7636''&'&&'.'"6&'6'..'/"&'&76.'7>767&.'"76.7"7"#76'&'.'2#22676767765'4.6326&'.'&'"'>7>&&'.54>'>7>67&'&#674&7767>&/45'.67>76'27".#6'>776'>7647>?6#76'6&'676'&67.'&'6.'.#&'.&6'&.5/a^D&"      4   $!   #          .0"Y +  !       $     "  +       Α      ^aa                        P   ' -( # * $  "  !     * !   (         $      2 ~/$4&"2 #"/&547#"32>32&4&&4V%54'j&&'/덹:,{ &4&&4&V%%l$65&b'Cr! " k[G +;%!5!!5!!5!#!"&5463!2#!"&5463!2#!"&5463!2&&&&&&&&&&&&@&&&&&&&&&&&&{#"'&5&763!2{' **)*)'/!5!#!"&5!3!26=#!5!463!5463!2!2^B@B^&@&`^B`8(@(8`B^ B^^B&&B^(88(^G 76#!"'&? #!"&5476 #"'&5463!2 '&763!2#"'c)'&@**@&('c (&*cc*&' *@&('c'(&*cc*&('c'(&@*19AS[#"&532327#!"&54>322>32"&462 &6 +&'654'32>32"&462QgRp|Kx;CByy 6Fe= BPPB =eF6 ԖV>!pRgQBC;xK|Ԗ{QNa*+%xx5eud_C(+5++5+(C_due2ԖԖ>NQ{u%+*jԖԖp!Ci4/&#"#".'32?64/&#"327.546326#"/&547'#"/&4?632632(* 8( !)(A(')* 8( !USxySSXXVzxTTUSxySSXXVzxT@(  (8 *(('( (8 SSUSx{VXXTTSSUSx{VXXT#!"5467&5432632t,Ԟ;F`j)6,>jK?s !%#!"&7#"&463!2+!'5#8EjjE8@&&&&@XYY&4&&4&qDS%q%N\jx2"&4#"'#"'&7>76326?'&'#"'.'&676326326&'&#"32>'&#"3254?''74&&4&l NnbSVZ bRSD zz DSRb)+USbn \.2Q\dJ'.2Q\dJ.Q2.'Jd\Q2.'Jd`!O` ` &4&&4r$#@B10M5TNT{L5T II T5L;l'OT4M01B@#$*3;$*3;;3*$;3*$: $/ @@Qq`@"%3<2#!"&5!"&5467>3!263! !!#!!46!#!(88(@(8(8(`((8D<++<8(`(8(`8(@(88( 8((`(8((<`(8(``(8||?%#"'&54632#"'&#"32654'&#"#"'&54632|udqܟs] = OfjL?R@T?"& > f?rRX=Edudsq = _MjiL?T@R?E& f > =XRr?b!1E)!34&'.##!"&5#3463!24&+";26#!"&5463!2 08((88(@(8  8((88((`(1  `(88((88(@  `(88(@(8(`#!"&5463!2w@www`@www/%#!"&=463!2#!"&=463!2#!"&=463!2&&&&&&&&&&&&&&&&&&&&&&&&@'7G$"&462"&462#!"&=463!2"&462#!"&=463!2#!"&=463!2ppppppp @   ppp @    @   Рpppppp  ppp    <L\l|#"'732654'>75"##5!!&54>54&#"'>3235#!"&=463!2!5346=#'73#!"&=463!2#!"&=463!2}mQjB919+i1$AjM_3</BB/.#U_:IdDRE @  k*Gj @   @   TP\BX-@8 C)5Xs J@$3T4+,:;39SG2S.7<  vcc)( %Ll}    5e2#!"&=463%&'&5476!2/&'&#"!#"/&'&=4'&?5732767654'&@02uBo  T25XzrDCBBEh:%)0%HPIP{rQ9f#-+>;I@KM-/Q"@@@#-a[ $&P{<8[;:XICC>.'5oe71#.0(  l0&%,"J&9%$<=DTIcs&/6323276727#"327676767654./&'&'737#"'&'&'&54'&54&#!"3!260% <4"VRt8<@< -#=XYhW8+0$"+dTLx-'I&JKkmuw<=V@!X@ v '|N;!/!$8:IObV;C#V  &   ( mL.A:9 !./KLwPM$@@ /?O_o%54&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!2654&#!"3!26#!"&5463!2@@@@@@@@@^BB^^B@B^NB^^B@B^^#+3 '$"/&4762%/?/?/?/?%k*66bbbb|<<<bbbbbbbb%k66Ƒbbb<<<<^bbbbbb@M$4&"2!#"4&"2&#"&5!"&5#".54634&>?>;5463!2LhLLh LhLLhL! 'ԖԖ@' !&  ?&&LhLLhL hLLhL jjjj &@6/" &&J#"'676732>54.#"7>76'&54632#"&7>54&#"&54$ ok; -j=yhwi[+PM 3ѩk=J%62>VcaaQ^ ]G"'9r~:`}Ch 0=Z٤W=#uY2BrUI1^Fk[|aL2#!67673254.#"67676'&54632#"&7>54&#"#"&5463ww+U ,iXբW<"uW1AqSH1bdww"3g!"&'>32 327#".54632%#!654.54>4&'.'37!"463!2!#!!3 _Znh7 1-$ g &Wa3\@0g]Bj> ҩw,',CMC,.BA.51 KL~w9&!q[-A"" ""$!'JNv=Cdy4Shh/`R~ wITBqIE2;$@;Ft.  @M_~w`-co%4.'&#"32>4.#"326!#!".547>7&54>7#"&54676!#!5!3l $-1!6hpT6Gs~@;k^7x!=kB]f0@\3aWGN.BB.!5@@5!;y{^<% L@ (վ^lG'!$"" "$8^2"&5!#2!46#!"&5463!2rM* *M~~M**M~~M*jjj&&&&`P%挐|NN||NN|*jjjj@&&&&@ "'&463!2@4@&Z4@4&@ #!"&4762&&4Z4&&4@@ "'&4762&4@4&@&4&@ "&5462@@4&&44@&&@ 3!!%!!26#!"&5463!2`m` ^BB^^B@B^  `@B^^BB^^@ "'&463!2#!"&4762@4@&&&&44@4&Z4&&4@ "'&463!2@4@&4@4&@ #!"&4762&&4Z4&&4@:#!"&5;2>76%6+".'&$'.5463!2^B@B^,9j9Gv33vG9H9+bI\ A+=66=+A [">nSMA_:B^^B1&c*/11/*{'VO3@/$$/@*?Nh^l+!+"&5462!4&#"!/!#>32]_gTRdgdQV?U I*Gg?!2IbbIJaaiwE3300 084#"$'&6?6332>4.#"#!"&54766$32z䜬m IwhQQhbF*@&('kz   _hQнQGB'(&*eoz(q!#"'&547"'#"'&54>7632&4762.547>32#".'632%k'45%&+~(  (h  &  \(  (  &  ~+54'k%5%l%%l$65+~  &  (  (\  &  h(  (~+%'!)19K4&"24&"26.676&$4&"24&"24&"2#!"'&46$ KjKKj KjKKje2.e<^P,bKjKKjKjKKj KjKKj##LlLKjKKjK jKKjK~-M7>7&54$ LhяW.{+9E=cQdFK1A  0) pJ2`[Q?l&٫C58.H(Y':d 6?32$64&$ #"'#"&'&4>7>7.546'&'&'# '32$7>54'Yj`a#",5NK ~EVZ|$2 $ |: $ 2$|ZV:(t}hfR88T h̲X(  &%(Hw(%& (XZT\MKG{x|!#"'.7#"'&7>3!2%632u  j H{(e 9 1bU#!"&546;5!32#!"&546;5!32#!"&546;5463!5#"&5463!2+!2328((88(``(88((88(``(88((88(`L4`(88(@(88(`4L`(8 (88(@(88((88(@(88((88(@(84L8(@(88((8L48OY"&546226562#"'.#"#"'.'."#"'.'.#"#"&5476$32&"5462И&4&NdN!>! 1X:Dx+  +ww+  +xD:X1 -U !*,*&4&hh&&2NN2D &  ..J< $$ 767#"&'"&547&547&547.'&54>2l4  2cKEooED ) ) Dg-;</- ?.P^P.? -/<;-gYY  .2 L4H|O--O|HeO , , Oeq1Ls26%%4.2,44,2.4%%62sL1qcqAAq4#!#"'&547632!2#"&=!"&=463!54632  @  `     ` ?`   @  @  !    54&+4&+"#"276#!"5467&5432632   `  _ v,Ԝ;G_j)``    _ ԟ7 ,>jL>54'&";;265326#!"5467&5432632    v,Ԝ;G_j) `   `7 ,>jL>X`$"&462#!"&54>72654&'547 7"2654'54622654'54&'46.' &6 &4&&4&yy %:hD:FppG9Fj 8P8 LhL 8P8 E; Dh:% >4&&4&}yyD~s[4Dd=PppP=d>hh>@jY*(88(*Y4LL4Y*(88(*YDw" A4*[s~>M4&"27 $=.54632>32#"' 65#"&4632632 65.5462&4&&4G9& <#5KK5!!5KK5#< &ܤ9Gpp&4&&4&@>buោؐ&$KjKnjjKjK$&jjb>Ppp %!5!#"&5463!!35463!2+32@\\8(@(8\@@\\@\(88(\ -4#"&54"3#!"&5!"&56467&5462P;U gI@L4@Ԗ@4L8P8° U;Ig04LjjL4(88(¥'@"4&+32!#!"&+#!"&5463!2pP@@Pjj@@\@\&0pj \\&-B+"&5.5462265462265462+"&5#"&5463!2G9L44L9G&4&&4&&4&&4&&4&L44L &=d4LL4 d=&&`&&&&`&&&&4LL4  &(/C#!"&=463!25#!"&=463!2!!"&5!!&'&'#!"&5463!2@@`(8x 8((88((`8(`@@@@8( 9 h(88(@(8(`/?O_o-=%+"&=46;25+"&=46;2+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2+"&=46;2!!!5463!2#!"&5463!2 @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @ &&&&@  @ @  @  @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @ @  @  @  @   `&&&& /?O_o%+"&=46;25+"&=46;2+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2%+"&=46;2+"&=46;2%+"&=46;2+"&=46;2!!#!"&=!!5463!24&+"#54&+";26=3;26%#!"&5463!463!2!2 @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @ 8(@(8 @  @  @  @  @ &&&@8((8@&@  @ @  @  @  @ @  @ @  @ @  @ @  @ @  @ @  @  @  @  (88(  @  ``   `` -&&& (88(&@<c$4&"2!#4&"254&+54&+"#";;26=326+"&5!"&5#"&46346?>;463!2KjKKjKjKKj&ԖԖ&&@&&KjKKjK jKKjK .&jjjj&4&@@&&#'1?I54&+54&+"#";;26=326!5!#"&5463!!35463!2+32 \\8(@(8\ \\@\(88(\: #32+53##'53535'575#5#5733#5;2+3@E&&`@@` `@@`&&E%@`@ @ @      @ :#@!3!57#"&5'7!7!K5@   @5K@@@ #3%4&+"!4&+";265!;26#!"&5463!2&&&&&&&&w@www&&@&&&&@&&@www#354&#!4&+"!"3!;265!26#!"&5463!2&&&&&@&&@&w@www@&@&&&&&&@&:@www-M3)$"'&4762 "'&4762 s 2  .   2 w 2  .   2 w 2    2  ww  2    2  ww M3)"/&47 &4?62"/&47 &4?62S .  2 w 2   .  2 w 2  M . 2    2 .  . 2    2 .M3S)$"' "/&4762"' "/&47623 2  ww  2    2  ww  2    2 w 2   .v 2 w 2   .M3s)"'&4?62 62"'&4?62 623 .  . 2    2 .  . 2    2 .   2 w 2v .   2 w 2-Ms3 "'&4762s w 2  .   2 ww  2    2 MS3"/&47 &4?62S .  2 w 2  M . 2    2 .M 3S"' "/&47623 2  ww  2   m 2 w 2   .M-3s"'&4?62 623 .  . 2    2- .   2 w 2/4&#!"3!26#!#!"&54>5!"&5463!2  @ ^B && B^^B@B^ @  MB^%Q= &&& $$ (r^aa(^aa!C#!"&54>;2+";2#!"&54>;2+";2pPPpQh@&&@j8(PppPPpQh@&&@j8(Pp@PppPhQ&&j (8pPPppPhQ&&j (8p!C+"&=46;26=4&+"&5463!2+"&=46;26=4&+"&5463!2Qh@&&@j8(PppPPpQh@&&@j8(PppPPp@hQ&&j (8pPPppP@hQ&&j (8pPPpp !)19A$#"&4632"&462"&462"&462"&462$"&462"&462"&462U;bqb&44&ɢ5"  #D7euU6 &4&m 1X".4>2".4>24&#""'&#";2>#".'&547&5472632>3=T==T==T==T=v)GG+v@bRRb@=&\Nj!>3lkik3hPTDDTPTDDTPTDDTPTDD|x xXK--K|Mp<# )>dA{RXtfOT# RNftWQ,%4&#!"&=4&#!"3!26#!"&5463!2!28(@(88((88((8\@\\@\\(88(@(88(@(88@\\\\ u'E4#!"3!2676%!54&#!"&=4&#!">#!"&5463!2!2325([5@(\&8((88((8,9.+C\\@\ \6Z]#+#,k(88(@(88(;5E>:5E\\\ \1. #3C++"&=#"&=46;546;2324&#!"3!26#!"&5463!2@@8(@(88((8]@]]]`@@r(88(@(88@\\]/2#!"&54634&#!"3!262#!"&=463]]@]] 8(@(88((8]@\\]`(88(@(88@@$4@"&'&676267>"&462"&462.  > $$ n%%/02 KjKKjKKjKKjKfff^aayy/PccP/jKKjKKjKKjKffff@^aa$4@&'."'.7>2"&462"&462.  > $$ n20/%7KjKKjKKjKKjKfff^aa3/PccP/y jKKjKKjKKjKffff@^aa +7#!"&463!2"&462"&462.  > $$ &&&&KjKKjKKjKKjKfff^aa4&&4&jKKjKKjKKjKffff@^aa#+3C54&+54&+"#";;26=3264&"24&"2$#"'##"3!2@@KjKKjKKjKKjKܒ,gjKKjKKjKKjKXԀ,, #/;GS_kw+"=4;27+"=4;2'+"=4;2#!"=43!2%+"=4;2'+"=4;2+"=4;2'+"=4;2+"=4;2+"=4;2+"=4;2+"=4;2+"=4;54;2!#!"&5463!2`````````````````````p`K55KK55Kp`````````````````````````5KK55KK@*V#"'.#"63232+"&5.5462#"/.#"#"'&547>32327676R?d^7ac77,9xm#@#KjK# ڗXF@Fp:f_ #WIpp&3z h[ 17q%q#::#5KKu't#!X: %#+=&>7p @ *2Fr56565'5&'. #"32325#"'+"&5.5462#"/.#"#"'&547>32327676@ͳ8 2.,#,fk*1x-!#@#KjK# ڗXF@Fp:f_ #WIpp&3z e`vo8t-  :5 [*#::#5KKu't#!X: %#+=&>7p  3$ "/&47 &4?62#!"&=463!2I.  2 w 2   -@). 2    2 . -@@-S$9%"'&4762  /.7> "/&47 &4?62i2  .   2 w E > u > .  2 w 2   2    2  ww !   h. 2    2 . ;#"'&476#"'&7'.'#"'&476' )'s "+5+@ա' )'F*4*Er4M:}}8 GO *4*~ (-/' #"'%#"&7&67%632B;>< V??V --C4 <B=cB5 !% %!b 7I))9I7 #"'.5!".67632y( #  ##@,( )8! !++"&=!"&5#"&=46;546;2!76232-SSS  SS``  K$4&"24&"24&"27"&5467.546267>5.5462 8P88P88P88P8P88P4,DS,4pp4,,4pp4,6d7AL*',4ppP88P8P88P8HP88P8`4Y&+(>EY4PppP4Y4Y4PppP4Y%*54&#"#"/.7!2<'G,')7N;2]=A+#H  0PRH6^;<T%-S#:/*@Z}   >h.%#!"&=46;#"&=463!232#!"&=463!2&&&@@&&&@&&&&&&&&&&&&f&&&&b#!"&=463!2#!"&'&63!2&&&&''%@% &&&&&&&&k"G%#/&'#!53#5!36?!#!'&54>54&#"'6763235 Ź}4NZN4;)3.i%Sin1KXL7觧* #& *@jC?.>!&1' \%Awc8^;:+54&#"'6763235 Ź}4NZN4;)3.i%PlnEcdJ觧* #& *-@jC?.>!&1' \%AwcBiC:D'P%! #!"&'&6763!2P &:&? &:&?5"K,)""K,)h#".#""#"&54>54&#"#"'./"'"5327654.54632326732>32YO)I-D%n  "h.=T#)#lQTv%.%P_ % %_P%.%vUPl#)#T=@/#,-91P+R[Ql#)#|'' 59%D-I)OY[R+P19-,##,-91P+R[YO)I-D%95%_P%.%v'3!2#!"&463!5&=462 =462 &546 &&&&&4&r&4&@&4&&4&G݀&&&&f s CK&=462 #"'32=462!2#!"&463!5&'"/&4762%4632e*&4&i76`al&4&&&&&}n  R   R zfOego&&5`3&&&4&&4& D R   R zv"!676"'.5463!2@@w^Cct~5  5~tcC&&@?JV|RIIR|V&&#G!!%4&+";26%4&+";26%#!"&546;546;2!546;232@@@@L44LL4^B@B^^B@B^4L  N4LL44L`B^^B``B^^B`LL4&"2%#"'%.5!#!"&54675#"#"'.7>7&5462!467%632&4&&4  @ o&&}c ;pG=(  8Ai8^^.   &4&&4&` ` fs&& jo/;J!# 2 KAE*,B^^B! ` $ -4&"2#"/&7#"/&767%676$!28P88PQr @ U @ {`PTP88P8P`  @U @rQ!6'&'&'&+!!!!2е sXVqQ @@vt %764' 64/&"2 $$ f3f4:4^aaf4334f:4:^aa %64'&" 2 $$ :4f3f4F^aa4f44f^aa 764'&"27 2 $$ f:4:f4334^aaf4:4f3^aa %64/&" &"2 $$ -f44f4^aa4f3f4:w^aa@7!!/#35%!'!%j/d jg2|855dc b @! !%!!7!FG)DH:&H dS)U4&"2#"/ $'#"'&5463!2#"&=46;5.546232+>7'&763!2&4&&4f ]wq4qw] `dC&&:FԖF:&&Cd`4&&4& ]] `d[}&&"uFjjFu"&&y}[d#2#!"&546;4 +"&54&" (88(@(88( r&@&Ԗ8((88(@(8@&&jj'3"&462&    .  > $$ Ԗ>aX,fff^aaԖԖa>TX,,~ffff@^aa/+"&=46;2+"&=46;2+"&=46;28((88((88((88((88((88((8 (88((88((88((88((88((88/+"&=46;2+"&=46;2+"&=46;28((88((88((88((88((88((8 (88((88(88((88(88((885E$4&"2%&'&;26%&.$'&;276#!"&5463!2KjKKj   f  \ w@wwwjKKjK"H   ܚ  f   @www   $64'&327/a^ ! ^aaJ@%% 65/ 64'&"2 "/64&"'&476227<ij6j6u%k%~8p8}%%%k%}8p8~%<@% %% !232"'&76;!"/&76  ($>( J &% $%64/&"'&"2#!"&5463!2ff4-4ff4fw@wwwf4f-f4@www/#5#5'&76 764/&"%#!"&5463!248` # \P\w@www4`8  #@  `\P\`@www)4&#!"273276#!"&5463!2& *f4 'w@www`&')4f*@www%5 64'&"3276'7>332#!"&5463!2`'(wƒa8! ,j.( &w@www`4`*'?_`ze<  bw4/*@www-.  6 $$  (r^aaO(_^aa -"'&763!24&#!"3!26#!"&5463!2yB(( @   w@www]#@##   @ @www -#!"'&7624&#!"3!26#!"&5463!2y((@B@u @   w@www###@  @ @www -'&54764&#!"3!26#!"&5463!2@@####@w@wwwB((@@www`%#"'#"&=46;&7#"&=46;632/.#"!2#!!2#!32>?6#  !"'?_  BCbCaf\ + ~2   }0$  q 90r p r%D p u?#!"&=46;#"&=46;54632'.#"!2#!!546;2D a__ g *`-Uh1    ߫}   $^L  4b+"&=.'&?676032654.'.5467546;2'.#"ǟ B{PDg q%%Q{%P46'-N/B).ĝ 9kC< Q 7>W*_x*%K./58`7E%_ ,-3  cVO2")#,)9;J) "!* #VD,'#/&>AX>++"''&=46;267!"&=463!&+"&=463!2+32Ԫ$   pU9ӑ @/*f o  VRfq f=SE!#"&5!"&=463!5!"&=46;&76;2>76;232#!!2#![       % )   "  Jg Uh BW&WX hU g L\+"&5##"/&67>7> 7!"&=463!2+;26=46;2#!"&=463!2$=5R9[/*G :!3' &&@` fvQJ+-    (->K\rB&&@ n#467!!3'##467!++"'#+"&'#"&=46;'#"&=46;&76;2!6;2!6;232+32QKt# #FNQo!"դѧ !mY Zga~bm] [o"U+, @h h@@X hh @83H\#5"'#"&+73273&#&+5275363534."#22>4.#2>ut 3NtRP*Ho2 Lo@!R(Ozh=,GID2F 8PuE>.'%&TeQ,jm{+>R{?jJrL6V @`7>wmR1q uWei/rr :Vr" $7V4&#"326#"'&76;46;232!5346=#'73#"'&'73267##"&54632BX;4>ID2F +>R{8PuE>.'%&TeQ,jm{?jJrL6 @`rr :Vr3>wmR1q uWei@ \%4&#"326#!"&5463!2+".'&'.5467>767>7>7632!2&%%&&&& &7.' :@$LBWM{#&$h1D!  .I/! Nr&&%%&&&&V?, L=8=9%pEL+%%r@W!<%*',<2(<&L,"r@ \#"&546324&#!"3!26%#!#"'.'.'&'.'.546767>;&%%&&&& &i7qN !/I.  !D1h$&#{MWBL$@: '.&&%%&&&&=XNr%(M&<(2<,'*%<!W@r%%+LEp%9=8=L  +=\d%54#"327354"%###5#5#"'&53327#"'#3632#"'&=4762#3274645"=424'.'&!  7>76#'#3%54'&#"32763##"'&5#327#!"&5463!2BBPJNC'%! B? )#!CC $)  54f"@@ B+,A  A+&+A  ZK35N # J!1331CCC $)w@www2"33FYF~(-&"o4*)$(* (&;;&&:LA3  8334S,;;,WT+<<+T;(\g7x:&&::&&<r%-@www  +=[c}#"'632#542%35!33!3##"'&5#327%54'&#"5#353276%5##"=354'&#"32767654"2 '.'&547>76 3#&'&'3#"'&=47632%#5#"'&53327''RZZ:kid YYY .06 62+YY-06 R[!.'CD''EH$VVX::Y X;:Y fyd/%jG%EC&&CE%O[52. [$C-D..D^^* ly1%=^I86i077S 3 $EWgO%33%OO%35 EEFWt;PP;pt;PP;pqJgTFQ%33&PP%33%R 7>%3!+}{'+"&72'&76;2+"'66;2U &  ( P *'eJ."-dZ-n -'74'&+";27&+";276'56#!"&5463!2~} 7e  ۩w@www"  $Q #'!# @www/4'&327$ '.'.4>7>76 "!!jG~GkjGGk[J@&& @lAIddIAllAIddIA@ '5557 ,VWQV.RW=?l%l`~0~#%5!'#3! %% %=#y ?R'UaM|qByy[C#jXAAҷhUHG/?%##"547#3!264&#"3254&+";267#!"&5463!2R܂#-$䵀((((tQQttQvQtn?D~|D?x##))((QttQvQtt2#!"&54634&"2$4&"2ww@ww||||||w@www||||||| !3 37! $$ n6^55^h ^aaM1^aaP *Cg'.676.7>.'$7>&'.'&'? 7%&'.'.'>767$/u5'&$I7ob?K\[zH,1+.@\7':Yi4&67&'&676'.'>7646&' '7>6'&'&7>7#!"&5463!2PR$++'TJXj7-FC',,&C ."!$28 h /" +p^&+3$ i0(w@www+.i6=Bn \C1XR:#"'jj 8Q.cAj57!? "0D$4" P[ & 2@wwwN#3!!327#"'&'&'&5#567676l '2CusfLM`iQN<:[@@''|v$%L02366k67MN#3%5#"'&'&5!5!#33276#!"&5463!2cXV3%  10D*+=>NC>9w@www8c'#Z99*(lN+*$% @www@#"'&76;46;23   &  ++"&5#"&7632  ^  c  & @#!'&5476!2 &  ^  b '&=!"&=463!546  &    q&8#"'&#"#"5476323276326767q'T1[VA=QQ3qpHih"-bfGw^44O#A?66%CKJA}} !"䒐""A$@C3^q|z=KK?6 lk)  %!%!VVuuu^-m5w}n~7M[264&"264&"2"&546+"&=##"&5'#"&5!467'&766276#"&54632    *<;V<<O@-K<&4'>&4.'.'.'.'.'&6&'.'.6767645.'#.'6&'&7676"&'&627>76'&7>'&'&'&'&766'.7>7676>76&6763>6&'&232.'.6'4.?4.'&#>7626'.'&#"'.'.'&676.67>7>5'&7>.'&'&'&7>7>767&'&67636'.'&67>7>.'.67 \ U7  J#!W! '  " ';%  k )"    '   /7*   I ,6 *&"!   O6* O $.( *.'  .x,  $CN      * 8   7%&&_f& ",VL,G$3@@$+ "  V5 3"  ""#dA++ y0D- %&n 4P'A5j$9E#"c7Y 6" & 8Z(;=I50 ' !!e  R   "+0n?t(-z.'< >R$A"24B@( ~ 9B9, *$        < > ?0D9f?Ae  .(;1.D 4H&.Ct iY% *  7      J  <    W 0%$  ""I! *  D  ,4A'4J" .0f6D4pZ{+*D_wqi;W1G("% %T7F}AG!1#%  JG 3  '.2>Vb%&#'32&'!>?>'&' &>"6&#">&'>26 $$ *b6~#= XP2{&%gx| .W)oOLOsEzG< CK}E $MFD<5+ z^aa$MWM 1>]|YY^D եA<KmE6<" @9I5*^aa>^4./.543232654.#"#".#"32>#"'#"$&547&54632632':XM1h*+D($,/9p`DoC&JV*55K55K55q*)y(;:*h )k5=x*& *x?/%4&#!"3!264&#!"3!26#!"&5463!2  &&&&&&&&19#"'#++"&5#"&5475##"&54763!2"&4628(3- &B..B& -3(8IggI`(8+Ue&.BB.&+8(kk`%-"&5#"&5#"&5#"&5463!2"&4628P8@B\B@B\B@8P8pPPp@`(88(`p.BB.0.BB.(88(Pppͺ!%>&'&#"'.$ $$ ^/(V=$<;$=V).X^aaJ`"(("`J^aa,I4."2>%'%"/'&5%&'&?'&767%476762%6[՛[[՛o ܴ   $ $ " $ $  ՛[[՛[[5` ^ ^ 2` `2 ^ ^ ` 1%#"$54732$%#"$&546$76327668ʴhf킐&^zs,!V[vn) 6<ׂf{z}))Ns3(@ +4&#!"3!2#!"&5463!2#!"&5463!2@&&&f&&&&@&&&&4&&4&@&&&&&&&& `BH+"/##"./#"'.?&5#"&46;'&462!76232!46 `&C6@Bb03eI;:&&&4L4&F Z4&w4) '' 5r&4&&4&&4}G3#&/.#./.'&4?63%27>'./&'&7676>767>?>%6})N @2*&@P9A #sGq] #lh<* 46+(  < 5R5"*>%"/ +[>hy  K !/Ui%6&'&676&'&6'.7>%.$76$% $.5476$6?62'.76&&'&676%.76&'..676#"NDQt -okQ//jo_  %&JՂYJA-.-- 9\DtT+X?*<UW3' 26$>>W0 {"F!"E    ^f`$"_]\<`F`FDh>CwlsJ@ ;=?s  :i_^{8+?` ) O`s2RDE58/K` &1:%#"'>7&54&5#"'>71654'6&5%zxb(zxbACC=ggF0ɖF(U!,CC=ggFÜ ɖF(U f5B_< <<pU3U3]yn2@ z5u@55 z55@,s@@(@@- MM- MM @@ -`b $ 648""""""@N@ ,@ PBp<$H<TfT H R , D x 6 \ DLX*Dx8JN2f$P`"VtLv$~*h 6 n !&!v!!""p"#&##$8$%%f& &&'`''(*((()")X)* *B*+,n,-z..:../@//0D01~12l233R344>4458566V67"78P89|9::b:=>>l>>?R?@l@@ABBxBCCDCDZDEFrFGDGHHIFIIIIJJLJJJKK\KLJLM*MMNhNOFOPPjPQDQQR2RjRS2TVVVWLWxWXX\XXY@YjYYYZ0Z~Z[[6[[\V\t\]6]x]^@^^_d_`$aab(bhbc2cccdle?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq rstuvwxyz{|}~     " !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvuni00A0uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni202Funi205FuniE000glassmusicsearchenvelopeheartstar star_emptyuserfilmth_largethth_listokremovezoom_inzoom_outoffsignalcogtrashhomefile_alttimeroad download_altdownloaduploadinbox play_circlerepeatrefreshlist_altlockflag headphones volume_off volume_down volume_upqrcodebarcodetagtagsbookbookmarkprintcamerafontbolditalic text_height text_width align_left align_center align_right align_justifylist indent_left indent_rightfacetime_videopicturepencil map_markeradjusttinteditsharecheckmove step_backward fast_backwardbackwardplaypausestopforward fast_forward step_forwardeject chevron_left chevron_right plus_sign minus_sign remove_signok_sign question_sign info_sign screenshot remove_circle ok_circle ban_circle arrow_left arrow_rightarrow_up arrow_down share_alt resize_full resize_smallexclamation_signgiftleaffireeye_open eye_close warning_signplanecalendarrandomcommentmagnet chevron_up chevron_downretweet shopping_cart folder_close folder_openresize_verticalresize_horizontal bar_chart twitter_sign facebook_sign camera_retrokeycogscomments thumbs_up_altthumbs_down_alt star_half heart_emptysignout linkedin_signpushpin external_linksignintrophy github_sign upload_altlemonphone check_emptybookmark_empty phone_signtwitterfacebookgithubunlock credit_cardrsshddbullhornbell certificate hand_right hand_lefthand_up hand_downcircle_arrow_leftcircle_arrow_rightcircle_arrow_upcircle_arrow_downglobewrenchtasksfilter briefcase fullscreengrouplinkcloudbeakercutcopy paper_clipsave sign_blankreorderulol strikethrough underlinetablemagictruck pinterestpinterest_signgoogle_plus_sign google_plusmoney caret_downcaret_up caret_left caret_rightcolumnssort sort_downsort_up envelope_altlinkedinundolegal dashboard comment_alt comments_altboltsitemapumbrellapaste light_bulbexchangecloud_download cloud_uploaduser_md stethoscopesuitcasebell_altcoffeefood file_text_altbuildinghospital ambulancemedkit fighter_jetbeerh_signf0fedouble_angle_leftdouble_angle_rightdouble_angle_updouble_angle_down angle_left angle_rightangle_up angle_downdesktoplaptoptablet mobile_phone circle_blank quote_left quote_rightspinnercirclereply github_altfolder_close_altfolder_open_alt expand_alt collapse_altsmilefrownmehgamepadkeyboardflag_altflag_checkeredterminalcode reply_allstar_half_emptylocation_arrowcrop code_forkunlink_279 exclamation superscript subscript_283 puzzle_piece microphonemicrophone_offshieldcalendar_emptyfire_extinguisherrocketmaxcdnchevron_sign_leftchevron_sign_rightchevron_sign_upchevron_sign_downhtml5css3anchor unlock_altbullseyeellipsis_horizontalellipsis_vertical_303 play_signticketminus_sign_alt check_minuslevel_up level_down check_sign edit_sign_312 share_signcompasscollapse collapse_top_317eurgbpusdinrjpycnykrwbtcfile file_textsort_by_alphabet_329sort_by_attributessort_by_attributes_alt sort_by_ordersort_by_order_alt_334_335 youtube_signyoutubexing xing_sign youtube_playdropbox stackexchange instagramflickradnf171bitbucket_signtumblr tumblr_signlong_arrow_down long_arrow_uplong_arrow_leftlong_arrow_rightwindowsandroidlinuxdribbleskype foursquaretrellofemalemalegittipsun_366archivebugvkweiborenren_372_373_374QQdjango-guardian-1.4.1/docs/theme/rtd_theme/static/font/fontawesome_webfont.svg0000700000175000017500000060230512634712306030020 0ustar brianbrian00000000000000 django-guardian-1.4.1/docs/theme/rtd_theme/static/font/fontawesome_webfont.eot0000700000175000017500000011103512634712306030003 0ustar brianbrian000000000000007LPB5fFontAwesomeRegular$Version 3.2.0 2013&FontAwesome RegularBSGP/3{YD MFx>ޝƏ)[1ɵH-A)Fٜ1./ dU'&a /s%%<Ԯ O%pV "uKupp^cRB`\}TیW  ѣzҮ5*LzSq>?T35(}aKP'245$[GVzi\:^ C=P O*-<@a(0LšĻ "gmL%ƪiC̼aA^舱 hQ% ./"}҂JDpb('0E1 hQQJ Dp7Hk3L4K4d S*?e [!=8gF"bT*?G.W0Q'^xn?[("wZhMz"zh,jD &%X 5g"`hԚl 78K'ļmF{b|-4HfP[1<æͥ+-wE :hKuOTRT;)/- 9L2 %3LU28ŁY% )Gդ5uǝ:Ab65W{D!dNǣzKE<UEqU*5Z-}Sq*(Y!`;0.AGCF|y)5.@C4XGZ`-.a6 .m|>d;-xPKLH *KG%EF9B͆Hi0zu1Tp`FU ֏cc+U^µ4x(F4xXݽz(R6LV?SGc"W/B]O5!.Оu_wΥQC\UĶ`v=MHSײ7]\bB-Aj]zxW Wp_5MQTw"Aqތ].Rӡn (Zw)夶up[2bg8@W)ʿk쒫Y8L<'siBB D(OC0POtt6¨/6oÍMEh. 7MI$tUf! 96H"r6A8ʉE"Q)fRJJh$Ǧ9[Vf%cxPqDP͐$艨,vUIm5XLj$q51hh'  .L LЀӮbÑط%UFUt\şB] Sb[3fu{S6>%nEq`{1y*VJT(B+i0(/$و;*aR)I:Ra3َ{osCh"KAG<^a2PKHy3NyGFF)Lo7[: T*BIa xeU,ҵSG7Õqo99H+,WeNe{/#/(,w{ PzAt3hf'VR# ӻr뿾FfO[rI'"-:?$d@4t_I& AB$qb{F $à"ς)f68A۠5Lt|1Tqx| dZIZO(}͙DLȹbA2Fd =Gb4eD C"#'cKj}} 1x&$*<20b_e@ꕂ j o#)ϴYi2uv{@$A9zU:-k"HkGiA[="D\dj}r! Uߛ~e: +GQl'ZggW%Gn%i,Z.T0U+S>TJ|QTiLeD-A)p%F?ELb'͎HHMپ5E%2iUOhx#r.rl )5-C.(,o8'٭q& |7QoM+}Q.NW33: ;=wD_ jezO!fFNqQ=8"Ys1]#;+"HZ̈́yFDs}&^B.49`ģ'Zb0Jw0vTRW&x۲MAಈ7t=CF$m˥fjvB5lu I70<%h^ޡ~ܝ"_S&!";1pjd,*` z_!ohH(rz}v:c%]x*bbX|9(-("͊Lyϳ{"1P˒Z&B€xPS դYuJ e98GULcKP4IQ|j%H<"`2ZQuU1TWv Q,p[ @ f֤ۮEҳzfD2%+Tc)F9v"bc>F*gqR+Ձ9^NPbAnhZT)L!X2NUȊ =J+Pa3·Z |Fm-+}xP A0œ,JӪmPNb ;XSV]eSΫ2)l`3~ ]#/ҁZ{=dCR܂Z&fl>e3a ~`rIǴPNC`\QXAH$ΐ-;Dm靦 -~2{e.^{sLz݄[r$[3%儕*fZgt,H9NMO iR?G Qt:殾@03PPXCt\ROV-5pg2{Qʻo'a_ f?Mwpx dط ΜsU+6^vIV>̲wG>4KH\ r{¡]S Zhq^ <E#"I/CgvIy =Frގ^L`W4Ld a/;'f,9̤]N?.^d|K6z8>g\䯽@#sObTҴՕ rB@ %XBG{h.c*IbT/QRJ-= _*q}8ԍ/"]ţeF8b9鶾%+d)Rם@:M~!hFB ` Ŋj<^T^clw+X;rR*ZjT5L;([')3Ka+@.^?Sm#=c|aÈ5kRCA%hfyY} h\fas×(N~6,irث_z?`(m %ɉ|R"c@} py`SAv'PGa&,o^<gj B@ALڣu=lrjy#R_ [*h$ vH79 k2U+4W`DPFeEt+MdBpʼ+ D(@ۗFZ1BiKϵ8.rxB0ʨ^nx\'٘>VDϼ܁mPӀ(x9e{ܠL[E[n_Fc XQ68j-wkvsl[yl.i N4!hWy\FMb6Ol(X=mWCv8)\#3L,TGĈ{GӅB2FSsLR2Q|="*R~ƮSoPe79E8Z D`':HNR}/ʐ(%FhrbRA[6 浺ppB"ДDlSjA:ECUl.TzbA2рKMH̾T {5h[2`}Ux '/~EM{^,yJX u i4d;}h,\m$v /yQ 'X%, Zb3ª;@S} RI6Nguc#U @i&2P@8־u'b/Ч"NYPO6k6iCN!H"A`>@qN# @h((eJ7>†i_!afă:٫,"p e:l @ޢqkSNS b{s<l?t WLX'v<,FY/eMz0+Ԛy꜊B 0x]je]R\`u.E. ʻ릙B<6rj Ç9߃Bf-,QϑN#/M" qA@2X~D0]C[ipr @aTUtBoݕҜ]mA#O(JiԌ6v[{qyXR6Tv(rs5s<>$,mRF#:lBD`H9PUȂH4@ !465Dc>>˱:A0a0@Գثqėf[z_<~qbV!k29ňՌB=N'*W݉~Hx8ʘ TGFzh[o5,-X_dƆK@UOYc` Զ49J=P_8tnRE!5>D ./QДҨ1`n[4N}ݯ7}.pN ,n{rR Tař^*M|"*VigA FL 4$-᭄Ml6 >)}fq_WD! eo(H2?/H=?*qqUh6o} k-e0 $/~FBs :䴅cEvFsH%ine\5<b=f> PCEyd,K *3 ο3  ٥*F5bkeV]UOubd`Gs{KUOP݆gQH@e*Fĩ/jsW ]Qi yq@JSXU)r& U}ѓ5 /꼃M?Fo_So(qNV&,0Cɏ,\L￯5^ȿ #4JocԬXmTL4M&s ak#-PXD, ?pV02i n)nCwD1BȲ)xgk1>WF{Pi1U ;w,5Nt3/Ţ \KvprԒ q'#9THr6^d$E|clTCH{' * ;e<>¾4nUo~q1GM6QjJQT}FS𯮺髻@jfM-37qUmHYZ}*"'D{c ͠Z3 w 1)_o⬲\Q,F k i0 -aՉԍ$hJ^HUyh'7K7 b3t*iĭQgieI(t%a%R©83GTUz$M*~ƀJصnu B4γR-:;q(;QĘ)1EFv:T~לzi4*оT\<韴5Ǐ!efO/w)9E˱yFt/t>^G^,U Ɯًi!sNvS$!FqoB2 `Oe@5֑r;[U .|"?!̀j@=1Rkk||{%-='qb  6 tnK]{3HB v,n)Cő]|TQH<GMx4]b  P|' *!+qwtnω!%|O;6IiMқ(dVe0Bf\N〃|b_K1LT%Y0rExשyLc\YqPL5$|A}@t-{}lf~mKKڽh>G~^+qcEy'OC0C1"4O-܈|S8cZ<= M4t?]9 +J3Cd -dX!!ZVRg 呒ś̲\8^!#xi_Dƙ.:D3dVmwZlӟyB|tRA31>$Ul6@;_4tA!3|+p[AkXAkOFtnpP(6ؕ"7/1)A@V !1( C@ i.**PXC' ^GT O\/{7,Yo|Cs x!qF ײ`>֘F="g^Ř:WP*BMBɯ+6șDžiRgyr%/^MU{U%b (b9b/e(h|kBn FVK_#( J)gK+C (P*n4@,NkgTxXc6g/9XO,xčq+daSL.9؈l9 {eT1؆GG)xaG#\N$v؏`Kt|NU3kJ|Zq9z1!'"d} yuJYygF/F[r],O'ct4Ìw 6cH`&{SPe֖m'/j74Gj|>̩( EAf>bKu rn bc;gKC;8irA1/ j}yI@T|dP8τA=)%'n! k`X=em^0Ռi-GAM|3XpmEP̬30V^O߁,BQtGyy#BXD?fD,J׹^+4 7Jn?V99~:?׆(" tcfIb Ƒ>Ey?L `r&޸T 00!kwgT/u' FG'n2J 3L-%O 1LvއLo -ȩDZ_-scI/ܓj]+"׬yQ8yy C`M:XSk]M:Wq`$E蹘 {X7fQr9W6^@tQ$YFfhtnKKn(H[;}/#f|$T,@8@h68/RK㾯6{E% Ǚr@MH DFJ? *͎hBS4ȱTe,F BB7@պĽ^@8 q3"OHlN *iC-dwMP)71B[O o4 o73%Bᐙ3qhKY~eIPm2w"uʔ^*,;Ln:PĒ辂",#f]$W>d$æXqtldd]L& ^|j&G3\l ֛lU.@M\n)#2AǼIrn<ƓwTz0nʃr,(^&ҕWŘ08vգ"vϝc}B*@I,w0y) s7'ed22|4P?ҏkhfy !ś/gG#:Zm=Δ~ qN] %x`U`2#j8Rc\\{8LۈC-\ׯnOD`&Rl[gP`k6=թ%i~AgD@hO(f7X!)$b9S`XH e ~Ǹ@X֔c(@SU8%Q QzM(܉F}@7˰qqH{5Y4֍ NAIo|}- D&>/}|C[$:7*t,Pph iM- q4NI9 WzH1.#*Nzt$S\0.2)n).w,rˢ[;*\pH,?q8$̟a>{8M=OIA92@=|ɏ)Xhq-Xh-U2bHuߤ>lOJ'E8bjj JFA+M& Xϕ0:a،P,/(lu)%#r,fGDD^X%Q{i x`φ`dQIx I=aG6!g$YM4YA1MN趄(4qYlWg7$ ˨īY ઠ%%`'^F6ǵqmHi*ni,c~`4ȗ4g $a*x =20HBQ@5HhtULJ6 e)C<8-2z!脔wp֎X*1Z\bCe?# I0rCiƂ?X 0\ok"{5K*":r]?X9@#&ciACnt'*͕ P& 0 옙tn'6/hY;<$b1?=։+DX8TΤi ]8װ‚.V94qMLt4`) ak@Ivn.4_c?'P+UؓdrEr:g߄9NMz}ϬmNX2PE ;ёpQBOɳy@TUOGp?{9'Fr'@3 fC>wRTj@?#){Ā&Dsr>0+>:& 1Ad'Nf0\BZP`4G%C@9 d" AR2dW}ԘxF8YMMC1iB&~ mVŒѲWg}.5s{x.vaa^kQf \EjRGORbgH䗽IAbE7vE&*𥎰 AC.dܣkܦO T^.Fh1u5^(YʁʔI#s4 ^Rkjfuh g@XdI!fS4{A)nJBv.4>ކr9,it&+r63]46=M [vaQʂ(EguU;&2= #LpD,z yDWvȰi+{$x䉃-uFP]sȩDM(4yl!bBҰVJˊk<^'VfG_P0E3|bA8%QS='q6%3Y> ӟ,q96Jv,HdzL[g;>%|~{Gl~x1@Y:MTT$bA}$rL烤O č< NV!/˛iEe4]鏤\]9Q-xKS(#6dBB,';SШ`rs~I,// {JJ`|R손`4t$'wLf0Wv#msMuq2 L(C HSZH|zPpAҔԄ4=nV5ke3] +RRd ]D7q,{BFh@RG~{cHf Tl*%n*8v^ZQQ[uQ% s9^="EzS4h+%Xh$6@j0' ?,!3hГa@mFsX 9t!(4u>3P <_ۊ|4S&g|r;L.Q:p DfIPC4 ȍdHl,pd  pAoD 0(,dP6zdwyT#g\ameyDk`bИ~AԂ<2j"H3NLBc-}/=G"˷܆E8nU8&Yۏ?W&vn8nŏ22z\kl[HTe3\@&Ը%LEaP3 ljOuIp`1@!&63T1?;F;]TBMy ;Ɖlnt$Xvt2L0[Pi !=#A0TK/-đwܟ0.4"Wm47!Bg^PBF܊tH4xc8DhEge3hzWf H`"YP2Kzk; wIۈ\"11ቝŭb\8cNzH"j}:gurԚN,Y<3(ts|%Rb-ԐlɪFp+`3qs&h3 * 0˱CZ# =ah 8kv\XXQ"1,x :pmz!>RD'MN"y- 3#J{e*n<ߴy&ũB\m*[F O/l^ֺA[")0T@XD8b/( |WHBB)33 ǒ ~${::> Z2c^&C\VhڍKi%I,q?2!,a64h $w hɸ{z`GyvOm^Z1PV9l]Խ}Lپ: \xBnC>5? X, @p*x8C>v;^ K4p}%xKR j4GZW 0u< .B;4"/x\Ec|}M+sK= rf#unb1<  j/<$n2*ߚ,`RnPCJ ]- psoa Dd", ^]::1cw^_4Y 'ZCJB)wf,\j>,ʸ}Hܭ7 @ VF~dG(qy0,|\!*f 7D./B)ܝ[B',T<}bcS ^.5#bgxPK̒aD 6fWe&o圻(*+ P*8!_fkg+mƙmbM/dG~ *~-*w"=E9|G鋈W I5"`6:.߂LH̍*y Łz^N.Bќ2xe%5.;v+$BkRPH+ƤƑ@`? 􀲽S]BV|tzpC҇bX8TyK;TM ,C19% B̴"aƾ/3W "ʇYf5}I,=67ILɟ#{$L4V9 Vs ٔN^5JiG$  &v>^l@`\S+.fo.]t mzɍQ}-~QebIC4|HaY}1|˓?M;R~G4I8V--t]X>;*w'v$4v$_J{'_[suA,)z+z E~Q4Ip;/͐ϕG<߭nsv1ADSRݰ.~,҆fP""cCr :K 6c| #)3a5PrA BՀz o f ِ  )DCFIjfaD#e#\8bԨ>V#؎S! Vh^0#xH"ײ~Kלݤ$TĠ/[\wu!t s{XAM"G\{ bu!9Iv#"r0iNRG74Wc<;LonQ$@_v]<_#8 |Mfe@P+Za;%>B؉c(G{SgCyZaPvJ+a_VL=],^ В@Ӯ Ev+fs9Lp`xAHF`mXʹ^ ,tjԜ- ԧ ZcD7 AA?dp+)Sn< JB 灄^1O[\O VbH CP:`WI~CXSU72GάXֵE#2tHEs`w+D=u~bt[_N(vˋl(Bꄽ<(CBB#- ~],z@)!\|O_O3FM-ʡFӈ I6yu{pOY'"-.#ds!LGov$~]PN{No`obf,c8g3sߏJuX,zL5!9tqF5?XaS+&3a& Hd7t_$nD%DA*Hq*X=OOOjR8x$4Vu|Q:], 53Ia0_&,:ݜD=G ò1U^,Q> ޴ L5@ˉ*/^n`11QUUksCxヌG!r6syxId~ٍD]g>gXEy3n{XHPW> ' D򲙇xE&"&k8D='!Aͱ-Н4ԸKS یT" -WdHy]6Ŏ yg[@W@|`q_a1):l4s\/D D/5Ic U5ZEMGNH}iekhq`j GO 92C':Ҡٖb`j {Î]1F {@8wibxV)+Q9df{@b,";BcDQ9eNT&^2PoN06.6ymLB4[b(*NBQT"-᱇< 50yv[a۟\_&p4{d"ɂZx9'kG`ZDq8 #8@D0AkzD&qC@7ÌvpADQV)@\)P{_\) i@SPnHa>qaNBP|T)$ƥ0G-* "^ށצL,B0}ʁpB3P7B,X q0;܌<x,*;/'>rbab(Xai搐k.NLvc̉P|L(XICdaLV$'?JJT,pǙ%BŻɬ|9YcӢ}S#6Rl>Yx| P U8 4 V ƥC۴hБ#'*_ֽ!t.+"͚-z_F`P4rӊq}M4*0Ίc!Ԉ2'܅9AEoJWw;kzxsXmhbHthnHh?ۼ;MIz:a28zЈ}򊒩D}\WP(5c R}C(3Z)ī=#ig6R $qؚH,>,wT]70ѡ(4m %p%]ΌɣwWN"%Y8t>G<y!J,ymԅWo6`S Qkq% a5gcDVCKK*&(dj8*W ?{RU3xGhe?IKTX#Fb#$PɼP#^]yB:`xK0 鴧k]t$4¬xc?ĿWI %Á@8[na(pcFSdD3_ˈVv/3i^fbsxj3Y3l4 -E=s\YZ  k]k4m2pտ+ ~q!Q> tw,\ӛlMs@' y¡8hIS:0֒GzKnxFlۄ4n8HI 60~!l%t aʴT.RxeN@p a:i%C-0{19LKlKM9jI?iĕFc?hYl l^:ZmqLɐ$*'fXeF0nSlC\[,S@S@c녀C>` Zc4M,Pŭ5bz-6Qv8/@lKn/$09GCL\&f}@bw,IK9B *x4B1"kk2DIx 0jn07g ؐH.S vM\ &k3xO%"R$F &^25z5& ٮ"]kHx&U/1IVS{I^2xpԔց7g}BHz kw.G(D تml3gTIYi5"aL퍥B9 ,(C+"Ζg\oJJSb<%cFhj*Ykb@3M<3c4 XJqyA&E4kfA #_q7<_򔠷ۑLԊ 1Y^! [GxvcQ`!&M{0*nN .D*A:ILb;(PxϜ ALmS`VC= n!z+JA8H=^vψ9i|FtƆA 둽e&f'ٛ 2|`',3;(廨  M|`۹\kH^V<0>@5]PWRzi}fhi HۼDЙ[Q)֬dd E $ %JտZR)$sVCy-@Z6\]8l{=Ǘ4D;A}p7 /;G[ BH"2)~4-oZ?1 ĮVF+D"T+$z2.93d;!*U9̙/A#!D{m"~`XqSEk0ӷ3^ j~#뽸>/1X}nT9;qBSfr "f;럘(8["SZ K`O4O!pd7X@N73EP8rN#gm Ed1VƐn^ē,B,ul&!KlmӮѷEC|mjPh?P `2ДȐ9z˨C4Aq")݊jO%cwJE| 0:Mv}C Kv!+$GaS{{ kJ< /;W4E<-k8";#7t ֚|P2r= x=ƮRϗ/oub%b/qO[6[foh"x(.ߓ@ Jb3nAT$hbZ$Xx;Z7wNDISHUMR1#ukNt[&ZjwQe8`sI0aVqmx!9.#PŞOw+ <15EYS֜ :cxGd vf=i ϼ!L>ŁIdW `qb >L.K9O|.1Ci68IΒeG'JL[Y灌v&+PYMaBM5bCUAGm=PSʣ%Ѝ ݠj@櫙eL-V&u-ˑ(LK 4nGheɎg;jFOg2c$xJȣ+X0Mdf́g*p|Z(F_'+^OfEiVBsu=yY"T,T? |z lF?5> RlOO;Yj I?.86o&3Ua _ΕҔL?LV ò-%ƕFд†&PY& ]WPH "f}?b`'Av_# s\֢V9W ½3ʎ]qL?a~SNQt^0jvER̃ĵB Rds`'AV #-* V hkTU2 *K*lU<Aiٟs$<lt7&J+3?,fL4ք+%s6y*m9Pz[(-C;Tj.IQo_knT̢@3h+z#`gvU;pnzhbBb#^@o\a=]p=m4%Fi6Į![Fݎ%ZR{̈́MH}BQ^vym^= rώ{ Vd J>gƣTvn^CS˄R(p Z!4Z0(=۲LY`Fd ˶/ pm#0fީҺ tϣqe@[T .y$rh`@=:'8VV`孰K1 G}$70siY2?z}JA"wZzZwY v'H6$8ĉ?i'Eh} Eb5z_SReq X J Dhq >0⹜ /P~mMٱ6▂0q`1 Affiռ6$m/ХNphnC5VKEK<|Fghxg:#xYNnu37OBwvB-fzHC۟?D,hH[LxtOY`r k$-34ۀd@HK)-C:]mK@؛fN3aZ Xc}{6ckk /0TN Z]F,;8 EJ)zYp (DB0٩G@ +U5N@p-Jpo%z9΁p0lųI>"{b`EO藉$Ԯ i!Al\u,A]="/i c?ЗbY$b#C3܍f*j DFl|3bI) np2ߝ+q:&V򷇿wIzU y:W}6f#hKC#6nP2J yy&>]8_BpR `?gcF9V&$M`B^rUyA}4'᪊n 䉕{O.rU^Ld!|c#TnO$v׀tpRw^l* 5L/[(vTuơ3hHt;!dg F$ RDn^e\W/k__X'jJM LZfl4ћF茝`N$,- )H"¨;6:vi'Z>G(Apر5jeX!DBP ͠9! ,dTT(fhYX sɆ8#GQF3ƭn7bEtm ҈tΤ@OH2T߃ vOt#=$ꆨf4dO1 ųf(#eyt`[6%WUw\8*l8QJ`(B#\_ $;MN=1N] 54IM>( 0WѬ9p9lu`TR+X[ 2[ 9?StףbvlĂ5?.qú\|"e5X]wTɇ͏ )/~A|]~7T}z/ LCBX g#'g0[Ri*ydߢQ) 'wD]qe%S_|T󵍭wp?Pi,ƏiLQdH #2G`&io,wݡ%jYg*⧨& C'ݠ ai^@[ X]Fg 1̶4p%[oP.f,__Km z25 ؼ()VÕZ5yx+ill$hD`8zF3d<^,84{v~p0fpHlF TzP0[l/Z5f O/=(T 77TB{4qwX&c綒u_ʙ3oNPM9ջU̟c =<]R/*j_AO?g} 2Ǔe8=y6kªzRZfE䲩!E6p nE^(BiՒ'lKjXWx6d[.˒3JQݳYn"P%dQWLCE.Ֆ*ُ_½VqJjTϯ<xy);Z"HG* ۑ%h˭`whjqqAw}7PݸSW6eUkBW*B3&I(_ &t8,(G:J+1xlg1#1]Ba-H/7Z'i"^(vPXsxxIһT/O 5A46rh4iBOQh1%%r,cڎtSF5 Zz Lkn<ȲE,K.P%HX\͟ː[=n/ (|\ظ" P3ҁ@| tP?A_-$qݞT{CYD8tXyllPptvY-dKCWo )A:g)Ч 5☝bxL<19gqcQ8P̩PJv_\ݒ rXMxX:#"F1}[_(3RF. J <gdT O{adEiLACs-˱~5a,7+ǰp9` $$vىƨKQ|/&ÃY~9*v-a[:UKEvY9&cPD).&u$ :'9'0`li~HF;uB섾h_lZ:h|;»TD:2."pT4c/Z͕.D*oWvЀT[TA,IPd\ }/"bmA c2S4zμ 3p 5C <#$$2D Qn-VTao$>úNLkKkb񜟽st~r `W.  "Ը/dxB8;Th6Z$B7;2IeC7y^`Ik7)$ьQLS}%"q M, nL܊ssSm.!-Rc@kOtzǚЬOi;)Eo܀2k۳d]z ҊS 0b!0A >Xå):l~+d4D6gv8jqs]|ҳJwX +,Y"Pje(tDv:@4^A-#nv;MGCKZY6n2bvO9_U^|\` KwwKG;#EFH@!K ƈى.$k;X*70&ׯgvKJ(*8UK5OcLKoNΪDp!d0z((M f jTĵbX=lL@BRH6]I5R?pDVFxi ЧQTY(Lj~~XV0b\B.ɼa&֩m7FedQS7YԵF;wzeΌ:>mMONcOQ? 1N=wug#sTpQ3J(7s4ؑc/> Cg^c|!sI/,\JPP =cFn|5#u*g(U279<,NE~ZXs6z-%"慛J/"Ò3{lEJMs'Ne~! aZ1x@k`);l̤vy yM@+gf<0LZc%x}qEpQH^a+!q\/]l5ـ/f#/T3{ʍ6hQܶY԰qt)U.h*҄$Y·z%4u1]IE\)JSCff2jt@Y|\I~U "VOe-HvŴ 4mE"u" I>:ZǑO?ı@'Qܕdk*i[p^|en-y=.ī?'.^=tQmy2vp%tZEY,KtUΜ:p!"4؛Ί_oP "aȚ/. Y# 'eVxĶAGo #}ixr|2?*s( P;#;m89mJaZf6o}OA1߿ "yţK?"$ bh-wv+^'T.iٹ`T8Z7M5_C ;;>t{*g7W.Z㎙ʜJ(qKY8):YPbMwM4ޫ ;jKc-n}΢>۩FX#.ڨDi~CÃba3sC_y hN.fhH.QV͕KcǶq0c |Kh׮,\^e/1Q=fOL9}ri3ICPI\b5nb3xsw)1:3zs,@#b U0*ؙ)  ҡ l"Im0ôqDr%,Wbg!pFBݞi *6"8U(? cȯd6X$0Xl@eK>ƁmرJVʰHP`Fe$zޤG q 0[̆=鷑blCt+d{̂) }x$ק &/i6y5REFF\3cm=q/aZRe% w?V(:J݋kĢ^2PZK%c&-F@K#Ə>%M9 N`zcmap j5gaspglyfā2head16\"hhea$ hmtxlocaqmaxp namef<epost0 2webf,RQ=T02bDRR x͑JQn~ {(f@}ZRXH!e $6ADtV $Y,LԨn\ls!ųF*rR(TD)ɥ6P͸~/ 6ᣂh}c,8.E"5iHkHD G_xF%&).qW6$e . ܚ3fl?z]7ϫgyxx9+&mXGI?ك)O"MөH2tnxڼ |T0~s;Yd2d$dYĈ. (.*jԪ]jjW}mkWm^?[!sܙ$$}{}=ssxNl!v2] .q83H(Oe!өH: =Rrx7Oon6zż1*.`ts䊖`cubтJ­$C^8ޛd!Hܼ-;<|M`Mq7sf{c<>ܱnYz./ pB'GX a\N>qbwfAt0xvp)/2Q^S;~K^r5LX˧Վ״#1:;܉̉㺹A9$YGXv[esL' ilaGx{xP:)g.%2զ\ɲht%ho;:CsڿZQ^qF*G-&4R0LWqL|}RZ֋&{k;.ux̲]o~ms[?`S;f\ ojf.6%B*u)!f©cbS7CںGuб}֮E\j7hRnfLJ!ǾK T $#++ rv)gb<(}+o>c[ kϽ:[HouYs[-8swmǿq&dZ knzӸK;/sN[ ,Ճ3OG*Π `bL&.&}lg!pq_XXSW:U ?` I…D9> S ?>䫓hE R|z„;E< 4.50"9Sn)O*Obeɰȁ[oU7rLb}l 17W>zj7>gqOzU!M:GUcFYaǹX =fxGsfZh$̀_\)=ws'GSSU(7%\pQ*ۑ,=` U" )wL5&`3<p'9x$!Cfrvƪ ;IG= t&i雡Ζ L֍U-T 9>a 9f3Z|HS XʇP+$WACVwcO@J|AY#Cq1ܨjQ>>K+Ԓf'fRxD 0{Icm8\[uwٸ:!x8;ti)0,v{0j ]%N`41NU+V=TڔEx/#a+A-Az˒PA2qR[abn50"G_Cu "|D%oY 1bUMSB'_sqU3Qn![ mHy1QAN ;źRsɅX-;n^M•XyѢa(v}: ݀`D$?]|^D<[tås*r="kjaJ~0))zJ'z?<<#3&wccC9x0T)$ܣ>a ָO]}SZ_J zp9Sϕ `;fkbl9/߳0:FwQ ""Ԭ cH#U]oyF^W+گ|Xo@  53B+ǐ.Yӟ֎ݻ,sRܶ24?5/g_AFdŃYj_ӎ}XA\qaC(9A /A!ʚ>yi>ͧZyvȟ@5p }6e$k{b]mZﺘX@o-dYm8m#if8vy%G,xӵd%Dg~nA0M rk[n*ڊN> Y%8[z[o>ջcL+XZzCjb= wVۀ'~st#i!U(?t@)itq4I|I/Jv yٻE FR>K |f $,! ? φZJ}MɈ#7~/\ſkޣ7dbu֏΋~'*E #B b@71͝Hct<=t>N~L4_˥c+#?0HNjD7OV+}D=@i\$Zym /c4.K@fȏi< s%yw.!i+p9gSv–q>':0JʬK|e< (1w/dc\_:ѣEl;5 L$ffEyw DM!VdpXTW(ᇸ|dG{'OՙLJ3&f+ ntѱ!UO-Zs?!w |`pJ$Bjm{D[-~W-c wZ A6D^Ȍ45 ʠuX:)%7wQ ӻ!eD]F,!ÍCi;{NgpQ=sڐE0ç Aʜ8j<(4skP1 ģKUaWv~;Gs(ӱ<}$i¥7cd~MJ:y…3n>o[#IBّ2\?l@";%# fRm$ˤ:'?RO'%I9RJuAc7#zvl"Hu&cy!W瀦IbWv/WsHsHMeuHۦ_" 4n[xzԴq9X9DB tZR׫#}jcCbCW'pt8 \JJ4ǙȘq1擸RK)0&r)ڭFRO,K1hcǷz'm>cCxoO7Z =Cc#"}*f*H4l2+Naɳ;Tw. MȠSQ1!hZTJ)SMtGStǠ$Ci&G>nK~Z..13r?vvT&7KsSbn#IaYg$(3!p#mCr$2Huǽ$}xaa< b ~Ht$4bAXR0K8c l$o8 Z P6!nH!H f$>*e7#ֿ>O.(r2>I@hAߊ= =ͤo0նPk{EOa^㣷@887>jN_ҿL[g\zm0ڨE .Mlep&k_!#6.?{A9x[7QwFGU=ף ʒaJ&I!a| T9>gX;z 39"#^rS[q𩸓V,SԉP/URc75-EsaI C:gZrάm0$Uu8mLB1ٴ22उ;neF /S"I?F[QSs1A&7Q{nT*2N[_bj0+-s(9XZMUT`,,JOY93WYBQ=Opj qTD>1B9U؅Nh?^zu|mU|a.*4Ad>Zeew,3R\Wƹ^g2T馴L,jXLVvrwRͳh&6X[j%Uy^AOUIk+j:q0"Jc/{C!%gO,\tEq ?P'q% m01a\IH(qV-Pft tp`ё阚 ́#+Wj4Uj!kGcDWՍ&Ʒeb#*W; $%:SMT:cL/.s>Lc>TGgܔ0y3#Ŷc1oб-FṇVte:ЏezrYmLHz756/^ZH!(R ow,CEt!]|B.|881.0>VN?!pSˁF<+ɺFP/.~gPRlr]T|ʁ8з2IB'T~8ɲ*,JȔBh{@y9Q!R,4ﱏ9#dɩ02q"Pat\SW֒bpϋhBV%tBd3{j/hgE܉4Gl`Is= ɭ**sH>~;p-VLЯ%iPOq13bXw%Rb.5gNWx*21v$cOc٩q/>;V~*BKZVeE`QH6C ж񖧰|Y߼ʧ NǯtLGt(b:űsY^UCUwo% }u2ݦQJjdӧ>cSOD6Y6ҏ E83_2ޜ'E6Ó{(&qig K//?&eC̷+Ru].E)nԭ(0pWIbc+B4=&_diLcR̠MՎ&i&IBYZ` Qʊ1}QBX;d,}.u:-5_L=jBRNR:j _bLNtUx+e$Z|9 ҵ[G]+(ԣ@Rއ71b"SN~//@iX>(2I:;D^M%h⨪B-哯:-˅'q*58\oYUq"rZUYKEf`{|Džq΃T%9!]剦ԪutJY[asR7ᇵB_G^k#ll]\#Eٯ%+I|<?M\s5իt֦.lr_`,rd03K)e?{hwi㍉pxmjět^q&S/;8mhsXwہuYኴ.wE'Mv^M名7BJwʴaxw}/.v6:ÜI stny RݺWou=]۴D=-quX7ۻ\-+zſlZGټț[y#2/xzTr\tl+:'3FYb *L[`Zׯ"-T;cC xRGDmҲt&X/&@۰u4Ook]O `9c}Fqh c5( i~˞=3x{oaǺW|h>z}?lYuDo}ޟoA8 s]=ճ.)$il6BxvS">4<' die-I&_aޔt q Z*%r/Te6C D7a<} +-8 11ؗ Cz9-($A&eK~% UǙCaH\R1Yn![*q]v7 Q*%2ـ D(%lt *]@2?m%VR}YuKIuY&9/j 7^s2&s ]TX V?-/[ę_gTQ sQߺ+NXq!z{)j (I=,H3qzYj̽/hfq  1T0=7[hsq`lEgt膿|aI9v1|;zvJ"uMͭ+t/5xJƐ|SOAucj %(|bn/g@U(;+u:u R&.!-G),c妱\k93Vo1MxSNՑ[˔ΣNTU<1j廠ivBL|Ao.2"Iz'rT` rj 9[W 9qX&yVpriV,0W΁k^P$aFyG;j`C5- `4eцIyQMmRBMe1@.b1Bz;c0 `Ak̎ Otַfd⋧4&k{;Y>NUv#o܆}6>}p}&ݬC~N95|yMd˧X.!NkGF _־,B 5DɅ@Cn7>kSBUc(o#p &^]. ׷UׯoStV \2V 's|A|<y}O-l|zb :-r},ݗ~T lLg]5yQ L Ht)X-3ةAVGnWƥ>W΍}Gh40=ko珳 "}1[$߅%*:͌gJ2-1tI3Zt->}xԼl{wØC67g?yG_ &E:c&˚96̍K䡽/>: Ͼ'<9ipźZZ{gȁrw;^%7{樸?`7WTeQI?Mòǩ?beQUm/9X^ ٢?);)ݪI,h2ާMN.-&.Y{͇k% 4R`I]6 }fgRC Ve0=Bi>#)=d$\!$>nI G%v׷j"Ms,vTm56哊 pcQ.L׀TIj2 uN녭`"U#^T'"U]IRSa`2j \ig$ nZsVDStZm\zb5>OX?Hdd"hQgoj1 zv柽`m_.>n!躢%+&Kż3I;yޖ/m4dz;?|Ιϛ?'ڴrcpo>jӦ=x@qv:j1hX9f%Io>[yFQ [y<2ʖp |ܡ [ؼY-Hqc%xP6g_$oHhhLZ*y)ԢCR~X-P!Gf/ *.8$>.Zgc_⧂l")Kv0Gʺ̺=jO+q٤#;,d:aD/`3a"Oy1IuM Tc{{7~7/? wm+Ȍeո3/&*:*`mo<2IG>Dw LB_s*#S<@0jڗ7; ۧԸ+4G0mW+T,INv8&KXl33FZ,ԯmO$ e!_,ˌJ+$ni>z9f'Kt 4V#S҄ E< 0eGvSe|z`"$*%r :XWxT-wi =t%vˤ<.lK6QJ̪te ?Zr;*|9ۿi)?>n+;!-`c'-C?}/޵41Lf[ə~I&9nʈkaSv`}+sIv?D'Spqz aYF"lx*Jo o=^B<5Ȍfty=Z_+릲6y9u3TpJm!6(83N(4ħ08عhQg'j,/FF T<6;Ty9*Y'5(8]B:.%Ӳ5.ml  j2+ERWןg?fzj<5g|}S_F?ufoFW/=]/Q I^vk]?]9df,?qɻfZ!:W1i,Ym|_9+WyyCH:bѽB([bpH:> NB5=ļ޼qC]2װ̸i009dgG:쌘A_o9w"a{HZgtvvtBƺ5Ii d\֐KmظmrY+4[]]Nc5L|oܲe[y[0n8yUy˝JM31UGD|иmHEd~(OqY*[-63}L{.}F~qQךgVvǂXA)Rzew[Fiܳ`S~oGZ^B[ڷtڕ ]4La˦V Zu-䚳E+8?펕k?z{eJYw97>B-8tsT쌸@?/<;mnENh#AVl^W5D¿{Q.ZPpJjsWgղ&.k?Oxg8w.Jčg:Nv{(F1u܇W }l1[f{/umBgcoQ<^Q[GMtP니_S=dԞZ-Ag[T)Z,.Xƾ1.8Xj!CaJͦuY-* ]$3 R B)Vb33{.NTgMQўy6va&b!.N_ _]ڈA3]W{rj߀tG\H`/;ذ򙧵PI+q5pM{&sy'~c&T6 Zm}mm}^U+>Pc}Z#_GcCf78}z6.Y΂-xmv龾tex+b@)+q1+-V d+Vσhx':)Oy),m׬>!3}Ucs/-p%g|%Bs:ꚽw]jGfc5HF5i5W6TÄ;OW”&PAthg gDm{]2Yv'>Eg+c؃,-xg : -icvgdq薀bnvPtd\7onRڟno0hVLHTMMӐlCƲ5ݤn5`Rƥ|4e" Qt@iU4|,iՖOj~CpǗv h5}5E }~>q!ĸ71xku2'bi ƯyW3]3+[v:͝[vf1U3sfF%Y\dy].qRIvZv˅(jn{Jr=0az/:Ξ ?$ 9]ɢFk N۰ bc@ uNpU/< [t?̳$@7\[rϣ{Rcsx$_y,yŽs[Vg +&x\.^"M8sw۳?Y.q«e|m[By$V;U١:^fώ2ؖNVDd2˰2 N !O8?vUT#@hIq]M;VA"t0/Wܛ$v?%zhP-ªP.Wdn'֙cs/Qĥ̡sYrM暭I!qQ p `ѡfC[j@pP-xaAL/d*j_!$LU;a+:qާiNizL\8Ẕm\~$b*,d5D7FSF~u=X&6msXC16d] ]WBE}!:^oW*VZ뜢/S WEHSWty5kM9IϝlTNn׹c< p'>L&5 w>z R^e(&U^Iֆ$I67Y#6"UFQ6ND0)bEEAEq`[%IAJ2vH&jKϐEQ/:;,ixxky D|6Hv#! !rT%I&OĄ.)F" y DLfUzE-*|7 6&DkE{&*d @dECpK8FYEY^&!wZ;o6b8t7N d'`d:U6l4I"$6U;:"p<8@1l$J<`Akx$"xBp\ ]"ɨU]0%"1=Z`X"Xmk3 6RJN#E fl1PUp b`A &*vKY8Ι ydĈ$<\+ndC:bJER} ,N/9%A4# 5 # `*o3< v%Oi Q2Y!9yGE҈.I6ީA4݈5x;1+,KGU4I 4 $$փȂրL! pYID(p嚈^48jmA30- t-TKTn4`D 8;ǾAEW ZWS89yRάګhsc2Y#>%Bg"6~BW-N]ֲOH!(N ׉~QQ.jB#Yfdo4WfsQ3oNU"xbv*oDuW94Y;J?$ro5c8o_cv)Q9616"HB okkQX>Yv0ԷQEN aڵaۨXmg! eǾdd'Pє9rE +%Yk9wa K{@{.:@`3}K`&/,r<_ 1!5Esn-";Ln[ϸ@܄,"B0׫KܸW &n1/) 9WKW̞' ?w-e}Jsף<~uIm_G}sg_/] Ko֢uOcLOk(}!Wny[a+,%mB\$#ې3rX,N{3%m }ItKJ\e.I4rggnCs//5ML_w=+rK+׮]U/\#̾Z[c:A?3:u LT0>QwW\ykb%\.臰+M9rpc'i<0{CM{W3~+0;zm pY{ZwgP%K)-A"8?;o=:+rڻڻ3 , q혧/6NmGLv$(sK V n?wۘq 9:ٓL4ٷc.öPmi)1׎fˢ>j %J[f&ѝ: .~^ -vb8y:iž+-{XVܷb,6~}+7ɘp1a"?cs$WĒs7/xxүԼUC⿾BLD?$}ڥhĞCoo3oOZꪁ;ҫ,ŋU;f_8mU ^G[%gG8;9dx)2x;E$Ӗ2A >÷~ \qIEq.%vd2R!R4g*SHOAPy]yRdA,*,}s3c7|^袺NfV]e(݂FI/ҋW?sG㵇t-Jzp7ȭ-> k~nVz/]?<7\~bw+C[/g܊^2&^ø^R,l8y8+=_cߍӯw$,$! ]'ݦqyn-_aφ>JPVqsU樣CU_'v񪯩}*i|\KM宲dNvdjAOKpYє5ǜ[ sSl7kV_f.pŰ1!*tXmL\WrWvVAV_rda)>`1~gaLl%7yc-^vեoi3P<[&F^޶d6@zc=glO^O/3𖺮Ծ; ].`GמeMgK#p6p˹VŁ+֟X,xUr|%.:Į]V\}>] Gߚ3k[ڟ^:;wY?}ڦ{VϞW+-rL'7T^4:g```۵c7qJEfc;t }wLb}'<1)*:vSrTb[81'o, _p݉OBLZ@e+%1/)zx Cl*iv;8_p:ExӦ@pՅ?w% ͋Y34Yd`6u}B%=*n٩mfJUX."N!"SLLn򞉿k?w.2'xJRJ6'!j@jyAdty"ļBayF KozmԶ!i c!?,C%J3S}YKKtEvDo~zɶ٪qkkn;.u[  L$GG>q%VC̛Y]{CxI+[\3yCYC쟻9(K^WO3{?ΚȮ3osGq̖{wO^TONwSqorml;16m060ѫLI@BBztQnz6\9K}m % IⅾM _s9ݫnjh;}hAow}צ9+oG8Ċ5Qj5mX]*)\X9RO2 aBžh{FeӋpxZڿ #WoFZ%ÛW? ?Zp^tIruk;<mj5={j"]X=gPiޚv\R!ŵ]>2>a0E1|"8g`=W2M۽iVW :a#<~!DrQEU hh(6&tceiuϸ^j@κh'HMMDWƀXkƹ[$[-M>wu\rI&ΈtwGd2?;w-m zRw8-pjX]Yk7`Gͣ14-m]ǖw3PƝsf lp4D'hxn=S[nTx;nS.jy*ƍТ!dB窰qRDЀ[Fyta@fos68JuU3JGOĤ~rhc8&i_2c%T)|D~d(?`ڏn*CR#&EBDCEshIMI^\& )!qҀ|oJA!˳ g֣ &YN$STf `Y~>}Xrzhsd;a&%dR_LԠN0R% XvM32>Uvơf}QKц9qg}w#2KS2ѥ-&;0Bran2Vl/ fg! ^ wJ6"4'^ z哥+V$۫ԋ}ΙΦ-OG}+v1 <N%uA)۪n1'XVg7,2$⋅C$d+= s앫_\'=3k~jX гxhP":nXT*Eԡ4Nen1YRti%L@djh>S qyAWG'0_9P"- &fl c |Q߶`P+|f]u x FϮ2RF 8jyCNo~ozf^NlӥS,/pOLLz*7.ȸ`yzɼ+gF#󂴖Q :%c3I}\dDW&۩]vȖFoynT尘dH]1ZzR1$G5GH B](hZ#IcE?Mat.Gҹ{tSA6ILjQV.ʹccA%%3aO;PrH&3 NYˀ9SԱ׎C3Vv^Q/lQ03åWn'mYXDPK~t5  E?99pafhNTG"Sh)ZԱ`?.B)9))hmLQ$o>&}$ lH6 )$3D ӨނTT=)ldz\ːw>(0IGA^$5ZB$L]`L42\Zq H_gIys?k#KDa:a|RҽKmojumkSg:#}HncZkW/=s"h# (7ϴI̬yɤgjj4\-h36OP:r-PXYxYÖin[ȦM_ux/dKJb( e5׃N~R$č AD/#]~T Oav$F7hIyn!QXu,gd7YJHC8g.SÜ%ꋰ%v@N\h`?/~b$lZ˴o޷.ݛGNp>a-]}Y3?]~Cy\^1P< j!_vy?:Rz HOOud8q 7bTwzLHvi[z--6nl&췯=ikLF[`ށFJg]\~Ow:w-/Vm׾R{`ݑ/pǧ52lm?qN,hĦ=O]6ǥWAeyGrc,a0&RL&?\2cj m9O)TV{#4y)UZ40*OxL"vǩSR>EA-h͙+9fKcpd?[7nyjhyNOg8bzx# RRn8*f+^t.1N`BXoC@V5( (Q\ck-L˶(LIAK0Kmc*˖%*YZ2@6)n N")P`f2c[ jZ ӠVKAph9~$ۥO\n\+UQ(Q8 DO 7P: e%Gl#OrMa̜*r2¬'cdg**~>hӔ\\[۫^$*;ix|E)p8"Կqr΅ބ:R6~C+;MmR}ɞfɵG*6ҩ3\I|zcLcS?E¯|Ԙa2zd.F45`@ t&YZb@J|TmRQFʞwMsVi<6Prkm;oK/]{Iw \}d#0!ó& H1w_d ߴ4 ^oZO*? 2{G|hd\JW~EoG.0." vSfc#RZUh z#;oWDfD810xgϡ2*?כ8pۼ[I2^ cR;⑟h$L~EV5e1%-ݎ-]TyG'Pr+f?8#˓C7]q3u7}5:}s{{dǃf|}@%7UXٱkk9{U3=gں`ȊBv_72Zzg1&E4ɵd~P$senc1WfGF07VZ0Pe lN"o"v*]!^1Ɋ%vN]u2#.4jwGzNF+tvK7 7XgyjPG@kl=-M,cL1P<}1x9NueĔwS:L`xW,L ,{ɩTKb}A|pDM )hE%/orY2*@5&bNXJ)CzxhpA4k Ť%*9uJؕlZ+yU|, *Ǐ_2wxNʦoŊ>P^-":HUlTW2]7 w} z)m 㲵;lJ\W)-j3䪥D٠BnQ=r>C*B YÔC {5 ĿU6rdJϷ.1%n m/ȑ1]V&|3}#77A͛tMu]Mv{SZ(J N k~%R92jGPO }p>JG9Yϟi-7cX /e2#2 #a7d}JTEn7 (Sc"Yl$)>+|64AvM4Hp2&<'zN UEǒnOJa$qCSV\%ve(# ,Deꡃ;W>_L"(iq9oiZN(KNزJb^0\{cf$e*F&-&1\0cKS㥫Cc6y^3fc_n62Vw2cW'ʌ9c+M `v],IdFO@t$/]+QtX4qVߔ|뮑im'?Qx?^ڌ"ܼ{wp3uJI_,ǹ=*PjQF+KU@$XA'٤L fDnv :ڴ jJR'Xҍ]Sy]Z|ݨnn#iAGi^CI(0 U$ !+~|V^795hyXW&UFna%I䷿q]zPuEx}Q#VzStca_;F{mfdNq ѣåL'Q_Dnk^#SrQEY7aЏ>an˭X<%Yq2μihlk• t7pn{WNձA0-w N/mmsZsFwPsRW,cPVt@1psNw7ed|\:r: bF[ܞ f'SnjE&YBE3+'/w@}ҿ~܋\AW$ط*;H][RmO~PX5ًӳK,5Ա`oʙϮom*5[昫iYYIn((9O`OCd,!¤@Tfu ;2 h4uR$?wleG60y= *~w1e< Q9@*VaXLA 8DY#|r"HyGggd+]U W ]ͩ'h"UJjtg(\Ë~2O Aߥh4,!3I)}=+n3h˴؞WB0 S,DоKfOv(8N<}͗B ;ďC x>. wX4|> k^Zڍ :$4"8ELz,"L4n4s7_B~#8Kʕ;8Ľb@ Aٳ_2 ˓֍M ԵɾtRDx H y6 U' }ȵuWWhljԮBU ׭`?Pp=lǭ-b5a=\D xh<׊yG /'hS=zRzD-^^klc|zu,3ZjpN'YꢭEv[zt8KV`Ue_FCق2)=Thr1_Fg{>Hs>2=5E  (-,h1ڀ4*vTPN4B h$1U,H^"Eoxi6Ȭ5 _]WZ^x^e%zM2?~̇bWK+xwMt,WEEބ?L*SZB&% +o.0T8V2[ǡ:V (vkRhi 2ʤdtsKFisU<4ߢTW|q,\L8 -M_+ dl-J:\/DŻMmG'lCNdvv/^iM?ʾ_Q.tb21)yA`R$8I.9~Fذu=V (!gx(;D7l8O"J, g|xo95PȞ v=O5UvJcٱiOno̟}0Fxr ekgIA9Z}0)  @df - _́ ekNzd.eW}һ*5X\PkؓTF4}& ][uFVwW@&gKZ'U˥ 儸%F1h|`ԏbU,L@R`&*.cK쯈,ޥ-xqԶpN&/ń m Ș&sk8}MA*t 2mËjĩ]~x ip#J.H "KCԘ޾Jaw/=;n Y|Rrhyh*斅Cy@gbY4N_Re/@L-Tq Y :+wT0  eH)ːA&5YKL+i 1r銀SJLÈ)y͠Nb)kGB;)M7#e@,Y`gxL-~ WeeFN|H_Q> >FvsZ؉KSF%ҏ)XcS?x7Ju)葆2CtjIYٱ%ppu^o$oSKʖ黷c䬗sؤ@V !冀ÂS t~rJ'FXU\B98V\tʁgeE"(Z>*-1G/e}5y B׬^#ܭ/bKFD25OSh\ϋ _YXJ#Jl0zYQ D;Nkg ^7 gruݕ;D^h^&pF,Dǂe;UXSb|qjj6j5pC9fYj6~gMesCOp>oLGVӂ:QvEd ut-̾udp(,Y0;['L('GUŊ3{A?ZiA%N6"#:;z\4|3>VUxSIA 6ZT'Ο-&Qw9Nv$Y|wE"Gj4̅gGQ 1#oSR~*#Օ+?6UJ!}'oQU_ NA7f~(xa[ :6Mj9+C6u>;<7 urj!hf7/bʔE9UknF]aUNS nCD%m)5bx`E8Z͚R]+P3}OgciڎK6w <  sj"IʎF QEh1YbkF~|-&2c9N c-{^q֢ YwNlKo{]W';g= ~CpߺWaӦVcj<o.XĪՆv-C~Fwhlңk>4riPb_h0 EL&쑮-3On`p\UUQe~ZU[Vg(T2+Zn=1CwNM+׳5 ?1 Uϥ[A~f]b]厽-Py 8f/nʑz_u/h, j7uz21#z,zQAlzx-C#F^^0'DGfL¨<`u+<(rQ1~^pJZ,0}9-׬E\Nȸ882-7hX< ' Ο_u [8mYsgdرjJ.Xv"x,8'TV,3":b@ NGZz#ޣKf~>2^Yo /}a)̚qs._zf0 <61SEߘY>lZ7~&3I;O9z BwÙ̖t- I{.1?)#,N^'Ćpn!5"> 穿t>5jTO+4gOV[>I[iJć+kJyd = <6_#b:iΊ @ G0@bufT(w?R) ϹE:0%JePap#4xǧ y^gi0~po+GQ2o!z|iۗ cROQAE@ 4=inoaZ  g 7=s'8Dw޴f8Gkvo0]w0މԥ'N\W?V^A~:ԡY[b.Bxeg.togfO~_=o>tDž[=(^Of{{0[paGKGA$OČ0"P 18#AtW0G"b? N-./ &'6?_9fnXLjw#I$۝%z]M`/L4='2sylMwX#zU@P徦kI TfiFAj\l)W)j̯R)aq~dӸ:ڨ4b o|MH6WcqOȹQ[^lT5IZKi*+%E[ZetX/ UG%%'< 6*FXnXLkkB풞&lbvp)&Ѧ@;ghEά]iom匬_, oSӛƹzUm44}:S 4d|Ԍl  3S<]#p?F^nٶeմkfhk1[^LlJEWVE!߹3 ƲO61iM Qv Gj OՔ׏hn >,nKO4Ye |in#Q]LaG }: 3,c/tZǫ;tJ)I)VA }*A &>oI9݂|>f,Ź:FO'kf(d(s)eoZAg?oB.AIg,I |`4)ێG"c4.BI>U@ ߄3D ~ cYg’9^AW&*,RwϚ=v 6>:ìvۗ66w-wuz'fݻx>t~ޑ.HDnSv wufży452uH[pf>R۰2ۖZߙ `,}&=c{?d5E5ɼd<ɝ\$xyIab{V/$4X;c'=:|u~[33Y;m~ۡ60%K HlBUz[UuSn^mfWUǪ̪Ho2o7%^ض>گM[[Am/ |@L D)9cک+RJ-;6Bsj j t˝d ų < 4g5#!4QLA s #$hZ<֐` V‚,]G$gFlq<5E;|cXZj1 nDG>S'hKD?'AC . )4gRtXn?n"HQn.Zayb-}EBNȳf@(i-!4nlvvfaEv W۔tð4PpHK0HEb m^MحY[Ƥ [9%gFŰNJ; |erTFc4 #&E -j.Vai4YUUƐ 8BG͌[UuuNi0cX&a+Ӄ*v)Hq6#;.j=3VТh 54b9HUJZK yFhJ!(y%00a@teeU!jL5Ѳ{b2Pmc>]6stԲ Ѵt߶Z]M &q |K ZB y-],V$Q;qT<IT7S>96Nx4xd"PU8i6PYVuCV; jwy ^ - X]>%vQ`¬{*Q4w׸!QrUFkwJ^hUFϡМ$2 U , 9V;s6 6LlZc=G*h}k}֫4(!R4BJ.bﱖqǼfCz4tzO ՜@CƨqGSB=8Fr4j6|nX&S*n^R@ukQc]1 TZF-|&8֢FgWrj5NlK9fb_5]׸6rg՝˶oZڂѕ svwMd~;]2Q뎺}/Ec`c9@CaƋghLF4LxؐpxnG X\5>ÿj9{p#R: 5phW(w\r(7~rÍG:Wf2m#@eꙺ{]*:0}Bo[ڥdmwr 9 o8W_E-uϷOܟ?/\6{@kO6O.UqVԽhb`cKfۯZ$`h!ͲHCƋLJYt@ 0^BVct="`l=p*A"PkhojCOh#X<}]YU yhw;0}uҩn0eklBa08m^-YXqwnܾ-,"zj Nvw ~&O+HA "۷+9 Oe9D ͻLUY7@?K72'b185v4TUͭ;QW+k u0wgBFwjaءT^6YR)J;ULHW;aة Qޓgiv_"#E44r|ML,M3Ğ:L"yH,^JD .kxf[rqҨdn7TFiNJ2w_FRפqxKn!۸\(GN>XEcڃHXŊ,5*m[H,,T>Lw;RQQw˵c 5h=&{XJH h(g {fn0*\]6? as¦ևoݶXހ Az[祷R{OIozaUO=b ʧz E^B6hӳ+[NRN6~Vo~΀V+_^AW fz?b/C PAp<$L'4 1μDf>7=-ꩁ_^{I2T|vL{ RcƋ.t󊿢ğN&=?j^ůo_`e{ƫ[nHTp2ڌI \eo,B`tkr\0|0A VNp `ixNrl&j#!Mnf"u䵛5Hhqr!4#H&uhaElgF At K,W:DPY`Z Zhݢm=n5I,'\Ӏ"ʪiʖ4\}]6Zm@0ģε״q4²Qo(WW ',M'ͱώHGr3I5_?ZoXf*zl6uŵ`a9ɘ 17++o[X_ee`rENSAq@enoK㢐WVAQc`ZO^%s?}NdԫQkK_cb&j` x ֺhd1:ċ@v Æo%t,xg)d#xA+/=ؼ:lzgmUt|]'jkúYuuݰ G$W pO BSDHKޫRESjsU,6/:.*`*uѰ!P::]P6N.owx}=tN"on@GgoL+$絨-:^zTl&tT~zlq:zT9|)BeB#f oMx?.ڙ`8P'k-[ 7{qb޿ c|bgAb~Ļ<]oeGw_u]=7ǽ~]/HHt@2N/1( 84s5Z{/`>un{h)Erc{?exc``#10LbXɆYŃ/VeB؎Ӄs? \ugT5]·_߇@@#A-UB.Bۄ+D,D|]"'v@HRJZ([("{S 9rrrs˫[,&!RHA\OUBw蕬*VSLlQR %Uejjj6:ՖIUU_R?DZZ^h}fVݢYǚκG0Dxc`d``lgda& fB0xmN@+C\6Ƹpm1a*^B AR!J!mc.\ t'9{:E0d2g "9cV2y8ќ£ylEk^%'gVo?U_?a\e`!|G1|')|g9|_q?/+*&o;.!~')~%~_7-{ğgwĿoR@DU2FujPZԦuG4IS4MhڕviA'E{>/G::::i6,&L[h6:Nd:Nh;NgЙtMйtOЅt]LХt]NWЕt]Mеt]O7ЍtL&Bٕ!i@PHIi'%RF9-Tk?3}FٙXRΕrS)7rK)KJgrg>A`i-SߩJ)X<3#ͬSq62T$Ff晁 e^C~X24̳?A-K3<&&L+ȌHr1 XQͱQc%j~d˥NX#'g,~"R\ 0nH0ٟN Z#]Ѱ--50/5l)MՄV2ʼneu Eb}e=p~fRfzxY[닾ym6@n:"D)D B+Th~TDZoȡP$fP.L/$yd/3|ň7O--M<աit9e^)7YZ]e4xVhv)8e~4dplyz="a0gM!~Tco]`M-`ppUw4ċ&[m=جLN|wTr< dže1u[XU'*ckZ c !#N&'3FE3/%`@4;_#;O iV8w(FmIIZ(: UNJ)ma5]Rfj-jZCnXeaq|q1HϩUC5u64.-FNhES mrzY^ - &W$ &X5fP 0guTNsFr1: m򲊝/T-8"p'ƅfZ(&LvB%e*M:~lXS.EPkmMP^ 'MMv%:xO镾q2̹~Nt.(_w6I`X(wдSœ8.J֪YE CW<츚nՏxTuQu,VQdZ)CFY6g97mJotjHw.u޸[s:Ks97I5#]4%2F2XQ͔/1yAb>iIr:6oUk`& N!td y*[cگ WSk"+i"}Ɖ/jK:\d;s1~0TdϴC5g~\Msu[4?7EےqϪfN5 jwQQdjango-guardian-1.4.1/docs/theme/rtd_theme/static/theme.js0000600000175000017500000000130512634712306023706 0ustar brianbrian00000000000000$( document ).ready(function() { // Shift nav in mobile when clicking the menu. $("[data-toggle='wy-nav-top']").click(function() { $("[data-toggle='wy-nav-shift']").toggleClass("shift"); $("[data-toggle='rst-versions']").toggleClass("shift"); }); // Close menu when you click a link. $(".wy-menu-vertical .current ul li a").click(function() { $("[data-toggle='wy-nav-shift']").removeClass("shift"); $("[data-toggle='rst-versions']").toggleClass("shift"); }); $("[data-toggle='rst-current-version']").click(function() { $("[data-toggle='rst-versions']").toggleClass("shift-up"); }); $("table.docutils:not(.field-list").wrap("
    "); }); django-guardian-1.4.1/docs/theme/rtd_theme/static/badge_only.css0000600000175000017500000000576212634712306025076 0ustar brianbrian00000000000000*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.font-smooth,.icon:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:fontawesome-webfont;font-weight:normal;font-style:normal;src:url("font/fontawesome_webfont.eot");src:url("font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("font/fontawesome_webfont.woff") format("woff"),url("font/fontawesome_webfont.ttf") format("truetype"),url("font/fontawesome_webfont.svg#fontawesome-webfont") format("svg")}.icon:before{display:inline-block;font-family:fontawesome-webfont;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .icon{display:inline-block;text-decoration:inherit}li .icon{display:inline-block}li .icon-large:before,li .icon-large:before{width:1.875em}ul.icons{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.icons li .icon{width:0.8em}ul.icons li .icon-large:before,ul.icons li .icon-large:before{vertical-align:baseline}.icon-book:before{content:"\f02d"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;z-index:400;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} django-guardian-1.4.1/docs/theme/rtd_theme/search.html0000600000175000017500000000277512634712306023126 0ustar brianbrian00000000000000{# basic/search.html ~~~~~~~~~~~~~~~~~ Template for the search page. :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} {% set title = _('Search') %} {% set script_files = script_files + ['_static/searchtools.js'] %} {% block extrahead %} {# this is used when loading the search index using $.ajax fails, such as on Chrome for documents on localhost #} {{ super() }} {% endblock %} {% block body %} {% if search_performed %}

    {{ _('Search Results') }}

    {% if not search_results %}

    {{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}

    {% endif %} {% endif %}
    {% endblock %} django-guardian-1.4.1/docs/theme/rtd_theme/versions.html0000600000175000017500000000244512634712306023523 0ustar brianbrian00000000000000{% if READTHEDOCS %} {# Add rst-badge after rst-versions for small badge style. #}
    Read the Docs v: {{ current_version }} {% if current_version != "latest" %}(old) {% endif %}
    Versions
    {% for slug, url in versions %}
    {{ slug }}
    {% endfor %}
    Downloads
    {% for type, url in downloads %}
    {{ type }}
    {% endfor %}
    On Read the Docs
    Project Home
    Builds

    Free document hosting provided by Read the Docs.
    {% endif %} django-guardian-1.4.1/docs/api/0000700000175000017500000000000012644306605016454 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/api/guardian.mixins.rst0000600000175000017500000000057512616547266022330 0ustar brianbrian00000000000000.. _api-mixins: Mixins ====== .. versionadded:: 1.0.4 .. automodule:: guardian.mixins .. mixin:: LoginRequiredMixin LoginRequiredMixin ------------------ .. autoclass:: guardian.mixins.LoginRequiredMixin :members: .. mixin:: PermissionRequiredMixin PermissionRequiredMixin ----------------------- .. autoclass:: guardian.mixins.PermissionRequiredMixin :members: django-guardian-1.4.1/docs/api/guardian.management.commands.rst0000600000175000017500000000027012616547266024725 0ustar brianbrian00000000000000.. _api-management-commands: Management commands =================== .. command:: clean_orphan_obj_perms .. autoclass:: guardian.management.commands.clean_orphan_obj_perms.Command django-guardian-1.4.1/docs/api/guardian.core.rst0000600000175000017500000000025612616547266021745 0ustar brianbrian00000000000000.. _api-core: Core ==== .. automodule:: guardian.core ObjectPermissionChecker ----------------------- .. autoclass:: guardian.core.ObjectPermissionChecker :members: django-guardian-1.4.1/docs/api/guardian.templatetags.guardian_tags.rst0000600000175000017500000000031512616547266026312 0ustar brianbrian00000000000000.. _api-template-tags: Template tags ============= .. automodule:: guardian.templatetags.guardian_tags get_obj_perms ------------- .. autofunction:: guardian.templatetags.guardian_tags.get_obj_perms django-guardian-1.4.1/docs/api/guardian.managers.rst0000600000175000017500000000065212616547266022612 0ustar brianbrian00000000000000.. _api-managers: Managers ======== .. automodule:: guardian.managers .. manager:: UserObjectPermissionManager UserObjectPermissionManager --------------------------- .. autoclass:: guardian.managers.UserObjectPermissionManager :members: .. manager:: GroupObjectPermissionManager GroupObjectPermissionManager ---------------------------- .. autoclass:: guardian.managers.GroupObjectPermissionManager :members: django-guardian-1.4.1/docs/api/guardian.utils.rst0000600000175000017500000000051212616547266022150 0ustar brianbrian00000000000000.. _api-utils: .. currentmodule:: guardian.utils Utilities ========= .. automodule:: guardian.utils get_anonymous_user ------------------ .. autofunction:: get_anonymous_user get_identity ------------ .. autofunction:: get_identity clean_orphan_obj_perms ---------------------- .. autofunction:: clean_orphan_obj_perms django-guardian-1.4.1/docs/api/guardian.forms.rst0000600000175000017500000000112012616547266022132 0ustar brianbrian00000000000000.. _api-forms: Forms ===== .. automodule:: guardian.forms .. form:: UserObjectPermissionsForm UserObjectPermissionsForm ------------------------- .. autoclass:: guardian.forms.UserObjectPermissionsForm :members: :show-inheritance: .. form:: GroupObjectPermissionsForm GroupObjectPermissionsForm -------------------------- .. autoclass:: guardian.forms.GroupObjectPermissionsForm :members: :show-inheritance: .. form:: BaseObjectPermissionsForm BaseObjectPermissionsForm ------------------------- .. autoclass:: guardian.forms.BaseObjectPermissionsForm :members: django-guardian-1.4.1/docs/api/guardian.backends.rst0000600000175000017500000000030212616547266022557 0ustar brianbrian00000000000000.. _api-backends: Backends ======== .. automodule:: guardian.backends ObjectPermissionBackend ----------------------- .. autoclass:: guardian.backends.ObjectPermissionBackend :members: django-guardian-1.4.1/docs/api/guardian.decorators.rst0000600000175000017500000000061112616547266023155 0ustar brianbrian00000000000000.. _api-decorators: Decorators ========== .. automodule:: guardian.decorators .. _api-decorators-permission_required: permission_required ------------------- .. autofunction:: guardian.decorators.permission_required .. _api-decorators-permission_required_or_403: permission_required_or_403 -------------------------- .. autofunction:: guardian.decorators.permission_required_or_403 django-guardian-1.4.1/docs/api/guardian.shortcuts.rst0000600000175000017500000000211612634712306023035 0ustar brianbrian00000000000000.. _api-shortcuts: Shortcuts ========= .. automodule:: guardian.shortcuts .. _api-shortcuts-assign: assign_perm ----------- .. autofunction:: guardian.shortcuts.assign_perm .. _api-shortcuts-remove_perm: remove_perm ----------- .. autofunction:: guardian.shortcuts.remove_perm .. _api-shortcuts-get_perms: get_perms --------- .. autofunction:: guardian.shortcuts.get_perms .. _api-shortcuts-get_perms_for_model: get_perms_for_model ------------------- .. autofunction:: guardian.shortcuts.get_perms_for_model .. _api-shortcuts-get_users_with_perms: get_users_with_perms -------------------- .. autofunction:: guardian.shortcuts.get_users_with_perms .. _api-shortcuts-get_groups_with_perms: get_groups_with_perms --------------------- .. autofunction:: guardian.shortcuts.get_groups_with_perms .. _api-shortcuts-get_objects_for_user: .. shortcut:: get_objects_for_user get_objects_for_user -------------------- .. autofunction:: guardian.shortcuts.get_objects_for_user get_objects_for_group --------------------- .. autofunction:: guardian.shortcuts.get_objects_for_group django-guardian-1.4.1/docs/api/index.rst0000600000175000017500000000052512616547266020332 0ustar brianbrian00000000000000.. _api: API Reference ============= .. toctree:: :maxdepth: 2 guardian.admin guardian.backends guardian.core guardian.decorators guardian.forms guardian.management.commands guardian.managers guardian.mixins guardian.models guardian.shortcuts guardian.utils guardian.templatetags.guardian_tags django-guardian-1.4.1/docs/api/guardian.models.rst0000600000175000017500000000076212616547266022302 0ustar brianbrian00000000000000.. _api-models: Models ====== .. automodule:: guardian.models .. model:: BaseObjectPermission BaseObjectPermission -------------------- .. autoclass:: guardian.models.BaseObjectPermission :members: .. model:: UserObjectPermission UserObjectPermission -------------------- .. autoclass:: guardian.models.UserObjectPermission :members: .. model:: GroupObjectPermission GroupObjectPermission --------------------- .. autoclass:: guardian.models.GroupObjectPermission :members: django-guardian-1.4.1/docs/api/guardian.admin.rst0000600000175000017500000000027712616547266022110 0ustar brianbrian00000000000000.. _api-admin: Admin ===== .. automodule:: guardian.admin .. admin:: GuardedModelAdmin GuardedModelAdmin ----------------- .. autoclass:: guardian.admin.GuardedModelAdmin :members: django-guardian-1.4.1/docs/userguide/0000700000175000017500000000000012644306605017677 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/userguide/performance.rst0000600000175000017500000000752012642704641022740 0ustar brianbrian00000000000000.. _performance: Performance tuning =================== It is important to remember that by default ``django-guardian`` uses generic foreign keys to retain relation with any Django model. For most cases, it's probably good enough, however if we have a lot of queries being spanned and our database seems to be choking it might be a good choice to use *direct* foreign keys. Let's start with quick overview of how generic solution work and then we will move on to the tuning part. Default, generic solution ------------------------- ``django-guardian`` comes with two models: :model:`UserObjectPermission` and :model:`GroupObjectPermission`. They both have same, generic way of pointing to other models: - ``content_type`` field telling what table (model class) target permission references to (``ContentType`` instance) - ``object_pk`` field storing value of target model instance primary key - ``content_object`` field being a ``GenericForeignKey``. Actually, it is not a foreign key in standard, relational database meaning - it is simply a proxy that can retrieve proper model instance being targeted by two previous fields .. seealso:: https://docs.djangoproject.com/en/1.4/ref/contrib/contenttypes/#generic-relations Let's consider following model: .. code-block:: python class Project(models.Model): name = models.CharField(max_length=128, unique=True) In order to add a *change_project* permission for *joe* user we would use :ref:`api-shortcuts-assign` shortcut: .. code-block:: python >>> from guardian.shortcuts import assign_perm >>> project = Project.objects.get(name='Foobar') >>> joe = User.objects.get(username='joe') >>> assign_perm('change_project', joe, project) What it really does is: create an instance of :model:`UserObjectPermission`. Something similar to: .. code-block:: python >>> content_type = ContentType.objects.get_for_model(Project) >>> perm = Permission.objects.get(content_type__app_label='app', ... codename='change_project') >>> UserObjectPermission.objects.create(user=joe, content_type=content_type, ... permission=perm, object_pk=project.pk) As there are no real foreign keys pointing at the target model, this solution might not be enough for all cases. For example, if we try to build an issues tracking service and we'd like to be able to support thousands of users and their project/tickets, object level permission checks can be slow with this generic solution. .. _performance-direct-fk: Direct foreign keys ------------------- .. versionadded:: 1.1 In order to make our permission checks faster we can use direct foreign key solution. It actually is very simple to setup - we need to declare two new models next to our ``Project`` model, one for ``User`` and one for ``Group`` models: .. code-block:: python from guardian.models import UserObjectPermissionBase from guardian.models import GroupObjectPermissionBase class Project(models.Model): name = models.CharField(max_length=128, unique=True) class ProjectUserObjectPermission(UserObjectPermissionBase): content_object = models.ForeignKey(Project) class ProjectGroupObjectPermission(GroupObjectPermissionBase): content_object = models.ForeignKey(Project) .. important:: Name of the ``ForeignKey`` field is important and it should be ``content_object`` as underlying queries depends on it. From now on, ``guardian`` will figure out that ``Project`` model has direct relation for user/group object permissions and will use those models. It is also possible to use only user or only group-based direct relation, however it is discouraged (it's not consistent and might be a quick road to hell from the maintainence point of view, especially). .. note:: By defining direct relation models we can also tweak that object permission model, i.e. by adding some fields. django-guardian-1.4.1/docs/userguide/remove.rst0000600000175000017500000000120612616547266021740 0ustar brianbrian00000000000000.. _remove: Remove object permissions ========================= Removing object permissions is as easy as assigning them. Just instead of :func:`guardian.shortcuts.assign` we would use :func:`guardian.shortcuts.remove_perm` function (it accepts same arguments). Example ------- Let's get back to our fellow Joe:: >>> site = Site.object.get_current() >>> joe.has_perm('change_site', site) True Now, simply remove this permission:: >>> from guardian.shortcuts import remove_perm >>> remove_perm('change_site', joe, site) >>> joe = User.objects.get(username='joe') >>> joe.has_perm('change_site', site) False django-guardian-1.4.1/docs/userguide/assign.rst0000600000175000017500000001247512643667664021746 0ustar brianbrian00000000000000.. _assign: Assign object permissions ========================= Assigning object permissions should be very simple once permissions are created for models. Prepare permissions ------------------- Let's assume we have following model: .. code-block:: python class Task(models.Model): summary = models.CharField(max_length=32) content = models.TextField() reported_by = models.ForeignKey(User) created_at = models.DateTimeField(auto_now_add=True) ... and we want to be able to set custom permission *view_task*. We let Django know to do so by adding ``permissions`` tuple to ``Meta`` class and our final model could look like: .. code-block:: python class Task(models.Model): summary = models.CharField(max_length=32) content = models.TextField() reported_by = models.ForeignKey(User) created_at = models.DateTimeField(auto_now_add=True) class Meta: permissions = ( ('view_task', 'View task'), ) After we call ``syncdb`` (with a ``--all`` switch if you are using south) management command our *view_task* permission would be added to default set of permissions. .. note:: By default, Django adds 3 permissions for each registered model: - *add_modelname* - *change_modelname* - *delete_modelname* (where *modelname* is a simplified name of our model's class). See https://docs.djangoproject.com/en/dev/topics/auth/default/#default-permissions for more detail. There is nothing new here since creation of permissions is `handled by django `_. Now we can move to :ref:`assigning object permissions `. .. _assign-obj-perms: Assign object permissions ------------------------- We can assign permissions for any user/group and object pairs using same, convenient function: :func:`guardian.shortcuts.assign_perm`. For user ~~~~~~~~ Continuing our example we now can allow Joe user to view some task: .. code-block:: python >>> from django.contrib.auth.models import User >>> boss = User.objects.create(username='Big Boss') >>> joe = User.objects.create(username='joe') >>> task = Task.objects.create(summary='Some job', content='', reported_by=boss) >>> joe.has_perm('view_task', task) False Well, not so fast Joe, let us create an object permission finally: .. code-block:: python >>> from guardian.shortcuts import assign_perm >>> assign_perm('view_task', joe, task) >>> joe.has_perm('view_task', task) True For group ~~~~~~~~~ This case doesn't really differ from user permissions assignment. The only difference is we have to pass ``Group`` instance rather than ``User``. .. code-block:: python >>> from django.contrib.auth.models import Group >>> group = Group.objects.create(name='employees') >>> assign_perm('change_task', group, task) >>> joe.has_perm('change_task', task) False >>> # Well, joe is not yet within an *employees* group >>> joe.groups.add(group) >>> joe.has_perm('change_task', task) True Another example: .. code-block:: python >>> from django.contrib.auth.models import User, Group >>> from guardian.shortcuts import assign_perm # fictional companies >>> companyA = Company.objects.create(name="Company A") >>> companyB = Company.objects.create(name="Company B") # create groups >>> companyUserGroupA = Group.objects.create(name="Company User Group A") >>> companyUserGroupB = Group.objects.create(name="Company User Group B") # assign object specific permissions to groups >>> assign_perm('change_company', companyUserGroupA, companyA) >>> assign_perm('change_company', companyUserGroupB, companyB) # create user and add it to one group for testing >>> userA = User.objects.create(username="User A") >>> userA.groups.add(companyUserGroupA) >>> userA.has_perm('change_company', companyA) True >>> userA.has_perm('change_company', companyB) False >>> userB = User.objects.create(username="User B") >>> userB.has_perm('change_company', companyA) False >>> userA.has_perm('change_company', companyB) True Assigning Permissions inside Signals ------------------------------------ Note that the Anonymous User is created before the Permissions are created. This may result in Django signals, e.g. ``post_save`` being sent before the Permissions are created. You will need to take this into an account when processing the signal. .. code-block:: python @receiver(post_save, sender=User) def user_post_save(sender, **kwargs): """ Create a Profile instance for all newly created User instances. We only run on user creation to avoid having to check for existence on each call to User.save. """ user, created = kwargs["instance"], kwargs["created"] if created and user.pk != settings.ANONYMOUS_USER_ID: from profiles.models import Profile profile = Profile.objects.create(pk=user.pk, user=user, creator=user) assign_perm("change_user", user, user) assign_perm("change_profile", user, profile) The check for ``user.pk != settings.ANONYMOUS_USER_ID`` is required otherwise the ``assign_perm`` calls will occur when the Anonymous User is created, however before there are any permissions available. django-guardian-1.4.1/docs/userguide/example_project.rst0000600000175000017500000000121212634712306023606 0ustar brianbrian00000000000000.. _example-project: Example project =============== Example project should be boundled with archive and be available at ``example_project``. Before you can run it, some requirements have to be met. Those are easily installed using following command at example project's directory:: $ cd example_project $ pip install -r requirements.txt And last thing before we can run example project is to create sqlite database:: $ python manage.py syncdb Finally we can run dev server:: $ python manage.py runserver Project is really basic and shows almost nothing but eventually it should expose some ``django-guardian`` functionality. django-guardian-1.4.1/docs/userguide/check.rst0000600000175000017500000001433612634712306021515 0ustar brianbrian00000000000000.. _check: Check object permissions ======================== Once we have :ref:`assigned some permissions `, we can get into detail about verifying permissions of a user or group. Standard way ------------ Normally to check if Joe is permitted to change ``Site`` objects we call ``has_perm`` method on an ``User`` instance:: >>> joe.has_perm('sites.change_site') False And for a specific ``Site`` instance we do the same but we pass ``site`` as additional argument:: >>> site = Site.objects.get_current() >>> joe.has_perm('sites.change_site', site) False Let's assign permission and check again:: >>> from guardian.shortcuts import assign_perm >>> assign_perm('sites.change_site', joe, site) >>> joe = User.objects.get(username='joe') >>> joe.has_perm('sites.change_site', site) True This uses the backend we have specified at settings module (see :ref:`configuration`). More on the backend can be found at :class:`Backend's API `. Inside views ------------ Aside from the standard ``has_perm`` method, ``django-guardian`` provides some useful helpers for object permission checks. get_perms ~~~~~~~~~ To check permissions we can use a quick-and-dirty shortcut:: >>> from guardian.shortcuts import get_perms >>> >>> joe = User.objects.get(username='joe') >>> site = Site.objects.get_current() >>> >>> 'change_site' in get_perms(joe, site) True It is probably better to use standard ``has_perm`` method. But for ``Group`` instances it is not as easy and ``get_perms`` could be handy here as it accepts both ``User`` and ``Group`` instances. If we need to do some more work, we can use lower level ``ObjectPermissionChecker`` class which is described in the next section. get_objects_for_user ~~~~~~~~~~~~~~~~~~~~ Sometimes there is a need to extract list of objects based on particular user, type of the object and provided permissions. For instance, lets say there is a ``Project`` model at ``projects`` application with custom ``view_project`` permission. We want to show our users projects they can actually *view*. This could be easily achieved using :shortcut:`get_objects_for_user`: .. code-block:: python from django.shortcuts import render_to_response from django.template import RequestContext from projects.models import Project from guardian.shortcuts import get_objects_for_user def user_dashboard(request, template_name='projects/dashboard.html'): projects = get_objects_for_user(request.user, 'projects.view_project') return render_to_response(template_name, {'projects': projects}, RequestContext(request)) It is also possible to provide list of permissions rather than single string, own queryset (as ``klass`` argument) or control if result should be computed with (default) or without user's groups permissions. .. seealso:: Documentation for :shortcut:`get_objects_for_user` ObjectPermissionChecker ~~~~~~~~~~~~~~~~~~~~~~~ At the ``core`` module of ``django-guardian``, there is a :class:`guardian.core.ObjectPermissionChecker` which checks permission of user/group for specific object. It caches results so it may be used at part of codes where we check permissions more than once. Let's see it in action:: >>> joe = User.objects.get(username='joe') >>> site = Site.objects.get_current() >>> from guardian.core import ObjectPermissionChecker >>> checker = ObjectPermissionChecker(joe) # we can pass user or group >>> checker.has_perm('change_site', site) True >>> checker.has_perm('add_site', site) # no additional query made False >>> checker.get_perms(site) [u'change_site'] Using decorators ~~~~~~~~~~~~~~~~ Standard ``permission_required`` decorator doesn't allow to check for object permissions. ``django-guardian`` is shipped with two decorators which may be helpful for simple object permission checks but remember that those decorators hits database before decorated view is called - this means that if there is similar lookup made within a view then most probably one (or more, depending on lookups) extra database query would occur. Let's assume we pass ``'group_name'`` argument to our view function which returns form to edit the group. Moreover, we want to return 403 code if check fails. This can be simply achieved using ``permission_required_or_403`` decorator:: >>> joe = User.objects.get(username='joe') >>> foobars = Group.objects.create(name='foobars') >>> >>> from guardian.decorators import permission_required_or_403 >>> from django.http import HttpResponse >>> >>> @permission_required_or_403('auth.change_group', >>> (Group, 'name', 'group_name')) >>> def edit_group(request, group_name): >>> return HttpResponse('some form') >>> >>> from django.http import HttpRequest >>> request = HttpRequest() >>> request.user = joe >>> edit_group(request, group_name='foobars') >>> >>> joe.groups.add(foobars) >>> edit_group(request, group_name='foobars') >>> >>> from guardian.shortcuts import assign_perm >>> assign_perm('auth.change_group', joe, foobars) >>> >>> edit_group(request, group_name='foobars') >>> # Note that we now get normal HttpResponse, not forbidden More on decorators can be read at corresponding :ref:`API page `. .. note:: Overall idea of decorators' lookups was taken from `django-authority`_ and all credits go to it's creator, Jannis Leidel. Inside templates ---------------- ``django-guardian`` comes with special template tag :func:`guardian.templatetags.guardian_tags.get_obj_perms` which can store object permissions for a given user/group and instance pair. In order to use it we need to put following inside a template:: {% load guardian_tags %} get_obj_perms ~~~~~~~~~~~~~ .. autofunction:: guardian.templatetags.guardian_tags.get_obj_perms :noindex: .. _django-authority: http://bitbucket.org/jezdez/django-authority/ django-guardian-1.4.1/docs/userguide/caveats.rst0000600000175000017500000000736412634712306022071 0ustar brianbrian00000000000000.. _caveats: Caveats ======= Orphaned object permissions --------------------------- Note the following does not apply if using direct foreign keys, as documented in :ref:`performance-direct-fk`. Permissions, including so called *per object permissions*, are sometimes tricky to manage. One case is how we can manage permissions that are no longer used. Normally, there should be no problems, however with some particular setup it is possible to reuse primary keys of database models which were used in the past once. We will not answer how bad such situation can be - instead we will try to cover how we can deal with this. Let's imagine our table has primary key to the filesystem path. We have a record with pk equal to ``/home/www/joe.config``. User *jane* has read access to joe's configuration and we store that information in database by creating guardian's object permissions. Now, *joe* user removes account from our site and another user creates account with *joe* as username. The problem is that if we haven't removed object permissions explicitly in the process of first *joe* account removal, *jane* still has read permissions for *joe's* configuration file - but this is another user. There is no easy way to deal with orphaned permissions as they are not foreign keyed with objects directly. Even if they would, there are some database engines - or *ON DELETE* rules - which restricts removal of related objects. .. important:: It is **extremely** important to remove :model:`UserObjectPermission` and :model:`GroupObjectPermission` as we delete objects for which permissions are defined. Guardian comes with utility function which tries to help to remove orphaned object permissions. Remember - those are only helpers. Applications should remove those object permissions explicitly by itself. Taking our previous example, our application should remove user object for *joe*, however, permisions for *joe* user assigned to *jane* would **NOT** be removed. In this case, it would be very easy to remove user/group object permissions if we connect proper action with proper signal. This could be achieved by following snippet:: from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.db.models.signals import pre_delete from guardian.models import User from guardian.models import UserObjectPermission from guardian.models import GroupObjectPermission def remove_obj_perms_connected_with_user(sender, instance, **kwargs): filters = Q(content_type=ContentType.objects.get_for_model(instance), object_pk=instance.pk) UserObjectPermission.objects.filter(filters).delete() GroupObjectPermission.objects.filter(filters).delete() pre_delete.connect(remove_obj_perms_connected_with_user, sender=User) This signal handler would remove all object permissions connected with user just before user is actually removed. If we forgot to add such handlers, we may still remove orphaned object permissions by using :command:`clean_orphan_obj_perms` command. If our application uses celery_, it is also very easy to remove orphaned permissions periodically with :func:`guardian.utils.clean_orphan_obj_perms` function. We would still **strongly** advise to remove orphaned object permissions explicitly (i.e. at view that confirms object removal or using signals as described above). .. seealso:: - :func:`guardian.utils.clean_orphan_obj_perms` - :command:`clean_orphan_obj_perms` .. _celery: http://www.celeryproject.org/ Using multiple databases ------------------------ This is not supported at present time due to a Django bug. See 288_ and 16281_. .. _288: https://github.com/django-guardian/django-guardian/issues/288 .. _16281: https://code.djangoproject.com/ticket/16281 django-guardian-1.4.1/docs/userguide/admin-integration.rst0000600000175000017500000000526712634712306024054 0ustar brianbrian00000000000000.. _admin-integration: Admin integration ================= Django comes with excellent and widely used *Admin* application. Basically, it provides content management for Django applications. User with access to admin panel can manage users, groups, permissions and other data provided by system. ``django-guardian`` comes with simple object permissions management integration for Django's admin application. Usage ----- It is very easy to use admin integration. Simply use :admin:`GuardedModelAdmin` instead of standard ``django.contrib.admin.ModelAdmin`` class for registering models within the admin. In example, look at following model: .. code-block:: python from django.db import models class Post(models.Model): title = models.CharField('title', max_length=64) slug = models.SlugField(max_length=64) content = models.TextField('content') created_at = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: permissions = ( ('view_post', 'Can view post'), ) get_latest_by = 'created_at' def __unicode__(self): return self.title @models.permalink def get_absolute_url(self): return {'post_slug': self.slug} We want to register ``Post`` model within admin application. Normally, we would do this as follows within ``admin.py`` file of our application: .. code-block:: python from django.contrib import admin from posts.models import Post class PostAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("title",)} list_display = ('title', 'slug', 'created_at') search_fields = ('title', 'content') ordering = ('-created_at',) date_hierarchy = 'created_at' admin.site.register(Post, PostAdmin) If we would like to add object permissions management for ``Post`` model we would need to change ``PostAdmin`` base class into ``GuardedModelAdmin``. Our code could look as follows: .. code-block:: python from django.contrib import admin from posts.models import Post from guardian.admin import GuardedModelAdmin class PostAdmin(GuardedModelAdmin): prepopulated_fields = {"slug": ("title",)} list_display = ('title', 'slug', 'created_at') search_fields = ('title', 'content') ordering = ('-created_at',) date_hierarchy = 'created_at' admin.site.register(Post, PostAdmin) And thats it. We can now navigate to **change** post page and just next to the *history* link we can click *Object permissions* button to manage row level permissions. .. note:: Example above is shipped with ``django-guardian`` package with the example project. django-guardian-1.4.1/docs/userguide/index.rst0000600000175000017500000000030312634712306021534 0ustar brianbrian00000000000000.. _guide: User Guide ========== .. toctree:: :maxdepth: 2 example_project assign check remove admin-integration custom-user-model performance caveats django-guardian-1.4.1/docs/userguide/custom-user-model.rst0000600000175000017500000000521512634712306024020 0ustar brianbrian00000000000000.. _custom-user-model: Custom User model ================= .. versionadded:: 1.1 Django 1.5 comes with the ability to customize default ``auth.User`` model - either by subclassing ``AbstractUser`` or defining very own class. This can be very powerful, it must be done with caution, though. Basically, if we subclass ``AbstractUser`` or define many-to-many relation with ``auth.Group`` (and give reverse relate name **groups**) we should be fine. By default django-guardian monkey patches the user model to add some needed functionality. This can result in errors if guardian is imported into the models.py of the same app where the custom user model lives. To fix this, it is recommended to add the setting ``GUARDIAN_MONKEY_PATCH = False`` in your settings.py and add the ``GuardianUserMixin`` to your custom user model. .. important:: ``django-guardian`` relies **heavily** on the ``auth.User`` model. Specifically it was build from the ground-up with relation between ``auth.User`` and ``auth.Group`` models. Retaining this relation is crucial for ``guardian`` - **without many to many User (custom or default) and auth.Group relation django-guardian will BREAK**. .. seealso:: Read more about customizing User model introduced in Django 1.5 here: https://docs.djangoproject.com/en/1.5/topics/auth/customizing/#substituting-a-custom-user-model. .. _custom-user-model-anonymous: Anonymous user creation ----------------------- It is also possible to override default behavior of how instance for anonymous user is created. In example, let's imagine we have our user model as follows:: from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): real_username = models.CharField(max_length=120, unique=True) birth_date = models.DateField() # field without default value USERNAME_FIELD = 'real_username' Note that there is a ``birth_date`` field defined at the model and it does not have a default value. It would fail to create anonymous user instance as default implementation cannot know anything about ``CustomUser`` model. In order to override the way anonymous instance is created we need to make :setting:`GUARDIAN_GET_INIT_ANONYMOUS_USER` pointing at our custom implementation. In example, let's define our init function:: import datetime def get_anonymous_user_instance(User): return User(real_username='Anonymous', birth_date=datetime.date(1970, 1, 1)) and put it at ``myapp/models.py``. Last step is to set proper configuration in our settings module:: GUARDIAN_GET_INIT_ANONYMOUS_USER = 'myapp.models.get_anonymous_user_instance' django-guardian-1.4.1/docs/develop/0000700000175000017500000000000012644306605017341 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/develop/testing.rst0000600000175000017500000001022012643667664021563 0ustar brianbrian00000000000000.. _testing: Testing ======= Introduction ------------ ``django-guardian`` is extending capabilities of Django's authorization facilities and as so, it changes it's security somehow. It is extremaly important to provide as simplest :ref:`api` as possible. According to OWASP_, `broken authentication `_ is one of most commonly security issue exposed in web applications. Having this on mind we tried to build small set of necessary functions and created a lot of testing scenarios. Neverteless, if anyone would found a bug in this application, please take a minute and file it at `issue-tracker`_. Moreover, if someone would spot a *security hole* (a bug that might affect security of systems that use ``django-guardian`` as permission management library), please **DO NOT** create a public issue but contact me directly (lukaszbalcerzak@gmail.com). Running tests ------------- Tests are run by Django's buildin test runner. To call it simply run:: $ python setup.py test or inside a project with ``guardian`` set at ``INSTALLED_APPS``:: $ python manage.py test guardian or using the bundled ``testapp`` project:: $ python manage.py test Coverage support ---------------- Coverage_ is a tool for measuring code coverage of Python programs. It is great for tests and we use it as a backup - we try to cover 100% of the code used by ``django-guardian``. This of course does *NOT* mean that if all of the codebase is covered by tests we can be sure there is no bug (as specification of almost all applications requries some unique scenarios to be tested). On the other hand it definitely helps to track missing parts. To run tests with coverage_ support and show the report after we have provided simple bash script which can by called by running:: $ ./run_test_and_report.sh Result should be somehow similar to following:: (...) ................................................ ---------------------------------------------------------------------- Ran 48 tests in 2.516s OK Destroying test database 'default'... Name Stmts Exec Cover Missing ------------------------------------------------------------------- guardian/__init__ 4 4 100% guardian/backends 20 20 100% guardian/conf/__init__ 1 1 100% guardian/core 29 29 100% guardian/exceptions 8 8 100% guardian/management/__init__ 10 10 100% guardian/managers 40 40 100% guardian/models 36 36 100% guardian/shortcuts 30 30 100% guardian/templatetags/__init__ 1 1 100% guardian/templatetags/guardian_tags 39 39 100% guardian/utils 13 13 100% ------------------------------------------------------------------- TOTAL 231 231 100% Tox --- .. versionadded:: 1.0.4 We also started using tox_ to ensure ``django-guardian``'s tests would pass on all supported Python and Django versions (see :ref:`supported-versions`). To use it, simply install ``tox``:: pip install tox and run it within ``django-guardian`` checkout directory:: tox First time should take some time (it needs to create separate virtual environments and pull dependencies) but would ensure everything is fine. Travis CI --------- .. versionadded:: 1.0.4 .. image:: https://secure.travis-ci.org/lukaszb/django-guardian.png?branch=master :target: http://travis-ci.org/lukaszb/django-guardian Recently we have added support for Travis_, continuous integration server so it is even more easy to follow if test fails with new commits: http://travis-ci.org/#!/lukaszb/django-guardian. .. _owasp: http://www.owasp.org/ .. _issue-tracker: http://github.com/lukaszb/django-guardian .. _coverage: http://nedbatchelder.com/code/coverage/ .. _tox: http://pypi.python.org/pypi/tox .. _travis: http://travis-ci.org/ django-guardian-1.4.1/docs/develop/supported-versions.rst0000600000175000017500000000052612634712306023771 0ustar brianbrian00000000000000.. _supported-versions: Supported versions ================== ``django-guardian`` supports Python 2.7+/3.3+ and Django 1.7+. Rules ----- * We would support Python 2.7. We also support Python 3.3+. * Support for Python 3.3 may get dropped in the future. * We support Django 1.7+. This is due to many simplifications in code we could do. django-guardian-1.4.1/docs/develop/index.rst0000600000175000017500000000016212634712306021201 0ustar brianbrian00000000000000.. _develop: Development =========== .. toctree:: overview testing supported-versions changes django-guardian-1.4.1/docs/develop/overview.rst0000600000175000017500000000535312634712306021747 0ustar brianbrian00000000000000.. _dev_overview: Overview ======== Here we describe the development process overview. It's in F.A.Q. format to make it simple. Why devel is default branch? ---------------------------- Since version 1.2 we try to make ``master`` in a production-ready state. It does NOT mean it is production ready, but it SHOULD be. In example, tests at ``master`` should always pass. Actually, whole tox suite should pass. And it's test coverage should be at 100% level. ``devel`` branch, on the other hand, can break. It shouldn't but it is acceptable. As a user, you should NEVER use non-master branches at production. All the changes are pushed from ``devel`` to ``master`` before next release. It might happen more frequently. How to file a ticket? --------------------- Just go to https://github.com/django-guardian/django-guardian/issues and create new one. How do I get involved? ---------------------- It's simple! If you want to fix a bug, extend documentation or whatever you think is appropriate for the project and involves changes, just fork the project at github (https://github.com/django-guardian/django-guardian), create a separate branch, hack on it, publish changes at your fork and create a pull request. Here is a quick how to: 1. Fork a project: https://github.com/django-guardian/django-guardian/fork 2. Checkout project to your local machine:: $ git clone git@github.com:YOUR_NAME/django-guardian.git 3. Create a new branch with name describing change you are going to work on:: $ git checkout -b bugfix/support-for-custom-model 4. Perform changes at newly created branch. Remember to include tests (if this is code related change) and run test suite. See :ref:`running tests documentation `. Also, remember to add yourself to the ``AUTHORS`` file. 5. (Optional) Squash commits. If you have multiple commits and it doesn't make much sense to have them separated (and it usually makes little sense), please consider merging all changes into single commit. Please see https://help.github.com/articles/interactive-rebase if you need help with that. 6. Publish changes:: $ git push origin YOUR_BRANCH_NAME 6. Create a Pull Request (https://help.github.com/articles/creating-a-pull-request). Usually it's as simple as opening up https://github.com/YOUR_NAME/django-guardian and clicking on review button for newly created branch. There you can make final review of your changes and if everything seems fine, create a Pull Request. Why my issue/pull request was closed? ------------------------------------- We usually put an explonation while we close issue or PR. It might be for various reasons, i.e. there were no reply for over a month after our last comment, there were no tests for the changes etc. django-guardian-1.4.1/docs/develop/changes.rst0000600000175000017500000000007712616547266021522 0ustar brianbrian00000000000000.. _changes: Changelog --------- .. include:: ../../CHANGES django-guardian-1.4.1/docs/exts.py0000600000175000017500000000223212616547266017252 0ustar brianbrian00000000000000 def setup(app): app.add_crossref_type( directivename = "admin", rolename = "admin", indextemplate = "pair: %s; admin", ) app.add_crossref_type( directivename = "command", rolename = "command", indextemplate = "pair: %s; command", ) app.add_crossref_type( directivename = "form", rolename = "form", indextemplate = "pair: %s; form", ) app.add_crossref_type( directivename = "manager", rolename = "manager", indextemplate = "pair: %s; manager", ) app.add_crossref_type( directivename = "mixin", rolename = "mixin", indextemplate = "pair: %s; mixin", ) app.add_crossref_type( directivename = "model", rolename = "model", indextemplate = "pair: %s; model", ) app.add_crossref_type( directivename = "setting", rolename = "setting", indextemplate = "pair: %s; setting", ) app.add_crossref_type( directivename = "shortcut", rolename = "shortcut", indextemplate = "pair: %s; shortcut", ) django-guardian-1.4.1/docs/make.bat0000600000175000017500000000632412616547266017330 0ustar brianbrian00000000000000@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. pdf to make PDF document echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-guardian.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-guardian.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "pdf" ( %SPHINXBUILD% -b pdf %ALLSPHINXOPTS% %BUILDDIR%/pdf echo. echo.Build finished. The PDF files are in %BUILDDIR%/pdf. goto end ) :end django-guardian-1.4.1/docs/Makefile0000600000175000017500000000636612616547266017371 0ustar brianbrian00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pdf to make PDF document" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-guardian.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-guardian.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pdf: $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) $(BUILDDIR)/pdf @echo @echo "Build finished. The PDF files are in $(BUILDDIR)/pdf." django-guardian-1.4.1/docs/configuration.rst0000600000175000017500000001033012634712306021301 0ustar brianbrian00000000000000.. _configuration: Configuration ============= After :ref:`installation ` we can prepare our project for object permissions handling. In a settings module we need to add guardian to ``INSTALLED_APPS``:: INSTALLED_APPS = ( # ... 'guardian', ) and hook guardian's authentication backend:: AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', # this is default 'guardian.backends.ObjectPermissionBackend', ) As ``django-guardian`` supports anonymous user's object permissions we also need to add following to our settings module:: ANONYMOUS_USER_ID = -1 .. note:: Once project is configured to work with ``django-guardian``, calling ``syncdb`` management command would create ``User`` instance for anonymous user support (with name of ``AnonymousUser``). .. note:: The Guardian anonymous user is different to the Django Anonymous user. The Django Anonymous user does not have an entry in the database, however the Guardian anonymous user does. This means that the following code will return an unexpected result: .. code-block:: python from guardian.compat import get_user_model User = get_user_model() anon = User.get_anonymous() anon.is_anonymous() # returns False If ``ANONYMOUS_USER_ID`` is set to ``None``, anonymous user object permissions are disabled. You may need to choose this option if creating a ``User`` object to represent anonymous users would be problematic in your environment. We can change id to whatever we like. Project should be now ready to use object permissions. Optional settings ================= In addition to required ``ANONYMOUS_USER_ID`` setting, guardian has following, optional configuration variables: .. setting:: GUARDIAN_RAISE_403 GUARDIAN_RAISE_403 ------------------ .. versionadded:: 1.0.4 If set to ``True``, guardian would raise ``django.core.exceptions.PermissionDenied`` error instead of returning empty ``django.http.HttpResponseForbidden``. .. warning:: Remember that you cannot use both :setting:`GUARDIAN_RENDER_403` **AND** :setting:`GUARDIAN_RAISE_403` - if both are set to ``True``, ``django.core.exceptions.ImproperlyConfigured`` would be raised. .. setting:: GUARDIAN_RENDER_403 GUARDIAN_RENDER_403 ------------------- .. versionadded:: 1.0.4 If set to ``True``, guardian would try to render 403 response rather than return contentless ``django.http.HttpResponseForbidden``. Would use template pointed by :setting:`GUARDIAN_TEMPLATE_403` to do that. Default is ``False``. .. warning:: Remember that you cannot use both :setting:`GUARDIAN_RENDER_403` **AND** :setting:`GUARDIAN_RAISE_403` - if both are set to ``True``, ``django.core.exceptions.ImproperlyConfigured`` would be raised. .. setting:: GUARDIAN_TEMPLATE_403 GUARDIAN_TEMPLATE_403 --------------------- .. versionadded:: 1.0.4 Tells parts of guardian what template to use for responses with status code ``403`` (i.e. :ref:`api-decorators-permission_required`). Defaults to ``403.html``. .. setting:: ANONYMOUS_DEFAULT_USERNAME_VALUE ANONYMOUS_DEFAULT_USERNAME_VALUE -------------------------------- .. versionadded:: 1.1 Due to changes introduced by Django 1.5 user model can have differently named ``username`` field (it can be removed too, but ``guardian`` currently depends on it). After ``syncdb`` command we create anonymous user for convenience, however it might be necessary to set this configuration in order to set proper value at ``username`` field. .. seealso:: https://docs.djangoproject.com/en/1.5/topics/auth/customizing/#substituting-a-custom-user-model .. setting:: GUARDIAN_GET_INIT_ANONYMOUS_USER GUARDIAN_GET_INIT_ANONYMOUS_USER -------------------------------- .. versionadded:: 1.2 Guardian supports object level permissions for anonymous users, however when in our project we use custom User model, default function might fail. This can lead to issues as ``guardian`` tries to create anonymous user after each ``syncdb`` call. Object that is going to be created is retrieved using function pointed by this setting. Once retrieved, ``save`` method would be called on that instance. Defaults to ``"guardian.management.get_init_anonymous_user"``. .. seealso:: :ref:`custom-user-model-anonymous` django-guardian-1.4.1/docs/installation.rst0000600000175000017500000000115012644306223021131 0ustar brianbrian00000000000000.. _installation: Installation ============ This application requires Django_ 1.7 or higher and it is the only prerequisite before ``django-guardian`` may be used. In order to install ``django-guardian`` simply use ``pip``:: pip install django-guardian or ``easy_install``:: easy_install django-guardian This would be enough to run ``django-guardian``. However, in order to run tests or boundled example application, there are some other requirements. See more details about the topics: - :ref:`Testing ` - :ref:`Example project ` .. _django: http://www.djangoproject.com/ django-guardian-1.4.1/docs/license.rst0000600000175000017500000000007712616547266020076 0ustar brianbrian00000000000000.. _license: License ======= .. literalinclude:: ../LICENSE django-guardian-1.4.1/docs/conf.py0000600000175000017500000001617412644306223017211 0ustar brianbrian00000000000000# -*- coding: utf-8 -*- # # django-guardian documentation build configuration file, created by # sphinx-quickstart on Thu Feb 18 23:18:28 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__)))) os.environ['DJANGO_SETTINGS_MODULE'] = 'guardian.testapp.testsettings' ANONYMOUS_USER_ID = -1 # Required by guardian guardian = __import__('guardian') import django django.setup() # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'exts'] try: import rst2pdf if rst2pdf.version >= '0.16': extensions.append('rst2pdf.pdfbuilder') except ImportError: print("[NOTE] In order to build PDF you need rst2pdf with version >=0.16") autoclass_content = "both" # Add any paths that contain templates here, relative to this directory. templates_path = ['templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'django-guardian' copyright = u'2010-2013, Lukasz Balcerzak' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # with open('../VERSION.txt', 'r') as f: # The short X.Y version. version = f.readline().strip() # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. #html_theme = 'default' # Theme URL: https://github.com/coordt/ADCtheme/ RTD_NEW_THEME = True html_theme = 'rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['theme'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['theme/rtd_theme/static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'guardiandoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'guardian.tex', u'guardian Documentation', u'Lukasz Balcerzak', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True pdf_documents = [ ('index', u'django-guardian', u'Documentation for django-guardian', u'Lukasz Balcerzak'), ] pdf_stylesheets = ['sphinx','kerning','a4'] pdf_break_level = 2 pdf_inline_footnotes = True #pdf_extensions = ['vectorpdf', 'dotted_toc'] django-guardian-1.4.1/docs/exts.pyc0000600000175000017500000000163212636353356017415 0ustar brianbrian00000000000000 :Vc@s dZdS(cCs|jdddddd|jdddddd|jdddddd |jdd dd dd |jdd dd dd |jdddddd|jdddddd|jdddddddS(Nt directivenametadmintrolenamet indextemplatespair: %s; admintcommandspair: %s; commandtformspair: %s; formtmanagerspair: %s; managertmixinspair: %s; mixintmodelspair: %s; modeltsettingspair: %s; settingtshortcutspair: %s; shortcut(tadd_crossref_type(tapp((s=/home/brian/tree/debian/upstream/django-guardian/docs/exts.pytsetups@        N(R (((s=/home/brian/tree/debian/upstream/django-guardian/docs/exts.pytsdjango-guardian-1.4.1/docs/index.rst0000600000175000017500000000113612634712306017545 0ustar brianbrian00000000000000.. _index: ===================================================== django-guardian - per object permissions for Django ===================================================== :Date: |today| :Version: |version| **Documentation**: .. image:: https://secure.travis-ci.org/lukaszb/django-guardian.png?branch=master :target: http://travis-ci.org/lukaszb/django-guardian .. toctree:: :maxdepth: 2 overview installation configuration userguide/index api/index develop/index license Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` django-guardian-1.4.1/docs/__pycache__/0000700000175000017500000000000012644306605020113 5ustar brianbrian00000000000000django-guardian-1.4.1/docs/__pycache__/exts.cpython-35.pyc0000600000175000017500000000142712636354014023527 0ustar brianbrian00000000000000 :V@sddZdS)cCs|jdddddd|jdddddd|jdddddd |jdd dd dd |jdd dd dd |jdddddd|jdddddd|jdddddddS)N directivenameadminrolename indextemplatezpair: %s; admincommandzpair: %s; commandformzpair: %s; formmanagerzpair: %s; managerZmixinzpair: %s; mixinmodelzpair: %s; modelsettingzpair: %s; settingshortcutzpair: %s; shortcut)add_crossref_type)appr =/home/brian/tree/debian/upstream/django-guardian/docs/exts.pysetups@        rN)rr r r rsdjango-guardian-1.4.1/docs/__pycache__/exts.cpython-34.pyc0000600000175000017500000000142712636353416023533 0ustar brianbrian00000000000000 :V@sddZdS)cCs|jdddddd|jdddddd|jdddddd |jdd dd dd |jdd dd dd |jdddddd|jdddddd|jdddddddS)N directivenameadminrolename indextemplatezpair: %s; admincommandzpair: %s; commandformzpair: %s; formmanagerzpair: %s; managerZmixinzpair: %s; mixinmodelzpair: %s; modelsettingzpair: %s; settingshortcutzpair: %s; shortcut)add_crossref_type)appr =/home/brian/tree/debian/upstream/django-guardian/docs/exts.pysetups@        rN)rr r r rsdjango-guardian-1.4.1/docs/__pycache__/exts.cpython-33.pyc0000600000175000017500000000167012636353640023531 0ustar brianbrian00000000000000 :Vc@sddZdS(cCs|jdddddd|jdddddd|jdddddd |jdd dd dd |jdd dd dd |jdddddd|jdddddd|jdddddddS(Nu directivenameuadminurolenameu indextemplateupair: %s; adminucommandupair: %s; commanduformupair: %s; formumanagerupair: %s; managerumixinupair: %s; mixinumodelupair: %s; modelusettingupair: %s; settingushortcutupair: %s; shortcut(uadd_crossref_type(uapp((u=/home/brian/tree/debian/upstream/django-guardian/docs/exts.pyusetups@        usetupN(usetup(((u=/home/brian/tree/debian/upstream/django-guardian/docs/exts.pyusdjango-guardian-1.4.1/docs/overview.rst0000600000175000017500000000240612634712306020305 0ustar brianbrian00000000000000.. _overview: Overview ======== ``django-guardian`` is an implementation of object permissions for Django_ providing extra *authentication backend*. Features -------- - Object permissions for Django_ - AnonymousUser support - High level API - Heavily tested - Django's admin integration - Decorators Incoming -------- - Admin templates for grappelli_ Source and issue tracker ------------------------ Sources are available at `issue-tracker`_. You may also file a bug there. Alternate projects ------------------ Django_ 1.2 still has *only* foundation for object permissions [1]_ and ``django-guardian`` make use of new facilities and it is based on them. There are some other pluggable applications which does *NOT* require latest 1.2 version of Django_. For instance, there are great `django-authority`_ or `django-permissions`_ available out there. .. _django: http://www.djangoproject.com/ .. _django-authority: http://bitbucket.org/jezdez/django-authority/ .. _django-permissions: http://bitbucket.org/diefenbach/django-permissions/ .. _issue-tracker: http://github.com/lukaszb/django-guardian .. _grappelli: https://github.com/sehmaschine/django-grappelli .. [1] See http://docs.djangoproject.com/en/1.2/topics/auth/#handling-object-permissions for more detail. django-guardian-1.4.1/docs/watch-docs.sh0000700000175000017500000000051312634712306020273 0ustar brianbrian00000000000000#!/bin/sh DIR="." CMD="make html" NOTIFY='growlnotify guardian Documentation -m Recreated' # Run command echo " => Documentation would be available at file://$PWD/build/html/index.html" $CMD $NOTIFY # Run command on .rst file change. watchmedo shell-command --pattern "*.rst" --recursive -w -c "clear && $CMD && $NOTIFY" $DIR django-guardian-1.4.1/MANIFEST.in0000600000175000017500000000073112634712306016512 0ustar brianbrian00000000000000include CHANGES include LICENSE include README.rst include MANIFEST.in include extras.py include tests.py include run_test_and_report.sh recursive-include guardian *.py recursive-include guardian/locale *.po *.mo recursive-include guardian/fixtures *.json recursive-include guardian/templates *.html recursive-include guardian/tests/templates *.html recursive-include docs * recursive-exclude example_project * recursive-exclude docs/build * recursive-exclude benchmarks * django-guardian-1.4.1/run_test_and_report.sh0000700000175000017500000000041512616547266021403 0ustar brianbrian00000000000000#!/bin/bash echo "Running test suite with coverage report at the end" echo -e "( would require coverage python package to be installed )\n" OMIT="guardian/testsettings.py,guardian/compat.py" coverage run setup.py test coverage report --omit "$OMIT" -m guardian/*.py django-guardian-1.4.1/VERSION.txt0000600000175000017500000000000612644306223016633 0ustar brianbrian000000000000001.4.1 django-guardian-1.4.1/setup.py0000600000175000017500000000365512644306223016474 0ustar brianbrian00000000000000import os import sys from setuptools import setup, find_packages from extras import RunFlakesCommand version_file = os.path.join(os.path.dirname(__file__), 'VERSION.txt') with open(version_file, 'r') as f: version = f.readline().strip() readme_file = os.path.join(os.path.dirname(__file__), 'README.rst') with open(version_file, 'r') as f: long_description = f.readline().strip() tests_require = ['mock', 'django-environ'] extra_kwargs = {} if sys.version_info >= (3,): extra_kwargs = {'use_2to3': True} elif sys.version_info < (2, 7): tests_require.append('unittest2') setup( name = 'django-guardian', version = version, url = 'http://github.com/django-guardian/django-guardian', author = 'Lukasz Balcerzak', author_email = 'lukaszbalcerzak@gmail.com', download_url='https://github.com/django-guardian/django-guardian/tags', description ="Implementation of per object permissions for Django.", long_description = long_description, zip_safe = False, packages = find_packages(), include_package_data = True, license = 'BSD', install_requires = [ 'Django >= 1.7', 'six', ], tests_require=tests_require, classifiers = ['Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Security', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', ], test_suite='tests.main', cmdclass={'flakes': RunFlakesCommand}, **extra_kwargs ) django-guardian-1.4.1/AUTHORS0000600000175000017500000000427212634712306016030 0ustar brianbrian00000000000000Authors ordered by first contribution - Lukasz Balcerzak - Cesar Canassa - Vincent Driessen - John Hensley - Ramanan Sivaranjan - Woosuk Suh - Bojan Mihelac - Rafael Ponieman - Daniel Sokolowski - Ali Lozano - BJ Dierkes - Rach Belaid - Michael Crosby - Greg Hinch - Aram Dulyan - Florian Hahn - Piotr Kilczuk - Reavis Sutphin-Gray - Adrián López - Jameel Al-Aziz - John P. Neumann - Andreas Madsack - Ivan Kharlamov - Miguel de Val-Borro - Jan Nakladal - Yonel Ceruto - Luke Faraone - John Wegis - Florentin Sardan - Geoff Greer - Hans Larsen - Fabio C. Barrionuevo da Luz @luzfcb - Tomasz Wsuł <2nickers@gmail.com> - Xavier Ordoquy - Joshua Bonnett - Jernej Kos - Bruno Ribeiro da Silva - Cezar Jenkins - Warren Volz - Omer Katz - Vishal Lal - Steven DeMartini - zauddelig - Remco Wendt - Kevin London - Kouhei Maeda - Samuel Sutch - Morgan Aubert - Brian May - Troy Grosfield - Michael Drescher - Verena Jaspersen - Bertrand Svetchine - Frank Wickström - George Karakostas