Administration interfaces

FeinCMS provides two ModelAdmin classes, ItemEditor, and TreeEditor. Their purpose and their customization hooks are briefly discussed here.

The tree editor

class feincms.admin.tree_editor.TreeEditor

The tree editor replaces the standard change list interface with a collapsible item tree. The model must be registered with django-mptt for this to work.

_images/tree_editor.png

Usage is as follows:

from django.db import models
from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel

class YourModel(MPTTModel):
    # model field definitions

    parent = TreeForeignKey('self', null=True, blank=True, related_name='children')

    class Meta:
        ordering = ['tree_id', 'lft']  # The TreeEditor needs this ordering definition

And inside your admin.py file:

from django.contrib import admin
from feincms.admin import tree_editor
from yourapp.models import YourModel

class YourModelAdmin(tree_editor.TreeEditor):
    pass

admin.site.register(YourModel, YourModelAdmin)

All standard ModelAdmin attributes such as ModelAdmin.list_display, ModelAdmin.list_editable, ModelAdmin.list_filter work as normally. The only exception to this rule is the column showing the tree structure (the second column in the image). There, we always show the value of Model.__str__ currently.

AJAX checkboxes

The tree editor allows you to define boolean columns which let the website administrator change the value of the boolean using a simple click on the icon. These boolean columns can be aware of the tree structure. For example, if an object’s active flag influences the state of its descendants, the tree editor interface is able to show not only the state of the modified element, but also the state of all its descendants without having to reload the page.

Currently, documentation for this feature is not available yet. You can take a look at the implementation of the is_visible and in_navigation columns of the page editor however.

Usage:

from django.contrib import admin
from feincms.admin import tree_editor
import mptt

class Category(models.Model):
    active = models.BooleanField()
    name = models.CharField(...)
    parent = models.ForeignKey('self', blank=True, null=True)

    # ...
mptt.register(Category)

class CategoryAdmin(tree_editor.TreeEditor):
    list_display = ('__str__', 'active_toggle')
    active_toggle = tree_editor.ajax_editable_boolean('active', _('active'))

The item editor

class feincms.admin.item_editor.ItemEditor

The tabbed interface below is used to edit content and other properties of the edited object. A tab is shown for every region of the template or element, depending on whether templates are activated for the object in question [1].

Here’s a screenshot of a content editing pane. The media file content is collapsed currently. New items can be added using the control bar at the bottom, and all content blocks can be reordered using drag and drop:

_images/item_editor_content.png
[1]Templates are required for the page module; blog entries managed through the item editor probably won’t have a use for them.

Customizing the item editor

New in version 1.2.0.

  • The ItemEditor now plays nicely with standard Django fieldsets; the content-editor is rendered as a replacement for a fieldset with the placeholder name matching FEINCMS_CONTENT_FIELDSET_NAME. If no such fieldset is present, one is inserted at the top automatically. If you wish to customise the location of the content-editor, simple include this fieldset at the desired location:

    from feincms.admin.item_editor import ItemEditor, FEINCMS_CONTENT_FIELDSET
    
    class MyAdmin(ItemEditor):
        fieldsets = (
            ('Important things', {'fields': ('title', 'slug', 'etc')}),
            FEINCMS_CONTENT_FIELDSET,
            ('Less important things',
                {
                    'fields': ('copyright', 'soforth'),
                    'classes': ('collapse',)
                }
            )
        )
    

Customizing the individual content type forms

Customizing the individual content type editors is easily possible through four settings on the content type model itself:

  • feincms_item_editor_context_processors:

    A list of callables using which you may add additional values to the item editor templates.

  • feincms_item_editor_form:

    You can specify the base class which should be used for the content type model. The default value is django.forms.ModelForm. If you want to customize the form, chances are it is a better idea to set feincms_item_editor_inline instead.

  • feincms_item_editor_includes:

    If you need additional JavaScript or CSS files or need to perform additional initialization on your content type forms, you can specify template fragments which are included in predefined places into the item editor.

    Currently, the only include region available is head:

    class ContentType(models.Model):
        feincms_item_editor_includes = {
            'head': ['content/init.html'],
            }
    
        # ...
    

    If you need to execute additional Javascript, for example to add a TinyMCE instance, it is recommended to add the initialization functions to the contentblock_init_handlers array, because the initialization needs to be performed not only on page load, but also when adding new content blocks. Please note that these functions will be called several times, also several times on the same content types. It is your responsibility to ensure that the handlers aren’t attached several times if this would be harmful.

    Additionally, several content types do not support being dragged. Rich text editors such as TinyMCE react badly to being dragged around - they are still visible, but the content disappears and nothing is clickable anymore. Because of this you might want to run routines before and after moving content types around. This is achieved by adding your JavaScript functions to the contentblock_move_handlers.poorify array for handlers to be executed before moving and contentblock_move_handlers.richify for handlers to be executed after moving. Please note that the item editor executes all handlers on every drag and drop, it is your responsibility to ensure that code is only executed if it has to.

    Take a look at the richtext item editor include files to understand how this should be done.

  • feincms_item_editor_inline:

    New in version 1.4.0.

    This can be used to override the InlineModelAdmin class used for the content type. The custom inline should inherit from FeinCMSInline or be configured the same way.

    If you override fieldsets or fields you must include region and ordering even though they aren’t shown in the administration interface.

Putting it all together

It is possible to build a limited, but fully functional page CMS administration interface using only the following code (urls.py and views.py are missing):

models.py:

from django.db import models
from mptt.models import MPTTModel
from feincms.models import create_base_model

class Page(create_base_model(MPTTModel)):
    active = models.BooleanField(default=True)
    title = models.CharField(max_length=100)
    slug = models.SlugField()

    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')

    def get_absolute_url(self):
        if self.parent_id:
            return u'%s%s/' % (self.parent.get_absolute_url(), self.slug)
        return u'/%s/' % self.slug

admin.py:

from django.contrib import admin
from feincms.admin import item_editor, tree_editor
from myapp.models import Page

class PageAdmin(item_editor.ItemEditor, tree_editor.TreeEditor):
    fieldsets = [
        (None, {
            'fields': ['active', 'title', 'slug'],
            }),
        item_editor.FEINCMS_CONTENT_FIELDSET,
        ]
    list_display = ['active', 'title']
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ['parent']
    search_fields = ['title', 'slug']

admin.site.register(Page, PageAdmin)

For a more complete (but also more verbose) implementation, have a look at the files inside feincms/module/page/.