First usable version of view.Node stuff for simple web sites
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1003 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
		
							parent
							
								
									c2806a8c10
								
							
						
					
					
						commit
						d61d424009
					
				
					 7 changed files with 320 additions and 19 deletions
				
			
		
							
								
								
									
										71
									
								
								README.txt
									
										
									
									
									
								
							
							
						
						
									
										71
									
								
								README.txt
									
										
									
									
									
								
							|  | @ -4,6 +4,14 @@ loops - Linked Objects for Organization and Processing Services | ||||||
| 
 | 
 | ||||||
|   ($Id$) |   ($Id$) | ||||||
| 
 | 
 | ||||||
|  |   >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown | ||||||
|  |   >>> site = placefulSetUp(True) | ||||||
|  |    | ||||||
|  |   >>> from zope.app import zapi | ||||||
|  |   >>> from zope.app.tests import ztapi | ||||||
|  |   >>> from zope.publisher.browser import TestRequest | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| Concepts and Relations | Concepts and Relations | ||||||
| ====================== | ====================== | ||||||
| 
 | 
 | ||||||
|  | @ -12,6 +20,8 @@ top-level loops container and a concept manager: | ||||||
| 
 | 
 | ||||||
|   >>> from loops import Loops |   >>> from loops import Loops | ||||||
|   >>> loops = Loops() |   >>> loops = Loops() | ||||||
|  |   >>> site['loops'] = Loops() | ||||||
|  |   >>> loops = site['loops'] | ||||||
| 
 | 
 | ||||||
|   >>> from loops.concept import ConceptManager, Concept |   >>> from loops.concept import ConceptManager, Concept | ||||||
|   >>> loops['concepts'] = ConceptManager() |   >>> loops['concepts'] = ConceptManager() | ||||||
|  | @ -97,8 +107,8 @@ below) via the getClients() method: | ||||||
|   >>> conc[0] is zope |   >>> conc[0] is zope | ||||||
|   True |   True | ||||||
| 
 | 
 | ||||||
| Views: Menus, Menu Items, Listings, etc | Views: Menus, Menu Items, Listings, Simple Content, etc | ||||||
| ======================================= | ======================================================= | ||||||
| 
 | 
 | ||||||
| We first need a view manager: | We first need a view manager: | ||||||
|      |      | ||||||
|  | @ -122,6 +132,57 @@ menu that may contain other nodes as menu or content items: | ||||||
|   >>> m112.description |   >>> m112.description | ||||||
|   u'' |   u'' | ||||||
| 
 | 
 | ||||||
|  | There are a few convienence methods for accessing parent and child nodes: | ||||||
|  | 
 | ||||||
|  |   >>> m1.getParentNode() is None | ||||||
|  |   True | ||||||
|  |   >>> m11.getParentNode() is m1 | ||||||
|  |   True | ||||||
|  |   >>> [zapi.getName(child) for child in m11.getChildNodes()] | ||||||
|  |   [u'm111', u'm112'] | ||||||
|  | 
 | ||||||
|  | What is returned by these may be controlled by the nodeType attribute: | ||||||
|  | 
 | ||||||
|  |   >>> m1.nodeType = 'menu' | ||||||
|  |   >>> m11.nodeType = 'page' | ||||||
|  |   >>> m11.getParentNode('menu') is m1 | ||||||
|  |   True | ||||||
|  |   >>> m11.getParentNode('page') is None | ||||||
|  |   True | ||||||
|  |   >>> m111.nodeType = 'info' | ||||||
|  |   >>> m112.nodeType = 'text' | ||||||
|  |   >>> len(m11.getChildNodes('text')) | ||||||
|  |   1 | ||||||
|  | 
 | ||||||
|  | There are also shortcut methods to retrieve certain types of nodes | ||||||
|  | in a simple and logical way: | ||||||
|  | 
 | ||||||
|  |   >>> m1.getMenu() is m1 | ||||||
|  |   True | ||||||
|  |   >>> m111.getMenu() is m1 | ||||||
|  |   True | ||||||
|  |   >>> m1.getPage() is m1 | ||||||
|  |   True | ||||||
|  |   >>> m111.getPage() is m111 | ||||||
|  |   True | ||||||
|  |   >>> m112.getPage() is m11 | ||||||
|  |   True | ||||||
|  |   >>> len(m1.getMenuItems()) | ||||||
|  |   1 | ||||||
|  |   >>> len(m11.getMenuItems()) | ||||||
|  |   0 | ||||||
|  |   >>> len(m111.getMenuItems()) | ||||||
|  |   0 | ||||||
|  |   >>> len(m1.getTextItems()) | ||||||
|  |   0 | ||||||
|  |   >>> len(m11.getTextItems()) | ||||||
|  |   1 | ||||||
|  |   >>> len(m111.getTextItems()) | ||||||
|  |   0 | ||||||
|  | 
 | ||||||
|  | Targets | ||||||
|  | ------- | ||||||
|  | 
 | ||||||
| We can associate a node with a concept or directly with a resource via the | We can associate a node with a concept or directly with a resource via the | ||||||
| view class's target attribute: | view class's target attribute: | ||||||
| 
 | 
 | ||||||
|  | @ -135,3 +196,9 @@ view class's target attribute: | ||||||
|   >>> m111.target is zope3 |   >>> m111.target is zope3 | ||||||
|   True |   True | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | Fin de partie | ||||||
|  | ============= | ||||||
|  | 
 | ||||||
|  |   >>> placefulTearDown() | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -5,6 +5,10 @@ | ||||||
|    xmlns="http://namespaces.zope.org/browser" |    xmlns="http://namespaces.zope.org/browser" | ||||||
|    i18n_domain="zope"> |    i18n_domain="zope"> | ||||||
| 
 | 
 | ||||||
|  |   <!-- resources --> | ||||||
|  | 
 | ||||||
|  |   <resource name="view.css" file="view.css" /> | ||||||
|  | 
 | ||||||
|   <!-- macros --> |   <!-- macros --> | ||||||
|     |     | ||||||
|   <page |   <page | ||||||
|  | @ -197,6 +201,7 @@ | ||||||
| 
 | 
 | ||||||
|   <containerViews |   <containerViews | ||||||
|       for="loops.interfaces.IViewManager" |       for="loops.interfaces.IViewManager" | ||||||
|  |       index="zope.View" | ||||||
|       contents="zope.ManageContent" |       contents="zope.ManageContent" | ||||||
|       add="zope.ManageContent" |       add="zope.ManageContent" | ||||||
|       /> |       /> | ||||||
|  | @ -208,11 +213,11 @@ | ||||||
|       name="AddLoopsNode.html" |       name="AddLoopsNode.html" | ||||||
|       content_factory="loops.view.Node" |       content_factory="loops.view.Node" | ||||||
|       schema="loops.interfaces.INode" |       schema="loops.interfaces.INode" | ||||||
|       fields="title description type body" |       fields="title description nodeType body" | ||||||
|       permission="zope.ManageContent"> |       permission="zope.ManageContent"> | ||||||
|        |        | ||||||
|     <widget field="description" height="2" /> |     <widget field="description" height="2" /> | ||||||
|     <widget field="body" height="4" /> |     <widget field="body" height="8" /> | ||||||
| 
 | 
 | ||||||
|   </addform> |   </addform> | ||||||
| 
 | 
 | ||||||
|  | @ -228,13 +233,13 @@ | ||||||
|       label="Edit Node" |       label="Edit Node" | ||||||
|       name="edit.html" |       name="edit.html" | ||||||
|       schema="loops.interfaces.INode" |       schema="loops.interfaces.INode" | ||||||
|       fields="title description type body" |       fields="title description nodeType body" | ||||||
|       for="loops.interfaces.INode" |       for="loops.interfaces.INode" | ||||||
|       permission="zope.ManageContent" |       permission="zope.ManageContent" | ||||||
|       menu="zmi_views" title="Edit"> |       menu="zmi_views" title="Edit"> | ||||||
| 
 | 
 | ||||||
|     <widget field="description" height="2" /> |     <widget field="description" height="2" /> | ||||||
|     <widget field="body" height="4" /> |     <widget field="body" height="15" /> | ||||||
| 
 | 
 | ||||||
|   </editform> |   </editform> | ||||||
| 
 | 
 | ||||||
|  | @ -242,6 +247,7 @@ | ||||||
|       name="node.html" |       name="node.html" | ||||||
|       for="loops.interfaces.INode" |       for="loops.interfaces.INode" | ||||||
|       template="node.pt" |       template="node.pt" | ||||||
|  |       class=".node.NodeView" | ||||||
|       permission="zope.View" |       permission="zope.View" | ||||||
|       /> |       /> | ||||||
|      |      | ||||||
|  |  | ||||||
|  | @ -3,22 +3,64 @@ | ||||||
| <head></head> | <head></head> | ||||||
| <body> | <body> | ||||||
| 
 | 
 | ||||||
|  |    | ||||||
|  |   <metal:css fill-slot="style_slot"> | ||||||
|  |     <style type="text/css" media="all" | ||||||
|  |            tal:content="string:@import url(${context/++resource++view.css});"> | ||||||
|  |       @import url(view.css); | ||||||
|  |     </style> | ||||||
|  |   </metal:css> | ||||||
|  |    | ||||||
|  | 
 | ||||||
|   <metal:body fill-slot="body"> |   <metal:body fill-slot="body"> | ||||||
|     <tal:content define="item context; |     <tal:content define="item view/getContent; | ||||||
|                          level level|python: 1"> |                          level level|python: 1"> | ||||||
| 
 | 
 | ||||||
|       <metal:block define-macro="content"> |       <metal:block define-macro="content"> | ||||||
|         <div tal:content="structure item/body">The body</div>         |         <tal:body define="body item/body" | ||||||
|         <div tal:define="level python:level+1"> |                   condition="body"> | ||||||
|           <tal:items repeat="item item/values"> |           <div class="content-1" | ||||||
|  |                tal:content="structure body" | ||||||
|  |                tal:attributes="class string:content-$level">The body</div> | ||||||
|  |         </tal:body> | ||||||
|  |         <tal:sub define="level python:level+1"> | ||||||
|  |           <tal:items repeat="item item/items"> | ||||||
|             <metal:portlet use-macro="views/node_macros/content" /> |             <metal:portlet use-macro="views/node_macros/content" /> | ||||||
|           </tal:items> |           </tal:items> | ||||||
|         </div> |         </tal:sub> | ||||||
|       </metal:block> |       </metal:block> | ||||||
|        |        | ||||||
|     </tal:content> |     </tal:content> | ||||||
|   </metal:body> |   </metal:body> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |   <div class="box" metal:fill-slot="navigators"> | ||||||
|  |     <h4>Navigation</h4> | ||||||
|  |     <tal:menu define="item view/getMenu; | ||||||
|  |                       level level|python: 1" | ||||||
|  |               condition="item"> | ||||||
|  | 
 | ||||||
|  |       <div class="body"> | ||||||
|  |         <metal:menu define-macro="menu"> | ||||||
|  |           <div class="menu-3" | ||||||
|  |                tal:attributes="class python: 'content ' | ||||||
|  |                     + (item['selected'] and 'even' or 'odd') | ||||||
|  |                     + ' menu-%i' % level"> | ||||||
|  |             <a href="#" class="" | ||||||
|  |                tal:content="item/title" | ||||||
|  |                tal:attributes="href item/url">Menu Text</a> | ||||||
|  |           </div> | ||||||
|  |           <tal:sub tal:define="level python:level+1"> | ||||||
|  |             <tal:items repeat="item item/items"> | ||||||
|  |               <metal:portlet use-macro="views/node_macros/menu" /> | ||||||
|  |             </tal:items> | ||||||
|  |           </tal:sub> | ||||||
|  |         </metal:menu> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|  |     </tal:menu> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
| </tal:show> | </tal:show> | ||||||
|  |  | ||||||
							
								
								
									
										75
									
								
								browser/node.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								browser/node.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | ||||||
|  | # | ||||||
|  | #  Copyright (c) 2006 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 | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | View class for Node objects. | ||||||
|  | 
 | ||||||
|  | $Id$ | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.app import zapi | ||||||
|  | from zope.app.dublincore.interfaces import ICMFDublinCore | ||||||
|  | from zope.proxy import removeAllProxies | ||||||
|  | from zope.security.proxy import removeSecurityProxy | ||||||
|  | 
 | ||||||
|  | from loops.interfaces import IConcept | ||||||
|  | 
 | ||||||
|  | class NodeView(object): | ||||||
|  | 
 | ||||||
|  |     def render(self, text): | ||||||
|  |         if not text: | ||||||
|  |             return u'' | ||||||
|  |         if text.startswith('<'):  # seems to be HTML | ||||||
|  |             return text | ||||||
|  |         source = zapi.createObject(self.context.contentType, text) | ||||||
|  |         view = zapi.getMultiAdapter((removeAllProxies(source), self.request)) | ||||||
|  |         return view.render() | ||||||
|  | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def modified(self): | ||||||
|  |         """ get date/time of last modification | ||||||
|  |         """ | ||||||
|  |         dc = ICMFDublinCore(self.context) | ||||||
|  |         d = dc.modified or dc.created | ||||||
|  |         return d and d.strftime('%Y-%m-%d %H:%M') or '' | ||||||
|  | 
 | ||||||
|  |     def getContent(self, item=None): | ||||||
|  |         if item is None: | ||||||
|  |             item = self.context.getPage() | ||||||
|  |         if item is None: | ||||||
|  |             item = self.context | ||||||
|  |         result = {'title': item.title, | ||||||
|  |                   'url': zapi.absoluteURL(item, self.request), | ||||||
|  |                   'body': self.render(item.body), | ||||||
|  |                   'items': [self.getContent(child) | ||||||
|  |                                 for child in item.getTextItems()]} | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|  |     def getMenu(self, item=None): | ||||||
|  |         if item is None: | ||||||
|  |             item = self.context.getMenu() | ||||||
|  |         if item is None: | ||||||
|  |             return None | ||||||
|  |         result = {'title': item.title, | ||||||
|  |                   'url': zapi.absoluteURL(item, self.request), | ||||||
|  |                   'selected': item == self.context, | ||||||
|  |                   'items': [self.getMenu(child) | ||||||
|  |                                 for child in item.getMenuItems()]} | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
							
								
								
									
										48
									
								
								browser/view.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								browser/view.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | /* | ||||||
|  |  $Id$ | ||||||
|  | 
 | ||||||
|  |  settings specific for view / node objects | ||||||
|  |   | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | .box { | ||||||
|  |     margin: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #body { | ||||||
|  |     margin-left: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content-1 h1 { | ||||||
|  |     font-size: 160%; | ||||||
|  |     font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content-2 h1, .content-1 h2, .content-1 h3 { | ||||||
|  |     font-size: 130%; | ||||||
|  |     font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content-3 h1, .content-2 h2, .content-2 h3 { | ||||||
|  |     font-size: 120%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content-4 h1, .content-3 h2, .content-3 h3   { | ||||||
|  |     font-size: 110%; | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content-5 h1, .content-4 h2, .content-4 h3 { | ||||||
|  |     font-size: 100%; | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .box div.body div.menu-3 { | ||||||
|  |     padding-left: 1.5em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .box div.body div.menu-4 { | ||||||
|  |     padding-left: 3em; | ||||||
|  |     font-size: 90% | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -180,11 +180,11 @@ class INode(IView, IOrderedContainer): | ||||||
|     """ |     """ | ||||||
|     contains(IView) |     contains(IView) | ||||||
| 
 | 
 | ||||||
|     type = schema.Choice( |     nodeType = schema.Choice( | ||||||
|         title=_(u'Type'), |         title=_(u'Node Type'), | ||||||
|         description=_(u'Type of the node'), |         description=_(u'Type of the node'), | ||||||
|         values=('page', 'text', 'menu', 'menuitem'), |         values=('text', 'page', 'menu', 'info'), | ||||||
|         default='page', |         default='info', | ||||||
|         required=True) |         required=True) | ||||||
| 
 | 
 | ||||||
|     body = schema.Text( |     body = schema.Text( | ||||||
|  | @ -194,6 +194,39 @@ class INode(IView, IOrderedContainer): | ||||||
|         default=u'', |         default=u'', | ||||||
|         required=False) |         required=False) | ||||||
| 
 | 
 | ||||||
|  |     contentType = Attribute(_(u'Content type (format) of the body')) | ||||||
|  | 
 | ||||||
|  |     def getParentNode(nodeTypes=None): | ||||||
|  |         """ Return the next node up the node hierarchy. If the nodeTypes | ||||||
|  |             parameter is given, search for the next node that has one of | ||||||
|  |             the types in the nodeTypes list. | ||||||
|  | 
 | ||||||
|  |             Return None if no suitable node can be found. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def getChildNodes(nodeTypes=None): | ||||||
|  |         """ Return a sequence of nodes contained in this node. If the | ||||||
|  |             nodeTypes parameter is given return only nodes of these types. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def getMenu(): | ||||||
|  |         """ Return the menu node this node belongs to or None if not found. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def getPage(): | ||||||
|  |         """ Return a page node or None if not found. | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def getMenuItems(): | ||||||
|  |         """ Return the menu items belonging to this object (that should be | ||||||
|  |             a menu). | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  |     def getTextItems(): | ||||||
|  |         """ Return the menu items belonging to this object (that should be | ||||||
|  |             a menu). | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class IViewManager(IContainer): | class IViewManager(IContainer): | ||||||
|     """ A manager/container for views. |     """ A manager/container for views. | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								view.py
									
										
									
									
									
								
							
							
						
						
									
										38
									
								
								view.py
									
										
									
									
									
								
							|  | @ -82,16 +82,46 @@ class Node(View, OrderedContainer): | ||||||
| 
 | 
 | ||||||
|     implements(INode) |     implements(INode) | ||||||
| 
 | 
 | ||||||
|     _type = 'page' |     _nodeType = 'info' | ||||||
|     def getType(self): return self._type |     def getNodeType(self): return self._nodeType | ||||||
|     def setType(self, type): self._type = type |     def setNodeType(self, nodeType): self._nodeType = nodeType | ||||||
|     type = property(getType, setType) |     nodeType = property(getNodeType, setNodeType) | ||||||
| 
 | 
 | ||||||
|     _body = u'' |     _body = u'' | ||||||
|     def getBody(self): return self._body |     def getBody(self): return self._body | ||||||
|     def setBody(self, body): self._body = body |     def setBody(self, body): self._body = body | ||||||
|     body = property(getBody, setBody) |     body = property(getBody, setBody) | ||||||
| 
 | 
 | ||||||
|  |     contentType = u'zope.source.rest' | ||||||
|  | 
 | ||||||
|  |     def getParentNode(self, nodeTypes=None): | ||||||
|  |         parent = zapi.getParent(self) | ||||||
|  |         while INode.providedBy(parent): | ||||||
|  |             if not nodeTypes or parent.nodeType in nodeTypes: | ||||||
|  |                 return parent | ||||||
|  |             parent = zapi.getParent(parent) | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  |     def getChildNodes(self, nodeTypes=None): | ||||||
|  |         return [item for item in self.values() | ||||||
|  |                     if INode.providedBy(item) | ||||||
|  |                         and (not nodeTypes or item.nodeType in nodeTypes)] | ||||||
|  | 
 | ||||||
|  |     def getMenu(self): | ||||||
|  |         return self.nodeType == 'menu' and self or self.getParentNode(['menu']) | ||||||
|  | 
 | ||||||
|  |     def getPage(self): | ||||||
|  |         pageTypes = ['page', 'menu', 'info'] | ||||||
|  |         if self.nodeType in pageTypes: | ||||||
|  |             return self | ||||||
|  |         return self.getParentNode(pageTypes) | ||||||
|  | 
 | ||||||
|  |     def getMenuItems(self): | ||||||
|  |         return self.getChildNodes(['page', 'menu']) | ||||||
|  | 
 | ||||||
|  |     def getTextItems(self): | ||||||
|  |         return self.getChildNodes(['text']) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class ViewManager(BTreeContainer): | class ViewManager(BTreeContainer): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 helmutm
						helmutm