Index: docs/ref/templates/api.txt
===================================================================
--- docs/ref/templates/api.txt	(revision 16022)
+++ docs/ref/templates/api.txt	(working copy)
@@ -195,6 +195,11 @@
     * A variable can only be called if it has no required arguments. Otherwise,
       the system will return an empty string.
 
+    * Occasionally you may want to turn off this feature, and tell the template
+      system to leave a variable un-called no matter what.  To disable this feature,
+      set a ``do_not_call_in_templates`` attribute on the callable variable.  The
+      template system then will act as if your variable is not callable.
+
     * Obviously, there can be side effects when calling some variables, and
       it'd be either foolish or a security hole to allow the template system
       to access them.
@@ -207,7 +212,8 @@
 
       To prevent this, set an ``alters_data`` attribute on the callable
       variable. The template system won't call a variable if it has
-      ``alters_data=True`` set. The dynamically-generated
+      ``alters_data=True`` set, and will instead replace the variable with an
+      empty string, unconditionally.  The dynamically-generated
       :meth:`~django.db.models.Model.delete` and
       :meth:`~django.db.models.Model.save` methods on Django model objects get
       ``alters_data=True`` automatically. Example::
Index: django/template/base.py
===================================================================
--- django/template/base.py	(revision 16022)
+++ django/template/base.py	(working copy)
@@ -692,7 +692,9 @@
                                 ):
                             raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
                 if callable(current):
-                    if getattr(current, 'alters_data', False):
+                    if getattr(current, 'do_not_call_in_templates', False):
+                        pass
+                    elif getattr(current, 'alters_data', False):
                         current = settings.TEMPLATE_STRING_IF_INVALID
                     else:
                         try: # method call (assuming no args required)
Index: tests/regressiontests/templates/tests.py
===================================================================
--- tests/regressiontests/templates/tests.py	(revision 16022)
+++ tests/regressiontests/templates/tests.py	(working copy)
@@ -24,6 +24,7 @@
 from django.utils.safestring import mark_safe
 from django.utils.tzinfo import LocalTimezone
 
+from callables import *
 from context import ContextTests
 from custom import CustomTagTests, CustomFilterTests
 from parser import ParserTests
Index: tests/regressiontests/templates/callables.py
===================================================================
--- tests/regressiontests/templates/callables.py	(revision 0)
+++ tests/regressiontests/templates/callables.py	(revision 0)
@@ -0,0 +1,121 @@
+from django import template
+from django.utils.unittest import TestCase
+
+class CallableVariablesTests(TestCase):
+    def test_callable(self):
+
+        class Doodad(object):
+            def __init__(self, value):
+                self.num_calls = 0
+                self.value = value
+            def __call__(self):
+                self.num_calls += 1
+                return {"the_value": self.value}
+
+        my_doodad = Doodad(42)
+        c = template.Context({"my_doodad": my_doodad})
+
+        # We can't access ``my_doodad.value`` in the template,
+        # because ``my_doodad.__call__`` will be invoked first.
+        # We end up with the empty string, because calling the
+        # variable yields a dict, which has no key ``value``.
+        t = template.Template('{{my_doodad.value}}')
+        self.assertEqual(t.render(c), u'')
+
+        # We can confirm that the doodad has been called,
+        # because we set a simple incrementing counter:
+        self.assertEqual(my_doodad.num_calls, 1)
+
+        # But we can access keys on the dict that's returned
+        # by the implicit ``__call__``, instead.
+        t = template.Template('{{my_doodad.the_value}}')
+        self.assertEqual(t.render(c), u'42')
+        self.assertEqual(my_doodad.num_calls, 2)
+
+    def test_alters_data(self):
+
+        class Doodad(object):
+            alters_data = True
+            def __init__(self, value):
+                self.num_calls = 0
+                self.value = value
+            def __call__(self):
+                self.num_calls += 1
+                return {"the_value": self.value}
+
+        my_doodad = Doodad(42)
+        c = template.Context({"my_doodad": my_doodad})
+
+        # Since ``my_doodad.alters_data`` is True, 
+        # the template system will not try to call our doodad.
+        # Instead it will replace it with the empty string.
+        # It won't even return the dict.
+        t = template.Template('{{my_doodad.value}}')
+        self.assertEqual(t.render(c), u'')
+        t = template.Template('{{my_doodad.the_value}}')
+        self.assertEqual(t.render(c), u'')
+
+        # And let's just double-check that the object was
+        # really never called during the template rendering.
+        self.assertEqual(my_doodad.num_calls, 0)
+
+    def test_do_not_call(self):
+        class Doodad(object):
+            do_not_call_in_templates = True
+            def __init__(self, value):
+                self.num_calls = 0
+                self.value = value
+            def __call__(self):
+                self.num_calls += 1
+                return {"the_value": self.value}
+
+        my_doodad = Doodad(42)
+        c = template.Context({"my_doodad": my_doodad})
+
+        # Since ``my_doodad.do_not_call_in_templates`` is True, 
+        # the template system will not try to call our doodad.
+        # Instead it will leave it alone.  We can access its
+        # attributes as normal, and we don't have access to 
+        # the dict that it returns when called.
+        t = template.Template('{{my_doodad.value}}')
+        self.assertEqual(t.render(c), u'42')
+        t = template.Template('{{my_doodad.the_value}}')
+        self.assertEqual(t.render(c), u'')
+        
+        # And let's just double-check that the object was
+        # really never called during the template rendering.
+        self.assertEqual(my_doodad.num_calls, 0)
+
+    def test_do_not_call_and_alters_data(self):
+
+        # If we combine ``alters_data`` and ``do_not_call_in_templates``
+        # the ``alters_data`` attribute will not make any difference in
+        # the template system's behavior.  The fact that calling the object
+        # alters data shouldn't matter, because the template system knows
+        # never to call it anyway.
+        class Doodad(object):
+            do_not_call_in_templates = True
+            alters_data = True
+            def __init__(self, value):
+                self.num_calls = 0
+                self.value = value
+            def __call__(self):
+                self.num_calls += 1
+                return {"the_value": self.value}
+
+        my_doodad = Doodad(42)
+        c = template.Context({"my_doodad": my_doodad})
+
+        # Since ``my_doodad.do_not_call_in_templates`` is True, 
+        # the template system will not try to call our doodad.
+        # Instead it will leave it alone.  We can access its
+        # attributes as normal, and we don't have access to 
+        # the dict that it returns when called.
+        t = template.Template('{{my_doodad.value}}')
+        self.assertEqual(t.render(c), u'42')
+        t = template.Template('{{my_doodad.the_value}}')
+        self.assertEqual(t.render(c), u'')
+        
+        # And let's just double-check that the object was
+        # really never called during the template rendering.
+        self.assertEqual(my_doodad.num_calls, 0)
