Package: horae.layout
IntroductionThehorae.layoutpackage provides the global layout for the Horae resource
planning system by defining base classes for views, forms (add, edit, delete and
display) and viewlets. Additionally the following features are provided:Registration of several viewlet managersIntegration ofmegrok.navigationHelper view to create tablesGeneric views for listing, adding, editing and deleting objects in a containerPossibility to registerfanstaticresources
through named adaptersRegistration of thehorae.datetimewidgetsRegistration of rich text widgets usingmegrok.formUsageViewsThe aforementioned base classes for views, forms and viewlets are defined inhorae.layout.layout. All the views defined there provide the functionality
to be called with two special parameters provided through GET or POST:plainIf this parameter is set to any truth value only the views result
is rendered without the whole layout put around.selectorIf this parameter is set to any CSS selector only the contents of the element
matching the selector is returned.Those base classes are also responsible for including thefanstaticresources provided by the named adapters
implementinghorae.layout.interfaces.IResourceProvider.Add formsCreating an add form usinghorae.layoutis done by sub-classing from the provided
base classhorae.layout.layout.AddFormand implementing the required methods:import grok

from horae.layout import layout
from some.module.interfaces import ISampleContent, ISampleContainer
from some.module.content import SampleContent

class AddSampleContent(layout.AddForm):
    grok.context(ISampleContainer)
    form_fields = grok.AutoFields(ISampleContent)

    def create(self, **data):
        return SampleContent()

    def add(self, obj):
        self.context[obj]This will render a form having aAddandCancelbutton.Edit formsEdit forms work pretty much the same as add forms. The only difference is the class to be
sub-classed and that there are no methods required to be implemented:class EditSampleContent(layout.EditForm):
    grok.context(ISampleContent)
    form_fields = grok.AutoFields(ISampleContent)This will render a form having aSave changesandCancelbutton.Display formsLike edit forms display forms require no implementation at all:class DisplaySampleContent(layout.DisplayForm):
    grok.context(ISampleContent)
    form_fields = grok.AutoFields(ISampleContent)Delete formsDelete forms provide a confirmation view before actually deleting an object and are implemented
by sub-classing fromhorae.layout.layout.DeleteForm:class DeleteSampleContent(layout.DeleteForm):
    grok.context(ISampleContent)

    def item_title(self):
        return self.context.titleView extendersAdditionally the views sub-classing from those base classes may be extended by other
packages using so called view extenders, which are named adapters implementinghorae.layout.interfaces.IViewExtenderand adapting the view itself. If for example
one would like to add an additional field to the aforementioned add form without
touching the form itself:from zope import schema
from horae.layout import interfaces

class AddSampleContentExtender(grok.Adapter):
    grok.context(AddSampleContent)
    grok.implements(interfaces.IViewExtender)
    grok.name('addsamplecontentextender')

    def pre_update(self):
        """ Called before the views update is called
        """
        self.context.form_fields = self.context.form_fields + \
            grok.Fields(additional_field=schema.TextLine(
                title=u'Additional field'
            ))

    def pre_setUpWidgets(self, ignore_request=False):
        """ Called before the forms setUpWidgets is called
        """
        # set custom widgets for fields using this method

    def post_update(self):
        """ Called after the views update is called
        """
        # set default values for fields using this method

    def apply(self, obj, **data):
        """ Called when applying changes to an object
        """
        obj.additional_field = data.get('additional_field', None)

    def validate(self, action, data):
        """ Called when validating a form
        """
        # do custom validation using this methodViewlet managersThe following viewlet managers are registered inhorae.layout.viewlets:TopManagerViewlet manager rendered on topHeaderLeftManagerViewlet manager rendered on the left in the headerHeaderRightManagerViewlet manager rendered on the right in the headerContentBeforeManagerViewlet manager rendered before the contentContentAfterManagerViewlet manager rendered after the contentSidebarManagerViewlet manager rendered in the sidebarFooterManagerViewlet manager rendered in the footerNavigationhorae.layoutregisters severalMenus using themegrok.navigationpackage. Those are defined
inhorae.layout.navigation:MainNavigation(horae.layout.interfaces.interfaces.IMainNavigation)Main navigation holds global items and is rendered on the top leftGlobalManageMenu(horae.layout.interfaces.interfaces.IGlobalManageMenu)Global manage menu part of the main navigationEditNavigation(horae.layout.interfaces.interfaces.IEditNavigation)Edit navigation holds sub menus and is rendered on the top rightContextualManageMenu(horae.layout.interfaces.interfaces.IContextualManageMenu)Contextual manage menu part of the edit navigationViewsMenu(horae.layout.interfaces.interfaces.IViewsMenu)Views menu part of the edit navigationActionsMenu(horae.layout.interfaces.interfaces.IActionsMenu)Actions menu part of the edit navigationAddMenu(horae.layout.interfaces.interfaces.IAddMenu)Add menu part of the edit navigationMainActionsMenu(horae.layout.interfaces.interfaces.IMainActionsMenu)Main actions menu rendered in the content areaTable helper viewThe table helper view is implemented inhorae.layout.tableand registered as
a view namedtable. The view handles sorting, filtering and pagination of
tabular data. An example usage of the view may be found inhorae.layout.objects.ObjectOverview.A simple usage may look like this:from zope.component import getMultiAdapter
from zope.schema import vocabulary

class SampleTableView(grok.View):

    def render_table(self):
        table = getMultiAdapter((self.context, self.request), name=u'table')
        table.page_size = 10
        table.columns = (
            ('col1', u'Column 1'),
            ('col2', u'Column 2')
        )
        table.sortable = {
            'col1': 'col1',
            'col2': 'col2'
        }
        table.filterable = {
            'col1': vocabulary.SimpleVocabulary.fromValues([
                        'Group 1',
                        'Group 2',
                        'Group 3'
                    ])
        }
        rows = [
            {'col1': 'Group 1', 'col2': 'Row 1'},
            {'col1': 'Group 1', 'col2': 'Row 2'},
            {'col1': 'Group 1', 'col2': 'Row 3'},
            {'col1': 'Group 2', 'col2': 'Row 4'},
            {'col1': 'Group 2', 'col2': 'Row 5'},
            {'col1': 'Group 2', 'col2': 'Row 6'},
            {'col1': 'Group 3', 'col2': 'Row 7'},
            {'col1': 'Group 3', 'col2': 'Row 8'},
            {'col1': 'Group 4', 'col2': 'Row 9'},
        ]
        filtering = table.filtering()
        if 'col1' in filtering:
            rows = [row for row in rows if row['col1'] in filtering['col1']]
        sort, reverse = table.sorting()
        if sort:
            rows = sorted(rows, key=lambda row: row[sort])
        if reverse:
            rows = reversed(rows)
        table.rows = rows
        return table()This would result in a table of two columns and nine rows which is sortable by both
columns and the first column is filterable.DependenciesHoraehorae.corehorae.datetimeThird partygrokfanstaticzope.fanstaticzc.relationjs.jqueryjs.jqueryuiBeautifulSoupgrokcore.chameleonmegrok.navigationmegrok.pagetemplatemegrok.formChangelog1.0a1 (2012-01-16)Initial release