├── djanjinja_test
├── __init__.py
├── cache
│ ├── __init__.py
│ ├── urls.py
│ ├── views.py
│ └── tests.py
├── simple
│ ├── __init__.py
│ ├── urls.py
│ ├── views.py
│ └── tests.py
├── generic
│ ├── __init__.py
│ ├── urls.py
│ └── tests.py
├── shortcuts
│ ├── __init__.py
│ ├── urls.py
│ ├── views.py
│ └── tests.py
├── templates
│ ├── 500.html
│ ├── plain.txt
│ ├── 404.html
│ ├── context.txt
│ ├── req_context.txt
│ ├── cache_global.txt
│ ├── middleware.txt
│ └── cache_fragment.txt
├── urls.py
├── manage.py
└── settings.py
├── .hgignore
├── djanjinja
├── extensions
│ ├── __init__.py
│ └── cache.py
├── bundles
│ ├── cache.py
│ ├── __init__.py
│ ├── humanize.py
│ ├── site.py
│ └── csrf.py
├── __init__.py
├── handlers.py
├── middleware.py
├── generic.py
├── bccache.py
├── views.py
├── loader.py
└── environment.py
├── .hgtags
├── setup.py
├── UNLICENSE
├── pavement.py
├── pylintrc
└── README.md
/djanjinja_test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djanjinja_test/cache/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djanjinja_test/simple/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djanjinja_test/generic/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djanjinja_test/shortcuts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/djanjinja_test/templates/500.html:
--------------------------------------------------------------------------------
1 | ERROR OCCURRED.
--------------------------------------------------------------------------------
/djanjinja_test/templates/plain.txt:
--------------------------------------------------------------------------------
1 | Hello, World!
--------------------------------------------------------------------------------
/djanjinja_test/templates/404.html:
--------------------------------------------------------------------------------
1 | NOT FOUND: {{ request_path }}
--------------------------------------------------------------------------------
/djanjinja_test/templates/context.txt:
--------------------------------------------------------------------------------
1 | a = {{ a }}; b = {{ b }}
--------------------------------------------------------------------------------
/djanjinja_test/templates/req_context.txt:
--------------------------------------------------------------------------------
1 | user.is_anonymous() => {{ user.is_anonymous() }}
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 |
3 | .DS_Store
4 | *.pyc
5 | build
6 | dist
7 | MANIFEST
8 | *.egg-info
9 |
--------------------------------------------------------------------------------
/djanjinja_test/templates/cache_global.txt:
--------------------------------------------------------------------------------
1 | {% do cache.set('key', 'value') %}
2 | {{ cache.get('key') }}
--------------------------------------------------------------------------------
/djanjinja_test/templates/middleware.txt:
--------------------------------------------------------------------------------
1 | {% if user.is_anonymous() %}anonymous{% endif %}, a{{ a }}, b{{ b }}
--------------------------------------------------------------------------------
/djanjinja_test/templates/cache_fragment.txt:
--------------------------------------------------------------------------------
1 | {% cache "fragment_caching", 3600 %}
2 | {{ call_state.call() }}
3 | {% endcache %}
--------------------------------------------------------------------------------
/djanjinja/extensions/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | Django-specific Jinja2 extensions.
5 |
6 | This module contains extensions to Jinja2 which will aid integration with
7 | Django projects and apps.
8 | """
--------------------------------------------------------------------------------
/djanjinja_test/cache/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls.defaults import patterns, url
4 |
5 |
6 | urlpatterns = patterns('djanjinja_test.cache.views',
7 | url(r'^global/$', 'global_', name='cache-global'),
8 | )
9 |
--------------------------------------------------------------------------------
/djanjinja/bundles/cache.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Add the Django cache object to the global template variables."""
4 |
5 | from django.core import cache
6 |
7 | from djanjinja.loader import Bundle
8 |
9 |
10 | bundle = Bundle()
11 | bundle.globals['cache'] = cache.cache
--------------------------------------------------------------------------------
/djanjinja/bundles/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | A number of useful bundles, including some Django ports.
3 |
4 | ``djanjinja.bundles`` contains a number of bundles, using the standard
5 | interface which DjanJinja expects. Some of these bundles are simple ports of
6 | existing Django functionality, others are DjanJinja-specific.
7 | """
8 |
9 | __all__ = ['cache', 'humanize', 'site']
--------------------------------------------------------------------------------
/djanjinja_test/simple/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls.defaults import patterns, url
4 |
5 |
6 | urlpatterns = patterns('djanjinja_test.simple.views',
7 | url(r'^plain/$', 'plain', name='simple-plain'),
8 | url(r'^context/$', 'context', name='simple-context'),
9 | url(r'^req_context/$', 'req_context', name='simple-req_context'),
10 | )
11 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | 0dd95006cad6ef14ef49582ac45bd6d0db218e42 v0.6
2 | 0dd95006cad6ef14ef49582ac45bd6d0db218e42 v0.6
3 | 0000000000000000000000000000000000000000 v0.6
4 | 0000000000000000000000000000000000000000 v0.6
5 | 713d942dbf8fa34606d5dc522a2a7315ffe33233 v0.6
6 | 9a4519f39fc2741acf9abcaf5e69ee39aab220fd v0.6
7 | 0ad994ff3795afd2b74301977c1b8957849d23df v0.7
8 | 951c6bc15ca2f8bcf4d643dcb204134b9d554773 v0.8
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from distutils.core import setup
4 |
5 |
6 | setup(
7 | name='DjanJinja',
8 | version='0.8',
9 | description='Easy Django and Jinja2 integration.',
10 | packages=['djanjinja', 'djanjinja.bundles', 'djanjinja.extensions'],
11 | url='http://bitbucket.org/zacharyvoase/djanjinja/',
12 |
13 | author='Zachary Voase',
14 | author_email='zacharyvoase@me.com',
15 | )
16 |
--------------------------------------------------------------------------------
/djanjinja_test/shortcuts/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls.defaults import patterns, url
4 |
5 |
6 | urlpatterns = patterns('djanjinja_test.shortcuts.views',
7 | url(r'^plain/$', 'plain', name='shortcuts-plain'),
8 | url(r'^context/$', 'context', name='shortcuts-context'),
9 | url(r'^req_context/$', 'req_context', name='shortcuts-req_context'),
10 | url(r'^middleware/$', 'middleware', name='shortcuts-middleware'),
11 | )
--------------------------------------------------------------------------------
/djanjinja_test/cache/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.http import HttpResponse
4 |
5 | import djanjinja
6 |
7 |
8 | local_env = djanjinja.get_env().copy()
9 | local_env.load('djanjinja', 'cache')
10 |
11 |
12 | def global_(request):
13 | """Renders a template which uses the global cache object."""
14 |
15 | template = local_env.get_template('cache_global.txt')
16 | content = template.render().strip()
17 | return HttpResponse(content=content)
18 |
--------------------------------------------------------------------------------
/djanjinja/bundles/humanize.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Port of django.contrib.humanize to Jinja2."""
4 |
5 | from django.contrib.humanize.templatetags import humanize
6 | from djanjinja.loader import Bundle
7 |
8 |
9 | bundle = Bundle()
10 | # All of the humanize filters are plain functions too, and Django's way of
11 | # storing filters is very similar; we just update our bundle's `filters`
12 | # attribute using humanize's register.
13 | bundle.filters.update(humanize.register.filters)
14 |
--------------------------------------------------------------------------------
/djanjinja_test/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls.defaults import include, patterns
4 |
5 |
6 | urlpatterns = patterns('',
7 | (r'^simple/', include('djanjinja_test.simple.urls')),
8 | (r'^shortcuts/', include('djanjinja_test.shortcuts.urls')),
9 | (r'^generic/', include('djanjinja_test.generic.urls')),
10 | (r'^cache/', include('djanjinja_test.cache.urls')),
11 | )
12 |
13 |
14 | handler404 = 'djanjinja.handlers.page_not_found'
15 | handler500 = 'djanjinja.handlers.server_error'
--------------------------------------------------------------------------------
/djanjinja_test/generic/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf.urls.defaults import patterns, url
4 |
5 |
6 | urlpatterns = patterns('djanjinja.generic',
7 | url(r'^plain/$', 'direct_to_template', {'template': 'plain.txt'},
8 | name='generic-plain'),
9 | url(r'^context/$', 'direct_to_template',
10 | {'template': 'context.txt', 'extra_context': {'a': 1, 'b': 2}},
11 | name='generic-context'),
12 | url(r'^req_context/$', 'direct_to_template',
13 | {'template': 'req_context.txt'}, name='generic-req_context')
14 | )
--------------------------------------------------------------------------------
/djanjinja_test/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from django.core.management import execute_manager
4 |
5 | try:
6 | # pylint: disable-msg=W0403
7 | import settings # Assumed to be in the same directory.
8 | except ImportError:
9 | import sys
10 | sys.stderr.write(
11 | """Error: Can't find the file 'settings.py' in the directory containing %r. \
12 | It appears you've customized things.\nYou'll have to run django-admin.py, \
13 | passing it your settings module.\n(If the file settings.py does indeed exist, \
14 | it's causing an ImportError somehow.)\n""" % __file__)
15 | sys.exit(1)
16 |
17 | if __name__ == "__main__":
18 | execute_manager(settings)
19 |
--------------------------------------------------------------------------------
/djanjinja/bundles/site.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Contains definitions for working with your Django site itself."""
4 |
5 | from djanjinja.loader import Bundle
6 |
7 |
8 | bundle = Bundle()
9 |
10 |
11 | @bundle.function
12 | def url(name, *args, **kwargs):
13 | """A simple wrapper around ``django.core.urlresolvers.reverse``."""
14 |
15 | from django.core.urlresolvers import reverse
16 | return reverse(name, args=args, kwargs=kwargs)
17 |
18 |
19 | @bundle.function
20 | def setting(name, default=None):
21 | """Get the value of a particular setting, defaulting to ``None``."""
22 |
23 | from django.conf import settings
24 | return getattr(settings, name, default)
--------------------------------------------------------------------------------
/djanjinja/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """djanjinja - A reusable Django app to use Jinja2 templates from Django."""
4 |
5 | from djanjinja import environment
6 | from djanjinja.environment import is_safe
7 |
8 |
9 | __all__ = [
10 | 'bccache',
11 | 'bundles',
12 | 'environment',
13 | 'extensions',
14 | 'generic',
15 | 'handlers',
16 | 'loader',
17 | 'middleware',
18 | 'views',
19 | ]
20 |
21 | __version__ = '0.8'
22 |
23 |
24 | def get_environment():
25 | """Return the template environment, bootstrapping if necessary."""
26 |
27 | if not environment.TEMPLATE_ENVIRONMENT:
28 | environment.bootstrap()
29 | return environment.TEMPLATE_ENVIRONMENT
30 |
31 | # Shorthand alias for `get_environment()`.
32 | get_env = get_environment
33 |
34 |
35 | def get_template(template_name):
36 | """Return the specified template."""
37 |
38 | return get_env().get_template(template_name)
39 |
--------------------------------------------------------------------------------
/djanjinja_test/generic/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Tests for views which render templates using the generic views."""
4 |
5 | from django.test import TestCase
6 |
7 |
8 | PLAIN_RESPONSE = 'Hello, World!'
9 | CONTEXT_RESPONSE = 'a = 1; b = 2'
10 | REQ_CONTEXT_RESPONSE = 'user.is_anonymous() => True'
11 |
12 |
13 | class GenericTest(TestCase):
14 |
15 | def test_plain(self):
16 | response = self.client.get('/generic/plain/')
17 | self.assertEqual(response.status_code, 200)
18 | self.assertEqual(response.content, PLAIN_RESPONSE)
19 |
20 | def test_context(self):
21 | response = self.client.get('/generic/context/')
22 | self.assertEqual(response.status_code, 200)
23 | self.assertEqual(response.content, CONTEXT_RESPONSE)
24 |
25 | def test_req_context(self):
26 | response = self.client.get('/generic/req_context/')
27 | self.assertEqual(response.status_code, 200)
28 | self.assertEqual(response.content, REQ_CONTEXT_RESPONSE)
29 |
--------------------------------------------------------------------------------
/djanjinja_test/shortcuts/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Views which use DjanJinja shortcut functions to render templates."""
4 |
5 | from django.template import RequestContext
6 |
7 | from djanjinja.views import context_to_dict, render_to_response
8 |
9 |
10 | def plain(request):
11 | """Renders a template with no context directly to a response."""
12 |
13 | return render_to_response('plain.txt')
14 |
15 |
16 | def context(request):
17 | """Renders a template with a context directly to a response."""
18 |
19 | return render_to_response('context.txt', {'a': 1, 'b': 2})
20 |
21 |
22 | def req_context(request):
23 | """Renders a template with a ``RequestContext`` directly to a response."""
24 |
25 | return render_to_response('req_context.txt',
26 | context_to_dict(RequestContext(request)))
27 |
28 |
29 | def middleware(request):
30 | """Renders a template with ``request.Context`` using middleware."""
31 |
32 | return request.Context({'a': 1, 'b': 2}).render_response('middleware.txt')
--------------------------------------------------------------------------------
/djanjinja_test/simple/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.http import HttpResponse
4 | from django.template import RequestContext
5 |
6 | import djanjinja
7 | from djanjinja.views import context_to_dict
8 |
9 |
10 | def plain(request):
11 | """Renders a template with no context and returns it in a response."""
12 |
13 | template = djanjinja.get_template('plain.txt')
14 | content = template.render()
15 | return HttpResponse(content=content)
16 |
17 |
18 | def context(request):
19 | """Renders a template with a context and returns it in a response."""
20 |
21 | template = djanjinja.get_template('context.txt')
22 | content = template.render({'a': 1, 'b': 2})
23 | return HttpResponse(content=content)
24 |
25 |
26 | def req_context(request):
27 | """Renders a template with a ``RequestContext`` and returns a repsonse."""
28 |
29 | template = djanjinja.get_template('req_context.txt')
30 | content = template.render(context_to_dict(RequestContext(request)))
31 | return HttpResponse(content=content)
32 |
--------------------------------------------------------------------------------
/djanjinja/handlers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Replacement for the default Django 404/500 exception handlers."""
4 |
5 | from djanjinja.views import RequestContext, render_to_response
6 |
7 |
8 | def page_not_found(request, template_name='404.html'):
9 |
10 | """
11 | 404 (page not found) handler which uses Jinja2 to render the template.
12 |
13 | The default template is ``404.html``, and its context will contain
14 | ``request_path`` (the path of the requested URL) and any additional
15 | parameters provided by the registered context processors (this view uses
16 | ``RequestContext``).
17 | """
18 |
19 | context = RequestContext(request, {'request_path': request.path})
20 | response = context.render_response(template_name)
21 | response.status_code = 404
22 | return response
23 |
24 |
25 | def server_error(request, template_name='500.html'):
26 |
27 | """
28 | 500 (server error) handler which uses Jinja2 to render the template.
29 |
30 | The default template is ``500.html``, and it will be rendered with a
31 | completely empty context. This is to prevent further exceptions from being
32 | raised.
33 | """
34 |
35 | return render_to_response(template_name)
36 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/djanjinja_test/shortcuts/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Tests for views which render templates using DjanJinja shortcuts."""
4 |
5 | from django.test import TestCase
6 |
7 |
8 | PLAIN_RESPONSE = 'Hello, World!'
9 | CONTEXT_RESPONSE = 'a = 1; b = 2'
10 | REQ_CONTEXT_RESPONSE = 'user.is_anonymous() => True'
11 | MIDDLEWARE_RESPONSE = 'anonymous, a1, b2'
12 |
13 |
14 | class ShortcutsTest(TestCase):
15 |
16 | def test_plain(self):
17 | response = self.client.get('/shortcuts/plain/')
18 | self.assertEqual(response.status_code, 200)
19 | self.assertEqual(response.content, PLAIN_RESPONSE)
20 |
21 | def test_context(self):
22 | response = self.client.get('/shortcuts/context/')
23 | self.assertEqual(response.status_code, 200)
24 | self.assertEqual(response.content, CONTEXT_RESPONSE)
25 |
26 | def test_req_context(self):
27 | response = self.client.get('/shortcuts/req_context/')
28 | self.assertEqual(response.status_code, 200)
29 | self.assertEqual(response.content, REQ_CONTEXT_RESPONSE)
30 |
31 | def test_middleware(self):
32 | response = self.client.get('/shortcuts/middleware/')
33 | self.assertEqual(response.status_code, 200)
34 | self.assertEqual(response.content, MIDDLEWARE_RESPONSE)
--------------------------------------------------------------------------------
/djanjinja/middleware.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | djanjinja.middleware - Helpful middleware for using Jinja2 from Django.
5 |
6 | This module contains middleware which helps you use Jinja2 from within your
7 | views. At the moment it only contains ``RequestContextMiddleware``, but may
8 | expand in future.
9 | """
10 |
11 | from djanjinja.views import RequestContext
12 |
13 |
14 | class RequestContextMiddleware(object):
15 |
16 | """Attach a special ``RequestContext`` class to each request object."""
17 |
18 | @staticmethod
19 | def process_request(request):
20 |
21 | """
22 | Attach a special ``RequestContext`` subclass to each request object.
23 |
24 | This is the only method in the ``RequestContextMiddleware`` Django
25 | middleware class. It attaches a ``RequestContext`` subclass to each
26 | request as the ``Context`` attribute. This subclass has the request
27 | object pre-specified, so you only need to use ``request.Context()`` to
28 | make instances of ``django.template.RequestContext``.
29 |
30 | Consult the documentation for ``djanjinja.views.RequestContext`` for
31 | more information.
32 | """
33 |
34 | request.Context = RequestContext.with_request(request)
35 |
--------------------------------------------------------------------------------
/djanjinja_test/simple/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Tests for simple views which render templates."""
4 |
5 | from django.test import TestCase
6 |
7 |
8 | PLAIN_RESPONSE = 'Hello, World!'
9 | CONTEXT_RESPONSE = 'a = 1; b = 2'
10 | REQ_CONTEXT_RESPONSE = 'user.is_anonymous() => True'
11 | NOT_FOUND_RESPONSE = 'NOT FOUND: /this/does/not/exist/'
12 | SERVER_ERROR_RESPONSE = 'ERROR OCCURRED.'
13 |
14 |
15 | class SimpleTest(TestCase):
16 |
17 | def test_plain(self):
18 | response = self.client.get('/simple/plain/')
19 | self.assertEqual(response.status_code, 200)
20 | self.assertEqual(response.content, PLAIN_RESPONSE)
21 |
22 | def test_context(self):
23 | response = self.client.get('/simple/context/')
24 | self.assertEqual(response.status_code, 200)
25 | self.assertEqual(response.content, CONTEXT_RESPONSE)
26 |
27 | def test_req_context(self):
28 | response = self.client.get('/simple/req_context/')
29 | self.assertEqual(response.status_code, 200)
30 | self.assertEqual(response.content, REQ_CONTEXT_RESPONSE)
31 |
32 | def test_404(self):
33 | response = self.client.get('/this/does/not/exist/')
34 | self.assertEqual(response.status_code, 404)
35 | self.assertEqual(response.content, NOT_FOUND_RESPONSE)
36 |
--------------------------------------------------------------------------------
/pavement.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from paver.easy import *
4 |
5 |
6 | @task
7 | def lint(options):
8 | """Run PyLint on the ``djanjinja`` and ``djanjinja_test`` directories."""
9 |
10 | import os
11 |
12 | os.environ['DJANGO_SETTINGS_MODULE'] = 'djanjinja_test.settings'
13 | rcfile = path(__file__).abspath().dirname() / 'pylintrc'
14 |
15 | run_pylint('djanjinja', rcfile=rcfile)
16 | run_pylint('djanjinja_test', rcfile=rcfile, disable_msg='C0111')
17 |
18 |
19 | def run_pylint(directory, **options):
20 | """Run PyLint on a given directory, with some command-line options."""
21 |
22 | from pylint import lint
23 |
24 | rcfile = options.pop('rcfile', None)
25 | if not rcfile:
26 | if (path(__file__).abspath().dirname() / 'pylintrc').exists():
27 | rcfile = path(__file__).abspath().dirname() / 'pylintrc'
28 | if rcfile:
29 | options['rcfile'] = rcfile
30 |
31 | arguments = []
32 | for option, value in options.items():
33 | arguments.append('--%s=%s' % (option.replace('_', '-'), value))
34 | arguments.append(directory)
35 |
36 | message = 'pylint ' + ' '.join(
37 | argument.replace(' ', r'\ ') for argument in arguments)
38 |
39 | try:
40 | dry(message, lint.Run, arguments)
41 | except SystemExit, exc:
42 | if exc.args[0] != 0:
43 | raise BuildFailure('PyLint returned with a non-zero exit code.')
44 |
--------------------------------------------------------------------------------
/djanjinja/bundles/csrf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Add a helper to the context for rendering CSRF tokens."""
4 |
5 | import django
6 | from django.core.exceptions import ImproperlyConfigured
7 | from djanjinja.loader import Bundle
8 |
9 |
10 | bundle = Bundle()
11 |
12 | if django.VERSION >= (1, 2): # CSRF changed in Django v1.2+
13 | @bundle.ctxfunction
14 | def csrf_token(context):
15 | token = context.get('csrf_token', None)
16 | if token:
17 | if token == 'NOTPROVIDED':
18 | return Markup(u'')
19 | return Markup(
20 | u'
'
21 | u''
22 | u'
' % (token,))
23 |
24 | if 'django.core.context_processors.csrf' in settings.TEMPLATE_CONTEXT_PROCESSORS:
25 | raise ImproperlyConfigured(
26 | "csrf_token() was used in a template, but a CSRF token was not "
27 | "present in the context. This is usually caused by not using "
28 | "RequestContext.")
29 | else:
30 | raise ImproperlyConfigured(
31 | "csrf_token() was used in a template, but a CSRF token was not "
32 | "present in the context. You need to add "
33 | "'django.core.context_processors.csrf' to the "
34 | "TEMPLATE_CONTEXT_PROCESSORS setting, and use RequestContext.")
35 |
--------------------------------------------------------------------------------
/djanjinja_test/cache/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Tests for views which render templates which use the caching extras."""
4 |
5 | from django.test import TestCase
6 |
7 | import djanjinja
8 |
9 |
10 | CACHE_GLOBAL_RESPONSE = u'value'
11 |
12 |
13 | class CacheTest(TestCase):
14 |
15 | def test_global(self):
16 | response = self.client.get('/cache/global/')
17 | self.assertEqual(response.status_code, 200)
18 | self.assertEqual(response.content, CACHE_GLOBAL_RESPONSE)
19 |
20 | def test_fragment(self):
21 | """Tests the fragment caching extension."""
22 |
23 | # A singleton which stores the number of times its `call()` method has
24 | # been called.
25 | # pylint: disable-msg=C0103
26 | class call_state(object):
27 | called = 0
28 |
29 | @classmethod
30 | def call(cls):
31 | cls.called += 1
32 |
33 | template = djanjinja.get_template('cache_fragment.txt')
34 | # In the beginning, `called` will be 0.
35 | self.assertEqual(call_state.called, 0)
36 |
37 | # After rendering once, `called` will be 1.
38 | template.render({'call_state': call_state})
39 | self.assertEqual(call_state.called, 1)
40 |
41 | # If fragment caching is working correctly, the output of the previous
42 | # render should be stored and the `call()` method should not be called
43 | # again.
44 | template.render({'call_state': call_state})
45 | self.assertEqual(call_state.called, 1)
--------------------------------------------------------------------------------
/djanjinja/generic.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | Generic views for Django which use Jinja2 instead.
5 |
6 | At the moment this module only contains ``direct_to_template``. Eventually it
7 | may house some more useful generic views, which have been made to use Jinja2
8 | instead of Django's built-in template language.
9 | """
10 |
11 | import mimetypes
12 |
13 | from djanjinja.middleware import RequestContextMiddleware
14 | from djanjinja.views import DEFAULT_CONTENT_TYPE
15 |
16 |
17 | def direct_to_template(request, template=None, extra_context=None,
18 | mimetype=None, *args, **kwargs):
19 |
20 | """
21 | A generic view, similar to that of the same name provided by Django.
22 |
23 | Please consult the documentation for Django's
24 | ``django.views.generic.simple.direct_to_template`` generic view. This
25 | function exports an identical calling signature, only it uses the Jinja2
26 | templating system instead.
27 | """
28 |
29 | # Ensure the request has a `Context` attribute. This means the middleware
30 | # does not have to be installed.
31 | if not hasattr(request, 'Context'):
32 | RequestContextMiddleware.process_request(request)
33 |
34 | # Build the context, optionally accepting additional context values.
35 | context = request.Context(dict=(extra_context or {}))
36 |
37 | # Build the `params` variable from the parameters passed into the view
38 | # from the URLconf.
39 | params = kwargs.copy()
40 | for i, value in enumerate(args):
41 | params[i] = value
42 | context['params'] = params
43 |
44 | # Ensure the mimetype is sensible; if not provided, it will be inferred
45 | # from the name of the template. If that fails, fall back to the default.
46 | if not mimetype:
47 | mimetype = mimetypes.guess_type(template)[0] or DEFAULT_CONTENT_TYPE
48 |
49 | return context.render_response(template, mimetype=mimetype)
50 |
--------------------------------------------------------------------------------
/djanjinja/bccache.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """A Jinja2 bytecode cache which uses the Django caching framework."""
4 |
5 | import jinja2
6 |
7 |
8 | class B64CacheClient(object):
9 |
10 | """
11 | A wrapper for the Django cache client which Base64-encodes everything.
12 |
13 | This wrapper is needed to use the Django cache with Jinja2. Because Django
14 | tries to store/retrieve everything as Unicode, it makes it impossible to
15 | store binary data. Since Jinja2 uses marshal to store bytecode, we need to
16 | Base64-encode the binary data and then we can send that into and get that
17 | out of the Django cache.
18 | """
19 |
20 | def __init__(self, cache):
21 | self.cache = cache
22 |
23 | def get(self, key):
24 | """Fetch a key from the cache, base64-decoding the result."""
25 | data = self.cache.get(key)
26 | if data is not None:
27 | return data.decode('base64')
28 |
29 | def set(self, key, value, timeout=None):
30 | """Set a value in the cache, performing base64 encoding beforehand."""
31 | if timeout is not None:
32 | self.cache.set(key, value.encode('base64'), timeout)
33 | else:
34 | self.cache.set(key, value.encode('base64'))
35 |
36 |
37 | def get_cache():
38 | """Get a Jinja2 bytecode cache which uses the configured Django cache."""
39 |
40 | from django.conf import settings
41 | from django.core import cache
42 |
43 | cache_backend = cache.parse_backend_uri(settings.CACHE_BACKEND)[0]
44 | memcached_client = None
45 |
46 | if cache_backend == 'memcached':
47 | # We can get the actual memcached client object itself. This will
48 | # avoid the Django problem of storing binary data (as Django tries to
49 | # coerce everything to Unicode).
50 |
51 | # Here, we look for either `cache.cache._cache` or
52 | # `cache.cache._client`; I believe there is some discrepancy between
53 | # different versions of Django and where they put this.
54 | memcached_client = getattr(
55 | cache.cache, '_cache', getattr(cache.cache, '_client', None))
56 |
57 | memcached_client = memcached_client or B64CacheClient(cache.cache)
58 |
59 | return jinja2.MemcachedBytecodeCache(memcached_client)
60 |
--------------------------------------------------------------------------------
/djanjinja_test/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for djanjinja_test project.
2 |
3 | import os
4 |
5 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
6 |
7 | DEBUG = True
8 | TEMPLATE_DEBUG = DEBUG
9 |
10 | ADMINS = (
11 | ('Zachary Voase', 'zacharyvoase@me.com'),
12 | )
13 |
14 | MANAGERS = ADMINS
15 |
16 | DATABASE_ENGINE = 'sqlite3'
17 | DATABASE_NAME = 'dev.db'
18 | DATABASE_USER = ''
19 | DATABASE_PASSWORD = ''
20 | DATABASE_HOST = ''
21 | DATABASE_PORT = ''
22 |
23 | # Local time zone for this installation. Choices can be found here:
24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
25 | # although not all choices may be available on all operating systems.
26 | # If running in a Windows environment this must be set to the same as your
27 | # system time zone.
28 | TIME_ZONE = 'Europe/Madrid'
29 |
30 | # Language code for this installation. All choices can be found here:
31 | # http://www.i18nguy.com/unicode/language-identifiers.html
32 | LANGUAGE_CODE = 'en-gb'
33 |
34 | SITE_ID = 1
35 |
36 | # If you set this to False, Django will make some optimizations so as not
37 | # to load the internationalization machinery.
38 | USE_I18N = True
39 |
40 | # Absolute path to the directory that holds media.
41 | # Example: "/home/media/media.lawrence.com/"
42 | MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media')
43 |
44 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
45 | # trailing slash if there is a path component (optional in other cases).
46 | # Examples: "http://media.lawrence.com", "http://example.com/media/"
47 | MEDIA_URL = '/media/'
48 |
49 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
50 | # trailing slash.
51 | # Examples: "http://foo.com/media/", "/media/".
52 | ADMIN_MEDIA_PREFIX = '/media/'
53 |
54 | # Make this unique, and don't share it with anybody.
55 | SECRET_KEY = 'vbz@n)b!xw+@qb5dqiblc&m%7u7u%r$b+qv7emz6ck^945q2!0'
56 |
57 | # List of callables that know how to import templates from various sources.
58 | TEMPLATE_LOADERS = (
59 | 'django.template.loaders.filesystem.load_template_source',
60 | 'django.template.loaders.app_directories.load_template_source',
61 | )
62 |
63 | MIDDLEWARE_CLASSES = (
64 | 'django.middleware.common.CommonMiddleware',
65 | 'django.contrib.sessions.middleware.SessionMiddleware',
66 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
67 | 'djanjinja.middleware.RequestContextMiddleware',
68 | )
69 |
70 | ROOT_URLCONF = 'djanjinja_test.urls'
71 |
72 | TEMPLATE_DIRS = (
73 | os.path.join(PROJECT_ROOT, 'templates'),
74 | )
75 |
76 | # Third-party apps
77 | INSTALLED_APPS = (
78 | 'django.contrib.auth',
79 | 'django.contrib.contenttypes',
80 | 'django.contrib.sessions',
81 | 'django.contrib.sites',
82 | 'djanjinja',
83 | )
84 |
85 | # Local apps
86 | INSTALLED_APPS += (
87 | 'djanjinja_test.simple',
88 | 'djanjinja_test.shortcuts',
89 | 'djanjinja_test.generic',
90 | 'djanjinja_test.cache',
91 | )
92 |
93 |
94 | DJANJINJA_BUNDLES = (
95 | 'djanjinja.cache',
96 | 'djanjinja.humanize',
97 | 'djanjinja.site',
98 | )
--------------------------------------------------------------------------------
/djanjinja/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | djanjinja.views - Utilities to help write Django views which use Jinja2.
5 |
6 | This module contains several functions and classes to make it easier to write
7 | views which use Jinja2. It replaces a couple of the most common Django
8 | template-rendering shortcuts, and features an extended ``RequestContext``.
9 | """
10 |
11 | from functools import partial
12 |
13 | from django import template
14 | from django.conf import settings
15 | from django.http import HttpResponse
16 |
17 | from djanjinja import get_env
18 |
19 |
20 | DEFAULT_CONTENT_TYPE = getattr(settings, 'DEFAULT_CONTENT_TYPE', 'text/html')
21 |
22 |
23 | class RequestContext(template.RequestContext):
24 |
25 | """A ``RequestContext`` with a pre-specified request attribute."""
26 |
27 | request = None
28 |
29 | def __init__(self, *args, **kwargs):
30 | # If the class has a `request` attribute which is not `None`, use that
31 | # to initialize the `RequestContext`.
32 | if self.request is not None:
33 | super(RequestContext, self).__init__(
34 | self.request, *args, **kwargs)
35 | else:
36 | # Otherwise, just act as if the normal ``RequestContext``
37 | # constructor was called.
38 | super(RequestContext, self).__init__(*args, **kwargs)
39 |
40 | @classmethod
41 | def with_request(cls, request):
42 | """Return a `RequestContext` subclass for a specified request."""
43 |
44 | # Subclasses `RequestContext` with a value for `request`, so that it
45 | # does not need it as an explicit request argument for initialization.
46 | return type(
47 | cls.__name__, (cls,),
48 | {'request': request, '__module__': cls.__module__})
49 |
50 | def render_string(self, filename):
51 | """Render a given template name to a string, using this context."""
52 |
53 | return render_to_string(filename, context=self)
54 |
55 | def render_response(self, filename, mimetype=DEFAULT_CONTENT_TYPE):
56 | """Render a given template name to a response, using this context."""
57 |
58 | return render_to_response(filename, context=self, mimetype=mimetype)
59 |
60 |
61 | def context_to_dict(context):
62 | """Flattens a Django context into a single dictionary."""
63 |
64 | if not isinstance(context, template.Context):
65 | return context
66 |
67 | dict_out = {}
68 |
69 | # This helps us handle the order of dictionaries in the context. By
70 | # default, the most recent (and therefore most significant/important)
71 | # sub-dictionaries are at the front of the list. This means that variables
72 | # defined later on need to be processed last, hence the use of the
73 | # `reversed()` built-in.
74 | for sub_dict in reversed(context.dicts):
75 | dict_out.update(sub_dict)
76 | return dict_out
77 |
78 |
79 | def render_to_string(filename, context=None, environment=None):
80 | """Renders a given template name to a string."""
81 |
82 | if context is None:
83 | context = {}
84 |
85 | if environment is None:
86 | environment = get_env()
87 |
88 | return environment.get_template(filename).render(
89 | context_to_dict(context))
90 |
91 |
92 | def render_to_response(filename, context=None, mimetype=DEFAULT_CONTENT_TYPE,
93 | environment=None):
94 | """Renders a given template name to a ``django.http.HttpResponse``."""
95 |
96 | return HttpResponse(
97 | render_to_string(filename, context=context, environment=environment),
98 | mimetype=mimetype)
99 |
100 |
101 | def shortcuts_for_environment(environment):
102 | """Returns shortcuts pre-configured for a given environment."""
103 |
104 | return (
105 | partial(render_to_string, environment=environment),
106 | partial(render_to_response, environment=environment))
107 |
--------------------------------------------------------------------------------
/djanjinja/extensions/cache.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | A Jinja2 template tag for fragment caching.
5 |
6 | This extension, copied mainly from the Jinja2 documentation on extensions,
7 | adds a ``{% cache %}`` tag which permits fragment caching, much like that in
8 | the native Django templating language.
9 |
10 | Usage is as follows:
11 |
12 | {% cache "cache_key", 3600 %}
13 | ...
14 | {% endcache %}
15 |
16 | This will cache the fragment between ``cache`` and ``endcache``, using the
17 | cache key ``"cache_key"`` and with a timeout of 3600 seconds.
18 |
19 | More complex cache keys can be specified by passing in a sequence (such as a
20 | list or tuple) of items. The entire sequence must be 'dumpable' using the
21 | standard ``marshal`` module in Python. For example:
22 |
23 | {% cache ("article", article.id), 3600 %}
24 | ...
25 | {% endcache %}
26 |
27 | To generate the key, the tuple is marshalled, the SHA1 hash of the resulting
28 | string is taken and base64-encoded, with newlines and padding stripped, and
29 | this is appended to the string ``jinja_frag_``. For more information, consult
30 | the code (located in ``djanjinja/extensions/cache.py``).
31 | """
32 |
33 | import hashlib
34 | import marshal
35 |
36 | from jinja2 import nodes
37 | from jinja2.ext import Extension
38 |
39 |
40 | class CacheExtension(Extension):
41 |
42 | """Fragment caching using the Django cache system."""
43 |
44 | tags = set(['cache'])
45 | cache_key_format = 'jinja_frag_%(hash)s'
46 |
47 | def __init__(self, environment):
48 | super(CacheExtension, self).__init__(environment)
49 |
50 | # Extend the environment with the default cache key prefix.
51 | environment.extend(cache_key_format=self.cache_key_format)
52 |
53 | def parse(self, parser):
54 | """Parse a fragment cache block in a Jinja2 template."""
55 |
56 | # The first parsed token will be 'cache', so we ignore that but keep
57 | # the line number to give to nodes we create later.
58 | lineno = parser.stream.next().lineno
59 |
60 | # This should be the cache key.
61 | args = [parser.parse_expression()]
62 |
63 | # This will check to see if the user provided a timeout parameter
64 | # (which would be separated by a comma).
65 | if parser.stream.skip_if('comma'):
66 | args.append(parser.parse_expression())
67 | else:
68 | args.append(nodes.Const(None))
69 | # Here, we parse up to {% endcache %} and drop the needle, which will
70 | # be the `endcache` tag itself.
71 | body = parser.parse_statements(['name:endcache'], drop_needle=True)
72 |
73 | # Now return a `CallBlock` node which calls the `_cache` method on the
74 | # extension.
75 | return nodes.CallBlock(
76 | self.call_method('_cache', args), [], [], body
77 | ).set_lineno(lineno)
78 |
79 | def _cache(self, parameters, timeout, caller):
80 | """Helper method for fragment caching."""
81 |
82 | # This is lazily loaded so that it can be set up without Django. If
83 | # you try to use it without Django, it will just render the fragment
84 | # as usual.
85 | try:
86 | from django.core import cache
87 | except ImportError:
88 | # `caller()` will render whatever is between {% cache %} and
89 | # {% endcache %}.
90 | return caller()
91 |
92 | key = self._generate_key(parameters)
93 |
94 | # If the fragment is cached, return it. Otherwise, render it, set the
95 | # key in the cache, and return it.
96 | retrieved_value = cache.cache.get(key)
97 | if retrieved_value is not None:
98 | return retrieved_value
99 |
100 | value = caller()
101 | cache.cache.set(key, value, timeout)
102 | return value
103 |
104 | def _generate_key(self, parameters):
105 | """Generate a cache key from some parameters (maybe a sequence)."""
106 |
107 | # Marshal => Hash => Prefix should generate a unique key for each
108 | # set of parameters which is the same for equal parameters.
109 | # Essentially, this is a 1:1 mapping.
110 | serialized = marshal.dumps(parameters)
111 | digest = (hashlib.sha1(serialized)
112 | .digest()
113 | .encode('base64')
114 | .rstrip('\r\n='))
115 |
116 | return self.environment.cache_key_format % {'hash': digest}
--------------------------------------------------------------------------------
/djanjinja/loader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Utilities for loading definitions from reusable Django apps."""
4 |
5 | import copy
6 |
7 | from django.core.exceptions import ImproperlyConfigured
8 | from django.utils.importlib import import_module
9 |
10 | from djanjinja import get_env
11 | from djanjinja.environment import Environment
12 |
13 |
14 | class Bundle(object):
15 |
16 | """
17 | Store a bunch of tests, filters and functions from a single app.
18 |
19 | Instances of the ``Bundle`` class store tests, filters and functions in an
20 | internal register that can then be lazily merged with the Jinja2
21 | environment later on. It is used when apps want to define a number of
22 | utilities for lazy loading from template code, typically using the
23 | ``{% load %}`` template tag defined in djanjinja.extensions.bundles.
24 |
25 | Essentially, a bundle is just an environment without the templating and
26 | loading parts. They can be pushed onto another environment, which will
27 | return a new environment with a new set of filters, globals and tests but
28 | all the other attributes from the original environment.
29 | """
30 |
31 | TYPES = (
32 | 'test',
33 | 'filter', 'ctxfilter', 'envfilter',
34 | 'function', 'ctxfunction', 'envfunction'
35 | )
36 |
37 | # Set these attributes now, to prevent pylint from flagging errors later.
38 | test = None
39 | filter = None
40 | ctxfilter = None
41 | envfilter = None
42 | function = None
43 | ctxfunction = None
44 | envfunction = None
45 |
46 | def __init__(self):
47 | self.filters = {}
48 | self.globals = {}
49 | self.tests = {}
50 |
51 | def merge_into(self, environment=None):
52 | """Push this bundle onto the environment, returning a new env."""
53 |
54 | if environment is None:
55 | environment = get_env()
56 |
57 | for attr in ['globals', 'filters', 'tests']:
58 | getattr(environment, attr).update(getattr(self, attr))
59 |
60 | return environment
61 |
62 |
63 | # Dynamically create decorators for each type.
64 | for type in TYPES:
65 | # We get the attribute from the `Environment` class itself because it
66 | # will be an unbound method at this point.
67 | # `im_func` is the underlying function definition. Within this
68 | # definition, `self` has no special meaning, so by copying it here
69 | # we can essentially repurpose the same method for this class. It's
70 | # actually similar to a mixin.
71 | vars()[type] = copy.copy(getattr(Environment, type).im_func)
72 |
73 | # Clean up ``type`` from the namespace.
74 | del type
75 |
76 |
77 | def get_bundle(app_label, bundle_name):
78 |
79 | """
80 | Loads the bundle with a given name for a specific app.
81 |
82 | First, we import the app. Then, we look in the ``bundles`` sub-module for
83 | the specified bundle name. If the given name is a top-level attribute of
84 | ``bundles``, we use that. Otherwise, we try to import a submodule of
85 | ``bundles`` with that name and look for a ``bundle`` attribute in that
86 | submodule.
87 |
88 | Note that this function only retrieves the bundle; it does not insert it
89 | into the environment. To do this in one step, use the ``load()`` function
90 | in this module.
91 | """
92 |
93 | from django.conf import settings
94 |
95 | # Load the app (this is a plain Python module).
96 | app, app_name = None, ''
97 | for full_app_name in settings.INSTALLED_APPS:
98 | if app_label in (full_app_name, full_app_name.split('.')[-1]):
99 | app = import_module(full_app_name)
100 | app_name = full_app_name
101 | break
102 | if not (app and app_name):
103 | raise ImproperlyConfigured(
104 | 'App with label %r not found' % (app_label,))
105 |
106 | # Try to find the bundles sub-module. Having this separate allows us to
107 | # provide a more detailed exception message.
108 | try:
109 | bundles = import_module('.bundles', package=app_name)
110 | except ImportError:
111 | raise ImproperlyConfigured(
112 | 'App %r has no `bundles` module' % (app_name,))
113 |
114 | # Now load the specified bundle name. First we look to see if it is a top-
115 | # level attribute of the bundles module:
116 | if hasattr(bundles, bundle_name):
117 | bundle = getattr(bundles, bundle_name)
118 | if isinstance(bundle, Bundle):
119 | return bundle
120 |
121 | try:
122 | bundle_mod = import_module(
123 | '.' + bundle_name, package=(app_name + '.bundles'))
124 | except ImportError:
125 | raise ImproperlyConfigured(
126 | 'Could not find bundle %r in app %r' % (bundle_name, app_name))
127 |
128 | if hasattr(bundle_mod, 'bundle'):
129 | return getattr(bundle_mod, 'bundle')
130 | raise ImproperlyConfigured(
131 | "Module '%s.bundles.%s' has no `bundle` attribute" % (
132 | app_name, bundle_name))
133 |
134 |
135 | def load(app_label, bundle_name, environment=None, reload=False):
136 | """Load a specified bundle into an/the environment."""
137 |
138 | if environment is None:
139 | environment = get_env()
140 |
141 | bundle = get_bundle(app_label, bundle_name)
142 | if (bundle not in environment.loaded_bundles) or reload:
143 | bundle.merge_into(environment)
144 | environment.loaded_bundles.add(bundle)
145 | return bundle
--------------------------------------------------------------------------------
/djanjinja/environment.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | Django-compatible Jinja2 Environment and helpers.
5 |
6 | This module contains a subclass of ``jinja2.Environment`` and several other
7 | functions which help use Jinja2 from within your Django projects.
8 | """
9 |
10 | from functools import wraps
11 | try:
12 | set
13 | except NameError:
14 | from sets import Set as set
15 |
16 | import jinja2
17 |
18 |
19 | TEMPLATE_ENVIRONMENT = None
20 |
21 |
22 | class Environment(jinja2.Environment):
23 |
24 | """An environment with decorators for filters, functions and tests."""
25 |
26 | def __init__(self, *args, **kwargs):
27 | super(Environment, self).__init__(*args, **kwargs)
28 | # Add a `set()` attribute which stores the loaded bundles.
29 | self.loaded_bundles = set()
30 |
31 | def load(self, app_label, bundle_name, reload=False):
32 | """Load the specified bundle into this environment."""
33 |
34 | from djanjinja import loader
35 |
36 | # Returns the loaded bundle.
37 | return loader.load(
38 | app_label, bundle_name, environment=self, reload=reload)
39 |
40 | def copy(self):
41 | """Create a copy of the environment."""
42 |
43 | copy = self.overlay()
44 | for attr in ['loaded_bundles', 'globals', 'filters', 'tests']:
45 | setattr(copy, attr, getattr(self, attr).copy())
46 |
47 | return copy
48 |
49 | # pylint: disable-msg=C0111
50 | def adder(attribute, wrapper, name, docstring):
51 |
52 | """
53 | Generate decorator methods for adding filters, functions and tests.
54 |
55 | Note that this function is not a method; it is deleted before the end
56 | of the class definition and is only used to generate the decorator
57 | methods. It helps to remove a lot of boilerplate.
58 | """
59 |
60 | def adder_(self, function, name=None):
61 | """Add the function to the environment with wrappers, etc."""
62 |
63 | key = name or function.__name__
64 | value = wrapper and wrapper(function) or function
65 | getattr(self, attribute)[key] = value
66 | return function
67 |
68 | def decorator(self, *args, **kwargs):
69 | """Boilerplate which allows both normal calling and decoration."""
70 |
71 | def wrapper(*args):
72 | return adder_(self, *args, **kwargs)
73 | if args:
74 | return wrapper(*args)
75 | return wrapper
76 |
77 | decorator.__name__ = name
78 | decorator.__doc__ = docstring
79 | return decorator
80 |
81 | ## Simple
82 |
83 | filter = adder('filters', None, 'filter',
84 | 'Decorate a function as a simple template filter.')
85 |
86 | test = adder('tests', None, 'test',
87 | 'Decorate a function as a simple template test.')
88 |
89 | function = adder('globals', None, 'function',
90 | 'Decorate a function as a simple global template function.')
91 |
92 | ## Environment
93 | # Note that environment- and context-tests are not supported by Jinja2.
94 |
95 | envfilter = adder('filters', jinja2.environmentfilter, 'envfilter',
96 | 'Decorate a function as an environment filter.')
97 |
98 | envfunction = adder('globals', jinja2.environmentfunction, 'envfunction',
99 | 'Decorate a function as a global environment function.')
100 |
101 | ## Context
102 |
103 | ctxfilter = adder('filters', jinja2.contextfilter, 'ctxfilter',
104 | 'Decorate a function as a context filter.')
105 |
106 | ctxfunction = adder('globals', jinja2.contextfunction, 'ctxfunction',
107 | 'Decorate a function as a global context function.')
108 |
109 | # Clean up the namespace. Also, without this, `type` will try to convert
110 | # `adder()` into a method. Which it most certainly is not.
111 | del adder
112 |
113 |
114 | def get_template_source(name):
115 |
116 | """
117 | Interface with Django to load the source for a given template name.
118 |
119 | This function is a simple wrapper around
120 | ``django.template.loader.find_template_source()`` to support the behaviour
121 | expected by the ``jinja2.FunctionLoader`` loader class. It requires Django
122 | to be configured (i.e. the settings need to be loaded).
123 | """
124 |
125 | from django.template import loader
126 | # `loader.find_template_source()` returns a 2-tuple of the source and a
127 | # `LoaderOrigin` object.
128 | source = loader.find_template_source(name)[0]
129 |
130 | # `jinja2.FunctionLoader` expects a triple of the source of the template,
131 | # the name used to load it, and a 0-ary callable which will return whether
132 | # or not the template needs to be reloaded. The callable will only ever be
133 | # called if auto-reload is on. In this case, we'll just assume that the
134 | # template does need to be reloaded.
135 | return (source, name, lambda: False)
136 |
137 |
138 | def bootstrap():
139 | """Load the TEMPLATE_ENVIRONMENT global variable."""
140 |
141 | from django.conf import settings
142 | if not settings.configured:
143 | # At least this will make it work, even if it's using the defaults.
144 | settings.configure()
145 |
146 | from djanjinja import bccache
147 | from djanjinja.extensions.cache import CacheExtension
148 |
149 | # Get the bytecode cache object.
150 | bytecode_cache = bccache.get_cache()
151 |
152 | default_extensions = set([
153 | 'jinja2.ext.do', 'jinja2.ext.loopcontrols', CacheExtension])
154 | if getattr(settings, 'USE_I18N', False):
155 | default_extensions.add('jinja2.ext.i18n')
156 | autoescape = getattr(settings, 'JINJA_AUTOESCAPE', False)
157 | if autoescape:
158 | default_extensions.add('jinja2.ext.autoescape')
159 |
160 | extensions = list(set(getattr(settings, 'JINJA_EXTENSIONS', [])).union(default_extensions))
161 |
162 | # Set up global `TEMPLATE_ENVIRONMENT` variable.
163 | global TEMPLATE_ENVIRONMENT
164 |
165 | TEMPLATE_ENVIRONMENT = Environment(
166 | loader=jinja2.FunctionLoader(get_template_source),
167 | auto_reload=getattr(settings, 'DEBUG', True), autoescape=autoescape,
168 | bytecode_cache=bytecode_cache, extensions=extensions)
169 |
170 | if getattr(settings, 'USE_I18N', False):
171 | # The `django.utils.translation` module behaves like a singleton of
172 | # `gettext.GNUTranslations`, since it exports all the necessary
173 | # methods.
174 | from django.utils import translation
175 | # pylint: disable-msg=E1101
176 | TEMPLATE_ENVIRONMENT.install_gettext_translations(translation)
177 |
178 | bundles = getattr(settings, 'DJANJINJA_BUNDLES', [])
179 | for bundle_specifier in bundles:
180 | app_label, bundle_name = bundle_specifier.rsplit('.', 1)
181 | TEMPLATE_ENVIRONMENT.load(app_label, bundle_name)
182 |
183 |
184 | def is_safe(function):
185 | """Decorator which declares that a function returns safe markup."""
186 |
187 | @wraps(function)
188 | def safe_wrapper(*args, **kwargs):
189 | """Wraps the output of the function as safe markup."""
190 |
191 | # All this wrapper does is to wrap the output of the function with
192 | # `jinja2.Markup`, which declares to the template that the string is
193 | # safe.
194 | result = function(*args, **kwargs)
195 | if not isinstance(result, jinja2.Markup):
196 | return jinja2.Markup(result)
197 | return result
198 |
199 | return safe_wrapper
200 |
--------------------------------------------------------------------------------
/pylintrc:
--------------------------------------------------------------------------------
1 | # lint Python modules using external checkers.
2 | #
3 | # This is the main checker controlling the other ones and the reports
4 | # generation. It is itself both a raw checker and an astng checker in order
5 | # to:
6 | # * handle message activation / deactivation at the module level
7 | # * handle some basic but necessary stats'data (number of classes, methods...)
8 | #
9 | [MASTER]
10 |
11 | # Specify a configuration file.
12 | #rcfile=
13 |
14 | # Python code to execute, usually for sys.path manipulation such as
15 | # pygtk.require().
16 | #init-hook=
17 |
18 | # Profiled execution.
19 | profile=no
20 |
21 | # Add to the black list. It should be a base name, not a
22 | # path. You may set this option multiple times.
23 | ignore=CVS
24 |
25 | # Pickle collected data for later comparisons.
26 | persistent=yes
27 |
28 | # Set the cache size for astng objects.
29 | cache-size=500
30 |
31 | # List of plugins (as comma separated values of python modules names) to load,
32 | # usually to register additional checkers.
33 | load-plugins=
34 |
35 |
36 | [MESSAGES CONTROL]
37 |
38 | # Enable only checker(s) with the given id(s). This option conflicts with the
39 | # disable-checker option
40 | #enable-checker=
41 |
42 | # Enable all checker(s) except those with the given id(s). This option
43 | # conflicts with the enable-checker option
44 | #disable-checker=
45 |
46 | # Enable all messages in the listed categories (IRCWEF).
47 | #enable-msg-cat=
48 |
49 | # Disable all messages in the listed categories (IRCWEF).
50 | disable-msg-cat=I
51 |
52 | # Enable the message(s) with the given id(s).
53 | #enable-msg=
54 |
55 | # Disable the message(s) with the given id(s).
56 | disable-msg=E0213,R0401,R0903,R0904,W0142,W0603,W0613,W0622
57 |
58 |
59 | [REPORTS]
60 |
61 | # Set the output format. Available formats are text, parseable, colorized, msvs
62 | # (visual studio) and html
63 | output-format=text
64 |
65 | # Include message's id in output
66 | include-ids=yes
67 |
68 | # Put messages in a separate file for each module / package specified on the
69 | # command line instead of printing them on stdout. Reports (if any) will be
70 | # written in a file name "pylint_global.[txt|html]".
71 | files-output=no
72 |
73 | # Tells wether to display a full report or only the messages
74 | reports=yes
75 |
76 | # Python expression which should return a note less than 10 (10 is the highest
77 | # note). You have access to the variables errors warning, statement which
78 | # respectivly contain the number of errors / warnings messages and the total
79 | # number of statements analyzed. This is used by the global evaluation report
80 | # (R0004).
81 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
82 |
83 | # Add a comment according to your evaluation note. This is used by the global
84 | # evaluation report (R0004).
85 | comment=no
86 |
87 | # Enable the report(s) with the given id(s).
88 | #enable-report=
89 |
90 | # Disable the report(s) with the given id(s).
91 | #disable-report=
92 |
93 |
94 | # checks for :
95 | # * doc strings
96 | # * modules / classes / functions / methods / arguments / variables name
97 | # * number of arguments, local variables, branchs, returns and statements in
98 | # functions, methods
99 | # * required module attributes
100 | # * dangerous default values as arguments
101 | # * redefinition of function / method / class
102 | # * uses of the global statement
103 | #
104 | [BASIC]
105 |
106 | # Required attributes for module, separated by a comma
107 | required-attributes=
108 |
109 | # Regular expression which should only match functions or classes name which do
110 | # not require a docstring
111 | no-docstring-rgx=__.*__
112 |
113 | # Regular expression which should only match correct module names
114 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
115 |
116 | # Regular expression which should only match correct module level names
117 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|([a-z_][a-z0-9_]{2,30}))$
118 |
119 | # Regular expression which should only match correct class names
120 | class-rgx=[A-Z_][a-zA-Z0-9]+$
121 |
122 | # Regular expression which should only match correct function names
123 | function-rgx=[a-z_][a-z0-9_]{2,30}$
124 |
125 | # Regular expression which should only match correct method names
126 | method-rgx=[a-z_][a-z0-9_]{2,30}$
127 |
128 | # Regular expression which should only match correct instance attribute names
129 | attr-rgx=[a-z_][a-z0-9_]{2,30}$
130 |
131 | # Regular expression which should only match correct argument names
132 | argument-rgx=[a-z_][a-z0-9_]{2,30}$
133 |
134 | # Regular expression which should only match correct variable names
135 | variable-rgx=[a-z_][a-z0-9_]{2,30}$
136 |
137 | # Regular expression which should only match correct list comprehension /
138 | # generator expression variable names
139 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
140 |
141 | # Good variable names which should always be accepted, separated by a comma
142 | good-names=i,j,k,ex,Run,_
143 |
144 | # Bad variable names which should always be refused, separated by a comma
145 | bad-names=foo,bar,baz,toto,tutu,tata
146 |
147 | # List of builtins function names that should not be used, separated by a comma
148 | bad-functions=apply,input
149 |
150 |
151 | # try to find bugs in the code using type inference
152 | #
153 | [TYPECHECK]
154 |
155 | # Tells wether missing members accessed in mixin class should be ignored. A
156 | # mixin class is detected if its name ends with "mixin" (case insensitive).
157 | ignore-mixin-members=yes
158 |
159 | # List of classes names for which member attributes should not be checked
160 | # (useful for classes with attributes dynamically set).
161 | ignored-classes=SQLObject
162 |
163 | # When zope mode is activated, add a predefined set of Zope acquired attributes
164 | # to generated-members.
165 | zope=no
166 |
167 | # List of members which are set dynamically and missed by pylint inference
168 | # system, and so shouldn't trigger E0201 when accessed.
169 | generated-members=REQUEST,acl_users,aq_parent
170 |
171 |
172 | # checks for
173 | # * unused variables / imports
174 | # * undefined variables
175 | # * redefinition of variable from builtins or from an outer scope
176 | # * use of variable before assigment
177 | #
178 | [VARIABLES]
179 |
180 | # Tells wether we should check for unused import in __init__ files.
181 | init-import=no
182 |
183 | # A regular expression matching names used for dummy variables (i.e. not used).
184 | dummy-variables-rgx=_|dummy
185 |
186 | # List of additional names supposed to be defined in builtins. Remember that
187 | # you should avoid to define new builtins when possible.
188 | additional-builtins=
189 |
190 |
191 | # checks for :
192 | # * methods without self as first argument
193 | # * overridden methods signature
194 | # * access only to existant members via self
195 | # * attributes not defined in the __init__ method
196 | # * supported interfaces implementation
197 | # * unreachable code
198 | #
199 | [CLASSES]
200 |
201 | # List of interface methods to ignore, separated by a comma. This is used for
202 | # instance to not check methods defines in Zope's Interface base class.
203 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
204 |
205 | # List of method names used to declare (i.e. assign) instance attributes.
206 | defining-attr-methods=__init__,__new__,setUp
207 |
208 |
209 | # checks for sign of poor/misdesign:
210 | # * number of methods, attributes, local variables...
211 | # * size, complexity of functions, methods
212 | #
213 | [DESIGN]
214 |
215 | # Maximum number of arguments for function / method
216 | max-args=5
217 |
218 | # Maximum number of locals for function / method body
219 | max-locals=15
220 |
221 | # Maximum number of return / yield for function / method body
222 | max-returns=6
223 |
224 | # Maximum number of branch for function / method body
225 | max-branchs=12
226 |
227 | # Maximum number of statements in function / method body
228 | max-statements=50
229 |
230 | # Maximum number of parents for a class (see R0901).
231 | max-parents=7
232 |
233 | # Maximum number of attributes for a class (see R0902).
234 | max-attributes=7
235 |
236 | # Minimum number of public methods for a class (see R0903).
237 | min-public-methods=2
238 |
239 | # Maximum number of public methods for a class (see R0904).
240 | max-public-methods=20
241 |
242 |
243 | # checks for
244 | # * external modules dependencies
245 | # * relative / wildcard imports
246 | # * cyclic imports
247 | # * uses of deprecated modules
248 | #
249 | [IMPORTS]
250 |
251 | # Deprecated modules which should not be used, separated by a comma
252 | deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
253 |
254 | # Create a graph of every (i.e. internal and external) dependencies in the
255 | # given file (report R0402 must not be disabled)
256 | import-graph=
257 |
258 | # Create a graph of external dependencies in the given file (report R0402 must
259 | # not be disabled)
260 | ext-import-graph=
261 |
262 | # Create a graph of internal dependencies in the given file (report R0402 must
263 | # not be disabled)
264 | int-import-graph=
265 |
266 |
267 | # checks for :
268 | # * unauthorized constructions
269 | # * strict indentation
270 | # * line length
271 | # * use of <> instead of !=
272 | #
273 | [FORMAT]
274 |
275 | # Maximum number of characters on a single line.
276 | max-line-length=80
277 |
278 | # Maximum number of lines in a module
279 | max-module-lines=1000
280 |
281 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
282 | # tab).
283 | indent-string=' '
284 |
285 |
286 | # checks for:
287 | # * warning notes in the code like FIXME, XXX
288 | # * PEP 263: source code with non ascii character but no encoding declaration
289 | #
290 | [MISCELLANEOUS]
291 |
292 | # List of note tags to take in consideration, separated by a comma.
293 | notes=FIXME,XXX,TODO
294 |
295 |
296 | # checks for similarities and duplicated code. This computation may be
297 | # memory / CPU intensive, so you should disable it if you experiments some
298 | # problems.
299 | #
300 | [SIMILARITIES]
301 |
302 | # Minimum lines number of a similarity.
303 | min-similarity-lines=4
304 |
305 | # Ignore comments when computing similarities.
306 | ignore-comments=yes
307 |
308 | # Ignore docstrings when computing similarities.
309 | ignore-docstrings=yes
310 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DjanJinja
2 |
3 | DjanJinja: the sound you make when you've got peanut butter stuck to the roof of
4 | your mouth. Incidentally, it also happens to be the name of a new re-usable
5 | Django app. This one, in fact.
6 |
7 | DjanJinja exists to help you leverage the power of
8 | [Jinja2](http://jinja.pocoo.org/2/) templates in your Django projects. It's
9 | simple to get started.
10 |
11 | ## Installing and Using DjanJinja
12 |
13 | ### Installing DjanJinja
14 |
15 | 1. Install DjanJinja using `easy_install djanjinja`, `pip install djanjinja`, or
16 | by grabbing a copy of the Mercurial repo and running `python setup.py
17 | install`.
18 | 2. Add `'djanjinja'` to your `INSTALLED_APPS` list.
19 | 3. (Optionally) add `'djanjinja.middleware.RequestContextMiddleware'` to your
20 | `MIDDLEWARE_CLASSES` list.
21 |
22 | It’s assumed that you have fairly recent versions of Jinja2 and Django
23 | installed. The author recommends the *most recent* stable versions of each,
24 | which should be installable via PyPI (i.e. a simple `easy_install` or `pip
25 | install`). A lot of people have their own ways of installing things, so the
26 | author hasn’t put any explicit requirements in the `setup.py` file. DjanJinja
27 | just expects to find `jinja2` and `django` on the import path.
28 |
29 | ### Using DjanJinja
30 |
31 | * Instead of using `django.shortcuts.render_to_response`, use one of the
32 | Jinja2-based functions provided.
33 | * Instead of using the Django loaders to load templates, get them from the
34 | Jinja2 environment created for you by DjanJinja.
35 | * Instead of using Django’s provided generic views, use those contained within
36 | `djanjinja.generic` (at the moment the only one is `direct_to_template()`).
37 |
38 | ## Shortcut Functions
39 |
40 | DjanJinja provides you with two shortcut functions for rendering templates,
41 | `render_to_response` and `render_to_string`. These are very similar to those
42 | provided by Django in the `django.shortcuts` module, except they use Jinja2
43 | instead of the Django templating system. To use them from your views, just do
44 | `from djanjinja.views import render_to_response, render_to_string` at the top of
45 | your views module.
46 |
47 | ## Bundles
48 |
49 | A Jinja2 environment can contain additional filters, tests and global variables
50 | which will be available in all templates rendered through that environment.
51 | Since individual Django apps will have their own set of things to add to the
52 | environment, DjanJinja adds the concept of ‘bundles’; small objects containing
53 | some global variables, filters and tests. Each app may define any number of
54 | these bundles which can then be loaded as required.
55 |
56 | ### Defining Bundles
57 |
58 | It’s relatively easy to define a bundle; an example is shown below:
59 |
60 | from djanjinja.loader import Bundle
61 |
62 | foo = Bundle()
63 | foo.globals['myvar'] = 12345
64 |
65 | @foo.envfilter
66 | def myenvfilter(environment, value):
67 | pass # do something here...
68 |
69 | @foo.ctxfunction
70 | def mycontextfunction(context, value):
71 | pass # do something here...
72 |
73 | Here we define a bundle called `foo`, with a global variable of `myvar`
74 | containing the value `12345`, an environment filter and a context function (for
75 | more information on each of these please consult the Jinja2 documentation). The
76 | `Bundle` class also supplies these handy decorators (the full list can be found
77 | as `djanjinja.loader.Bundle.TYPES`) to define various components.
78 |
79 | DjanJinja expects to find bundles in a `bundles` submodule of your Django app.
80 | You can lay things out in one of two ways:
81 |
82 | * Add a file called `bundles.py` to your app, and within this define multiple
83 | `Bundle` instances.
84 | * Add a package called `bundles` to your app (i.e. a `bundles` directory
85 | containing an empty file called `__init__.py`), and within this define
86 | submodules for each of your bundles. Each submodule should have a top-level
87 | `bundle` variable which is an instance of the `Bundle` class.
88 |
89 | You can actually mix and match these; you could add some bundle instances to the
90 | `bundles/__init__.py` file with different names, in addition to having the
91 | submodules. These are loaded lazily, so DjanJinja sees no real difference. It
92 | doesn’t scour the `bundles` module for definitions, it just loads what you ask
93 | it to.
94 |
95 | ### Addressing Bundles
96 |
97 | In order to use the functions, filters and tests defined in a bundle, you first
98 | have to load it into the environment. Bundles are specified in two parts: the
99 | ‘app label’ and the ‘bundle name’. The app label is simply the name of the app
100 | which contains it. For example, it may be `django.contrib.auth`, or simply
101 | `auth`, since you may just give the last part of the full name and DjanJinja
102 | will figure it out from looking at the `INSTALLED_APPS` setting.
103 |
104 | If a bundle is defined within a `bundles.py` or a `bundles/__init__.py` file,
105 | then the bundle name will be the name in the module with which it was defined.
106 | For example:
107 |
108 | # in the file `myapp/bundles.py`
109 | foo = Bundle()
110 | foo.globals['myvar'] = 12345
111 |
112 | In this case, the app label will be `myapp`, and the bundle name will be `foo`.
113 | If the bundles are defined in submodules, then the bundle name will be the name
114 | of the submodule.
115 |
116 | ### Loading Bundles
117 |
118 | In order to load any bundles into the global Jinja2 environment, you need to specify a
119 | `DJANJINJA_BUNDLES` setting in your `settings.py` file. This is a list or tuple
120 | of bundle specifiers in an `'app_label.bundle_name'` format. For example:
121 |
122 | DJANJINJA_BUNDLES = (
123 | 'djanjinja.cache',
124 | 'djanjinja.humanize',
125 | 'djanjinja.site',
126 | )
127 |
128 | You can also add bundles to the environment programmatically. This is useful
129 | when:
130 |
131 | * Your app needs to do some initial setup before a bundle is loaded.
132 | * Your app relies on a particular bundle being present in the global environment
133 | anyway, and you don’t want the user to have to add the bundle to
134 | `DJANJINJA_BUNDLES` manually.
135 | * Your app needs to load bundles dynamically.
136 | * Your app wants to use a bundle locally, not globally.
137 |
138 | You can load a bundle into an environment like this:
139 |
140 | import djanjinja
141 | env = djanjinja.get_env()
142 | env.load('app_label', 'bundle_name', reload=False)
143 |
144 | This will load the bundle into the environment, passing through if it’s already
145 | loaded. If you specify `reload=True`, you can make it reload a bundle even if
146 | it’s been loaded.
147 |
148 | You should put this code somewhere where it will get executed when you want it
149 | to. If you want it to be executed immediately, as Django starts up, put it in
150 | `myapp/__init__.py`.
151 |
152 | You can also use a bundle within only one app, by using a local environment
153 | copied from the global environment. Here’s how you might do this:
154 |
155 | import djanjinja
156 | global_env = djanjinja.get_env()
157 | local_env = global_env.copy()
158 | local_env.load('app_label', 'bundle_name')
159 |
160 | You'd then use that local environment later on in your code. For example, the
161 | above code might be in `myapp/__init__.py`; so your views might look like this:
162 |
163 | from django.http import HttpResponse
164 | from myapp import local_env
165 |
166 | def view(request):
167 | template = local_env.get_template('template_name.html')
168 | data = template.render({'key1': 'value1', 'key2': 'value2'})
169 | return HttpResponse(content=data)
170 |
171 | Of course, you’re probably going to want to use the usual shortcuts (i.e.
172 | `render_to_response()` and `render_to_string()`). You can build these easily
173 | using `djanjinja.views.shortcuts_for_environment()`:
174 |
175 | from djanjinja.views import shortcuts_for_environment
176 |
177 | render_to_response, render_to_string = shortcuts_for_environment(local_env)
178 |
179 | Do this at the top of your `views.py` file, and then you can use the generated
180 | functions throughout all of your views.
181 |
182 | ### Caveats and Limitations
183 |
184 | Jinja2 does not yet support scoped filters and tests; as a result of this, the
185 | contents of bundles specified in `DJANJINJA_BUNDLES` will be loaded into the global environment. It is important
186 | to make sure that definitions in your bundle do not override those in another
187 | bundle. This is especially important with threaded web applications, as multiple
188 | bundles overriding one another could cause unpredictable behavior in the
189 | templates.
190 |
191 | ### Included Bundles
192 |
193 | DjanJinja provides three bundles already which either replace Django
194 | counterparts or add some useful functionality to your Jinja2 templates:
195 |
196 | * `djanjinja.cache`: Loading this bundle will add a global `cache` object to the
197 | environment; this is the Django cache, and allows you to carry out caching
198 | operations from within your templates (such as `cache.get(key)`, et cetera).
199 |
200 | * `djanjinja.humanize`: This will add all of the filters contained within the
201 | `django.contrib.humanize` app; consult the official Django docs for more
202 | information on the filters provided.
203 |
204 | * `djanjinja.site`: This will add two functions to the global environment:
205 | `url`, and `setting`. The former acts like Django’s template tag, by reversing
206 | URLconf names and views into URLs, but because Jinja2 supports a richer
207 | syntax, it can be used via `{{ url(name, *args, **kwargs) }}` instead.
208 | `setting` attempts to resolve a setting name into a value, returning an
209 | optional default instead (i.e. `setting('MEDIA_URL', '/media')`).
210 |
211 | ## Extensions
212 |
213 | Jinja2 supports the concept of *environment extensions*; these are non-trivial
214 | plugins which enhance the Jinja2 templating engine itself. By default, the
215 | environment is configured with the `do` statement and the loop controls (i.e.
216 | `break` and `continue`), but if you want to add extensions to the environment
217 | then you can do so with the `JINJA_EXTENSIONS` setting. Just add this to your
218 | `settings.py` file:
219 |
220 | JINJA_EXTENSIONS = (
221 | 'jinja2.ext.i18n', # i18n Extension
222 | ...
223 | )
224 |
225 | For all the extensions you wish to load. This will be passed in directly to the
226 | `jinja2.Environment` constructor.
227 |
228 | If you have set `USE_I18N = True` in your settings file, then DjanJinja will
229 | automatically initialize the i18n machinery for the Jinja2 environment, loading
230 | your Django translations during the bootstrapping process. For more information
231 | on how to use the Jinja2 i18n extension, please consult the Jinja2
232 | documentation.
233 |
234 | ### Autoescaping
235 |
236 | To enable autoescaping, just add `JINJA_AUTOESCAPE = True` to your settings
237 | file. This will add the extension and set up the Jinja2 environment correctly.
238 |
239 | ### Cache
240 |
241 | DjanJinja also provides an extension for fragment caching using the Django cache
242 | system. The code for this borrows heavily from the example in the Jinja2
243 | documentation, but with a few extras thrown in. You can use the extension like
244 | this:
245 |
246 | {% cache (parameter1, param2, param3), timeout %}
247 | ...
248 | {% endcache %}
249 |
250 | The tuple of parameters is used to generate the cache key for this fragment. You
251 | can place any object here, so long as it is suitable for serialization by the
252 | standard library `marshal` module. The cache key for the fragment is generated
253 | by marshaling the parameters, hashing them and then using the digest with a
254 | prefix as the key. This allows you to specify cached fragments which vary
255 | depending on multiple variables. The timeout is optional, and should be given in
256 | seconds.
257 |
258 | ## 404 and 500 Handlers
259 |
260 | Your project’s URLconf must specify two variables—`handler404` and `handler500`—which give the name of a Django view to be processed in the event of a 404 "Not Found" and a 500 "Server Error" response respectively. These are set to a default which uses the Django templating system to render a response from templates called `404.html` and `500.html`. If you were to use the Jinja2 templating system instead, you will be able to define richer error pages, and your error pages will be able to inherit from and extend other Jinja2 master templates on the template path.
261 |
262 | It’s relatively simple to set Django up to do this. Simply override the handler variables like so from your `urls.py` file:
263 |
264 | handler404 = 'djanjinja.handlers.page_not_found'
265 | handler500 = 'djanjinja.handlers.server_error'
266 |
267 | ## `RequestContext`
268 |
269 | One of Django's most useful features is the `RequestContext` class, which allows
270 | you to specify several context processors which each add some information to the
271 | context before templates are rendered. Luckily, this feature is
272 | template-agnostic, and is therefore fully compatible with DjanJinja.
273 |
274 | However, DjanJinja also provides you with some very helpful shortcuts for using
275 | request contexts. Usually, without DjanJinja, you would use them like this:
276 |
277 | from django.shortcuts import render_to_response
278 | from django.template import RequestContext
279 |
280 | def myview(request):
281 | context = {'foo': bar, 'spam': eggs}
282 | return render_to_response('template_name.html',
283 | context, context_instance=RequestContext())
284 |
285 | To be honest,this doesn't look very much like a 'shortcut' at all. For this
286 | reason, DjanJinja contains a subclass of `RequestContext` specialised for
287 | Jinja2, which is used like this:
288 |
289 | from djanjinja.views import RequestContext
290 |
291 | def myview(request):
292 | context = RequestContext(request, {'foo': bar, 'spam': eggs})
293 | return context.render_response('template_name.html')
294 |
295 | This code is much more concise, but loses none of the flexibility of the
296 | previous example. The main changes made are the addition of `render_response`
297 | and `render_string` methods to the context object itself. This is highly
298 | specialised to rendering Jinja2 templates, so it may not be a very reusable
299 | approach (indeed, other code which does not use Jinja2 will need to use the full
300 | Django syntax), but it works for the problem domain it was designed for.
301 |
302 | ## Middleware
303 |
304 | One important thing to note from before is that each time a `RequestContext`
305 | instance is constructed, it is necessary to explicitly pass the request. In
306 | object-oriented programming, and Python expecially, when we have functions to
307 | which we must always pass an object of a certain type, it makes sense to make
308 | that function a *method* of the type. When that function is not, in fact, a
309 | function, but a constructor, this seems more difficult. However, thanks to a
310 | feature of Python known as metaprogramming, we can do this very easily. Because
311 | it's not exactly obvious how to do so, DjanJinja includes a special piece of
312 | middleware which can help make your code a lot shorter yet *still* retain all
313 | the functionality and flexibility of the previous two examples.
314 |
315 | To use this middleware, simply add
316 | `'djanjinja.middleware.RequestContextMiddleware'` to your `MIDDLEWARE_CLASSES`
317 | list in the settings module of your project. Then, you can write view code like
318 | this:
319 |
320 | def myview(request):
321 | return request.Context({'foo': bar, 'spam': eggs}).render_response(
322 | 'template_name.html')
323 |
324 | As you can see, we've greatly reduced the verbosity of the previous code, but
325 | it's still obvious what this code does. The middleware attaches a `Context`
326 | attribute to each request object. This attribute is in fact a fully-fledged
327 | Python class, which may itself be subclassed and modified later on. When
328 | constructed, it behaves almost exactly the same as the usual `RequestContext`,
329 | only it uses the request object to which it has been attached, so you don't have
330 | to pass it in to the constructor every time.
331 |
332 | ## Template Loading
333 |
334 | DjanJinja hooks directly into the Django template loader machinery to load
335 | templates. This means you can mix Jinja2 templates freely with Django templates,
336 | in your `TEMPLATE_DIRS` and your applications, and render each type
337 | independently and seamlessly. If you want more information on how it actually
338 | works, please consult the `djanjinja/environment.py` file.
339 |
340 | ## (Un)license
341 |
342 | This is free and unencumbered software released into the public domain.
343 |
344 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
345 | software, either in source code form or as a compiled binary, for any purpose,
346 | commercial or non-commercial, and by any means.
347 |
348 | In jurisdictions that recognize copyright laws, the author or authors of this
349 | software dedicate any and all copyright interest in the software to the public
350 | domain. We make this dedication for the benefit of the public at large and to
351 | the detriment of our heirs and successors. We intend this dedication to be an
352 | overt act of relinquishment in perpetuity of all present and future rights to
353 | this software under copyright law.
354 |
355 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
356 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
357 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
358 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
359 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
360 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
361 |
362 | For more information, please refer to
363 |
--------------------------------------------------------------------------------