Index: django/shortcuts/__init__.py
===================================================================
--- django/shortcuts/__init__.py	(revision 1)
+++ django/shortcuts/__init__.py	(working copy)
@@ -11,14 +11,28 @@
 from django.db.models.query import QuerySet
 from django.core import urlresolvers
 
+def http_response_using(func, *args, **kwargs):
+    """
+    Returns a HttpResponse whose content is filled with the result of calling
+    func() with the passed arguments.
+    """
+    httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype', None)}
+    return HttpResponse(func(*args, **kwargs), **httpresponse_kwargs)
+
 def render_to_response(*args, **kwargs):
     """
     Returns a HttpResponse whose content is filled with the result of calling
     django.template.loader.render_to_string() with the passed arguments.
     """
-    httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype', None)}
-    return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)
+    return http_response_using(loader.render_to_string, *args, **kwargs)
 
+def stream_to_response(*args, **kwargs):
+    """
+    Returns a HttpResponse whose content is filled with the result of calling
+    django.template.loader.get_render_stream() with the passed arguments.
+    """
+    return http_response_using(loader.stream, *args, **kwargs)
+
 def redirect(to, *args, **kwargs):
     """
     Returns an HttpResponseRedirect to the apropriate URL for the arguments
Index: django/template/__init__.py
===================================================================
--- django/template/__init__.py	(revision 1)
+++ django/template/__init__.py	(working copy)
@@ -166,11 +166,17 @@
     def _render(self, context):
         return self.nodelist.render(context)
 
+    def _stream(self, context):
+        return self.nodelist.stream(context)
+
     def render(self, context):
         "Display stage -- can be called many times"
+        return ''.join(self.stream(context))
+
+    def stream(self, context):
         context.render_context.push()
         try:
-            return self._render(context)
+            return self._stream(context)
         finally:
             context.render_context.pop()
 
@@ -770,6 +776,10 @@
         "Return the node rendered as a string"
         pass
 
+    def stream(self, context):
+        "Returns a generator that progressively renders the node"
+        yield self.render(context)
+
     def __iter__(self):
         yield self
 
@@ -790,13 +800,15 @@
     contains_nontext = False
 
     def render(self, context):
-        bits = []
+        return ''.join(self.stream(context))
+        
+    def stream(self, context):
         for node in self:
             if isinstance(node, Node):
-                bits.append(self.render_node(node, context))
+                for bit in self.stream_node(node, context):
+                    yield mark_safe(force_unicode(bit))
             else:
-                bits.append(node)
-        return mark_safe(''.join([force_unicode(b) for b in bits]))
+                yield mark_safe(force_unicode(node))
 
     def get_nodes_by_type(self, nodetype):
         "Return a list of all nodes of the given type"
@@ -808,6 +820,9 @@
     def render_node(self, node, context):
         return node.render(context)
 
+    def stream_node(self, node, context):
+        return node.stream(context)
+
 class TextNode(Node):
     def __init__(self, s):
         self.s = s
Index: django/template/defaulttags.py
===================================================================
--- django/template/defaulttags.py	(revision 1)
+++ django/template/defaulttags.py	(working copy)
@@ -20,17 +20,21 @@
     """Implements the actions of the autoescape tag."""
     def __init__(self, setting, nodelist):
         self.setting, self.nodelist = setting, nodelist
-
-    def render(self, context):
+        
+    def stream(self, context):
         old_setting = context.autoescape
         context.autoescape = self.setting
-        output = self.nodelist.render(context)
-        context.autoescape = old_setting
         if self.setting:
-            return mark_safe(output)
+            for chunk in self.nodelist.stream(context):
+                yield mark_safe(chunk)
         else:
-            return output
+            for chunk in self.nodelist.stream(context):
+                yield chunk
+        context.autoescape = old_setting
 
+    def render(self, context):
+        ''.join(self.stream(context))
+
 class CommentNode(Node):
     def render(self, context):
         return ''
@@ -79,12 +83,15 @@
         self.filter_expr, self.nodelist = filter_expr, nodelist
 
     def render(self, context):
-        output = self.nodelist.render(context)
-        # Apply filters.
-        context.update({'var': output})
-        filtered = self.filter_expr.resolve(context)
+        return ''.join(self.stream(context))
+
+    def stream(self, context):
+        for output in self.nodelist.stream(context):
+            # Apply filters.
+            context.update({'var': output})
+            filtered = self.filter_expr.resolve(context)
+            yield filtered
         context.pop()
-        return filtered
 
 class FirstOfNode(Node):
     def __init__(self, vars):
@@ -122,6 +129,9 @@
             yield node
 
     def render(self, context):
+        return ''.join(self.stream(context))
+
+    def stream(self, context):
         if 'forloop' in context:
             parentloop = context['forloop']
         else:
@@ -133,19 +143,35 @@
             values = []
         if values is None:
             values = []
-        if not hasattr(values, '__len__'):
-            values = list(values)
-        len_values = len(values)
-        if len_values < 1:
-            context.pop()
-            return self.nodelist_empty.render(context)
-        nodelist = NodeList()
-        if self.is_reversed:
-            values = reversed(values)
+        if 'generator' == values.__class__.__name__:
+            if self.is_reversed:
+                values = list(values)
+                len_values = len(values)
+                values = reversed(values)
+            else:
+                len_values = 0 # generators have unknown len, but if the len is needed, convert the
+                               # generator to list
+                for node in self.nodelist_loop:
+                    try:
+                        token = node.filter_expression.token
+                    except AttributeError:
+                        continue
+                    else:
+                        if token.startswith('forloop.revcounter') or token.startswith('forloop.last'):
+                            values = list(values)
+                            len_values = len(values)
+                            break
+        else: # not generator
+            if not hasattr(values, '__len__'):
+                values = list(values)
+            len_values = len(values)
+            if self.is_reversed:
+                values = reversed(values)
         unpack = len(self.loopvars) > 1
         # Create a forloop value in the context.  We'll update counters on each
         # iteration just below.
         loop_dict = context['forloop'] = {'parentloop': parentloop}
+        i = None
         for i, item in enumerate(values):
             # Shortcuts for current loop iteration number.
             loop_dict['counter0'] = i
@@ -163,8 +189,8 @@
                 context.update(dict(zip(self.loopvars, item)))
             else:
                 context[self.loopvars[0]] = item
-            for node in self.nodelist_loop:
-                nodelist.append(node.render(context))
+            for chunk in self.nodelist_loop.stream(context):
+                yield chunk
             if unpack:
                 # The loop variables were pushed on to the context so pop them
                 # off again. This is necessary because the tag lets the length
@@ -173,7 +199,9 @@
                 # context.
                 context.pop()
         context.pop()
-        return nodelist.render(context)
+        if i is None: # values was empty
+            for chunk in self.nodelist_empty.stream(context):
+                yield chunk
 
 class IfChangedNode(Node):
     child_nodelists = ('nodelist_true', 'nodelist_false')
@@ -219,11 +247,14 @@
         return "<IfEqualNode>"
 
     def render(self, context):
+        return ''.join(self.stream(context))
+        
+    def stream(self, context):
         val1 = self.var1.resolve(context, True)
         val2 = self.var2.resolve(context, True)
         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
-            return self.nodelist_true.render(context)
-        return self.nodelist_false.render(context)
+            return self.nodelist_true.stream(context)
+        return self.nodelist_false.stream(context)
 
 class IfNode(Node):
     child_nodelists = ('nodelist_true', 'nodelist_false')
@@ -242,15 +273,18 @@
             yield node
 
     def render(self, context):
+        return ''.join(self.stream(context))
+        
+    def stream(self, context):
         try:
             var = self.var.eval(context)
         except VariableDoesNotExist:
             var = None
 
         if var:
-            return self.nodelist_true.render(context)
+            return self.nodelist_true.stream(context)
         else:
-            return self.nodelist_false.render(context)
+            return self.nodelist_false.stream(context)
 
 class RegroupNode(Node):
     def __init__(self, target, expression, var_name):
@@ -281,13 +315,18 @@
 class SsiNode(Node):
     def __init__(self, filepath, parsed):
         self.filepath, self.parsed = filepath, parsed
+        
+     
+    def render(self, context):
+        return ''.join(self.stream(context))
 
-    def render(self, context):
+    def stream(self, context):
         if not include_is_allowed(self.filepath):
             if settings.DEBUG:
-                return "[Didn't have permission to include file]"
+                yield "[Didn't have permission to include file]"
             else:
-                return '' # Fail silently for invalid includes.
+                yield '' # Fail silently for invalid includes.
+            return
         try:
             fp = open(self.filepath, 'r')
             output = fp.read()
@@ -297,13 +336,15 @@
         if self.parsed:
             try:
                 t = Template(output, name=self.filepath)
-                return t.render(context)
+                for chunk in t.stream(context):
+                    yield chunk
             except TemplateSyntaxError, e:
                 if settings.DEBUG:
-                    return "[Included template had syntax error: %s]" % e
+                    yield "[Included template had syntax error: %s]" % e
                 else:
-                    return '' # Fail silently for invalid included templates.
-        return output
+                    yield '' # Fail silently for invalid included templates.
+        else:
+            yield output
 
 class LoadNode(Node):
     def render(self, context):
@@ -419,12 +460,15 @@
         return "<WithNode>"
 
     def render(self, context):
+        return ''.join(self.stream(context))
+    
+    def stream(self, context):
         val = self.var.resolve(context)
         context.push()
         context[self.name] = val
-        output = self.nodelist.render(context)
+        for output in self.nodelist.stream(context):
+            yield output
         context.pop()
-        return output
 
 #@register.tag
 def autoescape(parser, token):
Index: django/template/loader_tags.py
===================================================================
--- django/template/loader_tags.py	(revision 1)
+++ django/template/loader_tags.py	(working copy)
@@ -46,11 +46,15 @@
         return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
 
     def render(self, context):
+        return ''.join(self.stream(context))
+
+    def stream(self, context):
         block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
         context.push()
         if block_context is None:
             context['block'] = self
-            result = self.nodelist.render(context)
+            for chunk in self.nodelist.stream(context):
+                yield chunk
         else:
             push = block = block_context.pop(self.name)
             if block is None:
@@ -59,11 +63,11 @@
             block = BlockNode(block.name, block.nodelist)
             block.context = context
             context['block'] = block
-            result = block.nodelist.render(context)
+            for chunk in block.nodelist.stream(context):
+                yield chunk
             if push is not None:
                 block_context.push(self.name, push)
         context.pop()
-        return result
 
     def super(self):
         render_context = self.context.render_context
@@ -100,6 +104,9 @@
         return get_template(parent)
 
     def render(self, context):
+        return ''.join(self.stream(context))
+
+    def stream(self, context):
         compiled_parent = self.get_parent(context)
 
         if BLOCK_CONTEXT_KEY not in context.render_context:
@@ -120,9 +127,9 @@
                     block_context.add_blocks(blocks)
                 break
 
-        # Call Template._render explicitly so the parser context stays
+        # Call Template._stream explicitly so the parser context stays
         # the same.
-        return compiled_parent._render(context)
+        return compiled_parent._stream(context)
 
 class ConstantIncludeNode(Node):
     def __init__(self, template_path):
@@ -133,28 +140,34 @@
             if settings.TEMPLATE_DEBUG:
                 raise
             self.template = None
+            
+    def render(self, context):
+        ''.join(self.stream(context))
 
-    def render(self, context):
+    def stream(self, context):
         if self.template:
-            return self.template.render(context)
+            return self.template.stream(context)
         else:
-            return ''
+            return empty_string_generator()
 
 class IncludeNode(Node):
     def __init__(self, template_name):
         self.template_name = Variable(template_name)
 
     def render(self, context):
+        ''.join(self.stream(context))
+
+    def stream(self, context):
         try:
             template_name = self.template_name.resolve(context)
             t = get_template(template_name)
-            return t.render(context)
+            return t.stream(context)
         except TemplateSyntaxError, e:
             if settings.TEMPLATE_DEBUG:
                 raise
-            return ''
+            return empty_string_generator()
         except:
-            return '' # Fail silently for invalid included templates.
+            return empty_string_generator() # Fail silently for invalid included templates.
 
 def do_block(parser, token):
     """
@@ -214,6 +227,9 @@
     if path[0] in ('"', "'") and path[-1] == path[0]:
         return ConstantIncludeNode(path[1:-1])
     return IncludeNode(bits[1])
+    
+def empty_string_generator():
+    yield ''
 
 register.tag('block', do_block)
 register.tag('extends', do_extends)
Index: django/template/loader.py
===================================================================
--- django/template/loader.py	(revision 1)
+++ django/template/loader.py	(working copy)
@@ -174,6 +174,15 @@
     get_template, or it may be a tuple to use select_template to find one of
     the templates in the list. Returns a string.
     """
+    return ''.join(stream(template_name, dictionary, context_instance))
+
+def stream(template_name, dictionary=None, context_instance=None):
+    """
+    Loads the given template_name and streams it with the given dictionary as
+    context. The template_name may be a string to load a single template using
+    get_template, or it may be a tuple to use select_template to find one of
+    the templates in the list. Returns a string generator.
+    """
     dictionary = dictionary or {}
     if isinstance(template_name, (list, tuple)):
         t = select_template(template_name)
@@ -183,7 +192,7 @@
         context_instance.update(dictionary)
     else:
         context_instance = Context(dictionary)
-    return t.render(context_instance)
+    return t.stream(context_instance)
 
 def select_template(template_name_list):
     "Given a list of template names, returns the first that can be loaded."
Index: docs/howto/custom-template-tags.txt
===================================================================
--- docs/howto/custom-template-tags.txt	(revision 1)
+++ docs/howto/custom-template-tags.txt	(working copy)
@@ -932,3 +932,25 @@
 For more examples of complex rendering, see the source code for ``{% if %}``,
 ``{% for %}``, ``{% ifequal %}`` and ``{% ifchanged %}``. They live in
 ``django/template/defaulttags.py``.
+
+Add streaming support
+~~~~~~~~~~~~~~~~~~~~~
+
+Simple tag like the earlier example of ``CurrentTimeNode`` does not need to
+worry about streaming support. But if your custom tag is a more complex one
+that have a "begin" tag and "end" tag like the above ``{% upper %}`` .. 
+``{% endupper %}``, and you want the tag to be able to progressively render 
+its contents, you can define your own ``stream()`` method for the custom tag. 
+The ``stream()`` method takes the same arguments as ``render()`` but instead
+of returning string, it needs to return string generator.
+
+As an example, the ``stream()`` method for the ``{% upper %}`` tag above will
+look like this::
+
+    def stream(self, context):
+        for chunk in self.nodelist.stream(context):
+            yield chunk.upper()
+            
+If you define ``stream()`` in your custom template tag, you can define its
+``render()`` method as ``''.join(self.stream(context))`` to avoid duplicate
+codes.
Index: docs/topics/http/shortcuts.txt
===================================================================
--- docs/topics/http/shortcuts.txt	(revision 1)
+++ docs/topics/http/shortcuts.txt	(working copy)
@@ -80,6 +80,30 @@
         return HttpResponse(t.render(c),
             mimetype="application/xhtml+xml")
 
+``stream_to_response``
+======================
+
+.. function:: stream_to_response(template[, dictionary][, context_instance][, mimetype])
+
+   The ``stream_to_response`` shortcut is the same as
+   :func:`render_to_response`, except that ``stream_to_response`` returns an
+   :class:`~django.http.HttpResponse` object that gradually renders the
+   template, while ``render_to_response`` waits for the whole template to be
+   rendered before putting it into the response.
+   
+   This is useful if something inside the template takes a long time to
+   render. For example, if the template contains the following code::
+   
+    {% for item in warehouse %}
+        Current value: {{ item.calculate_depreciation }}
+    {% endfor %}
+   
+   If there are 100 items in warehouse and ``calculate_depreciation()`` takes
+   0.5 seconds to complete, ``render_to_response`` will need 50 seconds to
+   render all items before returning a response, while ``stream_to_response``
+   will immediately return a response that renders one item every half 
+   seconds.
+
 ``redirect``
 ============
 
Index: docs/ref/templates/api.txt
===================================================================
--- docs/ref/templates/api.txt	(revision 1)
+++ docs/ref/templates/api.txt	(working copy)
@@ -207,6 +207,15 @@
             self.database_record.delete()
         sensitive_function.alters_data = True
 
+Getting the rendering generator
+-------------------------------
+
+As an alternative to rendering a context all-at-once, you can get the string
+generator that render the context progressively by calling the ``Template``
+object's ``stream()`` method, passing the context as its argument. You can
+then pass the generator to :class:`~django.http.HttpResponse` to get a
+"streaming" effect on your view.
+
 .. _invalid-template-variables:
 
 How invalid variables are handled
@@ -690,6 +699,14 @@
 calls ``render_to_string`` and feeds the result into an ``HttpResponse``
 suitable for returning directly from a view.
 
+The ``stream()`` shortcut
+=========================
+
+This shortcut is like :func:`render_to_string` but instead of returning
+the whole rendered text of the template, this shortcut returns a generator
+that successively renders parts of the template. See
+:func:`~django.shortcuts.stream_to_response()` for an example of its usage.
+
 Configuring the template system in standalone mode
 ==================================================
 
