Customizing FeinCMS Part 4: Level of Navigation
Part 4 of the "Customizing FeinCMS" series. In this post we are going to limit the level of navigation of our website.
You can find the code used in this serie on github.
Why
I've developed many websites with strict levels of navigation, most of the time expressed by a single layer of sub pages, in these cases I want my CMS to validate the user actions with an "all you can do is all you can see" strategy.
This makes a lot of sense if you don't like being bothered by your clients asking you questions like: "Why I can't see my pages?".
What we need to do in a nutshell
We are going to extend the clean
method of PageAdminForm
by checking if the level of navigation is valid and showing a validation error message if not.
In addition, we can improve the user experience by hiding the add_child
icon from the change_list
where not allowed.
Implementation
First of all, let's add a new value in settings.py
expressing the max level of navigation
settings:
FEINCMS_NAVIGATION_LEVEL = 2
and create a couple of functions we will use whilst validating a page.
pages.admin:
def get_max_navigation_level():
return getattr(django_settings, 'FEINCMS_NAVIGATION_LEVEL', None)
def is_navigation_level_valid(level):
max_level = get_max_navigation_level()
if not max_level:
return True
return max_level >= level
Secondly we need to add another check in the PageAdminForm.clean
method and raise an exception if the user is adding a page at a level not allowed.
PageAdminForm:
def clean(self):
cleaned_data = super(PageAdminForm, self).clean()
# No need to think further, let the user correct errors first
if self._errors:
return cleaned_data
parent = cleaned_data.get('parent')
if parent:
template_key = cleaned_data['template_key']
template = self.Meta.model._feincms_templates[template_key]
parent_error = None
try:
check_template(
self.Meta.model, template, instance=self.instance, parent=parent
)
except UniqueTemplateException:
parent_error = _('Template already used somewhere else')
except FirstLevelOnlyTemplateException:
parent_error = _("This template can't be used as a subpage")
except NoChildrenTemplateException:
parent_error = _("Parent page can't have subpages")
else:
if not is_navigation_level_valid(parent.level+2):
parent_error = _(
"Only %d levels allowed" % get_max_navigation_level()
)
if parent_error:
self._errors['parent'] = ErrorList([parent_error])
del cleaned_data['parent']
return cleaned_data
Finally we can hide the add_child
icon from the list item if necessary.
PageAdmin:
def _actions_column(self, page):
actions = []
actions.append(u'<a href="%s" title="%s"><img src="%simg/admin/selector-search.gif" alt="%s" /></a>' % (
page.get_absolute_url(), _('View on site'), django_settings.ADMIN_MEDIA_PREFIX, _('View on site')))
template = self.model._feincms_templates.get(page.template_key)
no_children = template and template.no_children
valid_navigation = is_navigation_level_valid(page.level+2)
if not no_children and valid_navigation:
actions.append(u'<a href="add/?parent=%s" title="%s"><img src="%simg/admin/icon_addlink.gif" alt="%s"></a>' % (
page.pk, _('Add child page'), django_settings.ADMIN_MEDIA_PREFIX ,_('Add child page')))
else:
actions.append(u'<img src="%simg/admin/actions_placeholder.gif" alt="%s">' % (
django_settings.ADMIN_MEDIA_PREFIX ,_('No Action')))
actions.append(u'<a href="#" class="cut%s" onclick="return cut_item(\'%s\', this)" title="%s"><big>✂</big></a>' % (
' cant_have_children' if no_children else "", page.pk, _('Cut')))
actions.append(u'<a class="paste_target" href="#" onclick="return paste_item(\'%s\', \'left\')" title="%s">↱</a>' % (
page.pk, _('Insert before')))
if not no_children and valid_navigation:
actions.append(u'<a class="paste_target%s" href="#" onclick="return paste_item(\'%s\', \'last-child\')" title="%s">↳</a>' % (
" children" if page.parent else "", page.pk, _('Insert as child')))
return actions
UPDATE 10.2012
In new versions of Django and FeinCMS you should use the code below instead:
def _actions_column(self, page):
actions = super(PageAdmin, self)._actions_column(page)
template = self.model._feincms_templates.get(page.template_key)
no_children = template and template.no_children
valid_navigation = is_navigation_level_valid(page.level+2)
if (no_children or not valid_navigation) and getattr(page, 'feincms_editable', True):
actions[1] = u'<img src="%spages/img/actions_placeholder.gif">' % django_settings.STATIC_URL
return actions
Conclusions
This was the last part of the "Customizing FeinCMS" series, we've seen how easy it is to add new features and improve the user experience.
I hope that if tomorrow you receive an e-mail from one of your clients saying that your CMS sucks you'll feel a bit more guilty and you'll start building robust websites immediately.
23 Sept. 2010