21 |
22 | {% block body %}
23 | {% endblock %}
24 |
25 |
26 |
27 |
28 | {% block extra_body %}
29 | {% endblock %}
30 |
31 |
32 |
--------------------------------------------------------------------------------
/test_project/test_project/urls.py:
--------------------------------------------------------------------------------
1 | import django
2 | from django.conf.urls import include, url
3 | from django.views import generic
4 | from django.contrib.auth.models import User
5 |
6 | import rules_light
7 | # not need in this particular project ... oh well it'll serve as example
8 | rules_light.autodiscover()
9 | # import our project specific rules
10 | import auth_rules
11 | from django.contrib import admin
12 |
13 | urlpatterns = [
14 | url(r'^admin/', admin.site.urls),
15 | url(r'^auth/', include('django.contrib.auth.urls')),
16 | url(r'^rules/', include('rules_light.urls')),
17 |
18 | url(r'^$', generic.ListView.as_view(model=User,
19 | template_name='auth/user_list.html' if django.VERSION < (1, 7)
20 | else 'auth/new_user_list.html'), name='auth_user_list'),
21 | url(r'user/(?P[\w_-]+)/$',
22 | rules_light.class_decorator(generic.DetailView).as_view(
23 | slug_field='username', slug_url_kwarg='username', model=User),
24 | name='auth_user_detail'),
25 | url(r'user/(?P[\w_-]+)/update/$',
26 | rules_light.class_decorator(generic.UpdateView).as_view(
27 | slug_field='username', slug_url_kwarg='username', model=User),
28 | name='auth_user_update'),
29 | ]
30 |
--------------------------------------------------------------------------------
/test_project/test_project/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for test_project project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 |
18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
19 |
20 | # This application object is used by any WSGI server configured to use this
21 | # file. This includes Django's development server, if the WSGI_APPLICATION
22 | # setting points here.
23 | from django.core.wsgi import get_wsgi_application
24 | application = get_wsgi_application()
25 |
26 | # Apply WSGI middleware here.
27 | # from helloworld.wsgi import HelloWorldApplication
28 | # application = HelloWorldApplication(application)
29 |
--------------------------------------------------------------------------------
/rules_light/templatetags/rules_light_tags.py:
--------------------------------------------------------------------------------
1 | import inspect
2 |
3 | from django import template
4 |
5 | from classytags.core import Options
6 | from classytags.helpers import AsTag
7 | from classytags.arguments import (Argument, MultiKeywordArgument,
8 | MultiValueArgument)
9 |
10 | import rules_light
11 |
12 | register = template.Library()
13 |
14 |
15 | @register.filter
16 | def rule_code(registry, name):
17 | rule = registry[name]
18 |
19 | if hasattr(rule, '__call__'):
20 | return inspect.getsource(rule)
21 |
22 | return repr(rule)
23 |
24 |
25 | class Rule(AsTag):
26 | options = Options(
27 | Argument('rule_name'),
28 | MultiValueArgument('args', required=False),
29 | MultiKeywordArgument('kwargs', required=False),
30 | 'as',
31 | Argument('varname', resolve=False, required=False),
32 | )
33 |
34 | def get_value(self, context, rule_name, args, kwargs):
35 | try:
36 | return rules_light.run(context.request.user, rule_name,
37 | *args, **kwargs)
38 | except AttributeError:
39 | rules_light.run(context['request'].user, rule_name,
40 | *args, **kwargs)
41 |
42 |
43 | register.tag('rule', Rule)
44 |
--------------------------------------------------------------------------------
/rules_light/decorators.py:
--------------------------------------------------------------------------------
1 | """
2 | This module enables piling rules on each others.
3 |
4 | Consider this simple rule::
5 |
6 | def is_authenticated(user, *args, **kwargs):
7 | return user and user.is_authenticated()
8 |
9 | It can of course be used directly::
10 |
11 | rules_light.registry['do_something'] = is_authenticated
12 |
13 | But if defined using ``make_decorator`` as such::
14 |
15 | @rules_light.make_decorator
16 | def is_authenticated(user, *args, **kwargs):
17 | return user and user.is_authenticated()
18 |
19 | Then you can use it to decorate other rules too::
20 |
21 | @is_authenticated
22 | def my_book(user, rule, book):
23 | return user == book.author
24 |
25 | rules_light.registry['do_something'] = my_book
26 |
27 | """
28 | from __future__ import unicode_literals
29 |
30 |
31 | def make_decorator(_rule):
32 | def _decorator(*args, **kwargs):
33 | if len(args) == 1 and len(kwargs) == 0:
34 | func = args[0]
35 |
36 | def _decorated(user, rule, *args, **kwargs):
37 | if not _rule(user, rule, *args, **kwargs):
38 | return False
39 | return func(user, rule, *args, **kwargs)
40 | _decorated.__name__ = func.__name__
41 | return _decorated
42 | else: # rule
43 | return _rule(*args, **kwargs)
44 | _decorator.__name__ = _rule.__name__
45 | return _decorator
46 |
--------------------------------------------------------------------------------
/rules_light/tests/test_views.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | import pytest
3 | import unittest
4 |
5 | from django.test.client import RequestFactory
6 | from django.contrib.auth.models import User, AnonymousUser
7 |
8 | import rules_light
9 | from rules_light.views import RegistryView
10 |
11 |
12 | @pytest.mark.django_db
13 | class ViewsTestCase(unittest.TestCase):
14 | def setUp(self):
15 | """
16 | Note that URL doesn't matter here because the tests excute the views
17 | directly.
18 | """
19 | User.objects.all().delete()
20 |
21 | self.anonymous_request = RequestFactory().get('/')
22 | self.anonymous_request.user = AnonymousUser()
23 |
24 | self.user_request = RequestFactory().get('/')
25 | self.user_request.user, c = User.objects.get_or_create(
26 | username='foo', is_staff=False)
27 |
28 | self.admin_request = RequestFactory().get('/')
29 | self.admin_request.user, c = User.objects.get_or_create(
30 | username='bar', is_staff=True)
31 |
32 | def test_registry_view(self):
33 | view = RegistryView.as_view()
34 |
35 | with self.assertRaises(rules_light.Denied) as cm:
36 | view(self.anonymous_request)
37 |
38 | with self.assertRaises(rules_light.Denied) as cm:
39 | view(self.user_request)
40 |
41 | # it should not raise an exception
42 | view(self.admin_request)
43 |
--------------------------------------------------------------------------------
/rules_light/tests/test_shortcuts.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | import unittest
3 | import pytest
4 |
5 | from django.contrib.auth.models import User
6 |
7 | import rules_light
8 |
9 |
10 | @pytest.mark.django_db
11 | class ShortcutsTestCase(unittest.TestCase):
12 | def setUp(self):
13 | self.user, c = User.objects.get_or_create(username='foo')
14 | self.admin, c = User.objects.get_or_create(username='bar',
15 | is_staff=True)
16 |
17 | def test_is_authenticated_decorator(self):
18 | return_true = rules_light.is_authenticated(lambda u, r: True)
19 |
20 | self.assertFalse(return_true(None, 'foo'))
21 | self.assertTrue(return_true(self.user, 'foo'))
22 |
23 | def test_is_authenticated_rule(self):
24 | self.assertFalse(rules_light.is_authenticated(None, 'foo'))
25 | self.assertTrue(rules_light.is_authenticated(self.user, 'foo'))
26 |
27 | def test_is_staff_decorator(self):
28 | return_true = rules_light.is_staff(lambda u, r: True)
29 |
30 | self.assertFalse(return_true(None, 'foo'))
31 | self.assertFalse(return_true(self.user, 'foo'))
32 | self.assertTrue(return_true(self.admin, 'foo'))
33 |
34 | def test_is_staff_rule(self):
35 | self.assertFalse(rules_light.is_staff(None, 'foo'))
36 | self.assertFalse(rules_light.is_staff(self.user, 'foo'))
37 | self.assertTrue(rules_light.is_staff(self.admin, 'foo'))
38 |
--------------------------------------------------------------------------------
/rules_light/middleware.py:
--------------------------------------------------------------------------------
1 | """
2 | The role of the middleware is to present a user friendly error page when a rule
3 | denied process of the request by raising ``Denied``.
4 | """
5 | from __future__ import unicode_literals
6 |
7 | from django import template, http, VERSION
8 |
9 | from django.conf import settings
10 |
11 | from .exceptions import RulesLightException
12 |
13 |
14 | class Middleware(object):
15 | """
16 | Install this middleware by adding `rules_light.middleware.Middleware`` to
17 | ``settings.MIDDLEWARE_CLASSES`` or ``settings.MIDDLEWARE`` for Django1.10+
18 | """
19 | def process_exception(self, request, exception):
20 | """
21 | Render ``rules_light/exception.html`` when a ``Denied`` exception was
22 | raised.
23 | """
24 | if not isinstance(exception, RulesLightException):
25 | return
26 |
27 | if VERSION > (1, 8):
28 | ctx = dict(request=request, exception=exception, settings=settings)
29 | else:
30 | ctx = template.RequestContext(request, dict(exception=exception,
31 | settings=settings))
32 | return http.HttpResponseForbidden(template.loader.render_to_string(
33 | 'rules_light/exception.html', ctx))
34 |
35 | def __init__(self, get_response=None):
36 | super(Middleware, self).__init__()
37 | # Support Django 1.10 middleware.
38 | if get_response is not None:
39 | self.get_response = get_response
40 |
41 | def __call__(self, request):
42 | return self.get_response(request)
43 |
--------------------------------------------------------------------------------
/django_rules_light.egg-info/SOURCES.txt:
--------------------------------------------------------------------------------
1 | AUTHORS
2 | CHANGELOG
3 | MANIFEST.in
4 | README.rst
5 | setup.cfg
6 | setup.py
7 | django_rules_light.egg-info/PKG-INFO
8 | django_rules_light.egg-info/SOURCES.txt
9 | django_rules_light.egg-info/dependency_links.txt
10 | django_rules_light.egg-info/not-zip-safe
11 | django_rules_light.egg-info/requires.txt
12 | django_rules_light.egg-info/top_level.txt
13 | rules_light/__init__.py
14 | rules_light/apps.py
15 | rules_light/class_decorator.py
16 | rules_light/decorators.py
17 | rules_light/exceptions.py
18 | rules_light/middleware.py
19 | rules_light/models.py
20 | rules_light/registry.py
21 | rules_light/rules_light_registry.py
22 | rules_light/shortcuts.py
23 | rules_light/urls.py
24 | rules_light/views.py
25 | rules_light/locale/el/LC_MESSAGES/django.mo
26 | rules_light/locale/el/LC_MESSAGES/django.po
27 | rules_light/locale/fr/LC_MESSAGES/django.mo
28 | rules_light/locale/fr/LC_MESSAGES/django.po
29 | rules_light/locale/pt_BR/LC_MESSAGES/django.mo
30 | rules_light/locale/pt_BR/LC_MESSAGES/django.po
31 | rules_light/static/rules_light/github.css
32 | rules_light/static/rules_light/rainbow-custom.min.js
33 | rules_light/templates/rules_light/base.html
34 | rules_light/templates/rules_light/exception.html
35 | rules_light/templates/rules_light/registry.html
36 | rules_light/templatetags/__init__.py
37 | rules_light/templatetags/rules_light_tags.py
38 | rules_light/tests/__init__.py
39 | rules_light/tests/test_autodiscover.py
40 | rules_light/tests/test_class_decorator.py
41 | rules_light/tests/test_decorators.py
42 | rules_light/tests/test_middleware.py
43 | rules_light/tests/test_registry.py
44 | rules_light/tests/test_shortcuts.py
45 | rules_light/tests/test_views.py
46 | rules_light/tests/fixtures/__init__.py
47 | rules_light/tests/fixtures/class_decorator_classes.py
--------------------------------------------------------------------------------
/rules_light/static/rules_light/github.css:
--------------------------------------------------------------------------------
1 | /**
2 | * GitHub theme
3 | *
4 | * @author Craig Campbell
5 | * @version 1.0.4
6 | */
7 | pre {
8 | border: 1px solid #ccc;
9 | word-wrap: break-word;
10 | padding: 6px 10px;
11 | line-height: 19px;
12 | margin-bottom: 20px;
13 | }
14 |
15 | code {
16 | border: 1px solid #eaeaea;
17 | margin: 0px 2px;
18 | padding: 0px 5px;
19 | font-size: 12px;
20 | }
21 |
22 | pre code {
23 | border: 0px;
24 | padding: 0px;
25 | margin: 0px;
26 | -moz-border-radius: 0px;
27 | -webkit-border-radius: 0px;
28 | border-radius: 0px;
29 | }
30 |
31 | pre, code {
32 | font-family: Consolas, 'Liberation Mono', Courier, monospace;
33 | color: #333;
34 | background: #f8f8f8;
35 | -moz-border-radius: 3px;
36 | -webkit-border-radius: 3px;
37 | border-radius: 3px;
38 | }
39 |
40 | pre, pre code {
41 | font-size: 13px;
42 | }
43 |
44 | pre .comment {
45 | color: #998;
46 | }
47 |
48 | pre .support {
49 | color: #0086B3;
50 | }
51 |
52 | pre .tag, pre .tag-name {
53 | color: navy;
54 | }
55 |
56 | pre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function {
57 | font-weight: bold;
58 | }
59 |
60 | pre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace {
61 | color: #333;
62 | }
63 |
64 | pre .constant.numeric, pre .keyword.unit, pre .hex-color {
65 | font-weight: normal;
66 | color: #099;
67 | }
68 |
69 | pre .entity.class {
70 | color: #458;
71 | }
72 |
73 | pre .entity.id, pre .entity.function {
74 | color: #900;
75 | }
76 |
77 | pre .attribute, pre .variable {
78 | color: teal;
79 | }
80 |
81 | pre .string, pre .support.value {
82 | font-weight: normal;
83 | color: #d14;
84 | }
85 |
86 | pre .regexp {
87 | color: #009926;
88 | }
89 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os, sys
2 | from setuptools import setup, find_packages, Command
3 |
4 |
5 | def read(fname):
6 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
7 |
8 | class RunTests(Command):
9 | description = "Run the django test suite from the testproj dir."
10 |
11 | user_options = []
12 |
13 | def initialize_options(self):
14 | pass
15 |
16 | def finalize_options(self):
17 | pass
18 |
19 | def run(self):
20 | this_dir = os.getcwd()
21 | testproj_dir = os.path.join(this_dir, "test_project")
22 | os.chdir(testproj_dir)
23 | sys.path.append(testproj_dir)
24 | from django.core.management import execute_from_command_line
25 | os.environ["DJANGO_SETTINGS_MODULE"] = 'test_project.settings'
26 | settings_file = os.environ["DJANGO_SETTINGS_MODULE"]
27 | settings_mod = __import__(settings_file, {}, {}, [''])
28 | execute_from_command_line(
29 | argv=[__file__, "test", "rules_light"])
30 | os.chdir(this_dir)
31 |
32 | if 'sdist' in sys.argv:
33 | dir = os.getcwd()
34 | os.chdir(os.path.join(dir, 'rules_light'))
35 | os.system('django-admin.py compilemessages')
36 | os.chdir(dir)
37 |
38 |
39 | setup(
40 | name='django-rules-light',
41 | version='0.3.2',
42 | description='Rule registry for django',
43 | author='James Pic',
44 | author_email='jamespic@gmail.com',
45 | url='http://github.com/yourlabs/django-rules-light',
46 | packages=find_packages(),
47 | cmdclass={'test': RunTests},
48 | include_package_data=True,
49 | zip_safe=False,
50 | long_description=read('README.rst'),
51 | license='MIT',
52 | keywords='django security rules acl rbac',
53 | install_requires=[
54 | 'six',
55 | 'django-classy-tags',
56 | ],
57 | classifiers=[
58 | 'Development Status :: 5 - Production/Stable',
59 | 'Intended Audience :: Developers',
60 | 'License :: OSI Approved :: MIT License',
61 | 'Operating System :: OS Independent',
62 | 'Programming Language :: Python',
63 | 'Programming Language :: Python :: 2',
64 | 'Programming Language :: Python :: 3',
65 | 'Topic :: Software Development :: Libraries :: Python Modules',
66 | ]
67 | )
68 |
--------------------------------------------------------------------------------
/rules_light/shortcuts.py:
--------------------------------------------------------------------------------
1 | """
2 | It is trivial to take shortcuts because the rule registry is a simple dict.
3 |
4 | You can reuse your rules several times in standard python::
5 |
6 | def my_model_or_is_staff(user, rule, model, obj=None):
7 | return user.is_staff or (obj and obj.author == user)
8 |
9 | rules_light.registry.setdefault('your_app.your_model.create',
10 | my_model_or_is_staff)
11 | rules_light.registry.setdefault('your_app.your_model.update',
12 | my_model_or_is_staff)
13 | rules_light.registry.setdefault('your_app.your_model.delete',
14 | my_model_or_is_staff)
15 |
16 | This module provides some shortcut(s). Shortcuts are also usable as decorators
17 | too (see ``make_decorator``)::
18 |
19 | @rules_light.is_authenticated
20 | def my_book(user, rule, book):
21 | return book.author == user
22 |
23 | rules_light.registry.setdefault('your_app.your_model.update', my_book)
24 | """
25 | from __future__ import unicode_literals
26 |
27 | from .decorators import make_decorator
28 |
29 | __all__ = ['is_staff', 'is_authenticated']
30 |
31 |
32 | @make_decorator
33 | def is_staff(user, rulename, *args, **kwargs):
34 | """
35 | Return True if user.is_staff.
36 |
37 | For example, in ``your_app/rules_light_registry.py``::
38 |
39 | rules_light.registry.setdefault('your_app.your_model.create',
40 | rules_light.is_staff)
41 |
42 | Is equivalent to::
43 |
44 | rules_light.registry.setdefault('your_app.your_model.create',
45 | lambda: user, rulename, *args, **kwargs: user.is_staff)
46 |
47 | Also::
48 |
49 | rules_light.registry.setdefault('your_app.your_model.create',
50 | rules_light.is_staff(your_rule_callback))
51 |
52 | Is equivalent to::
53 |
54 | def staff_and_stuff(user, rule, *args, **kwargs):
55 | if not rules_light.is_staff(user, rule, *args, **kwargs):
56 | return False
57 |
58 | if your_stuff():
59 | return True
60 |
61 | rules_light.registry.setdefault('your_app.your_model.create',
62 | rules_light.is_staff(your_rule_callback))
63 | """
64 | return user and user.is_staff
65 |
66 |
67 | @make_decorator
68 | def is_authenticated(user, rulename, *args, **kwargs):
69 | """
70 | Return user.is_authenticated().
71 | """
72 | try:
73 | return user and user.is_authenticated()
74 | except Exception:
75 | return user and user.is_authenticated
76 |
--------------------------------------------------------------------------------
/rules_light/tests/test_registry.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | from __future__ import unicode_literals
3 | import logging
4 | import pytest
5 | import unittest
6 | from mock import Mock
7 |
8 | from django.contrib.auth.models import User
9 |
10 | import rules_light
11 |
12 |
13 | @pytest.mark.django_db
14 | class RegistryTestCase(unittest.TestCase):
15 | def setUp(self):
16 | self.registry = rules_light.RuleRegistry()
17 | self.registry.logger = Mock(spec_set=['debug', 'info', 'warn', 'error'])
18 | self.user, c = User.objects.get_or_create(username='test')
19 |
20 | def test_run_rule_no_args(self):
21 | mock = Mock(return_value=True, spec_set=['__call__'])
22 | self.registry['x.y.z'] = mock
23 |
24 | self.registry.logger.debug.assert_called_once_with(
25 | u'[rules_light] "x.y.z" registered with: Mock')
26 |
27 | result = self.registry.run(self.user, 'x.y.z')
28 |
29 | self.registry.logger.info.assert_called_once_with(
30 | u'[rules_light] Mock(test, "x.y.z") passed')
31 |
32 | self.assertEqual(result, True)
33 | mock.assert_called_once_with(self.user, 'x.y.z')
34 |
35 | def test_run_rule_with_args(self):
36 | mock = Mock(return_value=True, spec_set=['__call__'])
37 | self.registry['x.y.z'] = mock
38 |
39 | result = self.registry.run(self.user, 'x.y.z', 'foo', x='bar')
40 |
41 | self.registry.logger.info.assert_called_once_with(
42 | u'[rules_light] Mock(test, "x.y.z", "foo", x="bar") passed')
43 |
44 | self.assertEqual(result, True)
45 | mock.assert_called_once_with(self.user, 'x.y.z', 'foo', x='bar')
46 |
47 | def test_raises_Denied(self):
48 | mock = Mock(return_value=False, spec_set=['__call__'])
49 | self.registry['x.y.z'] = mock
50 |
51 | with self.assertRaises(rules_light.Denied) as cm:
52 | self.registry.require(self.user, 'x.y.z')
53 |
54 | self.registry.logger.warn.assert_called_once_with(
55 | u'[rules_light] Deny Mock(test, "x.y.z")')
56 |
57 | def test_return_False(self):
58 | mock = Mock(return_value=False, spec_set=['__call__'])
59 | self.registry['x.y.z'] = mock
60 |
61 | self.assertFalse(self.registry.run(self.user, 'x.y.z'))
62 | self.registry.logger.info.assert_called_once_with(
63 | u'[rules_light] Mock(test, "x.y.z") failed')
64 |
65 | def test_raises_RuleDoesNotExist(self):
66 | with self.assertRaises(rules_light.DoesNotExist) as cm:
67 | self.registry.run(self.user, 'x')
68 |
69 | self.registry.logger.error.assert_called_once_with(
70 | u'[rules_light] Rule does not exist "x"')
71 |
--------------------------------------------------------------------------------
/rules_light/tests/test_class_decorator.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | import unittest
3 | import pytest
4 |
5 | from django.views import generic
6 | from django.test.client import RequestFactory
7 | from django.contrib.auth.models import User
8 |
9 | import rules_light
10 | from rules_light.views import RegistryView
11 |
12 | from .fixtures.class_decorator_classes import *
13 |
14 |
15 | @pytest.mark.django_db
16 | class ClassDecoratorTestCase(unittest.TestCase):
17 | def setUp(self):
18 | self.request = RequestFactory().get('/')
19 | self.request.user, c = User.objects.get_or_create(username='foo')
20 |
21 | def test_create_view_decorator(self):
22 | rules_light.registry['auth.user.create'] = False
23 | view = CreateView.as_view()
24 |
25 | with self.assertRaises(rules_light.Denied) as cm:
26 | view(self.request)
27 |
28 | rules_light.registry['auth.user.create'] = True
29 | # it should not raise an exception
30 | view(self.request)
31 |
32 | def test_update_view_decorator(self):
33 | rules_light.registry['auth.user.update'] = False
34 | view = UpdateView.as_view()
35 |
36 | with self.assertRaises(rules_light.Denied) as cm:
37 | view(self.request, pk=1)
38 |
39 | rules_light.registry['auth.user.update'] = True
40 | # it should not raise an exception
41 | view(self.request, pk=1)
42 |
43 | def test_detail_view_decorator(self):
44 | rules_light.registry['auth.user.read'] = False
45 | view = DetailView.as_view()
46 |
47 | with self.assertRaises(rules_light.Denied) as cm:
48 | view(self.request, pk=1)
49 |
50 | rules_light.registry['auth.user.read'] = True
51 | # it should not raise an exception
52 | view(self.request, pk=1)
53 |
54 | def test_delete_view_decorator(self):
55 | rules_light.registry['auth.user.delete'] = False
56 | view = DeleteView.as_view()
57 |
58 | with self.assertRaises(rules_light.Denied) as cm:
59 | view(self.request, pk=1)
60 |
61 | rules_light.registry['auth.user.delete'] = True
62 | # it should not raise an exception
63 | view(self.request, pk=1)
64 |
65 | def test_funny_view_decorator(self):
66 | rules_light.registry['funny'] = False
67 | # ensure that it would not raise an exception if it tried
68 | # auth.user.read
69 | rules_light.registry['auth.user.read'] = True
70 | view = FunnyUpdateView.as_view()
71 |
72 | with self.assertRaises(rules_light.Denied) as cm:
73 | view(self.request, pk=1)
74 |
75 | rules_light.registry['funny'] = True
76 | # it should not raise an exception
77 | view(self.request, pk=1)
78 |
79 | def test_dispatch_decorator(self):
80 | rules_light.registry['foo'] = False
81 |
82 | @rules_light.class_decorator('foo')
83 | class MyView(generic.View):
84 | pass
85 | view = MyView.as_view()
86 |
87 | with self.assertRaises(rules_light.Denied) as cm:
88 | view(self.request)
89 |
90 | rules_light.registry['foo'] = True
91 | # it should not raise an exception
92 | view(self.request)
93 |
94 | def test_fail(self):
95 | with self.assertRaises(rules_light.RulesLightException) as cm:
96 | @rules_light.class_decorator
97 | class MyView(generic.View):
98 | pass
99 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | dist: xenial
4 | matrix:
5 | allow_failures:
6 | python:
7 | - "3.6"
8 | env:
9 | global:
10 | - PIP_DISABLE_PIP_VERSION_CHECK=true
11 | matrix:
12 | - TOXENV=py27-django18
13 | - TOXENV=py27-django19
14 | - TOXENV=py27-django110
15 | - TOXENV=py27-django111
16 |
17 | - TOXENV=py36-django18
18 | - TOXENV=py36-django19
19 | - TOXENV=py36-django110
20 | - TOXENV=py36-django111
21 | - TOXENV=py36-django20
22 | - TOXENV=py36-django21
23 |
24 | - TOXENV=checkqa-python2
25 | - TOXENV=checkqa
26 | install:
27 | # Create pip wrapper script, using travis_retry (a function) and
28 | # inject it into tox.ini.
29 | - mkdir -p bin
30 | - PATH=$PWD/bin:$PATH
31 | - PIP_INSTALL=bin/travis_pip_install
32 | - printf '#!/bin/bash -x\n' > $PIP_INSTALL
33 | - declare -f travis_retry >> $PIP_INSTALL
34 | - printf '\necho "=====\nUsing pip-wrapper for \"$@\"\n=====\n" >&2\n' >> $PIP_INSTALL
35 | # Handle "pip install -e" for usedevelop from tox.
36 | - printf '\nif [ "$1" = "-e" ]; then pip install "$@"; exit $?; fi\n' >> $PIP_INSTALL
37 | # First try to install from wheelhouse.
38 | - printf 'for i in "$@"; do pip install --no-index --find-links=${PIP_WHEELHOUSE} "$i"; done\n' >> $PIP_INSTALL
39 | # Then upgrade in case of outdated wheelhouse.
40 | - printf 'for i in "$@"; do travis_retry pip install --upgrade "$i"; done\n' >> $PIP_INSTALL
41 | # ..and add the new/current wheels.
42 | - printf 'pip wheel --wheel-dir=${PIP_WHEELHOUSE} --find-links=${PIP_WHEELHOUSE} "$@"\n' >> $PIP_INSTALL
43 | - chmod +x $PIP_INSTALL
44 |
45 | # Adjust tox.ini.
46 | - sed -i.bak 's/^\[testenv\]/\0\ninstall_command = travis_pip_install {opts} {packages}/' tox.ini
47 | - diff tox.ini tox.ini.bak && { echo "tox.ini was not changed."; return 1; } || true
48 | - sed -i.bak 's/whitelist_externals =/\0\n travis_pip_install/' tox.ini
49 | - diff tox.ini tox.ini.bak && { echo "tox.ini was not changed."; return 1; } || true
50 | # Inject wheel dependency into tox.ini, for travis_pip_install.
51 | - sed -i.bak -e 's/deps =.*/\0\n wheel/' tox.ini
52 | - diff tox.ini tox.ini.bak && { echo "tox.ini was not changed."; return 1; } || true
53 | # Conditionally inject postgres dependency and DSM setting into tox.ini.
54 | - if [ "$USE_POSTGRES" = 1 ]; then sed -i.bak -e 's/deps =/\0\n psycopg2/' -e 's/DJANGO_SETTINGS_MODULE=test_project.settings/\0_postgres/' tox.ini && diff tox.ini tox.ini.bak && { echo "tox.ini was not changed."; return 1; } || true; fi
55 | - cat $PIP_INSTALL
56 | - cat tox.ini
57 |
58 | # Create wheels (skips existing ones from the cached wheelhouse).
59 | - export PIP_WHEELHOUSE=$PWD/wheelhouse
60 |
61 | - travis_pip_install tox
62 | - if [ -n "$EXTRAREQ" ]; then travis_pip_install $EXTRAREQ; fi
63 |
64 | - pip freeze
65 | before_script:
66 | - RUN_TESTS="tox -- rules_light"
67 | # Run tests either with or without coverage (being installed).
68 | - command -v coveralls && RUN_TESTS="$RUN_TESTS --cov" || true
69 | before_install:
70 | - npm install phantomjs
71 | - phantomjs --version
72 | script:
73 | - ls -l $PWD/wheelhouse > /tmp/wheelhouse.before
74 | - $RUN_TESTS
75 | - ls -l $PWD/wheelhouse > /tmp/wheelhouse.after
76 | - diff /tmp/wheelhouse.before /tmp/wheelhouse.after || true
77 | - test -d .tox/$TOXENV/log && cat .tox/$TOXENV/log/*.log || true
78 | after_success:
79 | - command -v coveralls && { coveralls; return $?; } || true
80 | notifications:
81 | irc:
82 | channels:
83 | - "irc.freenode.org#yourlabs"
84 | template:
85 | - "%{repository} (%{commit} %{author}) : %{message} %{build_url} %{compare_url}"
86 |
87 | # Persistent cache across builds (http://docs.travis-ci.com/user/caching/).
88 | cache:
89 | directories:
90 | - $PWD/wheelhouse
91 | before_cache:
92 | - rm -f .tox/$TOXENV/log/*.log
93 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://secure.travis-ci.org/yourlabs/django-rules-light.png?branch=master
2 | :target: http://travis-ci.org/yourlabs/django-rules-light
3 | .. image:: https://img.shields.io/pypi/dm/django-rules-light.svg
4 | :target: https://crate.io/packages/django-rules-light
5 | .. image:: https://img.shields.io/pypi/v/django-rules-light.svg
6 | :target: https://crate.io/packages/django-rules-light
7 |
8 | This is a simple alternative to django-rules. Its core difference is that
9 | it does not rely on models. Instead, it uses a registry which can be
10 | modified at runtime.
11 |
12 | One of its goals is to enable developers of external apps to make rules,
13 | depend on it, while allowing a project to override rules.
14 |
15 | Example ``your_app/rules_light_registry.py``:
16 |
17 | .. code-block:: python
18 |
19 | # Everybody can read a blog post (for now!):
20 | rules_light.registry['blog.post.read'] = True
21 |
22 | # Require authentication to create a blog post, using a shortcut:
23 | rules_light.registry['blog.post.create'] = rules_light.is_authenticated
24 |
25 | # But others shouldn't mess with my posts !
26 | def is_staff_or_mine(user, rule, obj):
27 | return user.is_staff or obj.author == user
28 |
29 | rules_light.registry['blog.post.update'] = is_staff_or_mine
30 | rules_light.registry['blog.post.delete'] = is_staff_or_mine
31 |
32 | Example ``your_app/views.py``:
33 |
34 | .. code-block:: python
35 |
36 | @rules_light.class_decorator
37 | class PostDetailView(generic.DetailView):
38 | model = Post
39 |
40 | @rules_light.class_decorator
41 | class PostCreateView(generic.CreateView):
42 | model = Post
43 |
44 | @rules_light.class_decorator
45 | class PostUpdateView(generic.UpdateView):
46 | model = Post
47 |
48 | @rules_light.class_decorator
49 | class PostDeleteView(generic.DeleteView):
50 | model = Post
51 |
52 | You might want to read the `tutorial
53 | `_ for
54 | more.
55 |
56 | What's the catch ?
57 | ------------------
58 |
59 | The catch is that this approach does not offer any feature to get secure
60 | querysets.
61 |
62 | This means you have to:
63 |
64 | - think about security when making querysets,
65 | - `override
66 | `_
67 | eventual external app ListViews,
68 |
69 | Requirements
70 | ------------
71 |
72 | - Python 2.7+ (Python 3 supported)
73 | - Django 1.8+
74 |
75 | Quick Install
76 | -------------
77 |
78 | - Install module: ``pip install django-rules-light``,
79 | - Add to ``settings.INSTALLED_APPS``: ``rules_light``,
80 | - Add in ``settings.MIDDLEWARE_CLASSES`` (or ``settings.MIDDLEWARE`` for Django 1.10+): ``rules_light.middleware.Middleware``,
81 |
82 |
83 | You might want to read the `tutorial
84 | `_.
85 |
86 | There is also a lot of documentation, from the core to the tools, including
87 | pointers to debug, log and test your security.
88 |
89 | Contributing
90 | ------------
91 |
92 | Run tests with the `tox
93 | `_ command. Documented patches passing all
94 | tests have a better chance to get merged in. See `community guidelines
95 | `_ for details.
96 |
97 | Resources
98 | ---------
99 |
100 | To ask questions or just get informed about package updates, you could
101 | subscribe to the mailing list.
102 |
103 | - `Mailing list graciously hosted
104 | `_ by `Google
105 | `_
106 | - `Git graciously hosted
107 | `_ by `GitHub
108 | `_,
109 | - `Documentation graciously hosted
110 | `_ by `RTFD
111 | `_,
112 | - `Package graciously hosted
113 | `_ by `PyPi
114 | `_,
115 | - `Continuous integration graciously hosted
116 | `_ by `Travis-ci
117 | `_
118 |
--------------------------------------------------------------------------------
/rules_light/static/rules_light/rainbow-custom.min.js:
--------------------------------------------------------------------------------
1 | /* Rainbow v1.1.8 rainbowco.de | included languages: generic, python */
2 | window.Rainbow=function(){function q(a){var b,c=a.getAttribute&&a.getAttribute("data-language")||0;if(!c){a=a.attributes;for(b=0;b=e[d][c])delete e[d][c],delete j[d][c];if(a>=c&&ac&&b'+b+""}function s(a,b,c,h){var f=a.exec(c);if(f){++t;!b.name&&"string"==typeof b.matches[0]&&(b.name=b.matches[0],delete b.matches[0]);var k=f[0],i=f.index,u=f[0].length+i,g=function(){function f(){s(a,b,c,h)}t%100>0?f():setTimeout(f,0)};if(C(i,u))g();else{var m=v(b.matches),l=function(a,c,h){if(a>=c.length)h(k);else{var d=f[c[a]];if(d){var e=b.matches[c[a]],i=e.language,g=e.name&&e.matches?
4 | e.matches:e,j=function(b,d,e){var i;i=0;var g;for(g=1;g/g,">").replace(/&(?![\w\#]+;)/g,
6 | "&"),b,c)}function o(a,b,c){if(b= (1, 8):
85 | def new_get_form(self, *args, **kwargs):
86 | model = self.get_form_class().Meta.model
87 | try:
88 | model_name = model._meta.model_name
89 | except AttributeError:
90 | model_name = model._meta.module_name
91 | rule_name = '%s.%s.create' % (model._meta.app_label,
92 | model_name)
93 |
94 | registry.require(self.request.user, rule_name)
95 |
96 | return old_get_form(self, *args, **kwargs)
97 |
98 | else:
99 | def new_get_form(self, form_class, *args, **kwargs):
100 | model = form_class.Meta.model
101 | try:
102 | model_name = model._meta.model_name
103 | except AttributeError:
104 | model_name = model._meta.module_name
105 | rule_name = '%s.%s.create' % (model._meta.app_label,
106 | model_name)
107 |
108 | registry.require(self.request.user, rule_name)
109 |
110 | return old_get_form(self, form_class, *args, **kwargs)
111 |
112 | cls.get_form = new_get_form
113 |
114 | elif issubclass(cls, generic.UpdateView):
115 | patch_get_object(cls, 'update', self.rule)
116 |
117 | elif issubclass(cls, generic.DetailView):
118 | patch_get_object(cls, 'read', self.rule)
119 |
120 | elif issubclass(cls, generic.DeleteView):
121 | patch_get_object(cls, 'delete', self.rule)
122 |
123 | elif self.rule:
124 | if issubclass(cls, generic.detail.SingleObjectMixin):
125 | patch_get_object(cls, 'generic', self.rule)
126 | else:
127 | old_dispatch = cls.dispatch
128 |
129 | def new_dispatch(self, request, *args, **kwargs):
130 | registry.require(request.user, self.dispatch._rule)
131 | return old_dispatch(self, request, *args, **kwargs)
132 | new_dispatch._rule = self.rule
133 | cls.dispatch = new_dispatch
134 |
135 | else:
136 | raise RulesLightException('Dont understand what to do')
137 |
138 | return cls
139 |
--------------------------------------------------------------------------------
/django_rules_light.egg-info/PKG-INFO:
--------------------------------------------------------------------------------
1 | Metadata-Version: 1.1
2 | Name: django-rules-light
3 | Version: 0.3.1
4 | Summary: Rule registry for django
5 | Home-page: http://github.com/yourlabs/django-rules-light
6 | Author: James Pic
7 | Author-email: jamespic@gmail.com
8 | License: MIT
9 | Description: .. image:: https://secure.travis-ci.org/yourlabs/django-rules-light.png?branch=master
10 | :target: http://travis-ci.org/yourlabs/django-rules-light
11 | .. image:: https://img.shields.io/pypi/dm/django-rules-light.svg
12 | :target: https://crate.io/packages/django-rules-light
13 | .. image:: https://img.shields.io/pypi/v/django-rules-light.svg
14 | :target: https://crate.io/packages/django-rules-light
15 |
16 | This is a simple alternative to django-rules. Its core difference is that
17 | it does not rely on models. Instead, it uses a registry which can be
18 | modified at runtime.
19 |
20 | One of its goals is to enable developers of external apps to make rules,
21 | depend on it, while allowing a project to override rules.
22 |
23 | Example ``your_app/rules_light_registry.py``:
24 |
25 | .. code-block:: python
26 |
27 | # Everybody can read a blog post (for now!):
28 | rules_light.registry['blog.post.read'] = True
29 |
30 | # Require authentication to create a blog post, using a shortcut:
31 | rules_light.registry['blog.post.create'] = rules_light.is_authenticated
32 |
33 | # But others shouldn't mess with my posts !
34 | def is_staff_or_mine(user, rule, obj):
35 | return user.is_staff or obj.author == user
36 |
37 | rules_light.registry['blog.post.update'] = is_staff_or_mine
38 | rules_light.registry['blog.post.delete'] = is_staff_or_mine
39 |
40 | Example ``your_app/views.py``:
41 |
42 | .. code-block:: python
43 |
44 | @rules_light.class_decorator
45 | class PostDetailView(generic.DetailView):
46 | model = Post
47 |
48 | @rules_light.class_decorator
49 | class PostCreateView(generic.CreateView):
50 | model = Post
51 |
52 | @rules_light.class_decorator
53 | class PostUpdateView(generic.UpdateView):
54 | model = Post
55 |
56 | @rules_light.class_decorator
57 | class PostDeleteView(generic.DeleteView):
58 | model = Post
59 |
60 | You might want to read the `tutorial
61 | `_ for
62 | more.
63 |
64 | What's the catch ?
65 | ------------------
66 |
67 | The catch is that this approach does not offer any feature to get secure
68 | querysets.
69 |
70 | This means you have to:
71 |
72 | - think about security when making querysets,
73 | - `override
74 | `_
75 | eventual external app ListViews,
76 |
77 | Requirements
78 | ------------
79 |
80 | - Python 2.7+ (Python 3 supported)
81 | - Django 1.8+
82 |
83 | Quick Install
84 | -------------
85 |
86 | - Install module: ``pip install django-rules-light``,
87 | - Add to ``settings.INSTALLED_APPS``: ``rules_light``,
88 | - Add in ``settings.MIDDLEWARE_CLASSES`` (or ``settings.MIDDLEWARE`` for Django 1.10+): ``rules_light.middleware.Middleware``,
89 |
90 |
91 | You might want to read the `tutorial
92 | `_.
93 |
94 | There is also a lot of documentation, from the core to the tools, including
95 | pointers to debug, log and test your security.
96 |
97 | Contributing
98 | ------------
99 |
100 | Run tests with the `tox
101 | `_ command. Documented patches passing all
102 | tests have a better chance to get merged in. See `community guidelines
103 | `_ for details.
104 |
105 | Resources
106 | ---------
107 |
108 | To ask questions or just get informed about package updates, you could
109 | subscribe to the mailing list.
110 |
111 | - `Mailing list graciously hosted
112 | `_ by `Google
113 | `_
114 | - `Git graciously hosted
115 | `_ by `GitHub
116 | `_,
117 | - `Documentation graciously hosted
118 | `_ by `RTFD
119 | `_,
120 | - `Package graciously hosted
121 | `_ by `PyPi
122 | `_,
123 | - `Continuous integration graciously hosted
124 | `_ by `Travis-ci
125 | `_
126 |
127 | Keywords: django security rules acl rbac
128 | Platform: UNKNOWN
129 | Classifier: Development Status :: 5 - Production/Stable
130 | Classifier: Intended Audience :: Developers
131 | Classifier: License :: OSI Approved :: MIT License
132 | Classifier: Operating System :: OS Independent
133 | Classifier: Programming Language :: Python
134 | Classifier: Programming Language :: Python :: 2
135 | Classifier: Programming Language :: Python :: 3
136 | Classifier: Topic :: Software Development :: Libraries :: Python Modules
137 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-rules-light.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-rules-light.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | set DJANGO_SETTINGS_MODULE="test_project.test_project.settings"
46 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
47 | @echo
48 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
49 |
50 | dirhtml:
51 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
52 | @echo
53 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
54 |
55 | singlehtml:
56 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
57 | @echo
58 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
59 |
60 | pickle:
61 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
62 | @echo
63 | @echo "Build finished; now you can process the pickle files."
64 |
65 | json:
66 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
67 | @echo
68 | @echo "Build finished; now you can process the JSON files."
69 |
70 | htmlhelp:
71 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
72 | @echo
73 | @echo "Build finished; now you can run HTML Help Workshop with the" \
74 | ".hhp project file in $(BUILDDIR)/htmlhelp."
75 |
76 | qthelp:
77 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
78 | @echo
79 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
80 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
81 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-rules-light.qhcp"
82 | @echo "To view the help file:"
83 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-rules-light.qhc"
84 |
85 | devhelp:
86 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
87 | @echo
88 | @echo "Build finished."
89 | @echo "To view the help file:"
90 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-rules-light"
91 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-rules-light"
92 | @echo "# devhelp"
93 |
94 | epub:
95 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
96 | @echo
97 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
98 |
99 | latex:
100 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
101 | @echo
102 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
103 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
104 | "(use \`make latexpdf' here to do that automatically)."
105 |
106 | latexpdf:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo "Running LaTeX files through pdflatex..."
109 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
110 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
111 |
112 | text:
113 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
114 | @echo
115 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
116 |
117 | man:
118 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
119 | @echo
120 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
121 |
122 | texinfo:
123 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
124 | @echo
125 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
126 | @echo "Run \`make' in that directory to run these through makeinfo" \
127 | "(use \`make info' here to do that automatically)."
128 |
129 | info:
130 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
131 | @echo "Running Texinfo files through makeinfo..."
132 | make -C $(BUILDDIR)/texinfo info
133 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
134 |
135 | gettext:
136 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
137 | @echo
138 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
139 |
140 | changes:
141 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
142 | @echo
143 | @echo "The overview file is in $(BUILDDIR)/changes."
144 |
145 | linkcheck:
146 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
147 | @echo
148 | @echo "Link check complete; look for any errors in the above output " \
149 | "or in $(BUILDDIR)/linkcheck/output.txt."
150 |
151 | doctest:
152 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
153 | @echo "Testing of doctests in the sources finished, look at the " \
154 | "results in $(BUILDDIR)/doctest/output.txt."
155 |
--------------------------------------------------------------------------------
/test_project/test_project/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for test_project project.
2 |
3 | import os.path
4 | import posixpath
5 | import django
6 |
7 | CACHES = {
8 | 'default': {
9 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
10 | 'LOCATION': 'unique-snowflake'
11 | }
12 | }
13 |
14 | PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
15 |
16 | DEBUG = True
17 | TEMPLATE_DEBUG = DEBUG
18 |
19 | ADMINS = (
20 | # ('Your Name', 'your_email@example.com'),
21 | )
22 |
23 | FIXTURE_DIRS = [
24 | os.path.join(PROJECT_ROOT, 'fixtures'),
25 | ]
26 |
27 | MANAGERS = ADMINS
28 |
29 | LOGIN_URL='/auth/login/'
30 |
31 | DATABASES = {
32 | 'default': {
33 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
34 | 'NAME': 'db.sqlite', # Or path to database file if using sqlite3.
35 | }
36 | }
37 | # Local time zone for this installation. Choices can be found here:
38 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
39 | # although not all choices may be available on all operating systems.
40 | # In a Windows environment this must be set to your system time zone.
41 | TIME_ZONE = 'America/Chicago'
42 |
43 | # Language code for this installation. All choices can be found here:
44 | # http://www.i18nguy.com/unicode/language-identifiers.html
45 | LANGUAGE_CODE = 'en-us'
46 |
47 | SITE_ID = 1
48 |
49 | # If you set this to False, Django will make some optimizations so as not
50 | # to load the internationalization machinery.
51 | USE_I18N = True
52 |
53 | # If you set this to False, Django will not format dates, numbers and
54 | # calendars according to the current locale.
55 | USE_L10N = True
56 |
57 | # If you set this to False, Django will not use timezone-aware datetimes.
58 | USE_TZ = True
59 |
60 | # Absolute filesystem path to the directory that will hold user-uploaded files.
61 | # Example: "/home/media/media.lawrence.com/media/"
62 | MEDIA_ROOT = ''
63 |
64 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
65 | # trailing slash.
66 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
67 | MEDIA_URL = ''
68 |
69 | # Absolute path to the directory static files should be collected to.
70 | # Don't put anything in this directory yourself; store your static files
71 | # in apps' "static/" subdirectories and in STATICFILES_DIRS.
72 | # Example: "/home/media/media.lawrence.com/static/"
73 | STATIC_ROOT = ''
74 |
75 | # URL prefix for static files.
76 | # Example: "http://media.lawrence.com/static/"
77 | STATIC_URL = '/static/'
78 |
79 | # Additional locations of static files
80 | STATICFILES_DIRS = (
81 | os.path.join(PROJECT_ROOT, 'static'),
82 | )
83 |
84 | # List of finder classes that know how to find static files in
85 | # various locations.
86 | STATICFILES_FINDERS = (
87 | 'django.contrib.staticfiles.finders.FileSystemFinder',
88 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
89 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
90 | )
91 |
92 | # Make this unique, and don't share it with anybody.
93 | SECRET_KEY = 'vuu5^s4r7qj2g*np&vg*+f!%(2@ur=fgchp^2f$(9kjr1sy*yc'
94 |
95 | # List of callables that know how to import templates from various sources.
96 | TEMPLATE_LOADERS = (
97 | 'django.template.loaders.filesystem.Loader',
98 | 'django.template.loaders.app_directories.Loader',
99 | # 'django.template.loaders.eggs.Loader',
100 | )
101 |
102 | MIDDLEWARE = MIDDLEWARE_CLASSES = (
103 | 'django.middleware.common.CommonMiddleware',
104 | 'django.contrib.sessions.middleware.SessionMiddleware',
105 | 'django.middleware.csrf.CsrfViewMiddleware',
106 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
107 | 'django.contrib.messages.middleware.MessageMiddleware',
108 | 'rules_light.middleware.Middleware',
109 | # Uncomment the next line for simple clickjacking protection:
110 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
111 | )
112 |
113 | ROOT_URLCONF = 'test_project.urls'
114 |
115 | # Python dotted path to the WSGI application used by Django's runserver.
116 | WSGI_APPLICATION = 'test_project.wsgi.application'
117 |
118 | TEMPLATE_DIRS = (
119 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
120 | # Always use forward slashes, even on Windows.
121 | # Don't forget to use absolute paths, not relative paths.
122 | os.path.join(PROJECT_ROOT, 'templates'),
123 | )
124 |
125 | if django.VERSION < (1, 8):
126 | TEMPLATE_CONTEXT_PROCESSORS = (
127 | 'django.contrib.auth.context_processors.auth',
128 | 'django.core.context_processors.debug',
129 | 'django.core.context_processors.i18n',
130 | 'django.core.context_processors.media',
131 | 'django.core.context_processors.static',
132 | 'django.core.context_processors.tz',
133 | 'django.contrib.messages.context_processors.messages'
134 | )
135 | else:
136 | TEMPLATE_CONTEXT_PROCESSORS = (
137 | 'django.contrib.auth.context_processors.auth',
138 | 'django.template.context_processors.debug',
139 | 'django.template.context_processors.i18n',
140 | 'django.template.context_processors.media',
141 | 'django.template.context_processors.static',
142 | 'django.template.context_processors.tz',
143 | 'django.contrib.messages.context_processors.messages'
144 | )
145 |
146 | TEMPLATES = [
147 | {
148 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
149 | 'APP_DIRS': True,
150 | 'OPTIONS': {
151 | 'context_processors': TEMPLATE_CONTEXT_PROCESSORS,
152 | },
153 | 'DIRS': TEMPLATE_DIRS,
154 | },
155 | ]
156 |
157 | INSTALLED_APPS = (
158 | 'django.contrib.auth',
159 | 'django.contrib.contenttypes',
160 | 'django.contrib.sessions',
161 | 'django.contrib.sites',
162 | 'django.contrib.messages',
163 | 'django.contrib.staticfiles',
164 | 'django.contrib.admin',
165 | 'rules_light',
166 | 'test_rule',
167 | # Uncomment the next line to enable admin documentation:
168 | # 'django.contrib.admindocs',
169 | )
170 |
171 | # A sample logging configuration. The only tangible logging
172 | # performed by this configuration is to send an email to
173 | # the site admins on every HTTP 500 error when DEBUG=False.
174 | from .rules_logging import LOGGING
175 |
--------------------------------------------------------------------------------
/rules_light/registry.py:
--------------------------------------------------------------------------------
1 | """
2 | The rule registry is in charge of keeping and executing security rules.
3 |
4 | It is the core of this app, everything else is optionnal.
5 |
6 | This module provides a variable, ``registry``, which is just a module-level,
7 | default RuleRegistry instance.
8 |
9 | A rule can be a callback or a variable that will be evaluated as bool.
10 | """
11 | from __future__ import unicode_literals
12 |
13 | import logging
14 |
15 | from django.utils.encoding import smart_str as smart_text
16 |
17 | try:
18 | from django.utils.module_loading import autodiscover_modules
19 | except ImportError:
20 | autodiscover_modules = None
21 |
22 | from .exceptions import Denied, DoesNotExist
23 |
24 | __all__ = ('RuleRegistry', 'registry', 'require', 'run', 'autodiscover')
25 |
26 |
27 | class RuleRegistry(dict):
28 | """
29 | Dict subclass to manage rules.
30 |
31 | logger
32 | The standard logging logger instance to use.
33 | """
34 | def __init__(self):
35 | self.logger = logging.getLogger('rules_light')
36 |
37 | def __setitem__(self, key, value):
38 | """
39 | Adds a debug-level log on registration.
40 | """
41 | super(RuleRegistry, self).__setitem__(key, value)
42 | self.logger.debug(u'[rules_light] "%s" registered with: %s' % (
43 | key, self.rule_text_name(value)))
44 |
45 | def run(self, user, name, *args, **kwargs):
46 | """
47 | Run a rule, return True if whatever it returns evaluates to True.
48 |
49 | Also logs calls with the info-level.
50 | """
51 | if name not in self:
52 | self.logger.error(u'[rules_light] Rule does not exist "%s"' % name)
53 | raise DoesNotExist(name)
54 |
55 | rule = self[name]
56 |
57 | if hasattr(rule, '__call__'):
58 | result = self[name](user, name, *args, **kwargs)
59 | else:
60 | result = rule
61 |
62 | text = self.as_text(user, name, *args, **kwargs)
63 | if result:
64 | self.logger.info(u'[rules_light] %s passed' % text)
65 | return True
66 | else:
67 | self.logger.info(u'[rules_light] %s failed' % text)
68 | return False
69 |
70 | def require(self, user, name, *args, **kwargs):
71 | """
72 | Run a rule, raise ``rules_light.Denied`` if returned False.
73 |
74 | Log denials with warn-level.
75 | """
76 | result = self.run(user, name, *args, **kwargs)
77 |
78 | if not result:
79 | text = self.as_text(user, name, *args, **kwargs)
80 | self.logger.warn(u'[rules_light] Deny %s' % text)
81 | raise Denied(text)
82 |
83 | def as_text(self, user, name, *args, **kwargs):
84 | """ Format a rule to be human readable for logging """
85 | if name not in self:
86 | raise DoesNotExist(name)
87 |
88 | formated_args = []
89 | for arg in args:
90 | formated_args.append(u'"%s"' % smart_text(arg))
91 |
92 | for key, value in kwargs.items():
93 | formated_args.append(u'%s="%s"' % (smart_text(key),
94 | smart_text(value)))
95 | formated_args = u', '.join(formated_args)
96 |
97 | if hasattr(self[name], '__call__'):
98 | text_name = self.rule_text_name(self[name])
99 |
100 | if formated_args:
101 | return u'%s(%s, "%s", %s)' % (text_name, user, name,
102 | formated_args)
103 | else:
104 | return u'%s(%s, "%s")' % (text_name, user, name)
105 | else:
106 | return u'%s is %s' % (name, self[name])
107 |
108 | def rule_text_name(self, rule):
109 | if hasattr(rule, 'func_name'):
110 | return rule.func_name
111 | elif rule is True:
112 | return u'True'
113 | elif rule is False:
114 | return u'False'
115 | elif hasattr(rule, '__name__'):
116 | return rule.__name__
117 | elif hasattr(rule, '__class__'):
118 | return rule.__class__.__name__
119 | else:
120 | return smart_text(rule)
121 |
122 |
123 | registry = RuleRegistry()
124 |
125 |
126 | def run(user, name, *args, **kwargs):
127 | """ Proxy ``rules_light.registry.run()``. """
128 | return registry.run(user, name, *args, **kwargs)
129 |
130 |
131 | def require(user, name, *args, **kwargs):
132 | """ Proxy ``rules_light.registry.require()``. """
133 | registry.require(user, name, *args, **kwargs)
134 |
135 |
136 | def _autodiscover(registry):
137 | """See documentation for autodiscover (without the underscore)"""
138 | import copy
139 | from django.conf import settings
140 | from django.utils.importlib import import_module
141 | from django.utils.module_loading import module_has_submodule
142 |
143 | for app in settings.INSTALLED_APPS:
144 | mod = import_module(app)
145 | # Attempt to import the app's admin module.
146 | try:
147 | before_import_registry = copy.copy(registry)
148 | import_module('%s.rules_light_registry' % app)
149 | except Exception:
150 | # Reset the model registry to the state before the last import as
151 | # this import will have to reoccur on the next request and this
152 | # could raise NotRegistered and AlreadyRegistered exceptions
153 | # (see #8245).
154 | registry = before_import_registry
155 |
156 | # Decide whether to bubble up this error. If the app just
157 | # doesn't have an admin module, we can ignore the error
158 | # attempting to import it, otherwise we want it to bubble up.
159 | if module_has_submodule(mod, 'rules_light_registry'):
160 | raise
161 |
162 |
163 | def autodiscover():
164 | """
165 | Check all apps in INSTALLED_APPS for stuff related to rules_light.
166 |
167 | For each app, autodiscover imports ``app.rules_light_registry`` if
168 | available, resulting in execution of ``rules_light.registry[...] = ...``
169 | statements in that module, filling registry.
170 |
171 | Consider a standard app called 'cities_light' with such a structure::
172 |
173 | cities_light/
174 | __init__.py
175 | models.py
176 | urls.py
177 | views.py
178 | rules_light_registry.py
179 |
180 | With such a rules_light_registry.py::
181 |
182 | import rules_light
183 |
184 | rules_light.register('cities_light.city.read', True)
185 | rules_light.register('cities_light.city.update',
186 | lambda user, rulename, country: user.is_staff)
187 |
188 | When autodiscover() imports cities_light.rules_light_registry, both
189 | `'cities_light.city.read'` and `'cities_light.city.update'` will be
190 | registered.
191 | """
192 | if autodiscover_modules:
193 | autodiscover_modules('rules_light_registry')
194 | else:
195 | _autodiscover(registry)
196 |
--------------------------------------------------------------------------------
/docs/source/tutorial.rst:
--------------------------------------------------------------------------------
1 | Tutorial
2 | ========
3 |
4 | Install
5 | -------
6 |
7 | Either install the last release::
8 |
9 | pip install django-rules-light
10 |
11 | Either install a development version::
12 |
13 | pip install -e git+https://github.com/yourlabs/django-rules-light.git#egg=django-rules-light
14 |
15 | That should be enough to work with the registry.
16 |
17 | Middleware
18 | ``````````
19 |
20 | To enable the middleware that processes ``rules_light.Denied``
21 | exception, add to ``setings.MIDDLEWARE_CLASSES`` or ``settings.MIDDLEWARE`` for Django >= 1.10:
22 |
23 | .. code-block:: python
24 |
25 | MIDDLEWARE_CLASSES = (
26 | # ...
27 | 'rules_light.middleware.Middleware',
28 | )
29 |
30 | See :doc:`docs on middleware` for more details.
31 |
32 | Logging
33 | ```````
34 |
35 | To enable logging, add a ``rules_light`` logger for example:
36 |
37 | .. code-block:: python
38 |
39 | LOGGING = {
40 | # ...
41 | 'handlers': {
42 | # ...
43 | 'console':{
44 | 'level':'DEBUG',
45 | 'class':'logging.StreamHandler',
46 | },
47 | },
48 | 'loggers': {
49 | 'rules_light': {
50 | 'handlers': ['console'],
51 | 'propagate': True,
52 | 'level': 'DEBUG',
53 | }
54 | }
55 | }
56 |
57 | See :doc:`docs on logging` for more details on logging.
58 |
59 | Debug view
60 | ``````````
61 |
62 | Add to ``settings.INSTALLED_APPS``:
63 |
64 | .. code-block:: python
65 |
66 | INSTALLED_APPS = (
67 | 'rules_light',
68 | # ....
69 | )
70 |
71 | Then the view should be usable, install it as such:
72 |
73 | .. code-block:: python
74 |
75 | url(r'^rules/', include('rules_light.urls')),
76 |
77 | See :doc:`docs on debugging` for more details on debugging rules.
78 |
79 | Creating Rules
80 | --------------
81 |
82 | Declare rules
83 | `````````````
84 |
85 | Declaring rules consist of filling up the ``rules_light.registry`` dict. This
86 | dict uses rule "names" as keys, ie. ``do_something``,
87 | ``some_app.some_model.create``, etc, etc ... For values, it can use booleans:
88 |
89 | .. code-block:: python
90 |
91 | # Enable read for everybody
92 | rules_light.registry['your_app.your_model.read'] = True
93 |
94 | # Disable delete for everybody
95 | rules_light.registry['your_app.your_model.delete'] = False
96 |
97 | Optionnaly, use the Python dict method ``setdefault()`` in default rules. For
98 | example:
99 |
100 | .. code-block:: python
101 |
102 | # Only allow everybody if another (project-specific) callback was not set
103 | rules_light.registry.setdefault('your_app.your_model.read', True)
104 |
105 | It can also use callbacks:
106 |
107 | .. code-block:: python
108 |
109 | def your_custom_rule(user, rule_name, model, *args, **kwargs):
110 | if user in model.your_custom_stuff:
111 | return True # Allow user !
112 |
113 | rules_light.registry['app.model.read'] = your_custom_rule
114 |
115 | See :doc:`docs on registry` for more details.
116 |
117 | Mix rules, DRY security
118 | ```````````````````````
119 |
120 | Callbacks may also be used to decorate each other, using
121 | ``rules_light.make_decorator()`` will transform a simple rule callback, into a
122 | rule callback that can also be used as decorator for another callback.
123 |
124 | Just decorate a callback with ``make_decorator()`` to make it reusable as
125 | decorator:
126 |
127 | .. code-block:: python
128 |
129 | @rules_light.make_decorator
130 | def some_condition(user, rule, *args, **kwargs):
131 | # do stuff
132 |
133 | rules_light.registry.setdefault('your_app.your_model.create', some_condition)
134 |
135 | @some_condition
136 | def extra_condition(user, rule, *args, **kwargs):
137 | # do extra stuff
138 |
139 | rules_light.registry.setdefault('your_app.your_model.update', extra_condition)
140 |
141 | This will cause ``some_condition()`` to be evaluated first, and if it passes,
142 | ``extra_condition()`` will be evaluated to, for the update rule.
143 |
144 | See :doc:`docs on decorator` for more details.
145 |
146 | Using rules
147 | -----------
148 |
149 | The rule registry is in charge of using rules, using the ``run()`` method. It
150 | should return True or False.
151 |
152 | Run
153 | ```
154 |
155 | For example with this:
156 |
157 | .. code-block:: python
158 |
159 | def some_condition(user, rulename, *args, **kwargs):
160 | # ...
161 |
162 | rules_light.registry['your_app.your_model.create'] = some_condition
163 |
164 | Doing:
165 |
166 | .. code-block:: python
167 |
168 | rules_light.run(request.user, 'your_app.your_model.create')
169 |
170 | Will call:
171 |
172 | .. code-block:: python
173 |
174 | some_condition(request.user, 'your_app.your_model.create')
175 |
176 | Kwargs are forwarded, for example:
177 |
178 | .. code-block:: python
179 |
180 | rules_light.run(request.user, 'your_app.your_model.create',
181 | with_widget=request.GET['widget'])
182 |
183 | Will call:
184 |
185 | .. code-block:: python
186 |
187 | some_condition(request.user, 'your_app.your_model.create',
188 | with_widget=request.GET['widget'])
189 |
190 | See :doc:`docs on registry` for more details.
191 |
192 | Require
193 | ```````
194 |
195 | The ``require()`` method is useful too, it does the same as ``run()`` except
196 | that it will raise ``rules_light.Denied``. This will block the request process
197 | and will be catched by the middleware if installed.
198 |
199 | See :doc:`docs on registry` for more details.
200 |
201 | Decorator
202 | `````````
203 |
204 | You can decorate a class based view as such:
205 |
206 | .. code-block:: python
207 |
208 | @rules_light.class_decorator
209 | class SomeCreateView(views.CreateView):
210 | model=SomeModel
211 |
212 | This will automatically require ``'some_app.some_model.create'``.
213 |
214 | See :doc:`docs on class decorator` for more usages of the decorator.
215 |
216 | Template
217 | ````````
218 |
219 | In templates, you can run rules using '{% rule %}' templatetag.
220 |
221 | Usage:
222 |
223 | .. code-block:: django
224 |
225 | {% rule rule_name [args] [kwargs] as var_name %}
226 |
227 | This is an example from the test project:
228 |
229 | .. code-block:: django
230 |
231 | {% load rules_light_tags %}
232 |
233 |
234 | {% for user in object_list %}
235 | {% rule 'auth.user.read' user as can_read %}
236 | {% rule 'auth.user.update' user as can_update %}
237 |
238 |
244 |
245 |
246 | Tips and tricks
247 | ---------------
248 |
249 | Override rules
250 | ``````````````
251 |
252 | If your project wants to change the behaviour of ``your_app`` to allows users
253 | to create models and edit the models they have created, you could add after
254 | ``rules_light.autodiscover()``:
255 |
256 | .. code-block:: python
257 |
258 | def my_model_or_staff(user, rulename, obj):
259 | return user.is_staff or user == obj.author
260 |
261 | rules_light.registry['your_app.your_model.create'] = True
262 | rules_light.registry['your_app.your_model.update'] = my_model_or_staff
263 | rules_light.registry['your_app.your_model.delete'] = my_model_or_staff
264 |
265 | As you can see, a project can **completely** change the security logic of an
266 | app, which should enpower creative django developers hehe ...
267 |
268 | See :doc:`docs on registry` for more details.
269 |
270 | Take a shortcut
271 | ```````````````
272 |
273 | django-rules-light comes with a predefined ``is_staff`` rule which you could
274 | use in ``your_app/rules_light_registry.py``:
275 |
276 | .. code-block:: python
277 |
278 | import rules_light
279 |
280 | # Allow all users to see your_model
281 | rules_light.registry.setdefault('your_app.your_model.read', True)
282 |
283 | # Allow admins to create and edit models
284 | rules_light.registry.setdefault('your_app.your_model.create', rules_light.is_staff)
285 | rules_light.registry.setdefault('your_app.your_model.update', rules_light.is_staff)
286 | rules_light.registry.setdefault('your_app.your_model.delete', rules_light.is_staff)
287 |
288 | See :doc:`docs on shortcuts`.
289 |
290 | Test security
291 | `````````````
292 |
293 | See :doc:`security testing docs`.
294 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # django-rules-light documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Nov 26 00:08:46 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os, os.path
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #sys.path.insert(0, os.path.abspath('.'))
20 | sys.path.insert(0, os.path.abspath('../../'))
21 | sys.path.insert(0, os.path.abspath('../../../../lib/python2.7/site-packages/'))
22 | from django.conf import settings
23 | settings.configure()
24 |
25 | autoclass_content = "both"
26 |
27 | # -- General configuration -----------------------------------------------------
28 |
29 | # If your documentation needs a minimal Sphinx version, state it here.
30 | #needs_sphinx = '1.0'
31 |
32 | # Add any Sphinx extension module names here, as strings. They can be extensions
33 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
35 |
36 | # Add any paths that contain templates here, relative to this directory.
37 | templates_path = ['_templates']
38 |
39 | # The suffix of source filenames.
40 | source_suffix = '.rst'
41 |
42 | # The encoding of source files.
43 | #source_encoding = 'utf-8-sig'
44 |
45 | # The master toctree document.
46 | master_doc = 'index'
47 |
48 | # General information about the project.
49 | project = u'django-rules-light'
50 | copyright = u'2012-2015, James Pic and contributors'
51 |
52 | # The version info for the project you're documenting, acts as replacement for
53 | # |version| and |release|, also used in various other places throughout the
54 | # built documents.
55 | #
56 | # The short X.Y version.
57 | version = '0.3'
58 | # The full version, including alpha/beta/rc tags.
59 | release = '0.3.0'
60 |
61 | # The language for content autogenerated by Sphinx. Refer to documentation
62 | # for a list of supported languages.
63 | #language = None
64 |
65 | # There are two options for replacing |today|: either, you set today to some
66 | # non-false value, then it is used:
67 | #today = ''
68 | # Else, today_fmt is used as the format for a strftime call.
69 | #today_fmt = '%B %d, %Y'
70 |
71 | # List of patterns, relative to source directory, that match files and
72 | # directories to ignore when looking for source files.
73 | exclude_patterns = []
74 |
75 | # The reST default role (used for this markup: `text`) to use for all documents.
76 | #default_role = None
77 |
78 | # If true, '()' will be appended to :func: etc. cross-reference text.
79 | #add_function_parentheses = True
80 |
81 | # If true, the current module name will be prepended to all description
82 | # unit titles (such as .. function::).
83 | #add_module_names = True
84 |
85 | # If true, sectionauthor and moduleauthor directives will be shown in the
86 | # output. They are ignored by default.
87 | #show_authors = False
88 |
89 | # The name of the Pygments (syntax highlighting) style to use.
90 | pygments_style = 'sphinx'
91 |
92 | # A list of ignored prefixes for module index sorting.
93 | #modindex_common_prefix = []
94 |
95 |
96 | # -- Options for HTML output ---------------------------------------------------
97 |
98 | # The theme to use for HTML and HTML Help pages. See the documentation for
99 | # a list of builtin themes.
100 | html_theme = 'default'
101 |
102 | # Theme options are theme-specific and customize the look and feel of a theme
103 | # further. For a list of options available for each theme, see the
104 | # documentation.
105 | #html_theme_options = {}
106 |
107 | # Add any paths that contain custom themes here, relative to this directory.
108 | #html_theme_path = []
109 |
110 | # The name for this set of Sphinx documents. If None, it defaults to
111 | # " v documentation".
112 | #html_title = None
113 |
114 | # A shorter title for the navigation bar. Default is the same as html_title.
115 | #html_short_title = None
116 |
117 | # The name of an image file (relative to this directory) to place at the top
118 | # of the sidebar.
119 | #html_logo = None
120 |
121 | # The name of an image file (within the static path) to use as favicon of the
122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
123 | # pixels large.
124 | #html_favicon = None
125 |
126 | # Add any paths that contain custom static files (such as style sheets) here,
127 | # relative to this directory. They are copied after the builtin static files,
128 | # so a file named "default.css" will overwrite the builtin "default.css".
129 | html_static_path = ['_static']
130 |
131 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
132 | # using the given strftime format.
133 | #html_last_updated_fmt = '%b %d, %Y'
134 |
135 | # If true, SmartyPants will be used to convert quotes and dashes to
136 | # typographically correct entities.
137 | #html_use_smartypants = True
138 |
139 | # Custom sidebar templates, maps document names to template names.
140 | #html_sidebars = {}
141 |
142 | # Additional templates that should be rendered to pages, maps page names to
143 | # template names.
144 | #html_additional_pages = {}
145 |
146 | # If false, no module index is generated.
147 | #html_domain_indices = True
148 |
149 | # If false, no index is generated.
150 | #html_use_index = True
151 |
152 | # If true, the index is split into individual pages for each letter.
153 | #html_split_index = False
154 |
155 | # If true, links to the reST sources are added to the pages.
156 | #html_show_sourcelink = True
157 |
158 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
159 | #html_show_sphinx = True
160 |
161 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
162 | #html_show_copyright = True
163 |
164 | # If true, an OpenSearch description file will be output, and all pages will
165 | # contain a tag referring to it. The value of this option must be the
166 | # base URL from which the finished HTML is served.
167 | #html_use_opensearch = ''
168 |
169 | # This is the file name suffix for HTML files (e.g. ".xhtml").
170 | #html_file_suffix = None
171 |
172 | # Output file base name for HTML help builder.
173 | htmlhelp_basename = 'django-rules-lightdoc'
174 |
175 |
176 | # -- Options for LaTeX output --------------------------------------------------
177 |
178 | latex_elements = {
179 | # The paper size ('letterpaper' or 'a4paper').
180 | #'papersize': 'letterpaper',
181 |
182 | # The font size ('10pt', '11pt' or '12pt').
183 | #'pointsize': '10pt',
184 |
185 | # Additional stuff for the LaTeX preamble.
186 | #'preamble': '',
187 | }
188 |
189 | # Grouping the document tree into LaTeX files. List of tuples
190 | # (source start file, target name, title, author, documentclass [howto/manual]).
191 | latex_documents = [
192 | ('index', 'django-rules-light.tex', u'django-rules-light Documentation',
193 | u'James Pic', 'manual'),
194 | ]
195 |
196 | # The name of an image file (relative to this directory) to place at the top of
197 | # the title page.
198 | #latex_logo = None
199 |
200 | # For "manual" documents, if this is true, then toplevel headings are parts,
201 | # not chapters.
202 | #latex_use_parts = False
203 |
204 | # If true, show page references after internal links.
205 | #latex_show_pagerefs = False
206 |
207 | # If true, show URL addresses after external links.
208 | #latex_show_urls = False
209 |
210 | # Documents to append as an appendix to all manuals.
211 | #latex_appendices = []
212 |
213 | # If false, no module index is generated.
214 | #latex_domain_indices = True
215 |
216 |
217 | # -- Options for manual page output --------------------------------------------
218 |
219 | # One entry per manual page. List of tuples
220 | # (source start file, name, description, authors, manual section).
221 | man_pages = [
222 | ('index', 'django-rules-light', u'django-rules-light Documentation',
223 | [u'James Pic'], 1)
224 | ]
225 |
226 | # If true, show URL addresses after external links.
227 | #man_show_urls = False
228 |
229 |
230 | # -- Options for Texinfo output ------------------------------------------------
231 |
232 | # Grouping the document tree into Texinfo files. List of tuples
233 | # (source start file, target name, title, author,
234 | # dir menu entry, description, category)
235 | texinfo_documents = [
236 | ('index', 'django-rules-light', u'django-rules-light Documentation',
237 | u'James Pic', 'django-rules-light', 'One line description of project.',
238 | 'Miscellaneous'),
239 | ]
240 |
241 | # Documents to append as an appendix to all manuals.
242 | #texinfo_appendices = []
243 |
244 | # If false, no module index is generated.
245 | #texinfo_domain_indices = True
246 |
247 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
248 | #texinfo_show_urls = 'footnote'
249 |
250 |
251 | # Example configuration for intersphinx: refer to the Python standard library.
252 | intersphinx_mapping = {'http://docs.python.org/': None}
253 |
--------------------------------------------------------------------------------
/test_project/static/bootstrap/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.1.1
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade.in{top:auto}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/test_project/static/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(function(){e("body").on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})})}(window.jQuery),!function(e){"use strict";function r(){i(e(t)).removeClass("open")}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(function(){e("body").on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'