Index: django/contrib/flatpages/tests/__init__.py
===================================================================
--- django/contrib/flatpages/tests/__init__.py	(revision 0)
+++ django/contrib/flatpages/tests/__init__.py	(revision 0)
@@ -0,0 +1,109 @@
+import os
+
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser, User
+from django.http import HttpRequest
+from django.template import Template, Context
+from django.test import TestCase
+from django.contrib.flatpages.models import FlatPage
+from django.contrib.flatpages.middleware import FlatpageFallbackMiddleware
+from django.contrib.sites.models import Site
+
+
+class FlatpageTests(TestCase):
+    def setUp(self):
+        self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+        settings.TEMPLATE_DIRS = (
+            os.path.join(
+                os.path.dirname(__file__),
+                'templates'
+            )
+        ,)
+
+        page = FlatPage(
+            url='/page1/',
+            title='Some page',
+            content="<p>Some text that shouldn't be automatically escaped</p>",
+            registration_required=False,
+            )
+        page.save()
+        page.sites.add(Site.objects.get_current())
+        page.save()
+
+        registrationPage = FlatPage(
+            url='/registered/page2/',
+            title='Secret page',
+            content="<p>Great, you are registered</p>",
+            registration_required=True,
+            )
+        registrationPage.save()
+        registrationPage.sites.add(Site.objects.get_current())
+        registrationPage.save()
+
+    def tearDown(self):
+        settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+
+    def render(self, t, **c):
+        ctx = Context(c)
+        out = Template(t).render(ctx)
+        return ctx, out
+
+    def testFlatpage(self):
+        original_response = self.client.get('/page1/')
+        fake_request = HttpRequest()
+        fake_request.path_info = original_response.request['PATH_INFO']
+        flatpage = FlatpageFallbackMiddleware().process_response(fake_request,
+            original_response)
+        self.assertContains(flatpage,
+            "<p>Some text that shouldn't be automatically escaped</p>")
+
+    def testRegistrationRequiredRedirect(self):
+        original_response = self.client.get('/registered/page2/')
+        fake_request = HttpRequest()
+        fake_request.path = original_response.request['PATH_INFO']
+        fake_request.path_info = original_response.request['PATH_INFO']
+        fake_request.user = AnonymousUser()
+        flatpage = FlatpageFallbackMiddleware().process_response(fake_request,
+            original_response)
+        self.assertEquals(flatpage.status_code, 302)
+        self.assertEquals(flatpage['Location'],
+                          '/accounts/login/?next=/registered/page2/')
+
+    def testRegistrationRequiredRender(self):
+        self.client.login(username='testuser', password='testpw')
+        original_response = self.client.get('/registered/page2/')
+        fake_request = HttpRequest()
+        fake_request.path = original_response.request['PATH_INFO']
+        fake_request.path_info = original_response.request['PATH_INFO']
+        fake_request.user = User.objects.create_user('testuser',
+            'test@example.com', 'testpw')
+        flatpage = FlatpageFallbackMiddleware().process_response(fake_request,
+            original_response)
+        self.assertContains(flatpage, "<p>Great, you are registered</p>")
+
+    def testGetFlatpagesTag(self):
+        ctx, out = self.render("{% load flatpages %}"
+                               "{% get_flatpages as flatpages %}"
+                               "{% for page in flatpages %}"
+                               "{{ page.title }},"
+                               "{% endfor %}")
+        self.assertEquals(out, "Some page,Secret page,")
+
+    def testGetFlatpagesTagUsingStartsWithString(self):
+        ctx, out = self.render(
+                "{% load flatpages %}"
+                "{% get_flatpages '/registered/' as registered_flatpages %}"
+                "{% for page in registered_flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}")
+        self.assertEquals(out, "Secret page,")
+
+    def testGetFlatpagesTagUsingStartsWithVariable(self):
+        ctx, out = self.render(
+                "{% load flatpages %}"
+                "{% get_flatpages registered_prefix as registered_flatpages %}"
+                "{% for page in registered_flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}",
+                registered_prefix='/registered/')
+        self.assertEquals(out, "Secret page,")
Index: django/contrib/flatpages/tests/models.py
===================================================================
Index: django/contrib/flatpages/tests/templates/flatpages/default.html
===================================================================
--- django/contrib/flatpages/tests/templates/flatpages/default.html	(revision 0)
+++ django/contrib/flatpages/tests/templates/flatpages/default.html	(revision 0)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+    "http://www.w3.org/TR/REC-html40/loose.dtd">
+<html>
+<head>
+<title>{{ flatpage.title }}</title>
+</head>
+<body>
+{{ flatpage.content }}
+</body>
+</html>
Index: django/contrib/flatpages/tests/templates/404.html
===================================================================
--- django/contrib/flatpages/tests/templates/404.html	(revision 0)
+++ django/contrib/flatpages/tests/templates/404.html	(revision 0)
@@ -0,0 +1 @@
+<p>Not Found</p>
Index: django/contrib/flatpages/templatetags/__init__.py
===================================================================
--- django/contrib/flatpages/templatetags/__init__.py	(revision 0)
+++ django/contrib/flatpages/templatetags/__init__.py	(revision 0)
@@ -0,0 +1,2 @@
+
+
Index: django/contrib/flatpages/templatetags/flatpages.py
===================================================================
--- django/contrib/flatpages/templatetags/flatpages.py	(revision 0)
+++ django/contrib/flatpages/templatetags/flatpages.py	(revision 0)
@@ -0,0 +1,61 @@
+from django import template
+from django.contrib.flatpages.models import FlatPage
+from django.utils.translation import ugettext as _
+from django.conf import settings
+
+
+register = template.Library()
+
+
+class FlatpageNode(template.Node):
+    def __init__(self, context_name, starts_with=None):
+        self.context_name, self.starts_with = context_name, starts_with
+        if self.starts_with:
+            self.starts_with = template.Variable(starts_with)
+
+    def render(self, context):
+        flatpages = FlatPage.objects.filter(sites__id=settings.SITE_ID)
+        if self.starts_with:
+            flatpages = flatpages.filter(
+                url__startswith=self.starts_with.resolve(context))
+        context[self.context_name] = flatpages
+        return ''
+
+
+def get_flatpages(parser, token):
+    """
+    Retrieves all flatpage objects available for the current site and
+    populates the template context with them in a variable whose name
+    is defined by the 'as' clause.
+
+    An optional argument, ``starts_with``, can be applied to limit the
+    returned flatpages to those beginning with a particular base URL.
+    This argument can be passed as a variable or a string, as it resolves
+    from the template context.
+
+    Syntax::
+
+        {% get_flatpages ['url_starts_with'] as context_name %}
+
+    Example usage::
+
+        {% get_flatpages as flatpages %}
+        {% get_flatpages '/about/' as about_pages %}
+        {% get_flatpages about_prefix as about_pages %}
+    """
+    bits = token.split_contents()
+    if len(bits) is 3:
+        if bits[1] != 'as':
+            raise template.TemplateSyntaxError, _("%s expects a syntax of %s "
+                "['url_starts_with'] as context_name" % (bits[0], bits[0]))
+        return FlatpageNode(bits[2])
+    elif len(bits) is 4:
+        if bits[2] != 'as':
+            raise template.TemplateSyntaxError, _("%s expects a syntax of %s "
+                "['url_starts_with'] as context_name" % (bits[0], bits[0]))
+        return FlatpageNode(bits[3], bits[1])
+    else:
+        raise template.TemplateSyntaxError, _("%s expects a syntax of %s "
+            "['url_starts_with'] as context_name" % (bits[0], bits[0]))
+
+register.tag('get_flatpages', get_flatpages)
Index: docs/ref/contrib/flatpages.txt
===================================================================
--- docs/ref/contrib/flatpages.txt	(revision 12843)
+++ docs/ref/contrib/flatpages.txt	(working copy)
@@ -167,3 +167,63 @@
 both ``flatpage.title`` and ``flatpage.content`` are marked as **not**
 requiring :ref:`automatic HTML escaping <automatic-html-escaping>` in the
 template.
+
+Getting a list of :class:`~django.contrib.flatpages.models.Flatpage` objects in your templates
+==============================================================================================
+
+.. versionadded:: 1.3
+
+The flatpages app also comes with a template tag that allows you to iterate
+over all of the available flatpages on the :ref:`current site <hooking-into-current-site-from-views>`.
+
+Like all custom template tags, you'll need to :ref:`load its custom
+tag library <loading-custom-template-libraries>` before you can use it:
+
+.. code-block:: html+django
+
+    {% load flatpages %}
+
+After loading the library, you can retrieve all current flatpages via the
+:ttag:`get_flatpages` tag:
+
+.. code-block:: html+django
+
+    {% get_flatpages as flatpages %}
+    <ul>
+        {% for page in flatpages %}
+            <li><a href="{{ page.url }}">{{ page.title }}</a></li>
+        {% endfor %}
+    </ul>
+
+Limiting flatpages by base URL
+------------------------------
+
+An optional argument, ``starts_with``, can be applied to limit the
+returned pages to those beginning with a particular base URL.  This argument
+may be passed as a string or a variable, as it is resolved from the context:
+
+.. code-block:: html+django
+
+    {% get_flatpages ['url_starts_with'] as context_name %}
+
+For example:
+
+.. code-block:: html+django
+
+    {% get_flatpages '/about/' as about_pages %}
+    {% get_flatpages about_prefix as about_pages %}
+
+Only displaying flatpages accessible to anonymous users
+-------------------------------------------------------
+
+If you wish to filter flatpages out by :attr:`registration_required`, you
+can place an if statement around your for loop:
+
+.. code-block:: html+django
+
+    {% get_flatpages '/contact/' as contact_pages %}
+    {% for page in contact_pages %}
+        {% if not user.is_authenticated and page.registration_required %}
+            <h3><a href="{{page.url}}">{{page.title}}</a></h3>
+        {% endif %}
+    {% endfor %}
Index: docs/ref/contrib/sites.txt
===================================================================
--- docs/ref/contrib/sites.txt	(revision 12843)
+++ docs/ref/contrib/sites.txt	(working copy)
@@ -104,6 +104,8 @@
 
 This has the same benefits as described in the last section.
 
+.. _hooking-into-current-site-from-views:
+
 Hooking into the current site from views
 ----------------------------------------
 
