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
This commit is contained in:
Russell Keith-Magee 2010-08-28 11:59:14 +00:00
parent c00f35ae0d
commit e1e2726957
11 changed files with 369 additions and 10 deletions

View File

@ -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
}
}
]

View File

@ -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)

View File

@ -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 *

View File

@ -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"

View File

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

View File

@ -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>

View File

@ -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%}")

View File

@ -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"

View File

@ -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 %}

View File

@ -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
----------------------------------------