Skip to content

Commit

Permalink
[1.3.X] Fixes regression django#15721 -- {% include %} and RequestCon…
Browse files Browse the repository at this point in the history
…text not working together. Refs django#15814.

Backport of r16031, plus the utility from r16030.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16089 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
SmileyChris committed Apr 22, 2011
1 parent 5977193 commit 9269b60
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 20 deletions.
34 changes: 15 additions & 19 deletions django/template/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,16 @@ class ContextPopException(Exception):
"pop() has been called more times than push()"
pass

class EmptyClass(object):
# No-op class which takes no args to its __init__ method, to help implement
# __copy__
pass

class BaseContext(object):
def __init__(self, dict_=None):
dict_ = dict_ or {}
self.dicts = [dict_]
self._reset_dicts(dict_)

def _reset_dicts(self, value=None):
self.dicts = [value or {}]

def __copy__(self):
duplicate = EmptyClass()
duplicate.__class__ = self.__class__
duplicate.__dict__ = self.__dict__.copy()
duplicate.dicts = duplicate.dicts[:]
duplicate = copy(super(BaseContext, self))
duplicate.dicts = self.dicts[:]
return duplicate

def __repr__(self):
Expand Down Expand Up @@ -78,6 +73,15 @@ def get(self, key, otherwise=None):
return d[key]
return otherwise

def new(self, values=None):
"""
Returns a new context with the same properties, but with only the
values given in 'values' stored.
"""
new_context = copy(self)
new_context._reset_dicts(values)
return new_context

class Context(BaseContext):
"A stack container for variable context"
def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None):
Expand All @@ -99,14 +103,6 @@ def update(self, other_dict):
self.dicts.append(other_dict)
return other_dict

def new(self, values=None):
"""
Returns a new Context with the same 'autoescape' value etc, but with
only the values given in 'values' stored.
"""
return self.__class__(dict_=values, autoescape=self.autoescape,
current_app=self.current_app, use_l10n=self.use_l10n)

class RenderContext(BaseContext):
"""
A stack container for storing Template state.
Expand Down
43 changes: 42 additions & 1 deletion django/test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
from django.core import mail
from django.core.mail.backends import locmem
from django.test import signals
from django.template import Template
from django.template import Template, loader, TemplateDoesNotExist
from django.template.loaders import cached
from django.utils.translation import deactivate

__all__ = ('Approximate', 'ContextList', 'setup_test_environment',
'teardown_test_environment', 'get_runner')

RESTORE_LOADERS_ATTR = '_original_template_source_loaders'


class Approximate(object):
def __init__(self, val, places=7):
Expand Down Expand Up @@ -125,3 +128,41 @@ def get_runner(settings):
test_module = __import__(test_module_name, {}, {}, test_path[-1])
test_runner = getattr(test_module, test_path[-1])
return test_runner


def setup_test_template_loader(templates_dict, use_cached_loader=False):
"""
Changes Django to only find templates from within a dictionary (where each
key is the template name and each value is the corresponding template
content to return).
Use meth:`restore_template_loaders` to restore the original loaders.
"""
if hasattr(loader, RESTORE_LOADERS_ATTR):
raise Exception("loader.%s already exists" % RESTORE_LOADERS_ATTR)

def test_template_loader(template_name, template_dirs=None):
"A custom template loader that loads templates from a dictionary."
try:
return (templates_dict[template_name], "test:%s" % template_name)
except KeyError:
raise TemplateDoesNotExist(template_name)

if use_cached_loader:
template_loader = cached.Loader(('test_template_loader',))
template_loader._cached_loaders = (test_template_loader,)
else:
template_loader = test_template_loader

setattr(loader, RESTORE_LOADERS_ATTR, loader.template_source_loaders)
loader.template_source_loaders = (template_loader,)
return template_loader


def restore_template_loaders():
"""
Restores the original template loaders after
:meth:`setup_test_template_loader` has been run.
"""
loader.template_source_loaders = getattr(loader, RESTORE_LOADERS_ATTR)
delattr(loader, RESTORE_LOADERS_ATTR)
31 changes: 31 additions & 0 deletions tests/regressiontests/templates/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from django.core import urlresolvers
from django.template import loader
from django.template.loaders import app_directories, filesystem, cached
from django.test.utils import setup_test_template_loader,\
restore_template_loaders
from django.utils import unittest
from django.utils.translation import activate, deactivate, ugettext as _
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -1640,5 +1642,34 @@ def test_load_working_egg(self):
settings.INSTALLED_APPS = ('tagsegg',)
t = template.Template(ttext)


class RequestContextTests(BaseTemplateResponseTest):

def setUp(self):
templates = {
'child': Template('{{ var|default:"none" }}'),
}
setup_test_template_loader(templates)
self.fake_request = RequestFactory().get('/')

def tearDown(self):
restore_template_loaders()

def test_include_only(self):
"""
Regression test for #15721, ``{% include %}`` and ``RequestContext``
not playing together nicely.
"""
ctx = RequestContext(self.fake_request, {'var': 'parent'})
self.assertEqual(
template.Template('{% include "child" %}').render(ctx),
'parent'
)
self.assertEqual(
template.Template('{% include "child" only %}').render(ctx),
'none'
)


if __name__ == "__main__":
unittest.main()

0 comments on commit 9269b60

Please sign in to comment.