Commit adff499e authored by Preston Timmons's avatar Preston Timmons
Browse files

Fixed #24119, #24120 -- Formalized debug integration for template backends.

parent d1df1fd2
Loading
Loading
Loading
Loading
+24 −6
Original line number Diff line number Diff line
# Since this package contains a "django" module, this is required on Python 2.
from __future__ import absolute_import

import sys
import warnings

from django.conf import settings
from django.template import TemplateDoesNotExist
from django.template.context import Context, RequestContext, make_context
from django.template.engine import Engine, _dirs_undefined
from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning

from .base import BaseEngine
@@ -24,21 +27,23 @@ class DjangoTemplates(BaseEngine):
        self.engine = Engine(self.dirs, self.app_dirs, **options)

    def from_string(self, template_code):
        return Template(self.engine.from_string(template_code))
        return Template(self.engine.from_string(template_code), self)

    def get_template(self, template_name, dirs=_dirs_undefined):
        return Template(self.engine.get_template(template_name, dirs))
        try:
            return Template(self.engine.get_template(template_name, dirs), self)
        except TemplateDoesNotExist as exc:
            reraise(exc, self)


class Template(object):

    def __init__(self, template):
    def __init__(self, template, backend):
        self.template = template
        self.backend = backend

    @property
    def origin(self):
        # TODO: define the Origin API. For now simply forwarding to the
        #       underlying Template preserves backwards-compatibility.
        return self.template.origin

    def render(self, context=None, request=None):
@@ -71,4 +76,17 @@ class Template(object):
        else:
            context = make_context(context, request)

        try:
            return self.template.render(context)
        except TemplateDoesNotExist as exc:
            reraise(exc, self.backend)


def reraise(exc, backend):
    """
    Reraise TemplateDoesNotExist while maintaining template debug information.
    """
    new = exc.__class__(*exc.args, tried=exc.tried, backend=backend)
    if hasattr(exc, 'template_debug'):
        new.template_debug = exc.template_debug
    six.reraise(exc.__class__, new, sys.exc_info()[2])
+12 −4
Original line number Diff line number Diff line
# Since this package contains a "django" module, this is required on Python 2.
from __future__ import absolute_import

import errno
import io
import string

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.template import TemplateDoesNotExist
from django.template import Origin, TemplateDoesNotExist
from django.utils.html import conditional_escape

from .base import BaseEngine
@@ -29,17 +30,24 @@ class TemplateStrings(BaseEngine):
        return Template(template_code)

    def get_template(self, template_name):
        tried = []
        for template_file in self.iter_template_filenames(template_name):
            try:
                with io.open(template_file, encoding=settings.FILE_CHARSET) as fp:
                    template_code = fp.read()
            except IOError:
            except IOError as e:
                if e.errno == errno.ENOENT:
                    tried.append((
                        Origin(template_file, template_name, self),
                        'Source does not exist',
                    ))
                    continue
                raise

            return Template(template_code)

        else:
            raise TemplateDoesNotExist(template_name)
            raise TemplateDoesNotExist(template_name, tried=tried, backend=self)


class Template(string.Template):
+48 −4
Original line number Diff line number Diff line
@@ -41,17 +41,24 @@ class Jinja2(BaseEngine):
        try:
            return Template(self.env.get_template(template_name))
        except jinja2.TemplateNotFound as exc:
            six.reraise(TemplateDoesNotExist, TemplateDoesNotExist(exc.args),
                        sys.exc_info()[2])
            six.reraise(
                TemplateDoesNotExist,
                TemplateDoesNotExist(exc.name, backend=self),
                sys.exc_info()[2],
            )
        except jinja2.TemplateSyntaxError as exc:
            six.reraise(TemplateSyntaxError, TemplateSyntaxError(exc.args),
                        sys.exc_info()[2])
            new = TemplateSyntaxError(exc.args)
            new.template_debug = get_exception_info(exc)
            six.reraise(TemplateSyntaxError, new, sys.exc_info()[2])


class Template(object):

    def __init__(self, template):
        self.template = template
        self.origin = Origin(
            name=template.filename, template_name=template.name,
        )

    def render(self, context=None, request=None):
        if context is None:
@@ -61,3 +68,40 @@ class Template(object):
            context['csrf_input'] = csrf_input_lazy(request)
            context['csrf_token'] = csrf_token_lazy(request)
        return self.template.render(context)


class Origin(object):
    """
    A container to hold debug information as described in the template API
    documentation.
    """
    def __init__(self, name, template_name):
        self.name = name
        self.template_name = template_name


def get_exception_info(exception):
    """
    Formats exception information for display on the debug page using the
    structure described in the template API documentation.
    """
    context_lines = 10
    lineno = exception.lineno
    lines = list(enumerate(exception.source.strip().split("\n"), start=1))
    during = lines[lineno - 1][1]
    total = len(lines)
    top = max(0, lineno - context_lines - 1)
    bottom = min(total, lineno + context_lines)

    return {
        'name': exception.filename,
        'message': exception.message,
        'source_lines': lines[top:bottom],
        'line': lineno,
        'before': '',
        'during': during,
        'after': '',
        'total': total,
        'top': top,
        'bottom': bottom,
    }
+19 −5
Original line number Diff line number Diff line
@@ -135,13 +135,27 @@ class TemplateSyntaxError(Exception):

class TemplateDoesNotExist(Exception):
    """
    This exception is used when template loaders are unable to find a
    template. The tried argument is an optional list of tuples containing
    (origin, status), where origin is an Origin object and status is a string
    with the reason the template wasn't found.
    The exception used by backends when a template does not exist. Accepts the
    following optional arguments:

    backend
        The template backend class used when raising this exception.

    tried
        A list of sources that were tried when finding the template. This
        is formatted as a list of tuples containing (origin, status), where
        origin is an Origin object and status is a string with the reason the
        template wasn't found.

    chain
        A list of intermediate TemplateDoesNotExist exceptions. This is used to
        encapsulate multiple exceptions when loading templates from multiple
        engines.
    """
    def __init__(self, msg, tried=None):
    def __init__(self, msg, tried=None, backend=None, chain=None):
        self.backend = backend
        self.tried = tried or []
        self.chain = chain or []
        super(TemplateDoesNotExist, self).__init__(msg)


+9 −9
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):

    Raises TemplateDoesNotExist if no such template exists.
    """
    tried = []
    chain = []
    engines = _engine_list(using)
    for engine in engines:
        try:
@@ -33,9 +33,9 @@ def get_template(template_name, dirs=_dirs_undefined, using=None):
            else:
                return engine.get_template(template_name)
        except TemplateDoesNotExist as e:
            tried.extend(e.tried)
            chain.append(e)

    raise TemplateDoesNotExist(template_name, tried=tried)
    raise TemplateDoesNotExist(template_name, chain=chain)


def select_template(template_name_list, dirs=_dirs_undefined, using=None):
@@ -46,7 +46,7 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):

    Raises TemplateDoesNotExist if no such template exists.
    """
    tried = []
    chain = []
    engines = _engine_list(using)
    for template_name in template_name_list:
        for engine in engines:
@@ -63,10 +63,10 @@ def select_template(template_name_list, dirs=_dirs_undefined, using=None):
                else:
                    return engine.get_template(template_name)
            except TemplateDoesNotExist as e:
                tried.extend(e.tried)
                chain.append(e)

    if template_name_list:
        raise TemplateDoesNotExist(', '.join(template_name_list), tried=tried)
        raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
    else:
        raise TemplateDoesNotExist("No template names provided")

@@ -92,7 +92,7 @@ def render_to_string(template_name, context=None,
        return template.render(context, request)

    else:
        tried = []
        chain = []
        # Some deprecated arguments were passed - use the legacy code path
        for engine in _engine_list(using):
            try:
@@ -124,13 +124,13 @@ def render_to_string(template_name, context=None,
                        "method doesn't support the dictionary argument." %
                        engine.name, stacklevel=2)
            except TemplateDoesNotExist as e:
                tried.extend(e.tried)
                chain.append(e)
                continue

        if template_name:
            if isinstance(template_name, (list, tuple)):
                template_name = ', '.join(template_name)
            raise TemplateDoesNotExist(template_name, tried=tried)
            raise TemplateDoesNotExist(template_name, chain=chain)
        else:
            raise TemplateDoesNotExist("No template names provided")

Loading