Remove all, except what django-jinja is lacking.

Close #62.
This commit is contained in:
Michael Elsdörfer 2015-04-17 15:18:29 +02:00
parent d89a5313e5
commit 67e3b2d7cf
98 changed files with 281 additions and 3282 deletions

View File

@ -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)

View File

@ -1,2 +0,0 @@
clean:
@-rm -f *.pyc */*.pyc tags */tags

View File

@ -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 *

View File

@ -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)

View File

@ -1,4 +0,0 @@
from django.conf.urls import *
handler404 = 'coffin.views.defaults.page_not_found'
handler500 = 'coffin.views.defaults.server_error'

View File

@ -1 +0,0 @@
from django.contrib.auth.admin import *

View File

@ -1 +0,0 @@
from django.contrib.auth.backends import *

View File

@ -1 +0,0 @@
from django.contrib.auth.decorators import *

View File

@ -1 +0,0 @@
from django.contrib.auth.forms import *

View File

@ -1 +0,0 @@
from django.contrib.auth.handlers import *

View File

@ -1 +0,0 @@
from django.contrib.auth.middleware import *

View File

@ -1 +0,0 @@
from django.contrib.auth.models import *

View File

@ -1 +0,0 @@
from django.contrib.auth.tokens import *

View File

@ -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')

View File

@ -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)

View File

@ -1,2 +0,0 @@
# coding=utf-8
from . import context

View File

@ -1,2 +0,0 @@
# coding=utf-8
from django.contrib.flatpages.admin import *

View File

@ -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

View File

@ -1,6 +0,0 @@
import inspect
from django.contrib.flatpages.middleware import *
from coffin.contrib.flatpages.views import flatpage
exec inspect.getsource(FlatpageFallbackMiddleware)\

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.
"""

View File

@ -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)

View File

@ -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)

View File

@ -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'

View File

@ -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.
"""

View File

@ -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'

View File

@ -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.
"""

View File

@ -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)

View File

@ -1,6 +0,0 @@
import inspect
from django.views.generic.simple import *
from coffin.template import loader, RequestContext
exec inspect.getsource(direct_to_template)

View File

@ -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."

View File

@ -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

View File

@ -1,3 +0,0 @@
==========================
:mod:`coffin.contrib.auth`
==========================

View File

@ -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

View File

@ -1,3 +0,0 @@
============================
:mod:`coffin.contrib.markup`
============================

View File

@ -1,3 +0,0 @@
=================================
:mod:`coffin.contrib.syndication`
=================================

View File

@ -1,18 +0,0 @@
Coffin Documentation
====================
Contents:
.. toctree::
:maxdepth: 2
install
contrib/index
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -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`

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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'

View File

@ -1,2 +0,0 @@
{# this syntax is not supported by Django #}
{{ "JINJA WAS HERE (DESCRIPTION): %s" % obj }}

View File

@ -1,2 +0,0 @@
{# this syntax is not supported by Django #}
{{ "JINJA WAS HERE (TITLE): %s" % obj }}

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'})

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -1,2 +0,0 @@
{#- generic template for tests -#}
{{ x }}

View File

@ -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")),
)

View File

@ -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'),
)

View File

@ -1,5 +0,0 @@
def index(r):
pass
def sum(r):
pass

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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() == '&lt;b&gt;'
assert Template('{% load compat_filters %}{{ "<b>"|unsafe_output }}').render(Context()) == '&lt;b&gt;'
# 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() == '&lt;b&gt;'
assert Template('{% load compat_filters %}{{ "<b>"|django_escape_output }}').render(Context()) == '&lt;b&gt;'
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

View File

@ -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'

View File

@ -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'

View File

@ -1,3 +0,0 @@
def test_import():
# [bug] make sure the coffin.views module is importable.
from coffin import views