Posted by & filed under GeoDjango.

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

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

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

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

9 Responses to “Bending django flatpages to your will”

  1. Barry Rowlingson

    Don’t you think you are getting to the point where you’d be better off using a CMS? You’re hacking in hierarchies, menus… I’ve been looking at the Django-based LFC system recently and it does all that. I’ve even managed to hook URLs to non-LFC apps but retain the whole CMS structure… If that makes sense…

    Also, what are you using for the wysiwyg text area in the Django admin?

    • Tim Sutton

      Actually the main component of the site is satchmo (online store) but the client wants a few static pages on the site. I could have probably just hard coded links to the about etc in their template, but I wanted to give the client the ability to create new pages etc without needing me to hack templates each time. I’ll check out LFC but I’m guessing its overkill for their needs (and satchmo admin is already complex enough without adding more to it).

      The wysiwig component is provided by the tinymce django app and I’ve overloaded the admin base_site.html to include a bit of js that gets added to every admin page to ensure that text areas are rendered as tinymce widgets.

      Regards

      Tim

      • Barry Rowlingson

        Nice. django-tinymce is the next thing I want to hook onto my current django project.

  2. robos85

    Where are You extending all this things? Did You create a special app for this or what?

    Thanks.

    • Tim Sutton

      Hi

      I had a look but I’m not familiar with MPTT unfortunately. Hopefully someone on stackoverflow can help you!

      Regards

      Tim

  3. Lapin-Blanc

    I really can’t figure out how you order the pages following the self refering foreignkey

    flatPages = ExtendedFlatPage.objects.filter(child_of=thePage).order_by(‘show_after’)

    I tried it and also modified the order, and it doesn’t seem to work. Am I missing something ?

  4. Tim Sutton

    Hi

    The page ordering is done by entering an appropriate number and parent in the page’s admin interface.

    Regards

    Tim

Leave a Reply

You must be logged in to post a comment.