work in progress: layout managememnt
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2891 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
		
							parent
							
								
									7793462e9a
								
							
						
					
					
						commit
						97cea0f183
					
				
					 10 changed files with 161 additions and 86 deletions
				
			
		| 
						 | 
				
			
			@ -4,40 +4,52 @@ Layout Management
 | 
			
		|||
 | 
			
		||||
  ($Id$)
 | 
			
		||||
 | 
			
		||||
Let's start with some basic setup; the traversable adapter is needed for
 | 
			
		||||
rendering the page templates.
 | 
			
		||||
 | 
			
		||||
  >>> from zope import component
 | 
			
		||||
  >>> from zope.interface import Interface
 | 
			
		||||
  >>> from zope.traversing.adapters import DefaultTraversable
 | 
			
		||||
  >>> component.provideAdapter(DefaultTraversable, (Interface,))
 | 
			
		||||
 | 
			
		||||
For testing we define a simple content class.
 | 
			
		||||
 | 
			
		||||
  >>> class Document(object):
 | 
			
		||||
  ...     text = ''
 | 
			
		||||
 | 
			
		||||
The layout management is controlled by a global utility, the layout
 | 
			
		||||
manager.
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.base import LayoutManager, LayoutInstance
 | 
			
		||||
  >>> from cybertools.composer.layout.interfaces import ILayout
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.base import LayoutManager
 | 
			
		||||
  >>> manager = LayoutManager()
 | 
			
		||||
  >>> component.provideUtility(manager)
 | 
			
		||||
 | 
			
		||||
  >>> from zope.traversing.adapters import DefaultTraversable
 | 
			
		||||
  >>> component.provideAdapter(DefaultTraversable, (Interface,))
 | 
			
		||||
The layouts themselves are also specified as utilities.
 | 
			
		||||
 | 
			
		||||
  >>> #from cybertools.composer.layout.browser.liquid.default import css
 | 
			
		||||
  >>> #component.provideUtility(css, ILayout, name='css')
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.browser.liquid.default import body
 | 
			
		||||
  >>> component.provideUtility(body, ILayout, name='body.liquid')
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.browser.default import footer
 | 
			
		||||
  >>> component.provideUtility(footer, ILayout, name='footer.default')
 | 
			
		||||
 | 
			
		||||
In addition we have to provide at least one layout instance adapter that
 | 
			
		||||
connects a layout with the client object.
 | 
			
		||||
 | 
			
		||||
  >>> component.provideAdapter(LayoutInstance, (object,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Browser Views
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
  >>> from zope.app.pagetemplate import ViewPageTemplateFile
 | 
			
		||||
  >>> standardRenderers = ViewPageTemplateFile('browser/standard.pt').macros
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.base import Layout
 | 
			
		||||
  >>> from cybertools.composer.layout.interfaces import ILayout
 | 
			
		||||
 | 
			
		||||
  >>> #css = Layout('page.css', renderer=standardRenderers['css'])
 | 
			
		||||
  >>> # css = ResourceCollection('css', resourceRenderers['css'])
 | 
			
		||||
  >>> #component.provideUtility(css, ILayout, name='css')
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.browser.liquid.default import BodyLayout
 | 
			
		||||
  >>> bodyLayout = BodyLayout()
 | 
			
		||||
  >>> component.provideUtility(bodyLayout, ILayout, name='body.liquid')
 | 
			
		||||
 | 
			
		||||
  >>> footerLayout = Layout('body.footer', renderer=standardRenderers['footer'])
 | 
			
		||||
  >>> component.provideUtility(footerLayout, ILayout, name='footer.default')
 | 
			
		||||
 | 
			
		||||
  >>> from cybertools.composer.layout.browser.view import Page
 | 
			
		||||
  >>> from zope.publisher.browser import TestRequest
 | 
			
		||||
  >>> page = Page(None, TestRequest())
 | 
			
		||||
 | 
			
		||||
  >>> page = Page(Document(), TestRequest())
 | 
			
		||||
 | 
			
		||||
  >>> page()
 | 
			
		||||
  u'<!DOCTYPE ...>...<html ...>...</html>...'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,37 +47,35 @@ class LayoutManager(object):
 | 
			
		|||
            region.layouts.append(layout)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def getLayouts(self, key, **kw):
 | 
			
		||||
    def getLayouts(self, key, instance):
 | 
			
		||||
        region = self.regions.get(key)
 | 
			
		||||
        if region is None:
 | 
			
		||||
            return []
 | 
			
		||||
        # TODO: filter region.layouts
 | 
			
		||||
        return region.layouts
 | 
			
		||||
        result = []
 | 
			
		||||
        for layout in region.layouts:
 | 
			
		||||
            if self.check(layout, instance):
 | 
			
		||||
                result.append(layout)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def register(self, layout, regionName):
 | 
			
		||||
        region = self.regions.setdefault(regionName, Region(regionName))
 | 
			
		||||
        region.layouts.append(layout)
 | 
			
		||||
    def check(self, layout, instance):
 | 
			
		||||
        if instance is None or instance.checkLayout(layout):
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Layout(Template):
 | 
			
		||||
 | 
			
		||||
    implements(ILayout)
 | 
			
		||||
 | 
			
		||||
    name = ''
 | 
			
		||||
    title = description = u''
 | 
			
		||||
    category = 'default'
 | 
			
		||||
    renderer = None
 | 
			
		||||
    regionName = None
 | 
			
		||||
    skin = 'default'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, regionName, **kw):
 | 
			
		||||
    def __init__(self, name, regionName, **kw):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.regionName = regionName
 | 
			
		||||
        for k, v in kw.items():
 | 
			
		||||
            setattr(self, k, v)
 | 
			
		||||
 | 
			
		||||
    def registerFor(self, regionName):
 | 
			
		||||
        manager = component.getUtility(ILayoutManager)
 | 
			
		||||
        manager.register(self, regionName)
 | 
			
		||||
        self.regionName = regionName
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LayoutInstance(object):
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -91,3 +89,7 @@ class LayoutInstance(object):
 | 
			
		|||
    @property
 | 
			
		||||
    def renderer(self):
 | 
			
		||||
        return self.template.renderer
 | 
			
		||||
 | 
			
		||||
    def checkLayout(self, layout):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								composer/layout/browser/default.pt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								composer/layout/browser/default.pt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
<div metal:define-macro="footer">
 | 
			
		||||
  Some footer text.
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,8 @@ from zope.app.pagetemplate import ViewPageTemplateFile
 | 
			
		|||
from cybertools.composer.layout.base import Layout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
standardRenderers = ViewPageTemplateFile('standard.pt').macros
 | 
			
		||||
defaultRenderers = ViewPageTemplateFile('default.pt').macros
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
footer = Layout('body.footer', renderer=standardRenderers['footer'])
 | 
			
		||||
footer = Layout('footer.default', 'body.footer',
 | 
			
		||||
                renderer=defaultRenderers['footer'])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,17 +30,8 @@ from zope.interface import implements
 | 
			
		|||
from cybertools.composer.layout.base import Layout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
template = ViewPageTemplateFile('default.pt')
 | 
			
		||||
defaultRenderers = ViewPageTemplateFile('default.pt').macros
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BodyLayout(Layout):
 | 
			
		||||
 | 
			
		||||
    regionName = 'page.body'
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @Lazy
 | 
			
		||||
    def renderer(self):
 | 
			
		||||
        return template.macros['body']
 | 
			
		||||
body = Layout('body.liquid', 'page.body',
 | 
			
		||||
              renderer=defaultRenderers['body'])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,8 @@
 | 
			
		|||
      </title>
 | 
			
		||||
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 | 
			
		||||
 | 
			
		||||
      <tal:css repeat="macro view/resources/css">
 | 
			
		||||
        <metal:css use-macro="macro" />
 | 
			
		||||
      <tal:css repeat="view view/layouts/css">
 | 
			
		||||
        <metal:css use-macro="view/renderer" />
 | 
			
		||||
      </tal:css>
 | 
			
		||||
 | 
			
		||||
      <base href="." tal:attributes="href request/URL">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,7 @@
 | 
			
		|||
<div metal:define-macro="footer">
 | 
			
		||||
  Some footer text.
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<metal:css define-macro="css">
 | 
			
		||||
  <!--<style type="text/css" media="all"
 | 
			
		||||
         tal:attributes="media macro/media"
 | 
			
		||||
         tal:content="string:@import url(${resourceBase}${macro/resourceName});">
 | 
			
		||||
         tal:attributes="media view/media"
 | 
			
		||||
         tal:content="string:@import url(${view/page/resourceBase}${view/context/resourceName});">
 | 
			
		||||
      @import url(some.css);
 | 
			
		||||
  </style>-->
 | 
			
		||||
</metal:css>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								composer/layout/browser/standard.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								composer/layout/browser/standard.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
#
 | 
			
		||||
#  Copyright (c) 2008 Helmut Merz helmutm@cy55.de
 | 
			
		||||
#
 | 
			
		||||
#  This program is free software; you can redistribute it and/or modify
 | 
			
		||||
#  it under the terms of the GNU General Public License as published by
 | 
			
		||||
#  the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
#  (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
#  This program is distributed in the hope that it will be useful,
 | 
			
		||||
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
#  GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
#  You should have received a copy of the GNU General Public License
 | 
			
		||||
#  along with this program; if not, write to the Free Software
 | 
			
		||||
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Default layouts.
 | 
			
		||||
 | 
			
		||||
$Id$
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from zope.app.pagetemplate import ViewPageTemplateFile
 | 
			
		||||
 | 
			
		||||
from cybertools.composer.layout.base import Layout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
standardRenderers = ViewPageTemplateFile('standard.pt').macros
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
footer = Layout('footer.default', 'body.footer',
 | 
			
		||||
                renderer=standardRenderers['footer'])
 | 
			
		||||
| 
						 | 
				
			
			@ -27,38 +27,56 @@ from zope.interface import Interface, implements
 | 
			
		|||
from zope.cachedescriptors.property import Lazy
 | 
			
		||||
from zope.app.pagetemplate import ViewPageTemplateFile
 | 
			
		||||
 | 
			
		||||
from cybertools.composer.layout.base import Layout, LayoutInstance
 | 
			
		||||
from cybertools.composer.layout.interfaces import ILayoutManager
 | 
			
		||||
from cybertools.composer.layout.base import Layout
 | 
			
		||||
from cybertools.composer.layout.interfaces import ILayoutManager, ILayoutInstance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseView(object):
 | 
			
		||||
 | 
			
		||||
    template = ViewPageTemplateFile('base.pt')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, context, request, name=None):
 | 
			
		||||
    page = None
 | 
			
		||||
    parent = None
 | 
			
		||||
    skin = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, context, request, **kw):
 | 
			
		||||
        self.context = self.__parent__ = context
 | 
			
		||||
        self.request = request
 | 
			
		||||
        if name is not None:
 | 
			
		||||
            self.name = name
 | 
			
		||||
 | 
			
		||||
    def update(self):
 | 
			
		||||
        return True
 | 
			
		||||
        for k, v in kw.items():
 | 
			
		||||
            setattr(self, k, v)
 | 
			
		||||
 | 
			
		||||
    def __call__(self):
 | 
			
		||||
        return self.template(self)
 | 
			
		||||
 | 
			
		||||
    def update(self):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Page(BaseView):
 | 
			
		||||
 | 
			
		||||
    macroName = 'page'
 | 
			
		||||
 | 
			
		||||
    @Lazy
 | 
			
		||||
    def rootView(self):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __call__(self):
 | 
			
		||||
        layout = Layout('page')
 | 
			
		||||
        layout.renderer = ViewPageTemplateFile('main.pt').macros['page']
 | 
			
		||||
        instance = LayoutInstance(self.context)
 | 
			
		||||
        layout = Layout('page', 'page')
 | 
			
		||||
        layout.renderer = ViewPageTemplateFile('main.pt').macros[self.macroName]
 | 
			
		||||
        instance = ILayoutInstance(self.context)
 | 
			
		||||
        instance.template = layout
 | 
			
		||||
        view = LayoutView(instance, self.request, name='page')
 | 
			
		||||
        view = LayoutView(instance, self.request, name='page',
 | 
			
		||||
                          parent=self, page=self)
 | 
			
		||||
        view.body = view.layouts['body'][0]
 | 
			
		||||
        instance.view = view
 | 
			
		||||
        return view.template(view)
 | 
			
		||||
 | 
			
		||||
    @Lazy
 | 
			
		||||
    def resourceBase(self):
 | 
			
		||||
        skinSetter = self.skin and ('/++skin++' + self.skin.__name__) or ''
 | 
			
		||||
        # TODO: put '/@@' etc after path to site instead of directly after URL0
 | 
			
		||||
        return self.request.URL[0] + skinSetter + '/@@/'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LayoutView(BaseView):
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -83,9 +101,9 @@ class LayoutView(BaseView):
 | 
			
		|||
    def resources(self):
 | 
			
		||||
        return ViewResources(self)
 | 
			
		||||
 | 
			
		||||
    def getLayoutsFor(self, key, **kw):
 | 
			
		||||
    def getLayoutsFor(self, key):
 | 
			
		||||
        manager = component.getUtility(ILayoutManager)
 | 
			
		||||
        return manager.getLayouts('.'.join((self.name, key)), **kw)
 | 
			
		||||
        return manager.getLayouts('.'.join((self.name, key)), self.context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# subview providers
 | 
			
		||||
| 
						 | 
				
			
			@ -99,10 +117,12 @@ class ViewLayouts(object):
 | 
			
		|||
        view = self.view
 | 
			
		||||
        subviews = []
 | 
			
		||||
        for layout in view.getLayoutsFor(key):
 | 
			
		||||
            instance = LayoutInstance(view.client)
 | 
			
		||||
            instance = ILayoutInstance(view.client)
 | 
			
		||||
            instance.template = layout
 | 
			
		||||
            instance.view = view
 | 
			
		||||
            subviews.append(LayoutView(instance, view.request, name=key))
 | 
			
		||||
            v = LayoutView(instance, view.request, name=key,
 | 
			
		||||
                           parent=view, page=view.page)
 | 
			
		||||
            instance.view = v
 | 
			
		||||
            subviews.append(v)
 | 
			
		||||
        return subviews
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,17 +37,24 @@ class ILayoutManager(Interface):
 | 
			
		|||
    """ A utility that manages layouts and regions.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def register(layout, regionName):
 | 
			
		||||
        """ Register the layout given for the region specified.
 | 
			
		||||
    def getLayouts(regionName, instance):
 | 
			
		||||
        """ Return a sequence of layouts for the region given that are
 | 
			
		||||
            valid sub-layouts for the layout instance given.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    def check(layout, instance):
 | 
			
		||||
        """ Return True if the layout given is a valid sub-layout
 | 
			
		||||
            for the instance given.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ILayout(ITemplate):
 | 
			
		||||
    """ Represents an ordered sequence of layout elements.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = schema.ASCIILine(
 | 
			
		||||
                title=_(u'Layout name'),
 | 
			
		||||
                title=_(u'Layout Name'),
 | 
			
		||||
                description=_(u'The internal name of the layout.'),
 | 
			
		||||
                required=True,)
 | 
			
		||||
    title = schema.TextLine(
 | 
			
		||||
| 
						 | 
				
			
			@ -63,13 +70,14 @@ class ILayout(ITemplate):
 | 
			
		|||
                description=_(u'The name of a layout category this layout '
 | 
			
		||||
                    u'belongs to.'),
 | 
			
		||||
                required=False,)
 | 
			
		||||
    regionName = schema.ASCIILine(
 | 
			
		||||
                title=_(u'Region Name'),
 | 
			
		||||
                description=_(u'A dotted name that specifies the region '
 | 
			
		||||
                    u'this layout should be used for.'),
 | 
			
		||||
                required=True,)
 | 
			
		||||
 | 
			
		||||
    renderer = Attribute(u'An object responsible for rendering the layout.')
 | 
			
		||||
 | 
			
		||||
    def registerFor(regionName):
 | 
			
		||||
        """ Register the layout for the region specified.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ILayoutComponent(IComponent):
 | 
			
		||||
    """ May be used for data entry or display.
 | 
			
		||||
| 
						 | 
				
			
			@ -77,11 +85,11 @@ class ILayoutComponent(IComponent):
 | 
			
		|||
 | 
			
		||||
    name = schema.ASCIILine(
 | 
			
		||||
                title=_(u'Component name'),
 | 
			
		||||
                description=_(u'The internal name of the component'),
 | 
			
		||||
                description=_(u'The internal name of the component.'),
 | 
			
		||||
                required=True,)
 | 
			
		||||
    title = schema.TextLine(
 | 
			
		||||
                title=_(u'Title'),
 | 
			
		||||
                description=_(u'The title or label of the component'),
 | 
			
		||||
                description=_(u'The title or label of the component.'),
 | 
			
		||||
                required=True,)
 | 
			
		||||
    description = schema.Text(
 | 
			
		||||
                title=_(u'Description'),
 | 
			
		||||
| 
						 | 
				
			
			@ -100,16 +108,25 @@ class ILayoutInstance(IInstance):
 | 
			
		|||
 | 
			
		||||
    renderer = Attribute(u'An object responsible for rendering the layout.')
 | 
			
		||||
 | 
			
		||||
    def checkLayout(layout):
 | 
			
		||||
        """ Return True if the layout given is a valid sub-layout
 | 
			
		||||
            for this instance.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IRegion(Interface):
 | 
			
		||||
    """ A part of a layout "canvas" that may be filled with layout objects.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = schema.ASCIILine(
 | 
			
		||||
                title=_(u'Region name'),
 | 
			
		||||
                description=_(u'The internal name of the region.'),
 | 
			
		||||
                required=True,)
 | 
			
		||||
    allowedLayoutCategories = schema.List(
 | 
			
		||||
                title=_(u'Allowed layout categories'),
 | 
			
		||||
                description=_(u'A collection of names of layout categories '
 | 
			
		||||
                        u'to which layouts may belong that may be placed '
 | 
			
		||||
                        u'in this region'),
 | 
			
		||||
                        u'in this region.'),
 | 
			
		||||
                value_type=schema.ASCIILine(),
 | 
			
		||||
                required=False,)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue