I’ve been working on a web site for a client that uses django flatpages. Flatpages is a built in component that allows users to create static content within the admin interface and publish it to the web front end. Think of it as a basic content management system component for django. Out of the box django’s flatpages have some limitations though – you cannot easily dictate a sort order or heirachy for the pages. Let me demonstrate…
The old way:
Typically one might show a list of your flatpages on your website you would do something like this
{% load flatpages %} {# Custom tag defined in lib/templatetags/ #}
{% get_flatpages as flatpages %}
<ul>
{% for page in flatpages %}
<li><a href="{{ page.url }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
Which might generate some html rendered out like this:
Basic flat page listing
Extending the FlatPages model:
We can use model inheritance to add a couple of fields to the flatpages model as explained here. To do that I simply add something like this to my models.py:
from django.db import models
from django.contrib.flatpages.models import FlatPage
class ExtendedFlatPage(FlatPage):
show_after = models.ForeignKey('ExtendedFlatPage', \
null=True, blank=True, default=None, \
related_name="flatpage_predecessor", \
help_text="Page that this one should appear after (if any)")
child_of = models.ForeignKey('ExtendedFlatPage', \
null=True, blank=True, default=None, \
related_name="flatpage_parent", \
help_text="Page that shis one should appear under (if any)")
So my new model adds two fields to the flatpages model that will allow me to define ordering and heirachy. The show_after field means that when I create a new flatpage, I can specify which other page the new page should be shown after. The child_of field can be used to state that the new page being added is a child of another page. A quick run of
python manage.py syncdb
Will go ahead and create the new table for your extended flat pages model. If we want to migrate existing flatpages into the new model, you can run a little query from the postgres command prompt like this:
insert into localsite_extendedflatpage (flatpage_ptr_id)
select (id) from django_flatpage;
Where localsite_extendedflatpage is the table that was generated for your model (its name will vary depending on the name of your django app).
Registering the model in the admin interface:
To allow the user to administer the extended flat pages, you need to register your custom flatpages model with the admin interface and deregister the existing one (in admin.py):
from django.contrib import admin
from django.contrib.flatpages.admin import FlatpageForm, FlatPageAdmin
from django.contrib.flatpages.models import FlatPage
from models import ExtendedFlatPage
class ExtendedFlatPageForm(FlatpageForm):
class Meta:
model = ExtendedFlatPage
class ExtendedFlatPageAdmin(FlatPageAdmin):
form = ExtendedFlatPageForm
fieldsets = (
(None, {'fields': ('url', 'title', 'content', \
'sites', 'show_after', 'child_of' )}),
)
admin.site.unregister(FlatPage)
admin.site.register(ExtendedFlatPage, ExtendedFlatPageAdmin)
After restarting your web server, you should now see the ExtendedFlatPages model listed in your admin interface, and it should show you our two custom fields which we use to define order and heirachy.
Admin interface for our customised flatpages model
Creating a custom template tag:
The next step in our journey is to create a custom templatetag that will render our listing of flatpages according to their heirachy and order. I added this to <myapp>/templatetags/local_tags.py :
from django import template
from localsite.models import ExtendedFlatPage
register = template.Library()
@register.simple_tag
def show_ordered_flatpages():
flatPages = ExtendedFlatPage.objects.filter(child_of__isnull=True).order_by('-show_after')
myString = ""
for myPage in flatPages:
myString += get_ul_for_page( myPage )
return myString
def get_ul_for_page( thePage ):
flatPages = ExtendedFlatPage.objects.filter(child_of=thePage).order_by('show_after')
if len(flatPages) < 1:
return """<li><a href="%s">%s</a></li>""" % ( thePage.url, thePage.title )
else:
myString = """<li><a href="%s">%s</a>""" % ( thePage.url, thePage.title )
myString += "<ul>\n"
for myPage in flatPages:
myString += get_ul_for_page( myPage ) #recursion
myString += "</ul>\n"
myString += "</li>\n"
return myString
So the template tag uses a simple recursive function to generate a heirachical nested collection of unordered lists (ul). The last thing I need to do is update my template that shows a listing of available flatpages to look something like this:
{% load local_tags %}
<div class = "flat-page-list">
{% show_ordered_flatpages %}
</div>
The result:
After making the above changes, my contrived list of flatpages now looks like this when rendered:
Flatpages rendered using user defined order and nesting
A couple of gotchas:
My examples above do not check to see where the logged in user has the rights to view a page. Also my ExtendedFlatPages model needs to have some checks added to ensure that show_after and child_of fields can not be populated with a reference back to themselves (which would probably cause infinite recursion in my templatetag code).