Przeglądaj źródła

Fixed #6932 -- Added a template tag that gives a list of available flatpages for a given user. Thanks to Dmitri Fedortchenko for the suggestion, and to Mnewman, faldridge and Simon Meers for their work on the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13654 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Russell Keith-Magee 14 lat temu
rodzic
commit
e1e2726957

+ 31 - 0
django/contrib/flatpages/fixtures/sample_flatpages.json

@@ -17,6 +17,22 @@
     {
         "pk": 2,
         "model": "flatpages.flatpage",
+        "fields": {
+            "registration_required": false,
+            "title": "A Nested Flatpage",
+            "url": "/location/flatpage/",
+            "template_name": "",
+            "sites": [
+                1
+            ],
+            "content": "Isn't it flat and deep!",
+            "enable_comments": false
+        }
+    },
+
+    {
+        "pk": 101,
+        "model": "flatpages.flatpage",
         "fields": {
             "registration_required": true,
             "title": "Sekrit Flatpage",
@@ -28,5 +44,20 @@
             "content": "Isn't it sekrit!",
             "enable_comments": false
         }
+    },
+    {
+        "pk": 102,
+        "model": "flatpages.flatpage",
+        "fields": {
+            "registration_required": true,
+            "title": "Sekrit Nested Flatpage",
+            "url": "/location/sekrit/",
+            "template_name": "",
+            "sites": [
+                1
+            ],
+            "content": "Isn't it sekrit and deep!",
+            "enable_comments": false
+        }
     }
 ]

+ 0 - 0
django/contrib/flatpages/templatetags/__init__.py


+ 99 - 0
django/contrib/flatpages/templatetags/flatpages.py

@@ -0,0 +1,99 @@
+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, user=None):
+        self.context_name = context_name
+        if starts_with:
+            self.starts_with = template.Variable(starts_with)
+        else:
+            self.starts_with = None
+        if user:
+            self.user = template.Variable(user)
+        else:
+            self.user = None
+
+    def render(self, context):
+        flatpages = FlatPage.objects.filter(sites__id=settings.SITE_ID)
+        # If a prefix was specified, add a filter
+        if self.starts_with:
+            flatpages = flatpages.filter(
+                url__startswith=self.starts_with.resolve(context))
+
+        # If the provided user is not authenticated, or no user
+        # was provided, filter the list to only public flatpages.
+        if self.user:
+            user = self.user.resolve(context)
+            if not user.is_authenticated():
+                flatpages = flatpages.filter(registration_required=False)
+        else:
+            flatpages = flatpages.filter(registration_required=False)
+
+        context[self.context_name] = flatpages
+        return ''
+
+
+def get_flatpages(parser, token):
+    """
+    Retrieves all flatpage objects available for the current site and
+    visible to the specific user (or visible to all users if no user is
+    specified). Populates the template context with them in a variable
+    whose name is defined by the ``as`` clause.
+
+    An optional ``for`` clause can be used to control the user whose
+    permissions are to be used in determining which flatpages are visible.
+
+    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'] [for user] as context_name %}
+
+    Example usage::
+
+        {% get_flatpages as flatpages %}
+        {% get_flatpages for someuser as flatpages %}
+        {% get_flatpages '/about/' as about_pages %}
+        {% get_flatpages prefix as about_pages %}
+        {% get_flatpages '/about/' for someuser as about_pages %}
+    """
+    bits = token.split_contents()
+    syntax_message = _("%(tag_name)s expects a syntax of %(tag_name)s "
+                       "['url_starts_with'] [for user] as context_name" %
+                       dict(tag_name=bits[0]))
+   # Must have at 3-6 bits in the tag
+    if len(bits) >= 3 and len(bits) <= 6:
+
+        # If there's an even number of bits, there's no prefix
+        if len(bits) % 2 == 0:
+            prefix = bits[1]
+        else:
+            prefix = None
+
+        # The very last bit must be the context name
+        if bits[-2] != 'as':
+            raise template.TemplateSyntaxError(syntax_message)
+        context_name = bits[-1]
+
+        # If there are 5 or 6 bits, there is a user defined
+        if len(bits) >= 5:
+            if bits[-4] != 'for':
+                raise template.TemplateSyntaxError(syntax_message)
+            user = bits[-3]
+        else:
+            user = None
+
+        return FlatpageNode(context_name, starts_with=prefix, user=user)
+    else:
+        raise template.TemplateSyntaxError(syntax_message)
+
+register.tag('get_flatpages', get_flatpages)

+ 1 - 0
django/contrib/flatpages/tests/__init__.py

@@ -1,3 +1,4 @@
 from django.contrib.flatpages.tests.csrf import *
 from django.contrib.flatpages.tests.middleware import *
+from django.contrib.flatpages.tests.templatetags import *
 from django.contrib.flatpages.tests.views import *

+ 6 - 0
django/contrib/flatpages/tests/csrf.py

@@ -1,5 +1,6 @@
 import os
 from django.conf import settings
+from django.contrib.auth.models import User
 from django.test import TestCase, Client
 
 class FlatpageCSRFTests(TestCase):
@@ -42,6 +43,11 @@ class FlatpageCSRFTests(TestCase):
         "A flatpage served through a view can require authentication"
         response = self.client.get('/flatpage_root/sekrit/')
         self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/')
+        User.objects.create_user('testuser', 'test@example.com', 's3krit')
+        self.client.login(username='testuser',password='s3krit')
+        response = self.client.get('/flatpage_root/sekrit/')
+        self.assertEquals(response.status_code, 200)
+        self.assertContains(response, "<p>Isn't it sekrit!</p>")
 
     def test_fallback_flatpage(self):
         "A flatpage can be served by the fallback middlware"

+ 11 - 0
django/contrib/flatpages/tests/middleware.py

@@ -1,5 +1,6 @@
 import os
 from django.conf import settings
+from django.contrib.auth.models import User
 from django.test import TestCase
 
 class FlatpageMiddlewareTests(TestCase):
@@ -38,6 +39,11 @@ class FlatpageMiddlewareTests(TestCase):
         "A flatpage served through a view can require authentication"
         response = self.client.get('/flatpage_root/sekrit/')
         self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/')
+        User.objects.create_user('testuser', 'test@example.com', 's3krit')
+        self.client.login(username='testuser',password='s3krit')
+        response = self.client.get('/flatpage_root/sekrit/')
+        self.assertEquals(response.status_code, 200)
+        self.assertContains(response, "<p>Isn't it sekrit!</p>")
 
     def test_fallback_flatpage(self):
         "A flatpage can be served by the fallback middlware"
@@ -54,3 +60,8 @@ class FlatpageMiddlewareTests(TestCase):
         "A flatpage served by the middleware can require authentication"
         response = self.client.get('/sekrit/')
         self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
+        User.objects.create_user('testuser', 'test@example.com', 's3krit')
+        self.client.login(username='testuser',password='s3krit')
+        response = self.client.get('/sekrit/')
+        self.assertEquals(response.status_code, 200)
+        self.assertContains(response, "<p>Isn't it sekrit!</p>")

+ 10 - 2
django/contrib/flatpages/tests/templates/flatpages/default.html

@@ -1,2 +1,10 @@
-<h1>{{ flatpage.title }}</h1>
-<p>{{ flatpage.content }}</p>
+<!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>
+<p>{{ flatpage.content }}</p>
+</body>
+</html>

+ 134 - 0
django/contrib/flatpages/tests/templatetags.py

@@ -0,0 +1,134 @@
+import os
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser, User
+from django.template import Template, Context, TemplateSyntaxError
+from django.test import TestCase
+
+class FlatpageTemplateTagTests(TestCase):
+    fixtures = ['sample_flatpages']
+    urls = 'django.contrib.flatpages.tests.urls'
+
+    def setUp(self):
+        self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
+        flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'
+        if flatpage_middleware_class not in settings.MIDDLEWARE_CLASSES:
+            settings.MIDDLEWARE_CLASSES += (flatpage_middleware_class,)
+        self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+        settings.TEMPLATE_DIRS = (
+            os.path.join(
+                os.path.dirname(__file__),
+                'templates'
+            ),
+        )
+        self.me = User.objects.create_user('testuser', 'test@example.com', 's3krit')
+
+    def tearDown(self):
+        settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES
+        settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+
+    def test_get_flatpages_tag(self):
+        "The flatpage template tag retrives unregistered prefixed flatpages by default"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages as flatpages %}"
+                "{% for page in flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context())
+        self.assertEquals(out, "A Flatpage,A Nested Flatpage,")
+
+    def test_get_flatpages_tag_for_anon_user(self):
+        "The flatpage template tag retrives unregistered flatpages for an anonymous user"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages for anonuser as flatpages %}"
+                "{% for page in flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context({
+                'anonuser': AnonymousUser()
+            }))
+        self.assertEquals(out, "A Flatpage,A Nested Flatpage,")
+
+    def test_get_flatpages_tag_for_user(self):
+        "The flatpage template tag retrives all flatpages for an authenticated user"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages for me as flatpages %}"
+                "{% for page in flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context({
+                'me': self.me
+            }))
+        self.assertEquals(out, "A Flatpage,A Nested Flatpage,Sekrit Nested Flatpage,Sekrit Flatpage,")
+
+    def test_get_flatpages_with_prefix(self):
+        "The flatpage template tag retrives unregistered prefixed flatpages by default"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages '/location/' as location_flatpages %}"
+                "{% for page in location_flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context())
+        self.assertEquals(out, "A Nested Flatpage,")
+
+    def test_get_flatpages_with_prefix_for_anon_user(self):
+        "The flatpage template tag retrives unregistered prefixed flatpages for an anonymous user"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages '/location/' for anonuser as location_flatpages %}"
+                "{% for page in location_flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context({
+                'anonuser': AnonymousUser()
+            }))
+        self.assertEquals(out, "A Nested Flatpage,")
+
+    def test_get_flatpages_with_prefix_for_user(self):
+        "The flatpage template tag retrive prefixed flatpages for an authenticated user"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages '/location/' for me as location_flatpages %}"
+                "{% for page in location_flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context({
+                'me': self.me
+            }))
+        self.assertEquals(out, "A Nested Flatpage,Sekrit Nested Flatpage,")
+
+    def test_get_flatpages_with_variable_prefix(self):
+        "The prefix for the flatpage template tag can be a template variable"
+        out = Template(
+                "{% load flatpages %}"
+                "{% get_flatpages location_prefix as location_flatpages %}"
+                "{% for page in location_flatpages %}"
+                "{{ page.title }},"
+                "{% endfor %}"
+            ).render(Context({
+                'location_prefix': '/location/'
+            }))
+        self.assertEquals(out, "A Nested Flatpage,")
+
+    def test_parsing_errors(self):
+        "There are various ways that the flatpages template tag won't parse"
+        render = lambda t: Template(t).render(Context())
+
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages %}")
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages as %}")
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages cheesecake flatpages %}")
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages as flatpages asdf%}")
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages cheesecake user as flatpages %}")
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages for user as flatpages asdf%}")
+        self.assertRaises(TemplateSyntaxError, render,
+                          "{% load flatpages %}{% get_flatpages prefix for user as flatpages asdf%}")
+

+ 6 - 0
django/contrib/flatpages/tests/views.py

@@ -1,5 +1,6 @@
 import os
 from django.conf import settings
+from django.contrib.auth.models import User
 from django.test import TestCase
 
 class FlatpageViewTests(TestCase):
@@ -38,6 +39,11 @@ class FlatpageViewTests(TestCase):
         "A flatpage served through a view can require authentication"
         response = self.client.get('/flatpage_root/sekrit/')
         self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/')
+        User.objects.create_user('testuser', 'test@example.com', 's3krit')
+        self.client.login(username='testuser',password='s3krit')
+        response = self.client.get('/flatpage_root/sekrit/')
+        self.assertEquals(response.status_code, 200)
+        self.assertContains(response, "<p>Isn't it sekrit!</p>")
 
     def test_fallback_flatpage(self):
         "A fallback flatpage won't be served if the middleware is disabled"

+ 69 - 8
docs/ref/contrib/flatpages.txt

@@ -19,7 +19,7 @@ template. It can be associated with one, or multiple, sites.
 
 .. versionadded:: 1.0
 
-The content field may optionally be left blank if you prefer to put your 
+The content field may optionally be left blank if you prefer to put your
 content in a custom template.
 
 Here are some examples of flatpages on Django-powered sites:
@@ -35,20 +35,20 @@ To install the flatpages app, follow these steps:
     1. Install the :mod:`sites framework <django.contrib.sites>` by adding
        ``'django.contrib.sites'`` to your :setting:`INSTALLED_APPS` setting,
        if it's not already in there.
-       
+
        Also make sure you've correctly set :setting:`SITE_ID` to the ID of the
        site the settings file represents. This will usually be ``1`` (i.e.
        ``SITE_ID = 1``, but if you're using the sites framework to manage
        multiple sites, it could be the ID of a different site.
-       
+
     2. Add ``'django.contrib.flatpages'`` to your :setting:`INSTALLED_APPS`
        setting.
-       
+
     3. Add ``'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'``
        to your :setting:`MIDDLEWARE_CLASSES` setting.
-       
+
     4. Run the command :djadmin:`manage.py syncdb <syncdb>`.
- 
+
 How it works
 ============
 
@@ -67,7 +67,7 @@ If it finds a match, it follows this algorithm:
 
     * If the flatpage has a custom template, it loads that template. Otherwise,
       it loads the template :file:`flatpages/default.html`.
-    
+
     * It passes that template a single context variable, :data:`flatpage`, which
       is the flatpage object. It uses
       :class:`~django.template.context.RequestContext` in rendering the
@@ -94,7 +94,7 @@ For more on middleware, read the :doc:`middleware docs
 </topics/http/middleware>`.
 
 .. admonition:: Ensure that your 404 template works
-    
+
     Note that the
     :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`
     only steps in once another view has successfully produced a 404 response.
@@ -165,3 +165,64 @@ Since you're already entering raw HTML into the admin page for a flatpage,
 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 provides 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. After loading the library, you can retrieve all current flatpages
+via the :ttag:`get_flatpages` tag:
+
+.. code-block:: html+django
+
+    {% load flatpages %}
+    {% get_flatpages as flatpages %}
+    <ul>
+        {% for page in flatpages %}
+            <li><a href="{{ page.url }}">{{ page.title }}</a></li>
+        {% endfor %}
+    </ul>
+
+.. templatetag:: get_flatpages
+
+Displaying ``registration_required`` flatpages
+----------------------------------------------
+
+By default, the :ttag:`get_flatpages` templatetag will only show
+flatpages that are marked :attr:`registration_required`\=False. If you
+want to display registration-protected flatpages, you need to specify
+an authenticated user using a``for`` clause.
+
+For example:
+
+.. code-block:: html+django
+
+    {% get_flatpages for someuser as about_pages %}
+
+If you provide an anonymous user, :ttag:`get_flatpages` will behave
+the same as if you hadn't provided a user -- i.e., it will only show you
+public flatpages.
+
+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 as a variable to be resolved
+from the context.
+
+For example:
+
+.. code-block:: html+django
+
+    {% get_flatpages '/about/' as about_pages %}
+    {% get_flatpages about_prefix as about_pages %}
+    {% get_flatpages '/about/' for someuser as about_pages %}
+

+ 2 - 0
docs/ref/contrib/sites.txt

@@ -102,6 +102,8 @@ like this::
 
 This has the same benefits as described in the last section.
 
+.. _hooking-into-current-site-from-views:
+
 Hooking into the current site from views
 ----------------------------------------