From e1e2726957ef2c74c5e7e3461d37c7603998c1af Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 11:59:14 +0000 Subject: [PATCH] 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 --- .../flatpages/fixtures/sample_flatpages.json | 31 ++++ .../flatpages/templatetags/__init__.py | 0 .../flatpages/templatetags/flatpages.py | 99 +++++++++++++ django/contrib/flatpages/tests/__init__.py | 1 + django/contrib/flatpages/tests/csrf.py | 6 + django/contrib/flatpages/tests/middleware.py | 11 ++ .../tests/templates/flatpages/default.html | 12 +- .../contrib/flatpages/tests/templatetags.py | 134 ++++++++++++++++++ django/contrib/flatpages/tests/views.py | 6 + docs/ref/contrib/flatpages.txt | 77 ++++++++-- docs/ref/contrib/sites.txt | 2 + 11 files changed, 369 insertions(+), 10 deletions(-) create mode 100644 django/contrib/flatpages/templatetags/__init__.py create mode 100644 django/contrib/flatpages/templatetags/flatpages.py create mode 100644 django/contrib/flatpages/tests/templatetags.py diff --git a/django/contrib/flatpages/fixtures/sample_flatpages.json b/django/contrib/flatpages/fixtures/sample_flatpages.json index 79808c2d2b..885af1eb60 100644 --- a/django/contrib/flatpages/fixtures/sample_flatpages.json +++ b/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 + } } ] \ No newline at end of file diff --git a/django/contrib/flatpages/templatetags/__init__.py b/django/contrib/flatpages/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/flatpages/templatetags/flatpages.py b/django/contrib/flatpages/templatetags/flatpages.py new file mode 100644 index 0000000000..5c76699b79 --- /dev/null +++ b/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) diff --git a/django/contrib/flatpages/tests/__init__.py b/django/contrib/flatpages/tests/__init__.py index 2672dbfae8..edbc108d4c 100644 --- a/django/contrib/flatpages/tests/__init__.py +++ b/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 * diff --git a/django/contrib/flatpages/tests/csrf.py b/django/contrib/flatpages/tests/csrf.py index dca92bec5e..b65ee382a6 100644 --- a/django/contrib/flatpages/tests/csrf.py +++ b/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, "

Isn't it sekrit!

") def test_fallback_flatpage(self): "A flatpage can be served by the fallback middlware" diff --git a/django/contrib/flatpages/tests/middleware.py b/django/contrib/flatpages/tests/middleware.py index 04396d190e..bedaffc8ac 100644 --- a/django/contrib/flatpages/tests/middleware.py +++ b/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, "

Isn't it sekrit!

") 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, "

Isn't it sekrit!

") diff --git a/django/contrib/flatpages/tests/templates/flatpages/default.html b/django/contrib/flatpages/tests/templates/flatpages/default.html index c6323fd91d..1410e17adf 100644 --- a/django/contrib/flatpages/tests/templates/flatpages/default.html +++ b/django/contrib/flatpages/tests/templates/flatpages/default.html @@ -1,2 +1,10 @@ -

{{ flatpage.title }}

-

{{ flatpage.content }}

\ No newline at end of file + + + +{{ flatpage.title }} + + +

{{ flatpage.content }}

+ + diff --git a/django/contrib/flatpages/tests/templatetags.py b/django/contrib/flatpages/tests/templatetags.py new file mode 100644 index 0000000000..9f42381c29 --- /dev/null +++ b/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%}") + diff --git a/django/contrib/flatpages/tests/views.py b/django/contrib/flatpages/tests/views.py index a9004209b7..128669095e 100644 --- a/django/contrib/flatpages/tests/views.py +++ b/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, "

Isn't it sekrit!

") def test_fallback_flatpage(self): "A fallback flatpage won't be served if the middleware is disabled" diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index 46b28dcfb2..ce6fdfcd1c 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/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 ` 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 `. - + 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 `. .. 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 ` 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 +`. + +Like all custom template tags, you'll need to :ref:`load its custom +tag library ` 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 %} + + +.. 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 %} + diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 516233b596..7ff05a5c82 100644 --- a/docs/ref/contrib/sites.txt +++ b/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 ----------------------------------------