Skip to content

Commit

Permalink
Reverted large parts of the merge from WoLpH; Quite a lot of parts don't
Browse files Browse the repository at this point in the history
work as expected, or warrant more discussion.
  • Loading branch information
miracle2k committed Nov 9, 2010
1 parent d6e32ac commit a6d7602
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 148 deletions.
20 changes: 8 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ Example for a Jinja-enabled template library::
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, globals and
constants via your ``settings.py``::
You may also define additional extensions, filters, tests and globals via your ``settings.py``::

JINJA2_FILTERS = (
'path.to.myfilter',
Expand All @@ -125,12 +124,6 @@ constants via your ``settings.py``::
'jinja2.ext.do',
)

def MEDIA_URL():
return settings.MEDIA_URL

JINJA2_CONSTANTS = (
MEDIA_URL,
)

Other things of note
====================
Expand All @@ -143,10 +136,13 @@ templates anyway, it might be a good opportunity for this change.

(*) http://groups.google.com/group/django-developers/browse_thread/thread/f323338045ac2e5e

This version of coffin modifies Jinja 2's ``TemplateSyntaxError`` to be
compatible with Django. So there is no need to disable ``TEMPLATE_DEBUG``.
You can just keep `TEPMLATE_DEBUG=True`` in your settings to benefit from both
Jinja 2 and Django's template debugging.
Jinja2's ``TemplateSyntaxError`` (and potentially other exception types)
are not compatible with Django's own template exceptions with respect to
the TEMPLATE_DEBUG facility. If TEMPLATE_DEBUG is enabled and Jinja2 raises
an exception, Django's error 500 page will sometimes not be able to handle
it and crash. The solution is to disable the TEMPLATE_DEBUG setting in
Django. See http://code.djangoproject.com/ticket/10216 for further
information.

``coffin.template.loader`` is a port of ``django.template.loader`` and
comes with a Jinja2-enabled version of ``get_template()``.
Expand Down
11 changes: 2 additions & 9 deletions coffin/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

class CoffinEnvironment(Environment):
def __init__(self, filters={}, globals={}, tests={}, loader=None, extensions=[], **kwargs):
from django.conf import settings
if not loader:
loader = loaders.ChoiceLoader(self._get_loaders())
all_ext = self._get_all_extensions()
Expand Down Expand Up @@ -83,7 +82,7 @@ def _get_templatelibs(self):
for f in os.listdir(path):
if f == '__init__.py' or f.startswith('.'):
continue

if f.endswith('.py'):
try:
# TODO: will need updating when #6587 lands
Expand Down Expand Up @@ -136,7 +135,7 @@ def _load_lib(lib):

# Next, add the globally defined extensions
extensions.extend(list(getattr(settings, 'JINJA2_EXTENSIONS', [])))
def from_setting(setting, call=False):
def from_setting(setting):
retval = {}
setting = getattr(settings, setting, {})
if isinstance(setting, dict):
Expand All @@ -146,15 +145,9 @@ def from_setting(setting, call=False):
for value in setting:
value = callable(value) and value or get_callable(value)
retval[value.__name__] = value

if call:
for k, v in retval.items():
if callable(v):
retval[k] = v()
return retval
filters.update(from_setting('JINJA2_FILTERS'))
globals.update(from_setting('JINJA2_GLOBALS'))
globals.update(from_setting('JINJA2_CONSTANTS', True))
tests.update(from_setting('JINJA2_TESTS'))

# Finally, add extensions defined in application's templatetag libraries
Expand Down
140 changes: 13 additions & 127 deletions coffin/template/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
from jinja2 import (
exceptions as _jinja2_exceptions,
environment as _jinja2_environment,
)
from django.template import (
Context as DjangoContext,
add_to_builtins as django_add_to_builtins,
import_library,
TemplateSyntaxError as DjangoTemplateSyntaxError,
loader as django_loader,
)
from jinja2 import Template as _Jinja2Template

# Merge with ``django.template``.
from django.template import __all__
Expand All @@ -18,80 +13,7 @@
from library import *


def _generate_django_exception(e, source=None):
'''Generate a Django exception from a Jinja exception'''
from django.views.debug import linebreak_iter
import re

if source:
exception = DjangoTemplateSyntaxError(e.message)
exception_dict = e.__dict__
del exception_dict['source']

# Fetch the entire template in a string
template_string = source[0].reload()

# Get the line number from the error message, if available
match = re.match('.* at (\d+)$', e.message)

start_index = 0
stop_index = 0
if match:
# Convert the position found in the stacktrace to a position
# the Django template debug system can use
position = int(match.group(1)) + source[1][0] + 1

for index in linebreak_iter(template_string):
if index >= position:
stop_index = min(index, position + 3)
start_index = min(index, position - 2)
break
start_index = index

else:
# So there wasn't a matching error message, in that case we
# simply have to highlight the entire line instead of the specific
# words
ignore_lines = -1
for i, index in enumerate(linebreak_iter(template_string)):
if source[1][0] > index:
ignore_lines += 1

if i - ignore_lines == e.lineno:
stop_index = index
break

start_index = index

# Convert the positions to a source that is compatible with the
# Django template debugger
source = source[0], (
start_index,
stop_index,
)
else:
# No source available so we let Django fetch it for us
lineno = e.lineno - 1
template_string, source = django_loader.find_template_source(e.name)
exception = DjangoTemplateSyntaxError(e.message)

# Find the positions by the line number given in the exception
start_index = 0
for i in range(lineno):
start_index = template_string.index('\n', start_index + 1)

source = source, (
start_index + 1,
template_string.index('\n', start_index + 1) + 1,
)

# Set our custom source as source for the exception so the Django
# template debugger can use it
exception.source = source
return exception


class Template(_jinja2_environment.Template):
class Template(_Jinja2Template):
'''Fixes the incompabilites between Jinja2's template class and
Django's.
Expand All @@ -103,72 +25,36 @@ class Template(_jinja2_environment.Template):
coffin environment.
'''

def __new__(cls, template_string, origin=None, name=None, source=None):
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.

# source is expected to be a Django Template Loader source, it is not
# required but helps to provide useful stacktraces when executing
# Jinja code from Django templates
from coffin.common import env

try:
template = env.from_string(template_string, template_class=cls)
template.source = source
return template
except _jinja2_exceptions.TemplateSyntaxError, e:
raise _generate_django_exception(e, source)
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
"""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.
'''
if not context:
"""
if context is None:
context = {}
else:
context = dict_from_django_context(context)

try:
return super(Template, self).render(context)
except _jinja2_exceptions.TemplateSyntaxError, e:
raise _generate_django_exception(e)
except _jinja2_exceptions.UndefinedError, e:
# UndefinedErrors don't have a source attribute so we create one
import sys
import traceback
exc_traceback = sys.exc_info()[-1]
trace = traceback.extract_tb(exc_traceback)[-1]
e.lineno = trace[1]
source = None

# If we're getting <template> than we're being call from a memory
# template, this occurs when we use the {% jinja %} template tag
# In that case we use the Django source and find our position
# within that
if trace[0] == '<template>' and hasattr(self, 'source'):
source = self.source
e.name = source[0].name
e.source = source
else:
e.name = trace[0]

# We have to cleanup the trace manually, Python does _not_ clean
# it up for us!
del exc_traceback, trace

raise _generate_django_exception(e, source)
assert isinstance(context, dict) # Required for **-operator.
return super(Template, self).render(**context)


def dict_from_django_context(context):
'''Flattens a Django :class:`django.template.context.Context` object.'''
"""Flattens a Django :class:`django.template.context.Context` object.
"""
if not isinstance(context, DjangoContext):
return context
else:
Expand All @@ -184,7 +70,7 @@ def dict_from_django_context(context):


def add_to_builtins(module_name):
'''Add the given module to both Coffin's list of default template
"""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.
Expand All @@ -199,7 +85,7 @@ def add_to_builtins(module_name):
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)

Expand Down
4 changes: 4 additions & 0 deletions coffin/template/defaultfilters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from coffin.template import Library
from jinja2.runtime import Undefined
# from jinja2 import Markup
from jinja2 import filters

register = Library()
Expand Down Expand Up @@ -106,5 +107,8 @@ def floatformat(value, arg=-1):

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

0 comments on commit a6d7602

Please sign in to comment.