diff --git a/django/template/context.py b/django/template/context.py index b2f00abd31..76af916cb7 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -97,6 +97,15 @@ class BaseContext(object): new_context._reset_dicts(values) return new_context + def flatten(self): + """ + Returns self.dicts as one dictionary + """ + flat = {} + for d in self.dicts: + flat.update(d) + return flat + def __eq__(self, other): """ Compares two contexts by comparing theirs 'dicts' attributes. @@ -104,13 +113,7 @@ class BaseContext(object): if isinstance(other, BaseContext): # because dictionaries can be put in different order # we have to flatten them like in templates - def flatten(dicts): - flat = {} - for d in dicts: - flat.update(d) - return flat - - return flatten(self.dicts) == flatten(other.dicts) + return self.flatten() == other.flatten() # if it's not comparable return false return False diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 7627c02b6e..5701b1a9d1 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -368,6 +368,43 @@ the stack instead of an empty one. Using a ``Context`` as a stack comes in handy in some custom template tags, as you'll see below. +.. method:: Context.flatten() + +.. versionadded:: 1.7 + +Using ``flatten()`` method you can get whole ``Context`` stack as one dictionary +including builtin variables. + + >>> c = Context() + >>> c['foo'] = 'first level' + >>> c.update({'bar': 'second level'}) + {'bar': 'second level'} + >>> c.flatten() + {'True': True, 'None': None, 'foo': 'first level', 'False': False, 'bar': 'second level'} + +A ``flatten()`` method is also internally used to make ``Context`` objects comparable. + + >>> c1 = Context() + >>> c1['foo'] = 'first level' + >>> c1['bar'] = 'second level' + >>> c2 = Context() + >>> c2.update({'bar': 'second level', 'foo': 'first level'}) + {'foo': 'first level', 'bar': 'second level'} + >>> c1 == c2 + True + +Result from ``flatten()`` can be useful in unit tests to compare ``Context`` +against ``dict``:: + + class ContextTest(unittest.TestCase): + def test_against_dictionary(self): + c1 = Context() + c1['update'] = 'value' + self.assertEqual(c1.flatten(), { + 'True': True, 'None': None, 'False': False, + 'update': 'value'}) + + .. _subclassing-context-requestcontext: Subclassing Context: RequestContext diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index cd9a9dd225..8e241caf44 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -636,6 +636,14 @@ Templates parameters that are passed to the ``dict`` constructor used to build the new context level. +* The new :meth:`Context.flatten() ` method + returns a ``Context``'s stack as one flat dictionary. + +* ``Context`` objects can now be compared for equality (internally, this + uses :meth:`Context.flatten() ` so the + internal structure of each ``Context``'s stack doesn't matter as long as their + flattened version is identical). + * The :ttag:`widthratio` template tag now accepts an "as" parameter to capture the result in a variable. diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py index 64d0df8d10..dac74b36e3 100644 --- a/tests/template_tests/test_context.py +++ b/tests/template_tests/test_context.py @@ -50,6 +50,17 @@ class ContextTests(TestCase): test_context['fruit'] self.assertIsNone(test_context.get('fruit')) + def test_flatten_context(self): + a = Context() + a.update({'a': 2}) + a.update({'b': 4}) + a.update({'c': 8}) + + self.assertEqual(a.flatten(), { + 'False': False, 'None': None, 'True': True, + 'a': 2, 'b': 4, 'c': 8 + }) + def test_context_comparable(self): test_data = {'x': 'y', 'v': 'z', 'd': {'o': object, 'a': 'b'}}