parent
d89a5313e5
commit
67e3b2d7cf
218
README.rst
218
README.rst
|
@ -1,208 +1,20 @@
|
|||
Coffin: Jinja2 adapter for Django
|
||||
---------------------------------
|
||||
Coffin: Jinja2 extensions for Django
|
||||
------------------------------------
|
||||
|
||||
This used to be a full-featured standalone adapter. With Django adding
|
||||
support for other template backends, it's approach didn't make sense
|
||||
anymore. Please use ``django_jinja`` instead - you won't regret it:
|
||||
|
||||
http://niwibe.github.io/django-jinja/
|
||||
|
||||
|
||||
Supported Django template functionality
|
||||
=======================================
|
||||
|
||||
Coffin currently makes the following Django tags available in Jinja:
|
||||
|
||||
- {% cache %} - has currently an incompatibility: The second argument
|
||||
(the fragment name) needs to be specified with surrounding quotes
|
||||
if it is supposed to be a literal string, according to Jinja2 syntax.
|
||||
It will otherwise be considered an identifer and resolved as a
|
||||
variable.
|
||||
|
||||
- {% load %} - is actually a no-op in Coffin, since templatetag
|
||||
libraries are always loaded. See also "Custom Filters and extensions".
|
||||
This module now is a lean collection of some Django tags that are
|
||||
not included in django-jinja, namely:
|
||||
|
||||
- {% url %}
|
||||
- {% spaceless %}
|
||||
|
||||
- {% url %} - additionally, a ``"view"|url()`` filter is also
|
||||
available. Or use ``view|url()`` for Django 1.5 style lookups by the value of ``view``.
|
||||
|
||||
- {% with %}
|
||||
|
||||
- {% csrf_token %}
|
||||
|
||||
Django filters that are ported in Coffin:
|
||||
|
||||
- date
|
||||
- floatformat
|
||||
- pluralize (expects an optional second parameter rather than the
|
||||
comma syntax)
|
||||
- time
|
||||
- timesince
|
||||
- timeuntil
|
||||
- truncatewords
|
||||
- truncatewords_html
|
||||
|
||||
Note that for the most part, you can simply use filters written for Django
|
||||
directly in Coffin. For example, ``django.contrib.markup`` "just works" (tm).
|
||||
|
||||
The template-related functionality of the following contrib modules has
|
||||
been ported in Coffin:
|
||||
|
||||
- ``coffin.contrib.syndication``.
|
||||
|
||||
Jinja 2's ``i18n`` extension is hooked up with Django, and a custom version
|
||||
of makemessages supports string extraction from both Jinja2 and Django
|
||||
templates. Just add 'coffin' to your INSTALLED_APPS and run makemessages as
|
||||
usual, specifying additional Jinja extensions if necessary via the -e option.
|
||||
|
||||
Autoescape
|
||||
==========
|
||||
|
||||
When using Auto Escape you will notice that marking something as a
|
||||
Safestrings with Django will not affect the rendering in Jinja 2. To fix this
|
||||
you can monkeypatch Django to produce Jinja 2 compatible Safestrings::
|
||||
|
||||
'''Monkeypatch Django to mimic Jinja2 behaviour'''
|
||||
from django.utils import safestring
|
||||
if not hasattr(safestring, '__html__'):
|
||||
safestring.SafeString.__html__ = lambda self: str(self)
|
||||
safestring.SafeUnicode.__html__ = lambda self: unicode(self)
|
||||
|
||||
Rendering
|
||||
=========
|
||||
|
||||
Simply use the ``render_to_response`` replacement provided by coffin::
|
||||
|
||||
from coffin.shortcuts import render_to_response
|
||||
render_to_response('template.html', {'var': 1})
|
||||
|
||||
This will render ``template.html`` using Jinja2, and returns a
|
||||
``HttpResponse``.
|
||||
|
||||
|
||||
404 and 500 handlers
|
||||
====================
|
||||
|
||||
To have your HTTP 404 and 500 template rendered using Jinja, replace the
|
||||
line::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
in your ``urls.py`` (it should be there by default), with::
|
||||
|
||||
from coffin.conf.urls import *
|
||||
|
||||
|
||||
Custom filters and extensions
|
||||
=============================
|
||||
|
||||
Coffin uses the same templatetag library approach as Django, meaning
|
||||
your app has a ``templatetags`` directory, and each of it's modules
|
||||
represents a "template library", providing new filters and tags.
|
||||
|
||||
A custom ``Library`` class in ``coffin.template.Library`` can be used
|
||||
to register Jinja-specific components.
|
||||
|
||||
Coffin can automatically make your existing Django filters usable in
|
||||
Jinja, but not your custom tags - you need to rewrite those as Jinja
|
||||
extensions manually.
|
||||
|
||||
Example for a Jinja-enabled template library::
|
||||
|
||||
from coffin import template
|
||||
register = template.Library()
|
||||
|
||||
register.filter('plenk', plenk) # Filter for both Django and Jinja
|
||||
register.tag('foo', do_foo) # Django version of the tag
|
||||
register.tag(FooExtension) # Jinja version of the tag
|
||||
register.object(my_function_name) # A global function/object
|
||||
register.test(my_test_name) # A test function
|
||||
|
||||
You may also define additional extensions, filters, tests and globals via your ``settings.py``::
|
||||
|
||||
JINJA2_FILTERS = (
|
||||
'path.to.myfilter',
|
||||
)
|
||||
JINJA2_TESTS = {
|
||||
'test_name': 'path.to.mytest',
|
||||
}
|
||||
JINJA2_EXTENSIONS = (
|
||||
'jinja2.ext.do',
|
||||
)
|
||||
|
||||
|
||||
Other things of note
|
||||
====================
|
||||
|
||||
When porting Django functionality, Coffin currently tries to avoid
|
||||
Django's silent-errors approach, instead opting to be explicit. Django was
|
||||
discussing the same thing before it's 1.0 release (*), but was constrained
|
||||
by backwards-compatibility concerns. However, if you are converting your
|
||||
templates anyway, it might be a good opportunity for this change.
|
||||
|
||||
(*) http://groups.google.com/group/django-developers/browse_thread/thread/f323338045ac2e5e
|
||||
|
||||
``coffin.template.loader`` is a port of ``django.template.loader`` and
|
||||
comes with a Jinja2-enabled version of ``get_template()``.
|
||||
|
||||
``coffin.template.Template`` is a Jinja2-Template that supports the
|
||||
Django render interface (being passed an instance of Context), and uses
|
||||
Coffin's global Jinja2 environment.
|
||||
|
||||
``coffin.interop`` exposes functionality to manually convert Django
|
||||
filters to Jinja2 and vice-versa. This is also what Coffin's ``Library``
|
||||
object uses.
|
||||
|
||||
A Jinja2-enabled version of ``add_to_builtins`` can be found in the
|
||||
``django.template`` namespace.
|
||||
|
||||
You may specify additional arguments to send to the ``Environment`` via ``JINJA2_ENVIRONMENT_OPTIONS``::
|
||||
|
||||
from jinja2 import StrictUndefined
|
||||
JINJA2_ENVIRONMENT_OPTIONS = {
|
||||
'autoescape': False,
|
||||
'undefined': StrictUndefined,
|
||||
}
|
||||
|
||||
Things not supported by Coffin
|
||||
==============================
|
||||
|
||||
These is an incomplete list things that Coffin does not yet and possibly
|
||||
never will, requiring manual changes on your part:
|
||||
|
||||
- The ``slice`` filter works differently in Jinja2 and Django.
|
||||
Replace it with Jinja's slice syntax: ``x[0:1]``.
|
||||
|
||||
- Jinja2's ``default`` filter by itself only tests the variable for
|
||||
**existence**. To match Django's behaviour, you need to pass ``True``
|
||||
as the second argument, so that it will also provide the default
|
||||
value for things that are defined but evalute to ``False``
|
||||
|
||||
- Jinja2's loop variable is called ``loop``, but Django's ``forloop``.
|
||||
|
||||
- Implementing an equivalent to Django's cycle-tag might be difficult,
|
||||
see also Django tickets #5908 and #7501. Jinja's own facilities
|
||||
are the ``forloop.cycle()`` function and the global function
|
||||
``cycler``.
|
||||
|
||||
- The ``add`` filter might not be worth being implemented. ``{{ x+y }}``
|
||||
is a pretty basic feature of Jinja2, and could almost be lumped
|
||||
together with the other Django->Jinja2 syntax changes.
|
||||
|
||||
- Django-type safe strings passed through the context are not converted
|
||||
and therefore not recognized by Jinja2. For example, a notable place
|
||||
were this would occur is the HTML generation of Django Forms.
|
||||
|
||||
- The {% autoescape %} tag is immensily difficult to port and currently
|
||||
not supported.
|
||||
|
||||
- Literal strings from within a template are not automatically
|
||||
considered "safe" by Jinja2, different from Django. According to
|
||||
Armin Ronacher, this is a design limitation that will not be changed,
|
||||
due to many Python builtin functions and methods, whichyou are free
|
||||
to use in Jinja2, expecting raw, untainted strings and thus not being
|
||||
able to work with Jinja2's ``Markup`` string.
|
||||
|
||||
|
||||
Running the tests
|
||||
====================
|
||||
|
||||
Use the nose framework:
|
||||
|
||||
http://somethingaboutorange.com/mrl/projects/nose/
|
||||
|
||||
- {% load %} (as a noop)
|
||||
- {% get_media_prefix %}
|
||||
- {% get_static_prefix %}
|
||||
- {% static %} (in a base variant, and with django.contrib.staticfiles support)
|
|
@ -1,2 +0,0 @@
|
|||
clean:
|
||||
@-rm -f *.pyc */*.pyc tags */tags
|
|
@ -1,44 +1,4 @@
|
|||
"""
|
||||
Coffin
|
||||
~~~~~~
|
||||
|
||||
`Coffin <http://www.github.com/coffin/coffin>` is a package that resolves the
|
||||
impedance mismatch between `Django <http://www.djangoproject.com/>` and `Jinja2
|
||||
<http://jinja.pocoo.org/2/>` through various adapters. The aim is to use Coffin
|
||||
as a drop-in replacement for Django's template system to whatever extent is
|
||||
reasonable.
|
||||
|
||||
:copyright: 2008 by Christopher D. Leary
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
__all__ = ('__version__', '__build__', '__docformat__', 'get_revision')
|
||||
__version__ = (0, 4, '0')
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
def _get_git_revision(path):
|
||||
revision_file = os.path.join(path, 'refs', 'heads', 'master')
|
||||
if not os.path.exists(revision_file):
|
||||
return None
|
||||
fh = open(revision_file, 'r')
|
||||
try:
|
||||
return fh.read()
|
||||
finally:
|
||||
fh.close()
|
||||
|
||||
def get_revision():
|
||||
"""
|
||||
:returns: Revision number of this branch/checkout, if available. None if
|
||||
no revision number can be determined.
|
||||
"""
|
||||
package_dir = os.path.dirname(__file__)
|
||||
checkout_dir = os.path.normpath(os.path.join(package_dir, '..'))
|
||||
path = os.path.join(checkout_dir, '.git')
|
||||
if os.path.exists(path):
|
||||
return _get_git_revision(path)
|
||||
return None
|
||||
|
||||
__build__ = get_revision()
|
||||
from common import *
|
||||
from static import *
|
||||
|
|
450
coffin/common.py
450
coffin/common.py
|
@ -1,216 +1,292 @@
|
|||
import os
|
||||
import warnings
|
||||
from jinja2 import nodes
|
||||
from jinja2.ext import Extension
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2 import Markup
|
||||
from django.conf import settings
|
||||
|
||||
from django import dispatch
|
||||
from jinja2 import Environment, loaders
|
||||
from jinja2 import defaults as jinja2_defaults
|
||||
from coffin.template import Library as CoffinLibrary
|
||||
|
||||
__all__ = ('env',)
|
||||
class LoadExtension(Extension):
|
||||
"""The load-tag is a no-op in Coffin. Instead, all template libraries
|
||||
are always loaded.
|
||||
|
||||
env = None
|
||||
Note: Supporting a functioning load-tag in Jinja is tough, though
|
||||
theoretically possible. The trouble is activating new extensions while
|
||||
parsing is ongoing. The ``Parser.extensions`` dict of the current
|
||||
parser instance needs to be modified, but apparently the only way to
|
||||
get access would be by hacking the stack.
|
||||
"""
|
||||
tags = set(['load'])
|
||||
|
||||
_JINJA_I18N_EXTENSION_NAME = 'jinja2.ext.i18n'
|
||||
def parse(self, parser):
|
||||
while not parser.stream.current.type == 'block_end':
|
||||
parser.stream.next()
|
||||
return []
|
||||
|
||||
class CoffinEnvironment(Environment):
|
||||
def __init__(self, filters={}, globals={}, tests={}, loader=None, extensions=[], **kwargs):
|
||||
if not loader:
|
||||
loader = loaders.ChoiceLoader(self._get_loaders())
|
||||
all_ext = self._get_all_extensions()
|
||||
|
||||
extensions.extend(all_ext['extensions'])
|
||||
super(CoffinEnvironment, self).__init__(
|
||||
extensions=extensions,
|
||||
loader=loader,
|
||||
**kwargs
|
||||
)
|
||||
# Note: all_ext already includes Jinja2's own builtins (with
|
||||
# the proper priority), so we want to assign to these attributes.
|
||||
self.filters = all_ext['filters'].copy()
|
||||
self.filters.update(filters)
|
||||
self.globals.update(all_ext['globals'])
|
||||
self.globals.update(globals)
|
||||
self.tests = all_ext['tests'].copy()
|
||||
self.tests.update(tests)
|
||||
for key, value in all_ext['attrs'].items():
|
||||
setattr(self, key, value)
|
||||
"""class AutoescapeExtension(Extension):
|
||||
""#"
|
||||
Template to output works in three phases in Jinja2: parsing,
|
||||
generation (compilation, AST-traversal), and rendering (execution).
|
||||
|
||||
from coffin.template import Template as CoffinTemplate
|
||||
self.template_class = CoffinTemplate
|
||||
Unfortunatly, the environment ``autoescape`` option comes into effect
|
||||
during traversal, the part where we happen to have basically no control
|
||||
over as an extension. It determines whether output is wrapped in
|
||||
``escape()`` calls.
|
||||
|
||||
def _get_loaders(self):
|
||||
"""Tries to translate each template loader given in the Django settings
|
||||
(:mod:`django.settings`) to a similarly-behaving Jinja loader.
|
||||
Warns if a similar loader cannot be found.
|
||||
Allows for Jinja2 loader instances to be placed in the template loader
|
||||
settings.
|
||||
"""
|
||||
loaders = []
|
||||
Solutions that could possibly work:
|
||||
|
||||
from coffin.template.loaders import jinja_loader_from_django_loader
|
||||
from jinja2.loaders import BaseLoader as JinjaLoader
|
||||
* This extension could preprocess it's childnodes and wrap
|
||||
everything output related inside the appropriate
|
||||
``Markup()`` or escape() call.
|
||||
|
||||
from django.conf import settings
|
||||
_loaders = getattr(settings, 'JINJA2_TEMPLATE_LOADERS', settings.TEMPLATE_LOADERS)
|
||||
for loader in _loaders:
|
||||
if isinstance(loader, JinjaLoader):
|
||||
loaders.append(loader)
|
||||
* We could use the ``preprocess`` hook to insert the
|
||||
appropriate ``|safe`` and ``|escape`` filters on a
|
||||
string-basis. This is very unlikely to work well.
|
||||
|
||||
There's also the issue of inheritance and just generally the nesting
|
||||
of autoescape-tags to consider.
|
||||
|
||||
Other things of note:
|
||||
|
||||
* We can access ``parser.environment``, but that would only
|
||||
affect the **parsing** of our child nodes.
|
||||
|
||||
* In the commented-out code below we are trying to affect the
|
||||
autoescape setting during rendering. As noted, this could be
|
||||
necessary for rare border cases where custom extension use
|
||||
the autoescape attribute.
|
||||
|
||||
Both the above things would break Environment thread-safety though!
|
||||
|
||||
Overall, it's not looking to good for this extension.
|
||||
""#"
|
||||
|
||||
tags = ['autoescape']
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
|
||||
old_autoescape = parser.environment.autoescape
|
||||
parser.environment.autoescape = True
|
||||
try:
|
||||
body = parser.parse_statements(
|
||||
['name:endautoescape'], drop_needle=True)
|
||||
finally:
|
||||
parser.environment.autoescape = old_autoescape
|
||||
|
||||
# Not sure yet if the code below is necessary - it changes
|
||||
# environment.autoescape during template rendering. If for example
|
||||
# a CallBlock function accesses ``environment.autoescape``, it
|
||||
# presumably is.
|
||||
# This also should use try-finally though, which Jinja's API
|
||||
# doesn't support either. We could fake that as well by using
|
||||
# InternalNames that output the necessary indentation and keywords,
|
||||
# but at this point it starts to get really messy.
|
||||
#
|
||||
# TODO: Actually, there's ``nodes.EnvironmentAttribute``.
|
||||
#ae_setting = object.__new__(nodes.InternalName)
|
||||
#nodes.Node.__init__(ae_setting, 'environment.autoescape',
|
||||
lineno=lineno)
|
||||
#temp = parser.free_identifier()
|
||||
#body.insert(0, nodes.Assign(temp, ae_setting))
|
||||
#body.insert(1, nodes.Assign(ae_setting, nodes.Const(True)))
|
||||
#body.insert(len(body), nodes.Assign(ae_setting, temp))
|
||||
return body
|
||||
"""
|
||||
|
||||
|
||||
class URLExtension(Extension):
|
||||
"""Returns an absolute URL matching given view with its parameters.
|
||||
|
||||
This is a way to define links that aren't tied to a particular URL
|
||||
configuration::
|
||||
|
||||
{% url path.to.some_view arg1,arg2,name1=value1 %}
|
||||
|
||||
Known differences to Django's url-Tag:
|
||||
|
||||
- In Django, the view name may contain any non-space character.
|
||||
Since Jinja's lexer does not identify whitespace to us, only
|
||||
characters that make up valid identifers, plus dots and hyphens
|
||||
are allowed. Note that identifers in Jinja 2 may not contain
|
||||
non-ascii characters.
|
||||
|
||||
As an alternative, you may specifify the view as a string,
|
||||
which bypasses all these restrictions. It further allows you
|
||||
to apply filters:
|
||||
|
||||
{% url "меткаda.some-view"|afilter %}
|
||||
"""
|
||||
|
||||
tags = set(['url'])
|
||||
|
||||
def parse(self, parser):
|
||||
stream = parser.stream
|
||||
|
||||
tag = stream.next()
|
||||
|
||||
# get view name
|
||||
if stream.current.test('string'):
|
||||
# Need to work around Jinja2 syntax here. Jinja by default acts
|
||||
# like Python and concats subsequent strings. In this case
|
||||
# though, we want {% url "app.views.post" "1" %} to be treated
|
||||
# as view + argument, while still supporting
|
||||
# {% url "app.views.post"|filter %}. Essentially, what we do is
|
||||
# rather than let ``parser.parse_primary()`` deal with a "string"
|
||||
# token, we do so ourselves, and let parse_expression() handle all
|
||||
# other cases.
|
||||
if stream.look().test('string'):
|
||||
token = stream.next()
|
||||
viewname = nodes.Const(token.value, lineno=token.lineno)
|
||||
else:
|
||||
loader_name = args = None
|
||||
if isinstance(loader, basestring):
|
||||
loader_name = loader
|
||||
args = []
|
||||
elif isinstance(loader, (tuple, list)):
|
||||
loader_name = loader[0]
|
||||
args = loader[1]
|
||||
viewname = parser.parse_expression()
|
||||
else:
|
||||
# parse valid tokens and manually build a string from them
|
||||
bits = []
|
||||
name_allowed = True
|
||||
while True:
|
||||
if stream.current.test_any('dot', 'sub', 'colon'):
|
||||
bits.append(stream.next())
|
||||
name_allowed = True
|
||||
elif stream.current.test('name') and name_allowed:
|
||||
bits.append(stream.next())
|
||||
name_allowed = False
|
||||
else:
|
||||
break
|
||||
viewname = nodes.Const("".join([b.value for b in bits]))
|
||||
if not bits:
|
||||
raise TemplateSyntaxError("'%s' requires path to view" %
|
||||
tag.value, tag.lineno)
|
||||
|
||||
if loader_name:
|
||||
loader_obj = jinja_loader_from_django_loader(loader_name, args)
|
||||
if loader_obj:
|
||||
loaders.append(loader_obj)
|
||||
continue
|
||||
# get arguments
|
||||
args = []
|
||||
kwargs = []
|
||||
while not stream.current.test_any('block_end', 'name:as'):
|
||||
if args or kwargs:
|
||||
stream.expect('comma')
|
||||
if stream.current.test('name') and stream.look().test('assign'):
|
||||
key = nodes.Const(stream.next().value)
|
||||
stream.skip()
|
||||
value = parser.parse_expression()
|
||||
kwargs.append(nodes.Pair(key, value, lineno=key.lineno))
|
||||
else:
|
||||
args.append(parser.parse_expression())
|
||||
|
||||
warnings.warn('Cannot translate loader: %s' % loader)
|
||||
return loaders
|
||||
def make_call_node(*kw):
|
||||
return self.call_method('_reverse', args=[
|
||||
viewname,
|
||||
nodes.List(args),
|
||||
nodes.Dict(kwargs),
|
||||
nodes.Name('_current_app', 'load'),
|
||||
], kwargs=kw)
|
||||
|
||||
def _get_templatelibs(self):
|
||||
"""Return an iterable of template ``Library`` instances.
|
||||
# if an as-clause is specified, write the result to context...
|
||||
if stream.next_if('name:as'):
|
||||
var = nodes.Name(stream.expect('name').value, 'store')
|
||||
call_node = make_call_node(nodes.Keyword('fail',
|
||||
nodes.Const(False)))
|
||||
return nodes.Assign(var, call_node)
|
||||
# ...otherwise print it out.
|
||||
else:
|
||||
return nodes.Output([make_call_node()]).set_lineno(tag.lineno)
|
||||
|
||||
Since we cannot support the {% load %} tag in Jinja, we have to
|
||||
register all libraries globally.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.template import (
|
||||
get_library, import_library, InvalidTemplateLibrary)
|
||||
@classmethod
|
||||
def _reverse(self, viewname, args, kwargs, current_app=None, fail=True):
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
|
||||
libs = []
|
||||
for app in settings.INSTALLED_APPS:
|
||||
ns = app + '.templatetags'
|
||||
# Try to look up the URL twice: once given the view name,
|
||||
# and again relative to what we guess is the "main" app.
|
||||
url = ''
|
||||
urlconf=kwargs.pop('urlconf', None)
|
||||
try:
|
||||
url = reverse(viewname, urlconf=urlconf, args=args, kwargs=kwargs,
|
||||
current_app=current_app)
|
||||
except NoReverseMatch as ex:
|
||||
projectname = settings.SETTINGS_MODULE.split('.')[0]
|
||||
try:
|
||||
path = __import__(ns, {}, {}, ['__file__']).__file__
|
||||
path = os.path.dirname(path) # we now have the templatetags/ directory
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
for filename in os.listdir(path):
|
||||
if filename == '__init__.py' or filename.startswith('.'):
|
||||
continue
|
||||
url = reverse(projectname + '.' + viewname, urlconf=urlconf,
|
||||
args=args, kwargs=kwargs)
|
||||
except NoReverseMatch:
|
||||
if fail:
|
||||
raise ex
|
||||
else:
|
||||
return ''
|
||||
|
||||
if filename.endswith('.py'):
|
||||
try:
|
||||
module = "%s.%s" % (ns, os.path.splitext(filename)[0])
|
||||
l = import_library(module)
|
||||
libs.append(l)
|
||||
return url
|
||||
|
||||
except InvalidTemplateLibrary:
|
||||
pass
|
||||
|
||||
# In addition to loading application libraries, support a custom list
|
||||
for libname in getattr(settings, 'JINJA2_DJANGO_TEMPLATETAG_LIBRARIES', ()):
|
||||
libs.append(get_library(libname))
|
||||
class WithExtension(Extension):
|
||||
"""Adds a value to the context (inside this block) for caching and
|
||||
easy access, just like the Django-version does.
|
||||
|
||||
return libs
|
||||
For example::
|
||||
|
||||
def _get_all_extensions(self):
|
||||
from django.conf import settings
|
||||
from django.template import builtins as django_builtins
|
||||
from coffin.template import builtins as coffin_builtins
|
||||
from django.core.urlresolvers import get_callable
|
||||
{% with person.some_sql_method as total %}
|
||||
{{ total }} object{{ total|pluralize }}
|
||||
{% endwith %}
|
||||
|
||||
# Note that for extensions, the order in which we load the libraries
|
||||
# is not maintained (https://github.com/mitsuhiko/jinja2/issues#issue/3).
|
||||
# Extensions support priorities, which should be used instead.
|
||||
extensions, filters, globals, tests, attrs = [], {}, {}, {}, {}
|
||||
def _load_lib(lib):
|
||||
if not isinstance(lib, CoffinLibrary):
|
||||
# If this is only a standard Django library,
|
||||
# convert it. This will ensure that Django
|
||||
# filters in that library are converted and
|
||||
# made available in Jinja.
|
||||
lib = CoffinLibrary.from_django(lib)
|
||||
extensions.extend(getattr(lib, 'jinja2_extensions', []))
|
||||
filters.update(getattr(lib, 'jinja2_filters', {}))
|
||||
globals.update(getattr(lib, 'jinja2_globals', {}))
|
||||
tests.update(getattr(lib, 'jinja2_tests', {}))
|
||||
attrs.update(getattr(lib, 'jinja2_environment_attrs', {}))
|
||||
|
||||
# Start with Django's builtins; this give's us all of Django's
|
||||
# filters courtasy of our interop layer.
|
||||
for lib in django_builtins:
|
||||
_load_lib(lib)
|
||||
|
||||
# The stuff Jinja2 comes with by default should override Django.
|
||||
filters.update(jinja2_defaults.DEFAULT_FILTERS)
|
||||
tests.update(jinja2_defaults.DEFAULT_TESTS)
|
||||
globals.update(jinja2_defaults.DEFAULT_NAMESPACE)
|
||||
|
||||
# Our own set of builtins are next, overwriting Jinja2's.
|
||||
for lib in coffin_builtins:
|
||||
_load_lib(lib)
|
||||
|
||||
# Optionally, include the i18n extension.
|
||||
if settings.USE_I18N:
|
||||
extensions.append(_JINJA_I18N_EXTENSION_NAME)
|
||||
|
||||
# Next, add the globally defined extensions
|
||||
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', [])))
|
||||
def from_setting(setting, values_must_be_callable = False):
|
||||
retval = {}
|
||||
setting = getattr(settings, setting, {})
|
||||
if isinstance(setting, dict):
|
||||
for key, value in setting.iteritems():
|
||||
if values_must_be_callable and not callable(value):
|
||||
value = get_callable(value)
|
||||
retval[key] = value
|
||||
else:
|
||||
for value in setting:
|
||||
if values_must_be_callable and not callable(value):
|
||||
value = get_callable(value)
|
||||
retval[value.__name__] = value
|
||||
return retval
|
||||
|
||||
tests.update(from_setting('JINJA2_TESTS', True))
|
||||
filters.update(from_setting('JINJA2_FILTERS', True))
|
||||
globals.update(from_setting('JINJA2_GLOBALS'))
|
||||
|
||||
# Finally, add extensions defined in application's templatetag libraries
|
||||
for lib in self._get_templatelibs():
|
||||
_load_lib(lib)
|
||||
|
||||
return dict(
|
||||
extensions=extensions,
|
||||
filters=filters,
|
||||
globals=globals,
|
||||
tests=tests,
|
||||
attrs=attrs,
|
||||
)
|
||||
|
||||
def get_env():
|
||||
TODO: The new Scope node introduced in Jinja2 6334c1eade73 (the 2.2
|
||||
dev version) would help here, but we don't want to rely on that yet.
|
||||
See also:
|
||||
http://dev.pocoo.org/projects/jinja/browser/tests/test_ext.py
|
||||
http://dev.pocoo.org/projects/jinja/ticket/331
|
||||
http://dev.pocoo.org/projects/jinja/ticket/329
|
||||
"""
|
||||
:return: A Jinja2 environment singleton.
|
||||
|
||||
tags = set(['with'])
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
value = parser.parse_expression()
|
||||
parser.stream.expect('name:as')
|
||||
name = parser.stream.expect('name')
|
||||
body = parser.parse_statements(['name:endwith'], drop_needle=True)
|
||||
# Use a local variable instead of a macro argument to alias
|
||||
# the expression. This allows us to nest "with" statements.
|
||||
body.insert(0, nodes.Assign(nodes.Name(name.value, 'store'), value))
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_render_block'), [], [], body).\
|
||||
set_lineno(lineno)
|
||||
|
||||
def _render_block(self, caller=None):
|
||||
return caller()
|
||||
|
||||
|
||||
class SpacelessExtension(Extension):
|
||||
"""Removes whitespace between HTML tags, including tab and
|
||||
newline characters.
|
||||
|
||||
Works exactly like Django's own tag.
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
kwargs = {
|
||||
'autoescape': True,
|
||||
}
|
||||
kwargs.update(getattr(settings, 'JINJA2_ENVIRONMENT_OPTIONS', {}))
|
||||
tags = set(['spaceless'])
|
||||
|
||||
result = CoffinEnvironment(**kwargs)
|
||||
# Hook Jinja's i18n extension up to Django's translation backend
|
||||
# if i18n is enabled; note that we differ here from Django, in that
|
||||
# Django always has it's i18n functionality available (that is, if
|
||||
# enabled in a template via {% load %}), but uses a null backend if
|
||||
# the USE_I18N setting is disabled. Jinja2 provides something similar
|
||||
# (install_null_translations), but instead we are currently not
|
||||
# enabling the extension at all when USE_I18N=False.
|
||||
# While this is basically an incompatibility with Django, currently
|
||||
# the i18n tags work completely differently anyway, so for now, I
|
||||
# don't think it matters.
|
||||
if settings.USE_I18N:
|
||||
from django.utils import translation
|
||||
result.install_gettext_translations(translation)
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
body = parser.parse_statements(['name:endspaceless'], drop_needle=True)
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_strip_spaces', [], [], None, None),
|
||||
[], [], body,
|
||||
).set_lineno(lineno)
|
||||
|
||||
return result
|
||||
def _strip_spaces(self, caller=None):
|
||||
from django.utils.html import strip_spaces_between_tags
|
||||
return strip_spaces_between_tags(caller().strip())
|
||||
|
||||
env = get_env()
|
||||
# nicer import names
|
||||
load = LoadExtension
|
||||
url = URLExtension
|
||||
with_ = WithExtension
|
||||
spaceless = SpacelessExtension
|
||||
|
||||
|
||||
# # I wish adding extensions in this way was supported
|
||||
# try:
|
||||
# from django_jinja import library
|
||||
# except ImportError:
|
||||
# pass
|
||||
# else:
|
||||
# library.tag(load)
|
||||
# library.tag(url)
|
||||
# library.tag(with_)
|
||||
# library.tag(spaceless)
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
from django.conf.urls import *
|
||||
|
||||
handler404 = 'coffin.views.defaults.page_not_found'
|
||||
handler500 = 'coffin.views.defaults.server_error'
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.admin import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.backends import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.decorators import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.forms import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.handlers import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.middleware import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.models import *
|
|
@ -1 +0,0 @@
|
|||
from django.contrib.auth.tokens import *
|
|
@ -1,6 +0,0 @@
|
|||
import inspect
|
||||
|
||||
from django.contrib.auth import urls
|
||||
|
||||
exec inspect.getsource(urlpatterns)\
|
||||
.replace('django.contrib.auth.views', 'coffin.contrib.auth.views')
|
|
@ -1,50 +0,0 @@
|
|||
import inspect
|
||||
|
||||
from django.contrib.auth.views import *
|
||||
|
||||
# XXX: maybe approach this as importing the entire model, and doing string replacements
|
||||
# on the template and shortcut import lines?
|
||||
|
||||
from coffin.shortcuts import render_to_response
|
||||
from coffin.template import RequestContext, loader
|
||||
|
||||
exec inspect.getsource(logout)
|
||||
exec inspect.getsource(password_change_done)
|
||||
exec inspect.getsource(password_reset)
|
||||
exec inspect.getsource(password_reset_confirm)
|
||||
exec inspect.getsource(password_reset_done)
|
||||
exec inspect.getsource(password_reset_complete)
|
||||
|
||||
exec inspect.getsource(password_change.view_func)
|
||||
password_change = login_required(password_change)
|
||||
|
||||
# XXX: this function uses a decorator, which calls functools.wraps, which compiles the code
|
||||
# thus we cannot inspect the source
|
||||
def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
|
||||
"Displays the login form and handles the login action."
|
||||
redirect_to = request.REQUEST.get(redirect_field_name, '')
|
||||
if request.method == "POST":
|
||||
form = AuthenticationForm(data=request.POST)
|
||||
if form.is_valid():
|
||||
# Light security check -- make sure redirect_to isn't garbage.
|
||||
if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
from django.contrib.auth import login
|
||||
login(request, form.get_user())
|
||||
if request.session.test_cookie_worked():
|
||||
request.session.delete_test_cookie()
|
||||
return HttpResponseRedirect(redirect_to)
|
||||
else:
|
||||
form = AuthenticationForm(request)
|
||||
request.session.set_test_cookie()
|
||||
if Site._meta.installed:
|
||||
current_site = Site.objects.get_current()
|
||||
else:
|
||||
current_site = RequestSite(request)
|
||||
return render_to_response(template_name, {
|
||||
'form': form,
|
||||
redirect_field_name: redirect_to,
|
||||
'site': current_site,
|
||||
'site_name': current_site.name,
|
||||
}, context_instance=RequestContext(request))
|
||||
login = never_cache(login)
|
|
@ -1,2 +0,0 @@
|
|||
# coding=utf-8
|
||||
from . import context
|
|
@ -1,2 +0,0 @@
|
|||
# coding=utf-8
|
||||
from django.contrib.flatpages.admin import *
|
|
@ -1,29 +0,0 @@
|
|||
# coding=utf-8
|
||||
from django.conf import settings
|
||||
from django.contrib.flatpages.models import FlatPage
|
||||
from coffin.common import env
|
||||
|
||||
|
||||
def get_flatpages(starts_with=None, user=None, site_id=None):
|
||||
"""
|
||||
Context-function similar to get_flatpages tag in Django templates.
|
||||
|
||||
Usage:
|
||||
<ul>
|
||||
{% for page in get_flatpages(starts_with='/about/', user=user, site_id=site.pk) %}
|
||||
<li><a href="{{ page.url }}">{{ page.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
"""
|
||||
flatpages = FlatPage.objects.filter(sites__id=site_id or settings.SITE_ID)
|
||||
|
||||
if starts_with:
|
||||
flatpages = flatpages.filter(url__startswith=starts_with)
|
||||
|
||||
if not user or not user.is_authenticated():
|
||||
flatpages = flatpages.filter(registration_required=False)
|
||||
|
||||
return flatpages
|
||||
|
||||
env.globals['get_flatpages'] = get_flatpages
|
|
@ -1,6 +0,0 @@
|
|||
import inspect
|
||||
|
||||
from django.contrib.flatpages.middleware import *
|
||||
from coffin.contrib.flatpages.views import flatpage
|
||||
|
||||
exec inspect.getsource(FlatpageFallbackMiddleware)\
|
|
@ -1,54 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
from django.contrib.flatpages.models import FlatPage
|
||||
from django.contrib.flatpages.views import DEFAULT_TEMPLATE
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
from coffin.template import RequestContext, loader
|
||||
|
||||
# This view is called from FlatpageFallbackMiddleware.process_response
|
||||
# when a 404 is raised, which often means CsrfViewMiddleware.process_view
|
||||
# has not been called even if CsrfViewMiddleware is installed. So we need
|
||||
# to use @csrf_protect, in case the template needs {% csrf_token %}.
|
||||
@csrf_protect
|
||||
def flatpage(request, url):
|
||||
"""
|
||||
Flat page view.
|
||||
|
||||
Models: `flatpages.flatpages`
|
||||
Templates: Uses the template defined by the ``template_name`` field,
|
||||
or `flatpages/default.html` if template_name is not defined.
|
||||
Context:
|
||||
flatpage
|
||||
`flatpages.flatpages` object
|
||||
"""
|
||||
if not url.endswith('/') and settings.APPEND_SLASH:
|
||||
return HttpResponseRedirect("%s/" % request.path)
|
||||
if not url.startswith('/'):
|
||||
url = "/" + url
|
||||
f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID)
|
||||
# If registration is required for accessing this page, and the user isn't
|
||||
# logged in, redirect to the login page.
|
||||
if f.registration_required and not request.user.is_authenticated():
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
return redirect_to_login(request.path)
|
||||
if f.template_name:
|
||||
t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
|
||||
else:
|
||||
t = loader.get_template(DEFAULT_TEMPLATE)
|
||||
|
||||
# To avoid having to always use the "|safe" filter in flatpage templates,
|
||||
# mark the title and content as already safe (since they are raw HTML
|
||||
# content in the first place).
|
||||
f.title = mark_safe(f.title)
|
||||
f.content = mark_safe(f.content)
|
||||
|
||||
c = RequestContext(request, {
|
||||
'flatpage': f,
|
||||
})
|
||||
response = HttpResponse(t.render(c))
|
||||
return response
|
|
@ -1,47 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
A Django template loader wrapper for Coffin that intercepts
|
||||
requests for "*.jinja" templates, rendering them with Coffin
|
||||
instead of Django templates.
|
||||
|
||||
Usage:
|
||||
|
||||
TEMPLATE_LOADERS = (
|
||||
'coffin.contrib.loader.AppLoader',
|
||||
'coffin.contrib.loader.FileSystemLoader',
|
||||
)
|
||||
|
||||
"""
|
||||
|
||||
from os.path import splitext
|
||||
from coffin.common import env
|
||||
from django.conf import settings
|
||||
from django.template.loaders import app_directories, filesystem
|
||||
|
||||
|
||||
JINJA2_DEFAULT_TEMPLATE_EXTENSION = getattr(settings,
|
||||
'JINJA2_DEFAULT_TEMPLATE_EXTENSION', ('.jinja',))
|
||||
|
||||
if isinstance(JINJA2_DEFAULT_TEMPLATE_EXTENSION, basestring):
|
||||
JINJA2_DEFAULT_TEMPLATE_EXTENSION = (JINJA2_DEFAULT_TEMPLATE_EXTENSION,)
|
||||
|
||||
|
||||
class LoaderMixin(object):
|
||||
is_usable = True
|
||||
|
||||
def load_template(self, template_name, template_dirs=None):
|
||||
extension = splitext(template_name)[1]
|
||||
|
||||
if not extension in JINJA2_DEFAULT_TEMPLATE_EXTENSION:
|
||||
return super(LoaderMixin, self).load_template(template_name,
|
||||
template_dirs)
|
||||
template = env.get_template(template_name)
|
||||
return template, template.filename
|
||||
|
||||
|
||||
class FileSystemLoader(LoaderMixin, filesystem.Loader):
|
||||
pass
|
||||
|
||||
|
||||
class AppLoader(LoaderMixin, app_directories.Loader):
|
||||
pass
|
|
@ -1,33 +0,0 @@
|
|||
from django.contrib.syndication.feeds import * # merge modules
|
||||
|
||||
import sys
|
||||
from django.contrib.syndication.feeds import Feed as DjangoDeprecatedFeed
|
||||
from django.contrib.syndication.views import Feed as DjangoNewFeed
|
||||
from coffin.template import loader as coffin_loader
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
|
||||
|
||||
class Feed(DjangoDeprecatedFeed):
|
||||
"""Django changed the syndication framework in 1.2. This class
|
||||
represents the old way, ported to Coffin. If you are using 1.2,
|
||||
you should use the ``Feed`` class in
|
||||
``coffin.contrib.syndication.views``.
|
||||
|
||||
See also there for some notes on what we are doing here.
|
||||
"""
|
||||
|
||||
def get_feed(self, *args, **kwargs):
|
||||
if DJANGO_VERSION < (1,2):
|
||||
parent_module = sys.modules[DjangoDeprecatedFeed.__module__]
|
||||
else:
|
||||
# In Django 1.2, our parent DjangoDeprecatedFeed class really
|
||||
# inherits from DjangoNewFeed, so we need to patch the loader
|
||||
# in a different module.
|
||||
parent_module = sys.modules[DjangoNewFeed.__module__]
|
||||
|
||||
old_loader = parent_module.loader
|
||||
parent_module.loader = coffin_loader
|
||||
try:
|
||||
return super(Feed, self).get_feed(*args, **kwargs)
|
||||
finally:
|
||||
parent_module.loader = old_loader
|
|
@ -1,36 +0,0 @@
|
|||
from django.contrib.syndication.views import * # merge modules
|
||||
|
||||
import sys
|
||||
from django.contrib.syndication.views import Feed as DjangoFeed
|
||||
from coffin.template import loader as coffin_loader
|
||||
|
||||
|
||||
class Feed(DjangoFeed):
|
||||
"""A ``Feed`` implementation that renders it's title and
|
||||
description templates using Jinja2.
|
||||
|
||||
Unfortunately, Django's base ``Feed`` class is not very extensible
|
||||
in this respect at all. For a real solution, we'd have to essentially
|
||||
have to duplicate the whole class. So for now, we use this terrible
|
||||
non-thread safe hack.
|
||||
|
||||
Another, somewhat crazy option would be:
|
||||
* Render the templates ourselves through Jinja2 (possible
|
||||
introduce new attributes to avoid having to rewrite the
|
||||
existing ones).
|
||||
* Make the rendered result available to Django/the superclass by
|
||||
using a custom template loader using a prefix, say
|
||||
"feed:<myproject.app.views.MyFeed>". The loader would simply
|
||||
return the Jinja-rendered template (escaped), the Django template
|
||||
mechanism would find no nodes and just pass the output through.
|
||||
Possible even worse than this though.
|
||||
"""
|
||||
|
||||
def get_feed(self, *args, **kwargs):
|
||||
parent_module = sys.modules[DjangoFeed.__module__]
|
||||
old_loader = parent_module.loader
|
||||
parent_module.loader = coffin_loader
|
||||
try:
|
||||
return super(Feed, self).get_feed(*args, **kwargs)
|
||||
finally:
|
||||
parent_module.loader = old_loader
|
|
@ -1,66 +0,0 @@
|
|||
"""Jinja2's i18n functionality is not exactly the same as Django's.
|
||||
In particular, the tags names and their syntax are different:
|
||||
|
||||
1. The Django ``trans`` tag is replaced by a _() global.
|
||||
2. The Django ``blocktrans`` tag is called ``trans``.
|
||||
|
||||
(1) isn't an issue, since the whole ``makemessages`` process is based on
|
||||
converting the template tags to ``_()`` calls. However, (2) means that
|
||||
those Jinja2 ``trans`` tags will not be picked up my Django's
|
||||
``makemessage`` command.
|
||||
|
||||
There aren't any nice solutions here. While Jinja2's i18n extension does
|
||||
come with extraction capabilities built in, the code behind ``makemessages``
|
||||
unfortunately isn't extensible, so we can:
|
||||
|
||||
* Duplicate the command + code behind it.
|
||||
* Offer a separate command for Jinja2 extraction.
|
||||
* Try to get Django to offer hooks into makemessages().
|
||||
* Monkey-patch.
|
||||
|
||||
We are currently doing that last thing. It turns out there we are lucky
|
||||
for once: It's simply a matter of extending two regular expressions.
|
||||
Credit for the approach goes to:
|
||||
http://stackoverflow.com/questions/2090717/getting-translation-strings-for-jinja2-templates-integrated-with-django-1-x
|
||||
"""
|
||||
|
||||
import re
|
||||
from django.core.management.commands import makemessages
|
||||
from django.utils.translation import trans_real
|
||||
from django.template import BLOCK_TAG_START, BLOCK_TAG_END
|
||||
|
||||
strip_whitespace_right = re.compile(r"(%s-?\s*(trans|pluralize).*?-%s)\s+" % (BLOCK_TAG_START, BLOCK_TAG_END), re.U)
|
||||
strip_whitespace_left = re.compile(r"\s+(%s-\s*(endtrans|pluralize).*?-?%s)" % (BLOCK_TAG_START, BLOCK_TAG_END), re.U)
|
||||
|
||||
def strip_whitespaces(src):
|
||||
src = strip_whitespace_left.sub(r'\1', src)
|
||||
src = strip_whitespace_right.sub(r'\1', src)
|
||||
return src
|
||||
|
||||
class Command(makemessages.Command):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
old_endblock_re = trans_real.endblock_re
|
||||
old_block_re = trans_real.block_re
|
||||
old_templatize = trans_real.templatize
|
||||
# Extend the regular expressions that are used to detect
|
||||
# translation blocks with an "OR jinja-syntax" clause.
|
||||
trans_real.endblock_re = re.compile(
|
||||
trans_real.endblock_re.pattern + '|' + r"""^-?\s*endtrans\s*-?$""")
|
||||
trans_real.block_re = re.compile(
|
||||
trans_real.block_re.pattern + '|' + r"""^-?\s*trans(?:\s+(?!'|")(?=.*?=.*?)|-?$)""")
|
||||
trans_real.plural_re = re.compile(
|
||||
trans_real.plural_re.pattern + '|' + r"""^-?\s*pluralize(?:\s+.+|-?$)""")
|
||||
|
||||
def my_templatize(src, origin=None):
|
||||
new_src = strip_whitespaces(src)
|
||||
return old_templatize(new_src, origin)
|
||||
|
||||
trans_real.templatize = my_templatize
|
||||
|
||||
try:
|
||||
super(Command, self).handle(*args, **options)
|
||||
finally:
|
||||
trans_real.endblock_re = old_endblock_re
|
||||
trans_real.block_re = old_block_re
|
||||
trans_real.templatize = old_templatize
|
|
@ -1,50 +0,0 @@
|
|||
from django.http import HttpResponse
|
||||
|
||||
# Merge with original namespace so user
|
||||
# doesn't have to import twice.
|
||||
from django.shortcuts import *
|
||||
|
||||
|
||||
__all__ = ('render_to_string', 'render_to_response', 'render')
|
||||
|
||||
|
||||
# Is within ``template.loader`` as per Django specification -
|
||||
# but I think it fits very well here.
|
||||
from coffin.template.loader import render_to_string
|
||||
|
||||
|
||||
def render_to_response(template_name, dictionary=None, context_instance=None, **kwargs):
|
||||
"""
|
||||
:param template_name: Filename of the template to get or a sequence of
|
||||
filenames to try, in order.
|
||||
:param dictionary: Rendering context for the template.
|
||||
:returns: A response object with the evaluated template as a payload.
|
||||
"""
|
||||
rendered = render_to_string(template_name, dictionary, context_instance)
|
||||
return HttpResponse(rendered, **kwargs)
|
||||
|
||||
|
||||
def render(request, *args, **kwargs):
|
||||
"""
|
||||
Returns a HttpResponse whose content is filled with the result of calling
|
||||
coffin.template.loader.render_to_string() with the passed arguments.
|
||||
Uses a RequestContext by default.
|
||||
"""
|
||||
httpresponse_kwargs = {
|
||||
'content_type': kwargs.pop('content_type', None),
|
||||
'status': kwargs.pop('status', None),
|
||||
}
|
||||
|
||||
if 'context_instance' in kwargs:
|
||||
context_instance = kwargs.pop('context_instance')
|
||||
if kwargs.get('current_app', None):
|
||||
raise ValueError('If you provide a context_instance you must '
|
||||
'set its current_app before calling render()')
|
||||
else:
|
||||
current_app = kwargs.pop('current_app', None)
|
||||
context_instance = RequestContext(request, current_app=current_app)
|
||||
|
||||
kwargs['context_instance'] = context_instance
|
||||
|
||||
return HttpResponse(render_to_string(*args, **kwargs),
|
||||
**httpresponse_kwargs)
|
|
@ -3,15 +3,11 @@ try:
|
|||
except ImportError: # Python 2
|
||||
from urlparse import urljoin
|
||||
|
||||
from coffin.template import Library
|
||||
from jinja2.ext import Extension
|
||||
from jinja2 import nodes
|
||||
from django.utils.encoding import iri_to_uri
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
class PrefixExtension(Extension):
|
||||
|
||||
def parse(self, parser):
|
||||
|
@ -119,12 +115,3 @@ class StaticExtension(PrefixExtension):
|
|||
@classmethod
|
||||
def get_statc_url(cls, path):
|
||||
return urljoin(PrefixExtension.get_uri_setting("STATIC_URL"), path)
|
||||
|
||||
|
||||
register.tag(GetStaticPrefixExtension)
|
||||
register.tag(GetMediaPrefixExtension)
|
||||
register.tag(StaticExtension)
|
||||
|
||||
|
||||
def static(path):
|
||||
return StaticExtension.get_static_url(path)
|
|
@ -1,9 +1,5 @@
|
|||
from coffin import template
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from coffin.templatetags.static import StaticExtension
|
||||
|
||||
|
||||
register = template.Library()
|
||||
from coffin.static import StaticExtension
|
||||
|
||||
|
||||
class StaticExtension(StaticExtension):
|
||||
|
@ -29,10 +25,3 @@ class StaticExtension(StaticExtension):
|
|||
def get_statc_url(cls, path):
|
||||
return super(StaticExtension, cls).get_statc_url(
|
||||
staticfiles_storage.url(path))
|
||||
|
||||
|
||||
register.tag(StaticExtension)
|
||||
|
||||
|
||||
def static(path):
|
||||
return StaticExtension.get_static_url(path)
|
|
@ -1,115 +0,0 @@
|
|||
from django.template import (
|
||||
Context as DjangoContext,
|
||||
add_to_builtins as django_add_to_builtins,
|
||||
import_library,
|
||||
)
|
||||
from jinja2 import Template as _Jinja2Template
|
||||
from jinja2.runtime import Context as _Jinja2Context
|
||||
|
||||
# Merge with ``django.template``.
|
||||
from django.template import __all__
|
||||
from django.template import *
|
||||
from django.template import Origin
|
||||
from django.test import signals
|
||||
|
||||
# Override default library class with ours
|
||||
from library import *
|
||||
|
||||
class Template(_Jinja2Template):
|
||||
'''Fixes the incompabilites between Jinja2's template class and
|
||||
Django's.
|
||||
|
||||
The end result should be a class that renders Jinja2 templates but
|
||||
is compatible with the interface specfied by Django.
|
||||
|
||||
This includes flattening a ``Context`` instance passed to render
|
||||
and making sure that this class will automatically use the global
|
||||
coffin environment.
|
||||
'''
|
||||
|
||||
def __new__(cls, template_string, origin=None, name=None):
|
||||
# We accept the "origin" and "name" arguments, but discard them
|
||||
# right away - Jinja's Template class (apparently) stores no
|
||||
# equivalent information.
|
||||
from coffin.common import env
|
||||
|
||||
return env.from_string(template_string, template_class=cls)
|
||||
|
||||
def __iter__(self):
|
||||
# TODO: Django allows iterating over the templates nodes. Should
|
||||
# be parse ourself and iterate over the AST?
|
||||
raise NotImplementedError()
|
||||
|
||||
def render(self, context=None):
|
||||
"""Differs from Django's own render() slightly in that makes the
|
||||
``context`` parameter optional. We try to strike a middle ground
|
||||
here between implementing Django's interface while still supporting
|
||||
Jinja's own call syntax as well.
|
||||
"""
|
||||
dict_ = {}
|
||||
|
||||
if isinstance(context, dict):
|
||||
dict_ = context
|
||||
|
||||
if isinstance(context, DjangoContext):
|
||||
dict_ = dict_from_django_context(context)
|
||||
|
||||
if isinstance(context, _Jinja2Context):
|
||||
dict_ = context.get_all()
|
||||
|
||||
# It'd be nice to move this only into the test env
|
||||
signals.template_rendered.send(sender=self, template=self, context=DjangoContext(dict_))
|
||||
return super(Template, self).render(**dict_)
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return Origin(self.filename)
|
||||
|
||||
|
||||
def dict_from_django_context(context):
|
||||
"""Flattens a Django :class:`django.template.context.Context` object.
|
||||
"""
|
||||
dict_ = {}
|
||||
# Jinja2 internally converts the context instance to a dictionary, thus
|
||||
# we need to store the current_app attribute as a key/value pair.
|
||||
dict_['_current_app'] = getattr(context, 'current_app', None)
|
||||
|
||||
# Newest dicts are up front, so update from oldest to newest.
|
||||
for subcontext in reversed(list(context)):
|
||||
dict_.update(subcontext)
|
||||
return dict_
|
||||
|
||||
|
||||
# libraries to load by default for a new environment
|
||||
builtins = []
|
||||
|
||||
|
||||
def add_to_builtins(module_name):
|
||||
"""Add the given module to both Coffin's list of default template
|
||||
libraries as well as Django's. This makes sense, since Coffin
|
||||
libs are compatible with Django libraries.
|
||||
|
||||
You can still use Django's own ``add_to_builtins`` to register
|
||||
directly with Django and bypass Coffin.
|
||||
|
||||
Once thing that is special about Coffin is that because {% load %}
|
||||
is not supported in Coffin, *everything* it provides must be
|
||||
registered through the builtins.
|
||||
|
||||
TODO: Allow passing path to (or reference of) extensions and
|
||||
filters directly. This would make it easier to use this function
|
||||
with 3rd party Jinja extensions that do not know about Coffin and
|
||||
thus will not provide a Library object.
|
||||
|
||||
XXX/TODO: Why do we need our own custom list of builtins? Our
|
||||
Library object is compatible, remember!? We can just add them
|
||||
directly to Django's own list of builtins.
|
||||
"""
|
||||
builtins.append(import_library(module_name))
|
||||
django_add_to_builtins(module_name)
|
||||
|
||||
|
||||
add_to_builtins('coffin.template.defaulttags')
|
||||
add_to_builtins('coffin.template.defaultfilters')
|
||||
add_to_builtins('coffin.templatetags.static')
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
"""Coffin automatically makes Django's builtin filters available in Jinja2,
|
||||
through an interop-layer.
|
||||
|
||||
However, Jinja 2 provides room to improve the syntax of some of the
|
||||
filters. Those can be overridden here.
|
||||
|
||||
TODO: Most of the filters in here need to be updated for autoescaping.
|
||||
"""
|
||||
|
||||
from coffin.template import Library
|
||||
from jinja2.runtime import Undefined
|
||||
# from jinja2 import Markup
|
||||
from jinja2 import filters
|
||||
|
||||
register = Library()
|
||||
|
||||
def url(view_name, *args, **kwargs):
|
||||
"""This is an alternative to the {% url %} tag. It comes from a time
|
||||
before Coffin had a port of the tag.
|
||||
"""
|
||||
from coffin.template.defaulttags import url
|
||||
return url._reverse(view_name, args, kwargs)
|
||||
register.jinja2_filter(url, jinja2_only=True)
|
||||
register.object(url)
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def timesince(value, *arg):
|
||||
if value is None or isinstance(value, Undefined):
|
||||
return u''
|
||||
from django.utils.timesince import timesince
|
||||
return timesince(value, *arg)
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def timeuntil(value, *args):
|
||||
if value is None or isinstance(value, Undefined):
|
||||
return u''
|
||||
from django.utils.timesince import timeuntil
|
||||
return timeuntil(value, *args)
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def date(value, arg=None):
|
||||
"""Formats a date according to the given format."""
|
||||
if value is None or isinstance(value, Undefined):
|
||||
return u''
|
||||
from django.conf import settings
|
||||
from django.utils import formats
|
||||
from django.utils.dateformat import format
|
||||
if arg is None:
|
||||
arg = settings.DATE_FORMAT
|
||||
try:
|
||||
return formats.date_format(value, arg)
|
||||
except AttributeError:
|
||||
try:
|
||||
return format(value, arg)
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def time(value, arg=None):
|
||||
"""Formats a time according to the given format."""
|
||||
if value is None or isinstance(value, Undefined):
|
||||
return u''
|
||||
from django.conf import settings
|
||||
from django.utils import formats
|
||||
from django.utils.dateformat import time_format
|
||||
if arg is None:
|
||||
arg = settings.TIME_FORMAT
|
||||
try:
|
||||
return formats.time_format(value, arg)
|
||||
except AttributeError:
|
||||
try:
|
||||
return time_format(value, arg)
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def truncatewords(value, length):
|
||||
# Jinja2 has it's own ``truncate`` filter that supports word
|
||||
# boundaries and more stuff, but cannot deal with HTML.
|
||||
try:
|
||||
from django.utils.text import Truncator
|
||||
except ImportError:
|
||||
from django.utils.text import truncate_words # Django < 1.6
|
||||
else:
|
||||
truncate_words = lambda value, length: Truncator(value).words(length)
|
||||
return truncate_words(value, int(length))
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def truncatewords_html(value, length):
|
||||
try:
|
||||
from django.utils.text import Truncator
|
||||
except ImportError:
|
||||
from django.utils.text import truncate_html_words # Django < 1.6
|
||||
else:
|
||||
truncate_html_words = lambda value, length: Truncator(value).words(length, html=True)
|
||||
return truncate_html_words(value, int(length))
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def pluralize(value, s1='s', s2=None):
|
||||
"""Like Django's pluralize-filter, but instead of using an optional
|
||||
comma to separate singular and plural suffixes, it uses two distinct
|
||||
parameters.
|
||||
|
||||
It also is less forgiving if applied to values that do not allow
|
||||
making a decision between singular and plural.
|
||||
"""
|
||||
if s2 is not None:
|
||||
singular_suffix, plural_suffix = s1, s2
|
||||
else:
|
||||
plural_suffix = s1
|
||||
singular_suffix = ''
|
||||
|
||||
try:
|
||||
if int(value) != 1:
|
||||
return plural_suffix
|
||||
except TypeError: # not a string or a number; maybe it's a list?
|
||||
if len(value) != 1:
|
||||
return plural_suffix
|
||||
return singular_suffix
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def floatformat(value, arg=-1):
|
||||
"""Builds on top of Django's own version, but adds strict error
|
||||
checking, staying with the philosophy.
|
||||
"""
|
||||
from django.template.defaultfilters import floatformat
|
||||
from coffin.interop import django_filter_to_jinja2
|
||||
arg = int(arg) # raise exception
|
||||
result = django_filter_to_jinja2(floatformat)(value, arg)
|
||||
if result == '': # django couldn't handle the value
|
||||
raise ValueError(value)
|
||||
return result
|
||||
|
||||
@register.jinja2_filter(jinja2_only=True)
|
||||
def default(value, default_value=u'', boolean=True):
|
||||
"""Make the default filter, if used without arguments, behave like
|
||||
Django's own version.
|
||||
"""
|
||||
return filters.do_default(value, default_value, boolean)
|
||||
|
|
@ -1,411 +0,0 @@
|
|||
from jinja2 import nodes
|
||||
from jinja2.ext import Extension
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2 import Markup
|
||||
from django.conf import settings
|
||||
from coffin.template import Library
|
||||
|
||||
|
||||
class LoadExtension(Extension):
|
||||
"""The load-tag is a no-op in Coffin. Instead, all template libraries
|
||||
are always loaded.
|
||||
|
||||
Note: Supporting a functioning load-tag in Jinja is tough, though
|
||||
theoretically possible. The trouble is activating new extensions while
|
||||
parsing is ongoing. The ``Parser.extensions`` dict of the current
|
||||
parser instance needs to be modified, but apparently the only way to
|
||||
get access would be by hacking the stack.
|
||||
"""
|
||||
tags = set(['load'])
|
||||
|
||||
def parse(self, parser):
|
||||
while not parser.stream.current.type == 'block_end':
|
||||
parser.stream.next()
|
||||
return []
|
||||
|
||||
|
||||
"""class AutoescapeExtension(Extension):
|
||||
""#"
|
||||
Template to output works in three phases in Jinja2: parsing,
|
||||
generation (compilation, AST-traversal), and rendering (execution).
|
||||
|
||||
Unfortunatly, the environment ``autoescape`` option comes into effect
|
||||
during traversal, the part where we happen to have basically no control
|
||||
over as an extension. It determines whether output is wrapped in
|
||||
``escape()`` calls.
|
||||
|
||||
Solutions that could possibly work:
|
||||
|
||||
* This extension could preprocess it's childnodes and wrap
|
||||
everything output related inside the appropriate
|
||||
``Markup()`` or escape() call.
|
||||
|
||||
* We could use the ``preprocess`` hook to insert the
|
||||
appropriate ``|safe`` and ``|escape`` filters on a
|
||||
string-basis. This is very unlikely to work well.
|
||||
|
||||
There's also the issue of inheritance and just generally the nesting
|
||||
of autoescape-tags to consider.
|
||||
|
||||
Other things of note:
|
||||
|
||||
* We can access ``parser.environment``, but that would only
|
||||
affect the **parsing** of our child nodes.
|
||||
|
||||
* In the commented-out code below we are trying to affect the
|
||||
autoescape setting during rendering. As noted, this could be
|
||||
necessary for rare border cases where custom extension use
|
||||
the autoescape attribute.
|
||||
|
||||
Both the above things would break Environment thread-safety though!
|
||||
|
||||
Overall, it's not looking to good for this extension.
|
||||
""#"
|
||||
|
||||
tags = ['autoescape']
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
|
||||
old_autoescape = parser.environment.autoescape
|
||||
parser.environment.autoescape = True
|
||||
try:
|
||||
body = parser.parse_statements(
|
||||
['name:endautoescape'], drop_needle=True)
|
||||
finally:
|
||||
parser.environment.autoescape = old_autoescape
|
||||
|
||||
# Not sure yet if the code below is necessary - it changes
|
||||
# environment.autoescape during template rendering. If for example
|
||||
# a CallBlock function accesses ``environment.autoescape``, it
|
||||
# presumably is.
|
||||
# This also should use try-finally though, which Jinja's API
|
||||
# doesn't support either. We could fake that as well by using
|
||||
# InternalNames that output the necessary indentation and keywords,
|
||||
# but at this point it starts to get really messy.
|
||||
#
|
||||
# TODO: Actually, there's ``nodes.EnvironmentAttribute``.
|
||||
#ae_setting = object.__new__(nodes.InternalName)
|
||||
#nodes.Node.__init__(ae_setting, 'environment.autoescape',
|
||||
lineno=lineno)
|
||||
#temp = parser.free_identifier()
|
||||
#body.insert(0, nodes.Assign(temp, ae_setting))
|
||||
#body.insert(1, nodes.Assign(ae_setting, nodes.Const(True)))
|
||||
#body.insert(len(body), nodes.Assign(ae_setting, temp))
|
||||
return body
|
||||
"""
|
||||
|
||||
|
||||
class URLExtension(Extension):
|
||||
"""Returns an absolute URL matching given view with its parameters.
|
||||
|
||||
This is a way to define links that aren't tied to a particular URL
|
||||
configuration::
|
||||
|
||||
{% url path.to.some_view arg1,arg2,name1=value1 %}
|
||||
|
||||
Known differences to Django's url-Tag:
|
||||
|
||||
- In Django, the view name may contain any non-space character.
|
||||
Since Jinja's lexer does not identify whitespace to us, only
|
||||
characters that make up valid identifers, plus dots and hyphens
|
||||
are allowed. Note that identifers in Jinja 2 may not contain
|
||||
non-ascii characters.
|
||||
|
||||
As an alternative, you may specifify the view as a string,
|
||||
which bypasses all these restrictions. It further allows you
|
||||
to apply filters:
|
||||
|
||||
{% url "меткаda.some-view"|afilter %}
|
||||
"""
|
||||
|
||||
tags = set(['url'])
|
||||
|
||||
def parse(self, parser):
|
||||
stream = parser.stream
|
||||
|
||||
tag = stream.next()
|
||||
|
||||
# get view name
|
||||
if stream.current.test('string'):
|
||||
# Need to work around Jinja2 syntax here. Jinja by default acts
|
||||
# like Python and concats subsequent strings. In this case
|
||||
# though, we want {% url "app.views.post" "1" %} to be treated
|
||||
# as view + argument, while still supporting
|
||||
# {% url "app.views.post"|filter %}. Essentially, what we do is
|
||||
# rather than let ``parser.parse_primary()`` deal with a "string"
|
||||
# token, we do so ourselves, and let parse_expression() handle all
|
||||
# other cases.
|
||||
if stream.look().test('string'):
|
||||
token = stream.next()
|
||||
viewname = nodes.Const(token.value, lineno=token.lineno)
|
||||
else:
|
||||
viewname = parser.parse_expression()
|
||||
else:
|
||||
# parse valid tokens and manually build a string from them
|
||||
bits = []
|
||||
name_allowed = True
|
||||
while True:
|
||||
if stream.current.test_any('dot', 'sub', 'colon'):
|
||||
bits.append(stream.next())
|
||||
name_allowed = True
|
||||
elif stream.current.test('name') and name_allowed:
|
||||
bits.append(stream.next())
|
||||
name_allowed = False
|
||||
else:
|
||||
break
|
||||
viewname = nodes.Const("".join([b.value for b in bits]))
|
||||
if not bits:
|
||||
raise TemplateSyntaxError("'%s' requires path to view" %
|
||||
tag.value, tag.lineno)
|
||||
|
||||
# get arguments
|
||||
args = []
|
||||
kwargs = []
|
||||
while not stream.current.test_any('block_end', 'name:as'):
|
||||
if args or kwargs:
|
||||
stream.expect('comma')
|
||||
if stream.current.test('name') and stream.look().test('assign'):
|
||||
key = nodes.Const(stream.next().value)
|
||||
stream.skip()
|
||||
value = parser.parse_expression()
|
||||
kwargs.append(nodes.Pair(key, value, lineno=key.lineno))
|
||||
else:
|
||||
args.append(parser.parse_expression())
|
||||
|
||||
def make_call_node(*kw):
|
||||
return self.call_method('_reverse', args=[
|
||||
viewname,
|
||||
nodes.List(args),
|
||||
nodes.Dict(kwargs),
|
||||
nodes.Name('_current_app', 'load'),
|
||||
], kwargs=kw)
|
||||
|
||||
# if an as-clause is specified, write the result to context...
|
||||
if stream.next_if('name:as'):
|
||||
var = nodes.Name(stream.expect('name').value, 'store')
|
||||
call_node = make_call_node(nodes.Keyword('fail',
|
||||
nodes.Const(False)))
|
||||
return nodes.Assign(var, call_node)
|
||||
# ...otherwise print it out.
|
||||
else:
|
||||
return nodes.Output([make_call_node()]).set_lineno(tag.lineno)
|
||||
|
||||
@classmethod
|
||||
def _reverse(self, viewname, args, kwargs, current_app=None, fail=True):
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
|
||||
# Try to look up the URL twice: once given the view name,
|
||||
# and again relative to what we guess is the "main" app.
|
||||
url = ''
|
||||
urlconf=kwargs.pop('urlconf', None)
|
||||
try:
|
||||
url = reverse(viewname, urlconf=urlconf, args=args, kwargs=kwargs,
|
||||
current_app=current_app)
|
||||
except NoReverseMatch as ex:
|
||||
projectname = settings.SETTINGS_MODULE.split('.')[0]
|
||||
try:
|
||||
url = reverse(projectname + '.' + viewname, urlconf=urlconf,
|
||||
args=args, kwargs=kwargs)
|
||||
except NoReverseMatch:
|
||||
if fail:
|
||||
raise ex
|
||||
else:
|
||||
return ''
|
||||
|
||||
return url
|
||||
|
||||
|
||||
class WithExtension(Extension):
|
||||
"""Adds a value to the context (inside this block) for caching and
|
||||
easy access, just like the Django-version does.
|
||||
|
||||
For example::
|
||||
|
||||
{% with person.some_sql_method as total %}
|
||||
{{ total }} object{{ total|pluralize }}
|
||||
{% endwith %}
|
||||
|
||||
TODO: The new Scope node introduced in Jinja2 6334c1eade73 (the 2.2
|
||||
dev version) would help here, but we don't want to rely on that yet.
|
||||
See also:
|
||||
http://dev.pocoo.org/projects/jinja/browser/tests/test_ext.py
|
||||
http://dev.pocoo.org/projects/jinja/ticket/331
|
||||
http://dev.pocoo.org/projects/jinja/ticket/329
|
||||
"""
|
||||
|
||||
tags = set(['with'])
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
value = parser.parse_expression()
|
||||
parser.stream.expect('name:as')
|
||||
name = parser.stream.expect('name')
|
||||
body = parser.parse_statements(['name:endwith'], drop_needle=True)
|
||||
# Use a local variable instead of a macro argument to alias
|
||||
# the expression. This allows us to nest "with" statements.
|
||||
body.insert(0, nodes.Assign(nodes.Name(name.value, 'store'), value))
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_render_block'), [], [], body).\
|
||||
set_lineno(lineno)
|
||||
|
||||
def _render_block(self, caller=None):
|
||||
return caller()
|
||||
|
||||
|
||||
class CacheExtension(Extension):
|
||||
"""Exactly like Django's own tag, but supports full Jinja2
|
||||
expressiveness for all arguments.
|
||||
|
||||
{% cache gettimeout()*2 "foo"+options.cachename %}
|
||||
...
|
||||
{% endcache %}
|
||||
|
||||
This actually means that there is a considerable incompatibility
|
||||
to Django: In Django, the second argument is simply a name, but
|
||||
interpreted as a literal string. This tag, with Jinja2 stronger
|
||||
emphasis on consistent syntax, requires you to actually specify the
|
||||
quotes around the name to make it a string. Otherwise, allowing
|
||||
Jinja2 expressions would be very hard to impossible (one could use
|
||||
a lookahead to see if the name is followed by an operator, and
|
||||
evaluate it as an expression if so, or read it as a string if not.
|
||||
TODO: This may not be the right choice. Supporting expressions
|
||||
here is probably not very important, so compatibility should maybe
|
||||
prevail. Unfortunately, it is actually pretty hard to be compatibly
|
||||
in all cases, simply because Django's per-character parser will
|
||||
just eat everything until the next whitespace and consider it part
|
||||
of the fragment name, while we have to work token-based: ``x*2``
|
||||
would actually be considered ``"x*2"`` in Django, while Jinja2
|
||||
would give us three tokens: ``x``, ``*``, ``2``.
|
||||
|
||||
General Syntax:
|
||||
|
||||
{% cache [expire_time] [fragment_name] [var1] [var2] .. %}
|
||||
.. some expensive processing ..
|
||||
{% endcache %}
|
||||
|
||||
Available by default (does not need to be loaded).
|
||||
|
||||
Partly based on the ``FragmentCacheExtension`` from the Jinja2 docs.
|
||||
|
||||
TODO: Should there be scoping issues with the internal dummy macro
|
||||
limited access to certain outer variables in some cases, there is a
|
||||
different way to write this. Generated code would look like this:
|
||||
|
||||
internal_name = environment.extensions['..']._get_cache_value():
|
||||
if internal_name is not None:
|
||||
yield internal_name
|
||||
else:
|
||||
internal_name = "" # or maybe use [] and append() for performance
|
||||
internalname += "..."
|
||||
internalname += "..."
|
||||
internalname += "..."
|
||||
environment.extensions['..']._set_cache_value(internalname):
|
||||
yield internalname
|
||||
|
||||
In other words, instead of using a CallBlock which uses a local
|
||||
function and calls into python, we have to separate calls into
|
||||
python, but put the if-else logic itself into the compiled template.
|
||||
"""
|
||||
|
||||
tags = set(['cache'])
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
|
||||
expire_time = parser.parse_expression()
|
||||
fragment_name = parser.parse_expression()
|
||||
vary_on = []
|
||||
while not parser.stream.current.test('block_end'):
|
||||
vary_on.append(parser.parse_expression())
|
||||
|
||||
body = parser.parse_statements(['name:endcache'], drop_needle=True)
|
||||
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_cache_support',
|
||||
[expire_time, fragment_name,
|
||||
nodes.List(vary_on), nodes.Const(lineno)]),
|
||||
[], [], body).set_lineno(lineno)
|
||||
|
||||
def _cache_support(self, expire_time, fragm_name, vary_on, lineno, caller):
|
||||
from hashlib import md5
|
||||
from django.core.cache import cache # delay depending in settings
|
||||
from django.utils.http import urlquote
|
||||
|
||||
try:
|
||||
expire_time = int(expire_time)
|
||||
except (ValueError, TypeError):
|
||||
raise TemplateSyntaxError('"%s" tag got a non-integer timeout '
|
||||
'value: %r' % (list(self.tags)[0], expire_time), lineno)
|
||||
|
||||
args_string = u':'.join([urlquote(v) for v in vary_on])
|
||||
args_md5 = md5(args_string)
|
||||
cache_key = 'template.cache.%s.%s' % (fragm_name, args_md5.hexdigest())
|
||||
value = cache.get(cache_key)
|
||||
if value is None:
|
||||
value = caller()
|
||||
cache.set(cache_key, value, expire_time)
|
||||
return value
|
||||
|
||||
|
||||
class SpacelessExtension(Extension):
|
||||
"""Removes whitespace between HTML tags, including tab and
|
||||
newline characters.
|
||||
|
||||
Works exactly like Django's own tag.
|
||||
"""
|
||||
|
||||
tags = set(['spaceless'])
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
body = parser.parse_statements(['name:endspaceless'], drop_needle=True)
|
||||
return nodes.CallBlock(
|
||||
self.call_method('_strip_spaces', [], [], None, None),
|
||||
[], [], body,
|
||||
).set_lineno(lineno)
|
||||
|
||||
def _strip_spaces(self, caller=None):
|
||||
from django.utils.html import strip_spaces_between_tags
|
||||
return strip_spaces_between_tags(caller().strip())
|
||||
|
||||
|
||||
class CsrfTokenExtension(Extension):
|
||||
"""Jinja2-version of the ``csrf_token`` tag.
|
||||
|
||||
Adapted from a snippet by Jason Green:
|
||||
http://www.djangosnippets.org/snippets/1847/
|
||||
|
||||
This tag is a bit stricter than the Django tag in that it doesn't
|
||||
simply ignore any invalid arguments passed in.
|
||||
"""
|
||||
|
||||
tags = set(['csrf_token'])
|
||||
|
||||
def parse(self, parser):
|
||||
lineno = parser.stream.next().lineno
|
||||
return nodes.Output([
|
||||
self.call_method('_render', [nodes.Name('csrf_token', 'load')]),
|
||||
]).set_lineno(lineno)
|
||||
|
||||
def _render(self, csrf_token):
|
||||
from django.template.defaulttags import CsrfTokenNode
|
||||
return Markup(CsrfTokenNode().render({'csrf_token': csrf_token}))
|
||||
|
||||
|
||||
# nicer import names
|
||||
load = LoadExtension
|
||||
url = URLExtension
|
||||
with_ = WithExtension
|
||||
cache = CacheExtension
|
||||
spaceless = SpacelessExtension
|
||||
csrf_token = CsrfTokenExtension
|
||||
|
||||
register = Library()
|
||||
register.tag(load)
|
||||
register.tag(url)
|
||||
register.tag(with_)
|
||||
register.tag(cache)
|
||||
register.tag(spaceless)
|
||||
register.tag(csrf_token)
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
from django.template import Library as DjangoLibrary, InvalidTemplateLibrary
|
||||
from jinja2.ext import Extension as Jinja2Extension
|
||||
import types
|
||||
from coffin.interop import (
|
||||
DJANGO, JINJA2,
|
||||
guess_filter_type, jinja2_filter_to_django, django_filter_to_jinja2)
|
||||
|
||||
|
||||
__all__ = ['Library']
|
||||
|
||||
|
||||
class Library(DjangoLibrary):
|
||||
"""Version of the Django ``Library`` class that can handle both
|
||||
Django template engine tags and filters, as well as Jinja2
|
||||
extensions and filters.
|
||||
|
||||
Tries to present a common registration interface to the extension
|
||||
author, but provides both template engines with only those
|
||||
components they can support.
|
||||
|
||||
Since custom Django tags and Jinja2 extensions are two completely
|
||||
different beasts, they are handled completely separately. You can
|
||||
register custom Django tags as usual, for example:
|
||||
|
||||
register.tag('current_time', do_current_time)
|
||||
|
||||
Or register a Jinja2 extension like this:
|
||||
|
||||
register.tag(CurrentTimeNode)
|
||||
|
||||
Filters, on the other hand, work similarily in both engines, and
|
||||
for the most one can't tell whether a filter function was written
|
||||
for Django or Jinja2. A compatibility layer is used to make to
|
||||
make the filters you register usuable with both engines:
|
||||
|
||||
register.filter('cut', cut)
|
||||
|
||||
However, some of the more powerful filters just won't work in
|
||||
Django, for example if more than one argument is required, or if
|
||||
context- or environmentfilters are used. If ``cut`` in the above
|
||||
example where such an extended filter, it would only be registered
|
||||
with Jinja.
|
||||
|
||||
See also the module documentation for ``coffin.interop`` for
|
||||
information on some of the limitations of this conversion.
|
||||
|
||||
TODO: Jinja versions of the ``simple_tag`` and ``inclusion_tag``
|
||||
helpers would be nice, though since custom tags are not needed as
|
||||
often in Jinja, this is not urgent.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Library, self).__init__()
|
||||
self.jinja2_filters = {}
|
||||
self.jinja2_extensions = []
|
||||
self.jinja2_environment_attrs = {}
|
||||
self.jinja2_globals = {}
|
||||
self.jinja2_tests = {}
|
||||
|
||||
@classmethod
|
||||
def from_django(cls, django_library):
|
||||
"""Create a Coffin library object from a Django library.
|
||||
|
||||
Specifically, this ensures that filters already registered
|
||||
with the Django library are also made available to Jinja,
|
||||
where applicable.
|
||||
"""
|
||||
from copy import copy
|
||||
result = cls()
|
||||
result.tags = copy(django_library.tags)
|
||||
for name, func in django_library.filters.iteritems():
|
||||
result._register_filter(name, func, type='django')
|
||||
return result
|
||||
|
||||
def test(self, name=None, func=None):
|
||||
def inner(f):
|
||||
name = getattr(f, "_decorated_function", f).__name__
|
||||
self.jinja2_tests[name] = f
|
||||
return f
|
||||
if name == None and func == None:
|
||||
# @register.test()
|
||||
return inner
|
||||
elif func == None:
|
||||
if (callable(name)):
|
||||
# register.test()
|
||||
return inner(name)
|
||||
else:
|
||||
# @register.test('somename') or @register.test(name='somename')
|
||||
def dec(func):
|
||||
return self.test(name, func)
|
||||
return dec
|
||||
elif name != None and func != None:
|
||||
# register.filter('somename', somefunc)
|
||||
self.jinja2_tests[name] = func
|
||||
return func
|
||||
else:
|
||||
raise InvalidTemplateLibrary("Unsupported arguments to "
|
||||
"Library.test: (%r, %r)", (name, func))
|
||||
|
||||
def object(self, name=None, func=None):
|
||||
def inner(f):
|
||||
name = getattr(f, "_decorated_function", f).__name__
|
||||
self.jinja2_globals[name] = f
|
||||
return f
|
||||
if name == None and func == None:
|
||||
# @register.object()
|
||||
return inner
|
||||
elif func == None:
|
||||
if (callable(name)):
|
||||
# register.object()
|
||||
return inner(name)
|
||||
else:
|
||||
# @register.object('somename') or @register.object(name='somename')
|
||||
def dec(func):
|
||||
return self.object(name, func)
|
||||
return dec
|
||||
elif name != None and func != None:
|
||||
# register.object('somename', somefunc)
|
||||
self.jinja2_globals[name] = func
|
||||
return func
|
||||
else:
|
||||
raise InvalidTemplateLibrary("Unsupported arguments to "
|
||||
"Library.object: (%r, %r)", (name, func))
|
||||
|
||||
def tag(self, name_or_node=None, compile_function=None, environment={}):
|
||||
"""Register a Django template tag (1) or Jinja 2 extension (2).
|
||||
|
||||
For (1), supports the same invocation syntax as the original
|
||||
Django version, including use as a decorator.
|
||||
|
||||
For (2), since Jinja 2 extensions are classes (which can't be
|
||||
decorated), and have the tag name effectively built in, only the
|
||||
following syntax is supported:
|
||||
|
||||
register.tag(MyJinjaExtensionNode)
|
||||
|
||||
If your extension needs to be configured by setting environment
|
||||
attributes, you can can pass key-value pairs via ``environment``.
|
||||
"""
|
||||
if isinstance(name_or_node, type) and issubclass(name_or_node, Jinja2Extension):
|
||||
if compile_function:
|
||||
raise InvalidTemplateLibrary('"compile_function" argument not supported for Jinja2 extensions')
|
||||
self.jinja2_extensions.append(name_or_node)
|
||||
self.jinja2_environment_attrs.update(environment)
|
||||
return name_or_node
|
||||
else:
|
||||
if environment:
|
||||
raise InvalidTemplateLibrary('"environment" argument not supported for Django tags')
|
||||
return super(Library, self).tag(name_or_node, compile_function)
|
||||
|
||||
def tag_function(self, func_or_node):
|
||||
if not isinstance(func_or_node, types.FunctionType) and \
|
||||
issubclass(func_or_node, Jinja2Extension):
|
||||
self.jinja2_extensions.append(func_or_node)
|
||||
return func_or_node
|
||||
else:
|
||||
return super(Library, self).tag_function(func_or_node)
|
||||
|
||||
def filter(self, name=None, filter_func=None, type=None, jinja2_only=None):
|
||||
"""Register a filter with both the Django and Jinja2 template
|
||||
engines, if possible - or only Jinja2, if ``jinja2_only`` is
|
||||
specified. ``jinja2_only`` does not affect conversion of the
|
||||
filter if neccessary.
|
||||
|
||||
Implements a compatibility layer to handle the different
|
||||
auto-escaping approaches transparently. Extended Jinja2 filter
|
||||
features like environment- and contextfilters are however not
|
||||
supported in Django. Such filters will only be registered with
|
||||
Jinja.
|
||||
|
||||
If you know which template language the filter was written for,
|
||||
you may want to specify type="django" or type="jinja2", to disable
|
||||
the interop layer which in some cases might not be able to operate
|
||||
entirely opaque. For example, Jinja 2 filters may not receive a
|
||||
"Undefined" value if the interop layer is applied.
|
||||
|
||||
Supports the same invocation syntax as the original Django
|
||||
version, including use as a decorator.
|
||||
|
||||
If the function is supposed to return the registered filter
|
||||
(by example of the superclass implementation), but has
|
||||
registered multiple filters, a tuple of all filters is
|
||||
returned.
|
||||
"""
|
||||
def filter_function(f):
|
||||
return self._register_filter(
|
||||
getattr(f, "_decorated_function", f).__name__,
|
||||
f, type=type, jinja2_only=jinja2_only)
|
||||
if name == None and filter_func == None:
|
||||
# @register.filter()
|
||||
return filter_function
|
||||
elif filter_func == None:
|
||||
if (callable(name)):
|
||||
# @register.filter
|
||||
return filter_function(name)
|
||||
else:
|
||||
# @register.filter('somename') or @register.filter(name='somename')
|
||||
def dec(func):
|
||||
return self.filter(name, func, type=type,
|
||||
jinja2_only=jinja2_only)
|
||||
return dec
|
||||
elif name != None and filter_func != None:
|
||||
# register.filter('somename', somefunc)
|
||||
return self._register_filter(name, filter_func, type=type,
|
||||
jinja2_only=jinja2_only)
|
||||
else:
|
||||
raise InvalidTemplateLibrary("Unsupported arguments to "
|
||||
"Library.filter: (%r, %r)", (name, filter_func))
|
||||
|
||||
def jinja2_filter(self, *args, **kwargs):
|
||||
"""Shortcut for filter(type='jinja2').
|
||||
"""
|
||||
kw = {'type': JINJA2}
|
||||
kw.update(kwargs)
|
||||
return self.filter(*args, **kw)
|
||||
|
||||
def _register_filter(self, name, func, type=None, jinja2_only=None):
|
||||
assert type in (None, JINJA2, DJANGO,)
|
||||
|
||||
# The user might not specify the language the filter was written
|
||||
# for, but sometimes we can auto detect it.
|
||||
filter_type, can_be_ported = guess_filter_type(func)
|
||||
assert not (filter_type and type) or filter_type == type, \
|
||||
"guessed filter type (%s) not matching claimed type (%s)" % (
|
||||
filter_type, type,
|
||||
)
|
||||
if not filter_type and type:
|
||||
filter_type = type
|
||||
|
||||
if filter_type == JINJA2:
|
||||
self.jinja2_filters[name] = func
|
||||
if can_be_ported and not jinja2_only:
|
||||
self.filters[name] = jinja2_filter_to_django(func)
|
||||
return func
|
||||
elif filter_type == DJANGO:
|
||||
self.filters[name] = func
|
||||
if not can_be_ported and jinja2_only:
|
||||
raise ValueError('This filter cannot be ported to Jinja2.')
|
||||
if can_be_ported:
|
||||
self.jinja2_filters[name] = django_filter_to_jinja2(func)
|
||||
return func
|
||||
else:
|
||||
django_func = jinja2_filter_to_django(func)
|
||||
jinja2_func = django_filter_to_jinja2(func)
|
||||
if jinja2_only:
|
||||
self.jinja2_filters[name] = jinja2_func
|
||||
return jinja2_func
|
||||
else:
|
||||
# register the filter with both engines
|
||||
self.filters[name] = django_func
|
||||
self.jinja2_filters[name] = jinja2_func
|
||||
return (django_func, jinja2_func)
|
|
@ -1,69 +0,0 @@
|
|||
"""Replacement for ``django.template.loader`` that uses Jinja 2.
|
||||
|
||||
The module provides a generic way to load templates from an arbitrary
|
||||
backend storage (e.g. filesystem, database).
|
||||
"""
|
||||
|
||||
from django.template import TemplateDoesNotExist
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
|
||||
def find_template_source(name, dirs=None):
|
||||
# This is Django's most basic loading function through which
|
||||
# all template retrievals go. Not sure if Jinja 2 publishes
|
||||
# an equivalent, but no matter, it mostly for internal use
|
||||
# anyway - developers will want to start with
|
||||
# ``get_template()`` or ``get_template_from_string`` anyway.
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def get_template(template_name):
|
||||
# Jinja will handle this for us, and env also initializes
|
||||
# the loader backends the first time it is called.
|
||||
from coffin.common import env
|
||||
try:
|
||||
return env.get_template(template_name)
|
||||
except TemplateNotFound:
|
||||
raise TemplateDoesNotExist(template_name)
|
||||
|
||||
|
||||
def get_template_from_string(source):
|
||||
"""
|
||||
Does not support then ``name`` and ``origin`` parameters from
|
||||
the Django version.
|
||||
"""
|
||||
from coffin.common import env
|
||||
return env.from_string(source)
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||
"""Loads the given ``template_name`` and renders it with the given
|
||||
dictionary as context. The ``template_name`` may be a string to load
|
||||
a single template using ``get_template``, or it may be a tuple to use
|
||||
``select_template`` to find one of the templates in the list.
|
||||
|
||||
``dictionary`` may also be Django ``Context`` object.
|
||||
|
||||
Returns a string.
|
||||
"""
|
||||
dictionary = dictionary or {}
|
||||
if isinstance(template_name, (list, tuple)):
|
||||
template = select_template(template_name)
|
||||
else:
|
||||
template = get_template(template_name)
|
||||
if context_instance:
|
||||
context_instance.update(dictionary)
|
||||
else:
|
||||
context_instance = dictionary
|
||||
return template.render(context_instance)
|
||||
|
||||
|
||||
def select_template(template_name_list):
|
||||
"Given a list of template names, returns the first that can be loaded."
|
||||
for template_name in template_name_list:
|
||||
try:
|
||||
return get_template(template_name)
|
||||
except TemplateDoesNotExist:
|
||||
continue
|
||||
# If we get here, none of the templates could be loaded
|
||||
raise TemplateDoesNotExist(', '.join(template_name_list))
|
|
@ -1,91 +0,0 @@
|
|||
import re
|
||||
from jinja2 import loaders
|
||||
|
||||
match_loader = re.compile(r'^(django|coffin)\.')
|
||||
|
||||
|
||||
def jinja_loader_from_django_loader(django_loader, args=None):
|
||||
"""Attempts to make a conversion from the given Django loader to an
|
||||
similarly-behaving Jinja loader.
|
||||
|
||||
:param django_loader: Django loader module string.
|
||||
:return: The similarly-behaving Jinja loader, or None if a similar loader
|
||||
could not be found.
|
||||
"""
|
||||
if not match_loader.match(django_loader):
|
||||
return None
|
||||
for substr, func in _JINJA_LOADER_BY_DJANGO_SUBSTR.iteritems():
|
||||
if substr in django_loader:
|
||||
return func(*(args or []))
|
||||
return None
|
||||
|
||||
|
||||
def _make_jinja_app_loader():
|
||||
"""Makes an 'app loader' for Jinja which acts like
|
||||
:mod:`django.template.loaders.app_directories`.
|
||||
"""
|
||||
from django.template.loaders.app_directories import app_template_dirs
|
||||
return loaders.FileSystemLoader(app_template_dirs)
|
||||
|
||||
|
||||
def _make_jinja_filesystem_loader():
|
||||
"""Makes a 'filesystem loader' for Jinja which acts like
|
||||
:mod:`django.template.loaders.filesystem`.
|
||||
"""
|
||||
from django.conf import settings
|
||||
return loaders.FileSystemLoader(settings.TEMPLATE_DIRS)
|
||||
|
||||
|
||||
def _make_jinja_cached_loader(*loaders):
|
||||
"""Makes a loader for Jinja which acts like
|
||||
:mod:`django.template.loaders.cached`.
|
||||
"""
|
||||
return JinjaCachedLoader(
|
||||
[jinja_loader_from_django_loader(l) for l in loaders])
|
||||
|
||||
|
||||
# Determine loaders from Django's conf.
|
||||
_JINJA_LOADER_BY_DJANGO_SUBSTR = { # {substr: callable, ...}
|
||||
'app_directories': _make_jinja_app_loader,
|
||||
'filesystem': _make_jinja_filesystem_loader,
|
||||
'cached': _make_jinja_cached_loader,
|
||||
'AppLoader': _make_jinja_app_loader,
|
||||
'FileSystemLoader': _make_jinja_filesystem_loader,
|
||||
}
|
||||
|
||||
|
||||
class JinjaCachedLoader(loaders.BaseLoader):
|
||||
"""A "sort of" port of of Django's "cached" template loader
|
||||
to Jinja 2. It exists primarily to support Django's full
|
||||
TEMPLATE_LOADERS syntax.
|
||||
|
||||
However, note that it does not behave exactly like Django's cached
|
||||
loader: Rather than caching the compiled template, it only caches
|
||||
the template source, and recompiles the template every time. This is
|
||||
due to the way the Jinja2/Coffin loader setup works: The ChoiceLoader,
|
||||
which Coffin uses at the root to select from any of the configured
|
||||
loaders, calls the ``get_source`` method of each loader directly,
|
||||
bypassing ``load``. Our loader can therefore only hook into the process
|
||||
BEFORE template compilation.
|
||||
Caching the compiled templates by implementing ``load`` would only
|
||||
work if this loader instance were the root loader. See also the comments
|
||||
in Jinja2's BaseLoader class.
|
||||
|
||||
Note that Jinja2 has an environment-wide bytecode cache (i.e. it caches
|
||||
compiled templates), that can function alongside with this class.
|
||||
|
||||
Note further that Jinja2 has an environment-wide template cache (via the
|
||||
``auto_reload`` environment option), which duplicate the functionality
|
||||
of this class entirely, and should be preferred when possible.
|
||||
"""
|
||||
|
||||
def __init__(self, subloaders):
|
||||
self.loader = loaders.ChoiceLoader(subloaders)
|
||||
self.template_cache = {}
|
||||
|
||||
def get_source(self, environment, template):
|
||||
key = (environment, template)
|
||||
if key not in self.template_cache:
|
||||
result = self.loader.get_source(environment, template)
|
||||
self.template_cache[key] = result
|
||||
return self.template_cache[key]
|
|
@ -1,16 +0,0 @@
|
|||
from coffin.template import loader
|
||||
from django.template import response as django_response
|
||||
|
||||
|
||||
class SimpleTemplateResponse(django_response.SimpleTemplateResponse):
|
||||
def resolve_template(self, template):
|
||||
if isinstance(template, (list, tuple)):
|
||||
return loader.select_template(template)
|
||||
elif isinstance(template, basestring):
|
||||
return loader.get_template(template)
|
||||
else:
|
||||
return template
|
||||
|
||||
class TemplateResponse(django_response.TemplateResponse,
|
||||
SimpleTemplateResponse):
|
||||
pass
|
|
@ -1,9 +0,0 @@
|
|||
from coffin.template.response import TemplateResponse
|
||||
|
||||
def template_response(cls):
|
||||
"""
|
||||
A decorator to enforce class_based generic views
|
||||
to use coffin TemplateResponse
|
||||
"""
|
||||
cls.response_class = TemplateResponse
|
||||
return cls
|
|
@ -1,35 +0,0 @@
|
|||
from django import http
|
||||
from django.template import Context, RequestContext
|
||||
from coffin.template.loader import render_to_string
|
||||
|
||||
|
||||
__all__ = ('page_not_found', 'server_error', 'shortcut')
|
||||
|
||||
|
||||
# no Jinja version for this needed
|
||||
from django.views.defaults import shortcut
|
||||
|
||||
|
||||
def page_not_found(request, template_name='404.html'):
|
||||
"""
|
||||
Default 404 handler.
|
||||
|
||||
Templates: `404.html`
|
||||
Context:
|
||||
request_path
|
||||
The path of the requested URL (e.g., '/app/pages/bad_page/')
|
||||
"""
|
||||
content = render_to_string(template_name,
|
||||
RequestContext(request, {'request_path': request.path}))
|
||||
return http.HttpResponseNotFound(content)
|
||||
|
||||
|
||||
def server_error(request, template_name='500.html'):
|
||||
"""
|
||||
500 error handler.
|
||||
|
||||
Templates: `500.html`
|
||||
Context: None
|
||||
"""
|
||||
content = render_to_string(template_name, Context({}))
|
||||
return http.HttpResponseServerError(content)
|
|
@ -1,15 +0,0 @@
|
|||
from django.views.generic import GenericViewError
|
||||
try:
|
||||
from django.views.generic.base import View, RedirectView
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from coffin.views.generic.base import TemplateView
|
||||
from coffin.views.generic.dates import (ArchiveIndexView, YearArchiveView,
|
||||
MonthArchiveView, WeekArchiveView,
|
||||
DayArchiveView, TodayArchiveView,
|
||||
DateDetailView)
|
||||
from coffin.views.generic.detail import DetailView
|
||||
from coffin.views.generic.edit import (FormView, CreateView, UpdateView,
|
||||
DeleteView)
|
||||
from coffin.views.generic.list import ListView
|
|
@ -1,13 +0,0 @@
|
|||
import django.views.generic.base as _generic_base
|
||||
from coffin.template.response import TemplateResponse as JinjaTemplateResponse
|
||||
|
||||
class TemplateResponseMixin(_generic_base.TemplateResponseMixin):
|
||||
"""
|
||||
A mixin that can be used to render a template using Jinja.
|
||||
"""
|
||||
response_class = JinjaTemplateResponse
|
||||
|
||||
class TemplateView(TemplateResponseMixin, _generic_base.TemplateView):
|
||||
"""
|
||||
A view that renders a template using Jinja.
|
||||
"""
|
|
@ -1,7 +0,0 @@
|
|||
from coffin.template import loader
|
||||
from django.views.generic import create_update as _create_update
|
||||
import functools
|
||||
|
||||
create_object = functools.partial(_create_update.create_object, template_loader=loader)
|
||||
update_object = functools.partial(_create_update.update_object, template_loader=loader)
|
||||
delete_object = functools.partial(_create_update.delete_object, template_loader=loader)
|
|
@ -1,13 +0,0 @@
|
|||
from coffin.template import loader
|
||||
from django.views.generic import date_based as _date_based
|
||||
import functools
|
||||
|
||||
|
||||
archive_index = functools.partial(_date_based.archive_index, template_loader=loader)
|
||||
archive_year = functools.partial(_date_based.archive_year, template_loader=loader)
|
||||
archive_month = functools.partial(_date_based.archive_month, template_loader=loader)
|
||||
archive_week = functools.partial(_date_based.archive_week, template_loader=loader)
|
||||
archive_daye = functools.partial(_date_based.archive_day, template_loader=loader)
|
||||
archive_today = functools.partial(_date_based.archive_today, template_loader=loader)
|
||||
|
||||
object_detail = functools.partial(_date_based.object_detail, template_loader=loader)
|
|
@ -1,50 +0,0 @@
|
|||
from coffin.views.generic.detail import SingleObjectTemplateResponseMixin
|
||||
from coffin.views.generic.list import MultipleObjectTemplateResponseMixin
|
||||
import django.views.generic.dates as _generic_dates
|
||||
|
||||
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseArchiveIndexView):
|
||||
"""
|
||||
Equivalent of django generic view ArchiveIndexView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive'
|
||||
|
||||
|
||||
class YearArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseYearArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view YearArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive_year'
|
||||
|
||||
|
||||
class MonthArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseMonthArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view MonthArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive_month'
|
||||
|
||||
|
||||
class WeekArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseWeekArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view WeekArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_archive_week'
|
||||
|
||||
|
||||
class DayArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseDayArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view DayArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = "_archive_day"
|
||||
|
||||
class TodayArchiveView(MultipleObjectTemplateResponseMixin, _generic_dates.BaseTodayArchiveView):
|
||||
"""
|
||||
Equivalent of django generic view TodayArchiveView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = "_archive_day"
|
||||
|
||||
|
||||
class DateDetailView(SingleObjectTemplateResponseMixin, _generic_dates.BaseDateDetailView):
|
||||
"""
|
||||
Equivalent of django generic view DateDetailView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_detail'
|
|
@ -1,12 +0,0 @@
|
|||
import django.views.generic.detail as _generic_detail
|
||||
from coffin.views.generic.base import TemplateResponseMixin as JinjaTemplateResponseMixin
|
||||
|
||||
class SingleObjectTemplateResponseMixin(JinjaTemplateResponseMixin, _generic_detail.SingleObjectTemplateResponseMixin):
|
||||
"""
|
||||
Equivalent of django mixin SingleObjectTemplateResponseMixin, but uses Jinja template renderer.
|
||||
"""
|
||||
|
||||
class DetailView(SingleObjectTemplateResponseMixin, _generic_detail.BaseDetailView):
|
||||
"""
|
||||
Equivalent of django generic view DetailView, but uses Jinja template renderer.
|
||||
"""
|
|
@ -1,30 +0,0 @@
|
|||
from coffin.views.generic.base import TemplateResponseMixin
|
||||
from coffin.views.generic.detail import SingleObjectTemplateResponseMixin
|
||||
import django.views.generic.edit as _generic_edit
|
||||
|
||||
|
||||
class FormView(TemplateResponseMixin, _generic_edit.BaseFormView):
|
||||
"""
|
||||
Equivalent of django generic view FormView, but uses Jinja template renderer.
|
||||
"""
|
||||
|
||||
|
||||
class CreateView(SingleObjectTemplateResponseMixin, _generic_edit.BaseCreateView):
|
||||
"""
|
||||
Equivalent of django generic view CreateView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_form'
|
||||
|
||||
|
||||
class UpdateView(SingleObjectTemplateResponseMixin, _generic_edit.BaseUpdateView):
|
||||
"""
|
||||
Equivalent of django generic view UpdateView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_form'
|
||||
|
||||
|
||||
class DeleteView(SingleObjectTemplateResponseMixin, _generic_edit.BaseDeleteView):
|
||||
"""
|
||||
Equivalent of django generic view DeleteView, but uses Jinja template renderer.
|
||||
"""
|
||||
template_name_suffix = '_confirm_delete'
|
|
@ -1,12 +0,0 @@
|
|||
import django.views.generic.list as _generic_list
|
||||
from coffin.views.generic.base import TemplateResponseMixin as JinjaTemplateResponseMixin
|
||||
|
||||
class MultipleObjectTemplateResponseMixin(JinjaTemplateResponseMixin, _generic_list.MultipleObjectTemplateResponseMixin):
|
||||
"""
|
||||
Equivalent of django mixin MultipleObjectTemplateResponseMixin, but uses Jinja template renderer.
|
||||
"""
|
||||
|
||||
class ListView(MultipleObjectTemplateResponseMixin, _generic_list.BaseListView):
|
||||
"""
|
||||
Equivalent of django generic view ListView, but uses Jinja template renderer.
|
||||
"""
|
|
@ -1,6 +0,0 @@
|
|||
from coffin.template import loader
|
||||
from django.views.generic import list_detail as _list_detail
|
||||
import functools
|
||||
|
||||
object_list = functools.partial(_list_detail.object_list, template_loader=loader)
|
||||
object_detail = functools.partial(_list_detail.object_detail, template_loader=loader)
|
|
@ -1,6 +0,0 @@
|
|||
import inspect
|
||||
|
||||
from django.views.generic.simple import *
|
||||
from coffin.template import loader, RequestContext
|
||||
|
||||
exec inspect.getsource(direct_to_template)
|
|
@ -1,88 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf _build/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in _build/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in _build/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in _build/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in _build/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator _build/qthelp/Coffin.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile _build/qthelp/Coffin.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in _build/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
|
||||
@echo
|
||||
@echo "The overview file is in _build/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in _build/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in _build/doctest/output.txt."
|
195
docs/conf.py
195
docs/conf.py
|
@ -1,195 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Coffin documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Sep 8 15:22:15 2009.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Coffin'
|
||||
copyright = u'2009, Christopher D. Leary'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
|
||||
import coffin
|
||||
# The short X.Y version.
|
||||
version = '.'.join(map(str, coffin.__version__))
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '.'.join(map(str, coffin.__version__))
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Coffindoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Coffin.tex', u'Coffin Documentation',
|
||||
u'Christopher D. Leary', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
|
@ -1,3 +0,0 @@
|
|||
==========================
|
||||
:mod:`coffin.contrib.auth`
|
||||
==========================
|
|
@ -1,26 +0,0 @@
|
|||
=====================
|
||||
:mod:`coffin.contrib`
|
||||
=====================
|
||||
|
||||
Coffin includes replacements for several Django contrib modules.
|
||||
|
||||
To use this, simply change your import line from::
|
||||
|
||||
from django.contrib.<module>
|
||||
|
||||
To the following::
|
||||
|
||||
from coffin.contrib.<module>
|
||||
|
||||
-----------------
|
||||
Supported Modules
|
||||
-----------------
|
||||
|
||||
The following drop-in replacements are available:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
auth
|
||||
markup
|
||||
syndication
|
|
@ -1,3 +0,0 @@
|
|||
============================
|
||||
:mod:`coffin.contrib.markup`
|
||||
============================
|
|
@ -1,3 +0,0 @@
|
|||
=================================
|
||||
:mod:`coffin.contrib.syndication`
|
||||
=================================
|
|
@ -1,18 +0,0 @@
|
|||
Coffin Documentation
|
||||
====================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install
|
||||
contrib/index
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Install the package through PyPi::
|
||||
|
||||
easy_install Coffin
|
||||
|
||||
Or alternatively, get the source::
|
||||
|
||||
git clone git://github.com/dcramer/coffin.git
|
||||
cd coffin
|
||||
python setup.py install
|
||||
|
||||
Once installed, you will need to add Coffin to several places throughout your projects.
|
||||
|
||||
First, open up ``settings.py`` and add Coffin to your ``INSTALLED_APPS``::
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'coffin',
|
||||
...
|
||||
)
|
||||
|
||||
The easiest way to enable Jinja2, rather than Django, is to change your import paths. For example, if we're using the ``render_to_response`` shortcut, we simply need to tweak our import line::
|
||||
|
||||
from django.shortcuts import render_to_response
|
||||
|
||||
To the following::
|
||||
|
||||
from coffin.shortcuts import render_to_response
|
||||
|
||||
Coffin includes drop in replacements for the following Django modules:
|
||||
|
||||
* :mod:`django.shortcuts`
|
||||
* :mod:`django.views.generic.simple`
|
||||
* :mod:`django.contrib.auth`
|
||||
* :mod:`django.contrib.markup`
|
||||
* :mod:`django.contrib.syndication`
|
||||
* :mod:`django.template`
|
112
docs/make.bat
112
docs/make.bat
|
@ -1,112 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
set SPHINXBUILD=sphinx-build
|
||||
set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (_build\*) do rmdir /q /s %%i
|
||||
del /q /s _build\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in _build/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in _build/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in _build/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in _build/qthelp, like this:
|
||||
echo.^> qcollectiongenerator _build\qthelp\Coffin.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile _build\qthelp\Coffin.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in _build/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes
|
||||
echo.
|
||||
echo.The overview file is in _build/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in _build/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in _build/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -1,10 +0,0 @@
|
|||
from os import path
|
||||
import sys
|
||||
|
||||
# Setup Django with our test demo project. We need to do this in global
|
||||
# module code rather than setup_package(), because we want it to run
|
||||
# before any module-wide imports in any of the test modules.
|
||||
sys.path.insert(0, path.join(path.dirname(__file__), 'res', 'apps'))
|
||||
from django.core.management import setup_environ
|
||||
import settings
|
||||
setup_environ(settings)
|
|
@ -1,11 +0,0 @@
|
|||
"""A Django library, but but auto-loaded via the templatetags/ directory.
|
||||
|
||||
Instead, to use it, it needs to be added to the builtins.
|
||||
"""
|
||||
|
||||
def foo(value):
|
||||
return "{foo}"
|
||||
|
||||
from django.template import Library
|
||||
register = Library()
|
||||
register.filter('foo_django_builtin', foo)
|
|
@ -1,34 +0,0 @@
|
|||
from coffin.contrib.syndication.feeds import Feed as OldFeed
|
||||
|
||||
|
||||
class TestOldFeed(OldFeed):
|
||||
title = 'Foo'
|
||||
link = '/'
|
||||
|
||||
def items(self):
|
||||
return [1,2,3]
|
||||
|
||||
def item_link(self, item):
|
||||
return '/item'
|
||||
|
||||
title_template = 'feeds_app/feed_title.html'
|
||||
description_template = 'feeds_app/feed_description.html'
|
||||
|
||||
|
||||
try:
|
||||
from coffin.contrib.syndication.views import Feed as NewFeed
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
class TestNewFeed(NewFeed):
|
||||
title = 'Foo'
|
||||
link = '/'
|
||||
|
||||
def items(self):
|
||||
return [1,2,3]
|
||||
|
||||
def item_link(self, item):
|
||||
return '/item'
|
||||
|
||||
title_template = 'feeds_app/feed_title.html'
|
||||
description_template = 'feeds_app/feed_description.html'
|
|
@ -1,2 +0,0 @@
|
|||
{# this syntax is not supported by Django #}
|
||||
{{ "JINJA WAS HERE (DESCRIPTION): %s" % obj }}
|
|
@ -1,2 +0,0 @@
|
|||
{# this syntax is not supported by Django #}
|
||||
{{ "JINJA WAS HERE (TITLE): %s" % obj }}
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
from django.core.management import execute_manager
|
||||
try:
|
||||
import settings # Assumed to be in the same directory.
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute_manager(settings)
|
|
@ -1,21 +0,0 @@
|
|||
from os import path
|
||||
|
||||
|
||||
DATABASES = {
|
||||
'default': {}
|
||||
}
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'templatelibs_app',
|
||||
'feeds_app',
|
||||
'urls_app',
|
||||
)
|
||||
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.app_directories.load_template_source',
|
||||
'django.template.loaders.filesystem.load_template_source',
|
||||
)
|
||||
|
||||
TEMPLATE_DIRS = (path.join(path.dirname(__file__), 'templates'),)
|
||||
|
||||
ROOT_URLCONF = 'urls'
|
|
@ -1,42 +0,0 @@
|
|||
"""Register a number of portable filters (with a Coffin library object)
|
||||
that require a compatibility layer to function correctly in both engines.
|
||||
"""
|
||||
|
||||
from jinja2 import Markup
|
||||
from django.utils.safestring import mark_safe, mark_for_escaping
|
||||
|
||||
|
||||
def needing_autoescape(value, autoescape=None):
|
||||
return str(autoescape)
|
||||
needing_autoescape.needs_autoescape = True
|
||||
|
||||
|
||||
def jinja_safe_output(value):
|
||||
return Markup(value)
|
||||
|
||||
def django_safe_output(value):
|
||||
return mark_safe(value)
|
||||
|
||||
def unsafe_output(value):
|
||||
return unicode(value)
|
||||
|
||||
|
||||
def django_raw_output(value):
|
||||
return value
|
||||
|
||||
def django_escape_output(value):
|
||||
# Make sure the value is converted to unicode first, because otherwise,
|
||||
# if it is already SafeData (for example, when coming from the template
|
||||
# code), then mark_for_escaping would do nothing. We want to guarantee
|
||||
# a EscapeData return value in this filter though.
|
||||
return mark_for_escaping(unicode(value))
|
||||
|
||||
|
||||
from coffin.template import Library
|
||||
register = Library()
|
||||
register.filter('needing_autoescape', needing_autoescape)
|
||||
register.filter('jinja_safe_output', jinja_safe_output)
|
||||
register.filter('django_safe_output', django_safe_output)
|
||||
register.filter('django_raw_output', django_raw_output)
|
||||
register.filter('unsafe_output', unsafe_output)
|
||||
register.filter('django_escape_output', django_escape_output)
|
|
@ -1,9 +0,0 @@
|
|||
"""Register a filter with a Django library object.
|
||||
"""
|
||||
|
||||
def foo(value):
|
||||
return "{foo}"
|
||||
|
||||
from django.template import Library
|
||||
register = Library()
|
||||
register.filter('foo_django', foo)
|
|
@ -1,15 +0,0 @@
|
|||
"""Register a Django tag with a Coffin library object.
|
||||
"""
|
||||
|
||||
from django.template import Node
|
||||
|
||||
class FooNode(Node):
|
||||
def render(self, context):
|
||||
return u'{foo}'
|
||||
|
||||
def do_foo(parser, token):
|
||||
return FooNode()
|
||||
|
||||
from coffin.template import Library
|
||||
register = Library()
|
||||
register.tag('foo_coffin', do_foo)
|
|
@ -1,32 +0,0 @@
|
|||
"""Register a Jinja2 extension with a Coffin library object.
|
||||
"""
|
||||
|
||||
from jinja2.ext import Extension
|
||||
from jinja2 import nodes
|
||||
|
||||
class FooExtension(Extension):
|
||||
tags = set(['foo'])
|
||||
|
||||
def parse(self, parser):
|
||||
parser.stream.next()
|
||||
return nodes.Const('{foo}')
|
||||
|
||||
|
||||
class FooWithConfigExtension(Extension):
|
||||
tags = set(['foo_ex'])
|
||||
|
||||
def __init__(self, environment):
|
||||
Extension.__init__(self, environment)
|
||||
environment.extend(
|
||||
foo_custom_output='foo',
|
||||
)
|
||||
|
||||
def parse(self, parser):
|
||||
parser.stream.next()
|
||||
return nodes.Const('{%s}' % self.environment.foo_custom_output)
|
||||
|
||||
|
||||
from coffin.template import Library
|
||||
register = Library()
|
||||
register.tag(FooExtension)
|
||||
register.tag(FooWithConfigExtension, environment={'foo_custom_output': 'my_foo'})
|
|
@ -1,35 +0,0 @@
|
|||
"""Register a number of non-portable, Jinja2-only filters with a Coffin
|
||||
library object.
|
||||
"""
|
||||
|
||||
from jinja2 import environmentfilter, contextfilter
|
||||
|
||||
@environmentfilter
|
||||
def environment(environment, value):
|
||||
return ""
|
||||
|
||||
@contextfilter
|
||||
def context(context, value):
|
||||
return ""
|
||||
|
||||
def multiarg(value, arg1, arg2):
|
||||
return ""
|
||||
|
||||
def jinja_forced(value):
|
||||
return ""
|
||||
|
||||
def django_jinja_forced(value):
|
||||
# a django filter that returns a django-safestring. It will *only*
|
||||
# be added to jinja, and coffin will hopefully ensure the string
|
||||
# stays safe.
|
||||
from django.utils.safestring import mark_safe
|
||||
return mark_safe(value)
|
||||
|
||||
|
||||
from coffin.template import Library
|
||||
register = Library()
|
||||
register.filter('environment', environment)
|
||||
register.filter('context', context)
|
||||
register.filter('multiarg', multiarg)
|
||||
register.filter('jinja_forced', jinja_forced, jinja2_only=True)
|
||||
register.filter('django_jinja_forced', django_jinja_forced, jinja2_only=True)
|
|
@ -1,9 +0,0 @@
|
|||
"""Register a Jinja2 global object with a Coffin library object.
|
||||
"""
|
||||
|
||||
def hello_func(name):
|
||||
return u"Hello %s" % name
|
||||
|
||||
from coffin.template import Library
|
||||
register = Library()
|
||||
register.object('hello', hello_func)
|
|
@ -1,9 +0,0 @@
|
|||
"""Register a portable filter with a Coffin library object.
|
||||
"""
|
||||
|
||||
def foo(value):
|
||||
return "{foo}"
|
||||
|
||||
from coffin.template import Library
|
||||
register = Library()
|
||||
register.filter('foo', foo)
|
|
@ -1,2 +0,0 @@
|
|||
{#- generic template for tests -#}
|
||||
{{ x }}
|
|
@ -1,10 +0,0 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^url_test/', include('urls_app.urls')),
|
||||
|
||||
# These two are used to test that our url-tag implementation can
|
||||
# deal with application namespaces / the "current app".
|
||||
(r'^app/one/', include('urls_app.urls', app_name="testapp", namespace="testapp")), # default instance
|
||||
(r'^app/two/', include('urls_app.urls', app_name="testapp", namespace="two")),
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('apps.urls_app',
|
||||
# Test urls for testing reverse lookups
|
||||
url(r'^$', 'views.index', name='the-index-view'),
|
||||
(r'^sum/(?P<left>\d+),(?P<right>\d+)$', 'views.sum'),
|
||||
)
|
|
@ -1,5 +0,0 @@
|
|||
def index(r):
|
||||
pass
|
||||
|
||||
def sum(r):
|
||||
pass
|
|
@ -1,31 +0,0 @@
|
|||
from nose.plugins.skip import SkipTest
|
||||
import django
|
||||
|
||||
|
||||
class TestSyndication:
|
||||
|
||||
def test_old(self):
|
||||
from django.http import HttpRequest
|
||||
fake_request = HttpRequest()
|
||||
fake_request.META['SERVER_NAME'] = 'foo'
|
||||
fake_request.META['SERVER_PORT'] = 80
|
||||
|
||||
from apps.feeds_app.feeds import TestOldFeed
|
||||
feedgen = TestOldFeed('', fake_request).get_feed(None)
|
||||
s = feedgen.writeString('utf-8')
|
||||
assert 'JINJA WAS HERE (TITLE)' in s
|
||||
assert 'JINJA WAS HERE (DESCRIPTION)' in s
|
||||
|
||||
def test_new(self):
|
||||
if django.VERSION < (1,2):
|
||||
raise SkipTest()
|
||||
|
||||
from django.http import HttpRequest
|
||||
fake_request = HttpRequest()
|
||||
fake_request.META['SERVER_NAME'] = 'foo'
|
||||
fake_request.META['SERVER_PORT'] = 80
|
||||
|
||||
from apps.feeds_app.feeds import TestNewFeed
|
||||
response = TestNewFeed()(fake_request)
|
||||
assert 'JINJA WAS HERE (TITLE)' in response.content
|
||||
assert 'JINJA WAS HERE (DESCRIPTION)' in response.content
|
|
@ -97,18 +97,3 @@ def test_with():
|
|||
|
||||
assert env.from_string('{{ x }}{% with y as x %}{{ x }}{% endwith %}{{ x }}').render({'x': 'x', 'y': 'y'}) == 'xyx'
|
||||
|
||||
|
||||
def test_cache():
|
||||
from coffin.template.defaulttags import CacheExtension
|
||||
env = Environment(extensions=[CacheExtension])
|
||||
|
||||
x = 0
|
||||
assert env.from_string('{%cache 500 "ab"%}{{x}}{%endcache%}').render({'x': x}) == '0'
|
||||
# cache is used; Jinja2 expressions work
|
||||
x += 1
|
||||
assert env.from_string('{%cache 50*10 "a"+"b"%}{{x}}{%endcache%}').render({'x': x}) == '0'
|
||||
# vary-arguments can be used
|
||||
x += 1
|
||||
assert env.from_string('{%cache 50*10 "ab" x "foo"%}{{x}}{%endcache%}').render({'x': x}) == '2'
|
||||
x += 1
|
||||
assert env.from_string('{%cache 50*10 "ab" x "foo"%}{{x}}{%endcache%}').render({'x': x}) == '3'
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
from datetime import datetime, date
|
||||
from nose.tools import assert_raises
|
||||
|
||||
|
||||
def r(s, context={}):
|
||||
from coffin.common import env
|
||||
return env.from_string(s).render(context)
|
||||
|
||||
|
||||
def test_django_builtins_available():
|
||||
"""Many filters have not been re-implemented specifically for
|
||||
Coffin, but instead the Django version is used through an
|
||||
interop-layer.
|
||||
|
||||
Make sure that those are properly made available in Jinja2.
|
||||
"""
|
||||
from coffin.template import defaultfilters
|
||||
assert not hasattr(defaultfilters, 'get_digit') # has no port
|
||||
assert r('{{ "23475"|get_digit("2") }}') == '7'
|
||||
assert r('{{ unknown|get_digit("2") }}') == ''
|
||||
|
||||
|
||||
def test_jinja2_builtins():
|
||||
"""Ensure that the Jinja2 builtins are available, and take
|
||||
precedence over the Django builtins (which we automatically convert
|
||||
and install).
|
||||
"""
|
||||
# Django's default filter only accepts one argument.
|
||||
assert r('{{ unknown|default("2", True) }}') == '2'
|
||||
|
||||
|
||||
def test_url():
|
||||
# project name is optional
|
||||
assert r('{{ "urls_app.views.index"|url() }}') == '/url_test/'
|
||||
assert r('{{ "apps.urls_app.views.index"|url() }}') == '/url_test/'
|
||||
|
||||
|
||||
def test_default():
|
||||
"""We make the Jinja2 default filter behave like Django's without
|
||||
arguments, but still support Jinja2 extended syntax.
|
||||
"""
|
||||
assert r('{{ foo|default("default") }}') == 'default'
|
||||
assert r('{{ foo|default("default") }}', {'foo': False}) == 'default'
|
||||
assert r('{{ foo|default("default", False) }}', {'foo': False}) == 'False'
|
||||
|
||||
|
||||
def test_pluralize():
|
||||
assert r('vote{{ 0|pluralize }}') == 'votes'
|
||||
assert r('vote{{ 1|pluralize }}') == 'vote'
|
||||
assert r('class{{ 2|pluralize("es") }}') == 'classes'
|
||||
assert r('cand{{ 0|pluralize("y", "ies") }}') == 'candies'
|
||||
assert r('cand{{ 1|pluralize("y", "ies") }}') == 'candy'
|
||||
assert r('cand{{ 2|pluralize("y", "ies") }}') == 'candies'
|
||||
assert r('vote{{ [1,2,3]|pluralize }}') == 'votes'
|
||||
assert r('anonyme{{ 0|pluralize("r", "") }}') == 'anonyme'
|
||||
assert r('anonyme{{ 1|pluralize("r", "") }}') == 'anonymer'
|
||||
assert r('vote{{ 1|pluralize }}') == 'vote'
|
||||
assert_raises(TypeError, r, 'vote{{ x|pluralize }}', {'x': object()})
|
||||
assert_raises(ValueError, r, 'vote{{ x|pluralize }}', {'x': 'foo'})
|
||||
|
||||
|
||||
def test_floatformat():
|
||||
assert r('{{ 1.3434|floatformat }}') == '1.3'
|
||||
assert r('{{ 1.3511|floatformat }}') == '1.4'
|
||||
assert r('{{ 1.3|floatformat(2) }}') == '1.30'
|
||||
assert r('{{ 1.30|floatformat(-3) }}') == '1.300'
|
||||
assert r('{{ 1.000|floatformat(3) }}') == '1.000'
|
||||
assert r('{{ 1.000|floatformat(-3) }}') == '1'
|
||||
assert_raises(ValueError, r, '{{ "foo"|floatformat(3) }}')
|
||||
assert_raises(ValueError, r, '{{ 4.33|floatformat("foo") }}')
|
||||
|
||||
|
||||
def test_date_stuff():
|
||||
from coffin.common import env
|
||||
assert r('a{{ d|date("Y") }}b', {'d': date(2007, 01, 01)}) == 'a2007b'
|
||||
assert r('a{{ d|time("H") }}b', {'d': datetime(2007, 01, 01, 12, 01, 01)}) == 'a12b'
|
||||
# TODO: timesince, timeuntil
|
||||
|
||||
# Make sure the date filters can handle unset values gracefully.
|
||||
# While generally we'd like to be explicit instead of hiding errors,
|
||||
# this is a particular case where it makes sense.
|
||||
for f in ('date', 'time', 'timesince', 'timeuntil'):
|
||||
assert r('a{{ d|%s }}b' % f) == 'ab'
|
||||
assert r('a{{ d|%s }}b' % f, {'d': None}) == 'ab'
|
|
@ -1,46 +0,0 @@
|
|||
"""Test construction of the implicitly provided JinjaEnvironment,
|
||||
in the common.py module.
|
||||
"""
|
||||
|
||||
from coffin.common import get_env
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
def test_i18n():
|
||||
with override_settings(USE_I18N=True):
|
||||
assert get_env().from_string('{{ _("test") }}').render() == 'test'
|
||||
|
||||
|
||||
class TestLoaders:
|
||||
|
||||
def test_django_loader_replace(self):
|
||||
from coffin.template.loaders import jinja_loader_from_django_loader
|
||||
from jinja2 import loaders
|
||||
|
||||
# Test replacement of filesystem loader
|
||||
l = jinja_loader_from_django_loader('django.template.loaders.filesystem.Loader')
|
||||
assert isinstance(l, loaders.FileSystemLoader)
|
||||
|
||||
# Since we don't do exact matches for the loader string, make sure we
|
||||
# are not replacing loaders that are outside the Django namespace.
|
||||
l = jinja_loader_from_django_loader('djangoaddon.template.loaders.filesystem.Loader')
|
||||
assert not isinstance(l, loaders.FileSystemLoader)
|
||||
|
||||
def test_cached_loader(self):
|
||||
from jinja2 import loaders
|
||||
|
||||
with override_settings(TEMPLATE_LOADERS=[
|
||||
('django.template.loaders.cached.Loader', (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)),]):
|
||||
env = get_env()
|
||||
assert len(env.loader.loaders) == 1
|
||||
cached_loader = get_env().loader.loaders[0]
|
||||
assert hasattr(cached_loader, 'template_cache')
|
||||
assert len(cached_loader.loader.loaders) == 2
|
||||
assert isinstance(cached_loader.loader.loaders[0], loaders.FileSystemLoader)
|
||||
|
||||
# the cached loader can find a template too.
|
||||
assert env.loader.load(env, 'render-x.html').render({'x': 'foo'}) == 'foo'
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
"""Test the various features of our custom library object.
|
||||
"""
|
||||
|
||||
from nose.tools import assert_raises
|
||||
|
||||
from jinja2 import TemplateAssertionError as Jinja2TemplateAssertionError
|
||||
from django.template import Template, Context, \
|
||||
TemplateSyntaxError as DjangoTemplateSyntaxError
|
||||
|
||||
|
||||
# TODO: It would be preferrable to split these tests into those checks
|
||||
# which actually test the Library object, and those which test the assembly
|
||||
# of the Environment instance. Testcode for the former could be written more
|
||||
# cleanly by creating the library instances within the test code and
|
||||
# registering them manually with the environment, rather than having to
|
||||
# place them in fake Django apps in completely different files to have
|
||||
# them loaded.
|
||||
# The tests for the compatibility layer could also be factored out.
|
||||
|
||||
|
||||
def test_nodes_and_extensions():
|
||||
"""Test availability of registered nodes/extensions.
|
||||
"""
|
||||
from coffin.common import env
|
||||
|
||||
# Jinja2 extensions, loaded from a Coffin library
|
||||
assert env.from_string('a{% foo %}b').render() == 'a{foo}b'
|
||||
assert env.from_string('a{% foo_ex %}b').render() == 'a{my_foo}b'
|
||||
|
||||
# Django tags, loaded from a Coffin library
|
||||
assert Template('{% load django_tags %}a{% foo_coffin %}b').render(Context()) == 'a{foo}b'
|
||||
|
||||
|
||||
def test_objects():
|
||||
"""For coffin, global objects can be registered.
|
||||
"""
|
||||
from coffin.common import env
|
||||
|
||||
# Jinja2 global objects, loaded from a Coffin library
|
||||
assert env.from_string('{{ hello("John") }}').render() == 'Hello John'
|
||||
|
||||
|
||||
def test_filters():
|
||||
"""Test availability of registered filters.
|
||||
"""
|
||||
from coffin.common import env
|
||||
|
||||
# Filter registered with a Coffin library is available in Django and Jinja2
|
||||
assert env.from_string('a{{ "b"|foo }}c').render() == 'a{foo}c'
|
||||
assert Template('{% load portable_filters %}a{{ "b"|foo }}c').render(Context()) == 'a{foo}c'
|
||||
|
||||
# Filter registered with a Django library is also available in Jinja2
|
||||
Template('{% load django_library %}{{ "b"|foo_django }}').render(Context())
|
||||
assert env.from_string('a{{ "b"|foo }}c').render() == 'a{foo}c'
|
||||
|
||||
# Some filters, while registered with a Coffin library, are only
|
||||
# available in Jinja2:
|
||||
# - when using @environmentfilter
|
||||
env.from_string('{{ "b"|environment }}')
|
||||
assert_raises(Exception, Template, '{% load jinja2_filters %}{{ "b"|environment }}')
|
||||
# - when using @contextfilter
|
||||
env.from_string('{{ "b"|context }}')
|
||||
assert_raises(Exception, Template, '{% load jinja2_filters %}{{ "b"|context }}')
|
||||
# - when requiring more than one argument
|
||||
env.from_string('{{ "b"|multiarg(1,2) }}')
|
||||
assert_raises(Exception, Template, '{% load jinja2_filters %}{{ "b"|multiarg }}')
|
||||
# - when Jinja2-exclusivity is explicitly requested
|
||||
env.from_string('{{ "b"|jinja_forced }}')
|
||||
assert_raises(Exception, Template, '{% load jinja2_filters %}{{ "b"|jinja_forced }}')
|
||||
# [bug] Jinja2-exclusivity caused the compatibility layer to be not
|
||||
# applied, causing for example safe strings to be escaped.
|
||||
assert env.from_string('{{ "><"|django_jinja_forced }}').render() == '><'
|
||||
|
||||
|
||||
def test_env_builtins_django():
|
||||
"""Test that when the environment is assembled, Django libraries which
|
||||
are included in the list of builtins are properly supported.
|
||||
"""
|
||||
from coffin.common import get_env
|
||||
from coffin.template import add_to_builtins
|
||||
add_to_builtins('apps.django_lib')
|
||||
assert get_env().from_string('a{{ "b"|foo_django_builtin }}c').render() == 'a{foo}c'
|
||||
|
||||
|
||||
def test_filter_compat_safestrings():
|
||||
"""Test filter compatibility layer with respect to safe strings.
|
||||
"""
|
||||
from coffin.common import env
|
||||
env.autoescape = True
|
||||
|
||||
# Jinja-style safe output strings are considered "safe" by both engines
|
||||
assert env.from_string('{{ "<b>"|jinja_safe_output }}').render() == '<b>'
|
||||
# TODO: The below actually works regardless of our converting between
|
||||
# the same string types: Jinja's Markup() strings are actually immune
|
||||
# to Django's escape() attempt, since they have a custom version of
|
||||
# replace() that operates on an already escaped version.
|
||||
assert Template('{% load compat_filters %}{{ "<b>"|jinja_safe_output }}').render(Context()) == '<b>'
|
||||
|
||||
# Unsafe, unmarked output strings are considered "unsafe" by both engines
|
||||
assert env.from_string('{{ "<b>"|unsafe_output }}').render() == '<b>'
|
||||
assert Template('{% load compat_filters %}{{ "<b>"|unsafe_output }}').render(Context()) == '<b>'
|
||||
|
||||
# Django-style safe output strings are considered "safe" by both engines
|
||||
assert env.from_string('{{ "<b>"|django_safe_output }}').render() == '<b>'
|
||||
assert Template('{% load compat_filters %}{{ "<b>"|django_safe_output }}').render(Context()) == '<b>'
|
||||
|
||||
|
||||
def test_filter_compat_escapetrings():
|
||||
"""Test filter compatibility layer with respect to strings flagged as
|
||||
"wanted for escaping".
|
||||
"""
|
||||
from coffin.common import env
|
||||
env.autoescape = False
|
||||
|
||||
# Django-style "force escaping" works in both engines
|
||||
assert env.from_string('{{ "<b>"|django_escape_output }}').render() == '<b>'
|
||||
assert Template('{% load compat_filters %}{{ "<b>"|django_escape_output }}').render(Context()) == '<b>'
|
||||
|
||||
|
||||
def test_filter_compat_other():
|
||||
"""Test other features of the filter compatibility layer.
|
||||
"""
|
||||
# A Django filter with @needs_autoescape works in Jinja2
|
||||
from coffin.common import env
|
||||
env.autoescape = True
|
||||
assert env.from_string('{{ "b"|needing_autoescape }}').render() == 'True'
|
||||
env.autoescape = False
|
||||
assert env.from_string('{{ "b"|needing_autoescape }}').render() == 'False'
|
||||
|
||||
# [bug] @needs_autoescape also (still) works correctly in Django
|
||||
assert Template('{% load compat_filters %}{{ "b"|needing_autoescape }}').render(Context()) == 'True'
|
||||
|
||||
# The Django filters can handle "Undefined" values
|
||||
assert env.from_string('{{ doesnotexist|django_raw_output }}').render() == ''
|
||||
|
||||
# TODO: test @stringfilter
|
|
@ -1,5 +0,0 @@
|
|||
def test_render():
|
||||
"""Test the render shortcut."""
|
||||
from coffin.shortcuts import render
|
||||
response = render(None, 'render-x.html', {'x': 'foo'})
|
||||
assert response.content == 'foo'
|
|
@ -1,56 +0,0 @@
|
|||
"""Tests for ``coffin.template``.
|
||||
|
||||
``coffin.template.library``, ``coffin.template.defaultfilters`` and
|
||||
``coffin.template.defaulttags`` have their own test modules.
|
||||
"""
|
||||
|
||||
def test_template_class():
|
||||
from coffin.template import Template
|
||||
from coffin.common import env
|
||||
|
||||
# initializing a template directly uses Coffin's Jinja
|
||||
# environment - we know it does if our tags are available.
|
||||
t = Template('{% spaceless %}{{ ""|truncatewords }}{% endspaceless %}')
|
||||
assert t.environment == env
|
||||
|
||||
# render can accept a Django context object
|
||||
from django.template import Context
|
||||
c = Context()
|
||||
c.update({'x': '1'}) # update does a push
|
||||
c.update({'y': '2'})
|
||||
assert Template('{{x}};{{y}}').render(c) == '1;2'
|
||||
|
||||
# [bug] render can handle nested Context objects
|
||||
c1 = Context(); c2 = Context(); c3 = Context()
|
||||
c3['foo'] = 'bar'
|
||||
c2.update(c3)
|
||||
c1.update(c2)
|
||||
assert Template('{{foo}}').render(c1) == 'bar'
|
||||
|
||||
# There is a "origin" attribute for Django compatibility
|
||||
assert Template('{{foo}}').origin.name == '<template>'
|
||||
|
||||
|
||||
def test_render_to_string():
|
||||
# [bug] Test that the values given directly do overwrite does that
|
||||
# are already exist in the given context_instance. Due to a bug this
|
||||
# was previously not the case.
|
||||
from django.template import Context
|
||||
from coffin.template.loader import render_to_string
|
||||
assert render_to_string('render-x.html', {'x': 'new'},
|
||||
context_instance=Context({'x': 'old'})) == 'new'
|
||||
|
||||
# [bug] Test that the values from context_instance actually make it
|
||||
# into the template.
|
||||
assert render_to_string('render-x.html',
|
||||
context_instance=Context({'x': 'foo'})) == 'foo'
|
||||
|
||||
# [bug] Call without the optional ``context_instance`` argument works
|
||||
assert render_to_string('render-x.html', {'x': 'foo'}) == 'foo'
|
||||
|
||||
# ``dictionary`` argument may be a Context instance
|
||||
assert render_to_string('render-x.html', Context({'x': 'foo'})) == 'foo'
|
||||
|
||||
# [bug] Both ``dictionary`` and ``context_instance`` may be
|
||||
# Context objects
|
||||
assert render_to_string('render-x.html', Context({'x': 'foo'}), context_instance=Context()) == 'foo'
|
|
@ -1,3 +0,0 @@
|
|||
def test_import():
|
||||
# [bug] make sure the coffin.views module is importable.
|
||||
from coffin import views
|
Loading…
Reference in New Issue