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