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$)
|
||||
|
||||
>>> 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
|
||||
======================
|
||||
|
||||
|
@ -12,6 +20,8 @@ top-level loops container and a concept manager:
|
|||
|
||||
>>> from loops import Loops
|
||||
>>> loops = Loops()
|
||||
>>> site['loops'] = Loops()
|
||||
>>> loops = site['loops']
|
||||
|
||||
>>> from loops.concept import ConceptManager, Concept
|
||||
>>> loops['concepts'] = ConceptManager()
|
||||
|
@ -97,8 +107,8 @@ below) via the getClients() method:
|
|||
>>> conc[0] is zope
|
||||
True
|
||||
|
||||
Views: Menus, Menu Items, Listings, etc
|
||||
=======================================
|
||||
Views: Menus, Menu Items, Listings, Simple Content, etc
|
||||
=======================================================
|
||||
|
||||
We first need a view manager:
|
||||
|
||||
|
@ -122,6 +132,57 @@ menu that may contain other nodes as menu or content items:
|
|||
>>> m112.description
|
||||
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
|
||||
view class's target attribute:
|
||||
|
||||
|
@ -135,3 +196,9 @@ view class's target attribute:
|
|||
>>> m111.target is zope3
|
||||
True
|
||||
|
||||
|
||||
Fin de partie
|
||||
=============
|
||||
|
||||
>>> placefulTearDown()
|
||||
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
xmlns="http://namespaces.zope.org/browser"
|
||||
i18n_domain="zope">
|
||||
|
||||
<!-- resources -->
|
||||
|
||||
<resource name="view.css" file="view.css" />
|
||||
|
||||
<!-- macros -->
|
||||
|
||||
<page
|
||||
|
@ -197,6 +201,7 @@
|
|||
|
||||
<containerViews
|
||||
for="loops.interfaces.IViewManager"
|
||||
index="zope.View"
|
||||
contents="zope.ManageContent"
|
||||
add="zope.ManageContent"
|
||||
/>
|
||||
|
@ -208,11 +213,11 @@
|
|||
name="AddLoopsNode.html"
|
||||
content_factory="loops.view.Node"
|
||||
schema="loops.interfaces.INode"
|
||||
fields="title description type body"
|
||||
fields="title description nodeType body"
|
||||
permission="zope.ManageContent">
|
||||
|
||||
<widget field="description" height="2" />
|
||||
<widget field="body" height="4" />
|
||||
<widget field="body" height="8" />
|
||||
|
||||
</addform>
|
||||
|
||||
|
@ -228,13 +233,13 @@
|
|||
label="Edit Node"
|
||||
name="edit.html"
|
||||
schema="loops.interfaces.INode"
|
||||
fields="title description type body"
|
||||
fields="title description nodeType body"
|
||||
for="loops.interfaces.INode"
|
||||
permission="zope.ManageContent"
|
||||
menu="zmi_views" title="Edit">
|
||||
|
||||
<widget field="description" height="2" />
|
||||
<widget field="body" height="4" />
|
||||
<widget field="body" height="15" />
|
||||
|
||||
</editform>
|
||||
|
||||
|
@ -242,6 +247,7 @@
|
|||
name="node.html"
|
||||
for="loops.interfaces.INode"
|
||||
template="node.pt"
|
||||
class=".node.NodeView"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
|
|
|
@ -3,22 +3,64 @@
|
|||
<head></head>
|
||||
<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">
|
||||
<tal:content define="item context;
|
||||
<tal:content define="item view/getContent;
|
||||
level level|python: 1">
|
||||
|
||||
<metal:block define-macro="content">
|
||||
<div tal:content="structure item/body">The body</div>
|
||||
<div tal:define="level python:level+1">
|
||||
<tal:items repeat="item item/values">
|
||||
<tal:body define="body item/body"
|
||||
condition="body">
|
||||
<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" />
|
||||
</tal:items>
|
||||
</div>
|
||||
</tal:sub>
|
||||
</metal:block>
|
||||
|
||||
</tal:content>
|
||||
</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>
|
||||
</html>
|
||||
</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)
|
||||
|
||||
type = schema.Choice(
|
||||
title=_(u'Type'),
|
||||
nodeType = schema.Choice(
|
||||
title=_(u'Node Type'),
|
||||
description=_(u'Type of the node'),
|
||||
values=('page', 'text', 'menu', 'menuitem'),
|
||||
default='page',
|
||||
values=('text', 'page', 'menu', 'info'),
|
||||
default='info',
|
||||
required=True)
|
||||
|
||||
body = schema.Text(
|
||||
|
@ -194,6 +194,39 @@ class INode(IView, IOrderedContainer):
|
|||
default=u'',
|
||||
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):
|
||||
""" A manager/container for views.
|
||||
|
|
38
view.py
38
view.py
|
@ -82,16 +82,46 @@ class Node(View, OrderedContainer):
|
|||
|
||||
implements(INode)
|
||||
|
||||
_type = 'page'
|
||||
def getType(self): return self._type
|
||||
def setType(self, type): self._type = type
|
||||
type = property(getType, setType)
|
||||
_nodeType = 'info'
|
||||
def getNodeType(self): return self._nodeType
|
||||
def setNodeType(self, nodeType): self._nodeType = nodeType
|
||||
nodeType = property(getNodeType, setNodeType)
|
||||
|
||||
_body = u''
|
||||
def getBody(self): return self._body
|
||||
def setBody(self, body): self._body = body
|
||||
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):
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue