Nodes are now orderable via the ZMI
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1005 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
d61d424009
commit
4962810373
9 changed files with 419 additions and 22 deletions
67
README.txt
67
README.txt
|
@ -71,6 +71,7 @@ We can now ask our concepts for their related concepts:
|
|||
|
||||
TODO: Work with views...
|
||||
|
||||
|
||||
Resources and what they have to do with Concepts
|
||||
================================================
|
||||
|
||||
|
@ -107,8 +108,17 @@ below) via the getClients() method:
|
|||
>>> conc[0] is zope
|
||||
True
|
||||
|
||||
Views: Menus, Menu Items, Listings, Simple Content, etc
|
||||
=======================================================
|
||||
|
||||
Views/Nodes: Menus, Menu Items, Listings, Pages, etc
|
||||
====================================================
|
||||
|
||||
Note: the term "view" here is not directly related to the special
|
||||
Zop 3 term "view" (a multiadapter for presentation purposes) but basically
|
||||
bears the common sense meaning: an object (that may be persistent or
|
||||
created on the fly) that provides a view to content of whatever kind.
|
||||
|
||||
Views (or nodes - that's the only type of views existing at the moment)
|
||||
thus provide the presentation space to concepts and resources.
|
||||
|
||||
We first need a view manager:
|
||||
|
||||
|
@ -196,6 +206,59 @@ view class's target attribute:
|
|||
>>> m111.target is zope3
|
||||
True
|
||||
|
||||
Node views
|
||||
----------
|
||||
|
||||
>>> from loops.browser.node import NodeView
|
||||
>>> view = NodeView(m1, TestRequest())
|
||||
>>> view.menu()
|
||||
{'url': 'http://127.0.0.1/loops/views/m1',
|
||||
'items': [{'url': 'http://127.0.0.1/loops/views/m1/m11', 'items': [],
|
||||
'selected': False, 'title': u'Zope'}],
|
||||
'selected': True, 'title': u'Menu'}
|
||||
>>> view.content()
|
||||
{'url': 'http://127.0.0.1/loops/views/m1', 'body': u'', 'items': [],
|
||||
'title': u'Menu'}
|
||||
|
||||
>>> view = NodeView(m11, TestRequest())
|
||||
>>> view.menu()
|
||||
{'url': 'http://127.0.0.1/loops/views/m1',
|
||||
'items': [{'url': 'http://127.0.0.1/loops/views/m1/m11', 'items': [],
|
||||
'selected': True, 'title': u'Zope'}],
|
||||
'selected': False, 'title': u'Menu'}
|
||||
>>> view.content()
|
||||
{'url': 'http://127.0.0.1/loops/views/m1/m11', 'body': u'',
|
||||
'items': [{'url': 'http://127.0.0.1/loops/views/m1/m11/m112',
|
||||
'body': u'', 'items': [], 'title': u'Zope 3'}],
|
||||
'title': u'Zope'}
|
||||
|
||||
Ordering Nodes
|
||||
--------------
|
||||
|
||||
Let's add some more nodes and reorder them:
|
||||
|
||||
>>> m113 = Node()
|
||||
>>> m11['m113'] = m113
|
||||
>>> m114 = Node()
|
||||
>>> m11['m114'] = m114
|
||||
>>> m11.keys()
|
||||
['m111', 'm112', 'm113', 'm114']
|
||||
|
||||
>>> m11.moveSubNodesByDelta(['m113'], -1)
|
||||
>>> m11.keys()
|
||||
['m111', 'm113', 'm112', 'm114']
|
||||
|
||||
A special management view provides methods for moving objects down, up,
|
||||
to the bottom, and to the top
|
||||
|
||||
>>> from loops.browser.node import OrderedContainerView
|
||||
>>> view = OrderedContainerView(m11, TestRequest())
|
||||
>>> view.moveToBottom(('m113',))
|
||||
>>> m11.keys()
|
||||
['m111', 'm112', 'm114', 'm113']
|
||||
>>> view.moveUp(('m114',), 1)
|
||||
>>> m11.keys()
|
||||
['m111', 'm114', 'm112', 'm113']
|
||||
|
||||
Fin de partie
|
||||
=============
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<!-- resources -->
|
||||
|
||||
<resource name="view.css" file="view.css" />
|
||||
<resource name="node.css" file="node.css" />
|
||||
|
||||
<!-- macros -->
|
||||
|
||||
|
@ -199,13 +199,6 @@
|
|||
view="AddLoopsViewManager.html"
|
||||
/>
|
||||
|
||||
<containerViews
|
||||
for="loops.interfaces.IViewManager"
|
||||
index="zope.View"
|
||||
contents="zope.ManageContent"
|
||||
add="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<!-- node -->
|
||||
|
||||
<addform
|
||||
|
@ -256,10 +249,25 @@
|
|||
name="node.html"
|
||||
/>
|
||||
|
||||
<!-- move nodes up and down -->
|
||||
|
||||
<containerViews
|
||||
for="loops.interfaces.INode"
|
||||
contents="zope.ManageContent"
|
||||
for="loops.interfaces.IBaseNode"
|
||||
index="zope.ManageContent"
|
||||
add="zope.ManageContent"
|
||||
/>
|
||||
|
||||
<pages
|
||||
for="loops.interfaces.IBaseNode"
|
||||
class=".node.OrderedContainerView"
|
||||
permission="zope.ManageContent">
|
||||
<page name="contents.html" template="contents.pt"
|
||||
menu="zmi_views" title="Contents"
|
||||
/>
|
||||
<page name="move_down" attribute="moveDown" />
|
||||
<page name="move_up" attribute="moveUp" />
|
||||
<page name="move_bottom" attribute="moveToBottom" />
|
||||
<page name="move_top" attribute="moveToTop" />
|
||||
</pages>
|
||||
|
||||
</configure>
|
||||
|
|
216
browser/contents.pt
Normal file
216
browser/contents.pt
Normal file
|
@ -0,0 +1,216 @@
|
|||
<html metal:use-macro="context/@@standard_macros/view"
|
||||
i18n:domain="zope">
|
||||
<body>
|
||||
<div metal:fill-slot="body">
|
||||
<div metal:define-macro="contents">
|
||||
|
||||
<tal:checkmove define="isMoveAction view/checkMoveAction">
|
||||
|
||||
<tal:showlisting condition="not:isMoveAction">
|
||||
<form name="containerContentsForm" method="post" action="."
|
||||
tal:attributes="action request/URL"
|
||||
tal:define="container_contents view/listContentInfo">
|
||||
|
||||
<input type="hidden" name="type_name" value=""
|
||||
tal:attributes="value request/type_name"
|
||||
tal:condition="request/type_name|nothing"
|
||||
/>
|
||||
<input type="hidden" name="retitle_id" value=""
|
||||
tal:attributes="value request/retitle_id"
|
||||
tal:condition="request/retitle_id|nothing"
|
||||
/>
|
||||
|
||||
<div class="page_error"
|
||||
tal:condition="view/error"
|
||||
tal:content="view/error"
|
||||
i18n:translate="">
|
||||
Error message
|
||||
</div>
|
||||
|
||||
<table id="sortable" class="listing" summary="Content listing"
|
||||
i18n:attributes="summary">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th i18n:translate="">Name</th>
|
||||
<th i18n:translate="">Title</th>
|
||||
<th i18n:translate="">Size</th>
|
||||
<th i18n:translate="">Created</th>
|
||||
<th i18n:translate="">Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
<metal:block tal:condition="view/hasAdding">
|
||||
<tr tal:define="names_required context/@@+/nameAllowed"
|
||||
tal:condition="python:names_required and request.has_key('type_name')">
|
||||
<td></td>
|
||||
<td><input name="new_value" id="focusid" value="" /></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</metal:block>
|
||||
|
||||
<metal:block tal:define="supportsRename view/supportsRename"
|
||||
tal:repeat="item container_contents">
|
||||
<tr tal:define="oddrow repeat/item/odd; url item/url;
|
||||
id_quoted item/id/url:quote"
|
||||
tal:attributes="class python:oddrow and 'even' or 'odd'" >
|
||||
<td>
|
||||
<input type="checkbox" class="noborder" name="ids:list" id="#"
|
||||
value="#"
|
||||
tal:attributes="value item/id;
|
||||
id item/cb_id;
|
||||
checked request/ids_checked|nothing;"/>
|
||||
</td>
|
||||
<td><a href="#"
|
||||
tal:attributes="href
|
||||
string:${url}/@@SelectedManagementView.html"
|
||||
tal:content="structure item/icon|default">
|
||||
</a
|
||||
><span tal:condition="item/rename"
|
||||
><input name="new_value:list"
|
||||
tal:attributes="value item/id"
|
||||
/><input type="hidden" name="rename_ids:list" value=""
|
||||
tal:attributes="value item/rename"
|
||||
/></span
|
||||
><span tal:condition="not:item/rename">
|
||||
<a href="#"
|
||||
tal:attributes="href
|
||||
string:${url}/@@SelectedManagementView.html"
|
||||
tal:content="item/id"
|
||||
i18n:translate=""
|
||||
>foo</a
|
||||
><a href="#"
|
||||
tal:attributes="href
|
||||
string:${request/URL}?rename_ids:list=${id_quoted}"
|
||||
tal:condition="supportsRename"
|
||||
> </a
|
||||
></span
|
||||
></td>
|
||||
<td>
|
||||
<input name="new_value" id="focusid"
|
||||
tal:attributes="value item/title|nothing"
|
||||
tal:condition="item/retitle"
|
||||
/>
|
||||
<a href="#"
|
||||
tal:attributes="href
|
||||
string:${request/URL}?retitle_id=${id_quoted}"
|
||||
tal:condition="item/retitleable"
|
||||
tal:content="item/title|default"
|
||||
i18n:translate=""
|
||||
> </a>
|
||||
<span
|
||||
tal:condition="item/plaintitle"
|
||||
tal:content="item/title|default"
|
||||
i18n:translate=""
|
||||
> </span>
|
||||
</td>
|
||||
|
||||
<td><span tal:content="item/size/sizeForDisplay|nothing"
|
||||
i18n:translate="">
|
||||
</span></td>
|
||||
<td><span tal:define="created item/created|default"
|
||||
tal:content="created"
|
||||
i18n:translate=""> </span></td>
|
||||
<td><span tal:define="modified item/modified|default"
|
||||
tal:content="modified"
|
||||
i18n:translate=""> </span></td>
|
||||
</tr>
|
||||
</metal:block>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div tal:condition="view/normalButtons">
|
||||
|
||||
<input type="submit" name="container_rename_button" value="Rename"
|
||||
i18n:attributes="value container-rename-button"
|
||||
tal:condition="view/supportsRename"
|
||||
/>
|
||||
<input type="submit" name="container_cut_button" value="Cut"
|
||||
i18n:attributes="value container-cut-button"
|
||||
tal:condition="view/supportsCut"
|
||||
/>
|
||||
<input type="submit" name="container_copy_button" value="Copy"
|
||||
i18n:attributes="value container-copy-button"
|
||||
tal:condition="view/supportsCopy"
|
||||
/>
|
||||
<input type="submit" name="container_paste_button" value="Paste"
|
||||
tal:condition="view/hasClipboardContents"
|
||||
i18n:attributes="value container-paste-button"
|
||||
/>
|
||||
<input type="submit" name="container_delete_button" value="Delete"
|
||||
i18n:attributes="value container-delete-button"
|
||||
tal:condition="view/supportsDelete"
|
||||
i18n:domain="zope"
|
||||
/>
|
||||
|
||||
<div tal:condition="view/orderable">
|
||||
<input type="submit" name="move_top" value="Top"
|
||||
i18n:attributes="value container-movetop-button"
|
||||
i18n:domain="zope" />
|
||||
<input type="submit" name="move_up" value="Up"
|
||||
i18n:attributes="value container-moveup-button"
|
||||
i18n:domain="zope" />
|
||||
<input type="submit" name="move_down" value="Down"
|
||||
i18n:attributes="value container-moveup-button"
|
||||
i18n:domain="zope" />
|
||||
<input type="submit" name="move_bottom" value="Bottom"
|
||||
i18n:attributes="value container-movebottom-button"
|
||||
i18n:domain="zope" />
|
||||
<input type="hidden" name="delta" value="1" />
|
||||
</div>
|
||||
|
||||
<div tal:condition="view/hasAdding" tal:omit-tag="">
|
||||
<div tal:omit-tag=""
|
||||
tal:define="adding nocall:context/@@+;
|
||||
addingInfo adding/addingInfo;
|
||||
has_custom_add_view adding/hasCustomAddView;
|
||||
names_required adding/nameAllowed"
|
||||
tal:condition="adding/isSingleMenuItem">
|
||||
<input type="submit" name="container_add_button" value="Add"
|
||||
i18n:attributes="value add-button"
|
||||
i18n:domain="zope"
|
||||
/>
|
||||
<input type="text" name="single_new_value" id="focusid"
|
||||
tal:condition="python:names_required and not has_custom_add_view"
|
||||
i18n:domain="zope"
|
||||
/>
|
||||
<input type="hidden" name="single_type_name"
|
||||
value=""
|
||||
tal:attributes="value python:addingInfo[0]['action']"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div tal:condition="view/specialButtons">
|
||||
<input type="submit" value="Apply"
|
||||
i18n:attributes="value container-apply-button"
|
||||
/>
|
||||
<input type="submit" name="container_cancel_button" value="Cancel"
|
||||
i18n:attributes="value container-cancel-button"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</tal:showlisting>
|
||||
</tal:checkmove>
|
||||
|
||||
|
||||
<script type="text/javascript"><!--
|
||||
if (document.containerContentsForm.new_value)
|
||||
document.containerContentsForm.new_value.focus();
|
||||
//-->
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
<metal:css fill-slot="style_slot">
|
||||
<style type="text/css" media="all"
|
||||
tal:content="string:@import url(${context/++resource++view.css});">
|
||||
tal:content="string:@import url(${context/++resource++node.css});">
|
||||
@import url(view.css);
|
||||
</style>
|
||||
</metal:css>
|
||||
|
||||
|
||||
<metal:body fill-slot="body">
|
||||
<tal:content define="item view/getContent;
|
||||
<tal:content define="item view/content;
|
||||
level level|python: 1">
|
||||
|
||||
<metal:block define-macro="content">
|
||||
|
@ -36,7 +36,7 @@
|
|||
|
||||
<div class="box" metal:fill-slot="navigators">
|
||||
<h4>Navigation</h4>
|
||||
<tal:menu define="item view/getMenu;
|
||||
<tal:menu define="item view/menu;
|
||||
level level|python: 1"
|
||||
condition="item">
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ $Id$
|
|||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.app import zapi
|
||||
from zope.app.container.browser.contents import JustContents
|
||||
from zope.app.dublincore.interfaces import ICMFDublinCore
|
||||
from zope.proxy import removeAllProxies
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
@ -32,6 +33,10 @@ from loops.interfaces import IConcept
|
|||
|
||||
class NodeView(object):
|
||||
|
||||
def __init__(self, context, request):
|
||||
self.context = context
|
||||
self.request = request
|
||||
|
||||
def render(self, text):
|
||||
if not text:
|
||||
return u''
|
||||
|
@ -49,7 +54,7 @@ class NodeView(object):
|
|||
d = dc.modified or dc.created
|
||||
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
||||
|
||||
def getContent(self, item=None):
|
||||
def content(self, item=None):
|
||||
if item is None:
|
||||
item = self.context.getPage()
|
||||
if item is None:
|
||||
|
@ -57,11 +62,11 @@ class NodeView(object):
|
|||
result = {'title': item.title,
|
||||
'url': zapi.absoluteURL(item, self.request),
|
||||
'body': self.render(item.body),
|
||||
'items': [self.getContent(child)
|
||||
'items': [self.content(child)
|
||||
for child in item.getTextItems()]}
|
||||
return result
|
||||
|
||||
def getMenu(self, item=None):
|
||||
def menu(self, item=None):
|
||||
if item is None:
|
||||
item = self.context.getMenu()
|
||||
if item is None:
|
||||
|
@ -69,7 +74,53 @@ class NodeView(object):
|
|||
result = {'title': item.title,
|
||||
'url': zapi.absoluteURL(item, self.request),
|
||||
'selected': item == self.context,
|
||||
'items': [self.getMenu(child)
|
||||
'items': [self.menu(child)
|
||||
for child in item.getMenuItems()]}
|
||||
return result
|
||||
|
||||
|
||||
class OrderedContainerView(JustContents):
|
||||
""" A view providing the necessary methods for moving sub-objects
|
||||
within an ordered container.
|
||||
"""
|
||||
|
||||
@Lazy
|
||||
def url(self):
|
||||
return zapi.absoluteURL(self.context, self.request)
|
||||
|
||||
@Lazy
|
||||
def orderable(self):
|
||||
return len(self.context) > 1
|
||||
|
||||
def checkMoveAction(self):
|
||||
request = self.request
|
||||
for var in request:
|
||||
if var.startswith('move_'):
|
||||
params = []
|
||||
if 'delta' in request:
|
||||
params.append('delta=' + request['delta'])
|
||||
if 'ids' in request:
|
||||
for id in request['ids']:
|
||||
params.append('ids:list=' + id)
|
||||
request.response.redirect('%s/%s?%s'
|
||||
% (self.url, var, '&'.join(params)))
|
||||
return True
|
||||
return False
|
||||
|
||||
def moveDown(self, ids, delta=1):
|
||||
self.context.moveSubNodesByDelta(ids, int(delta))
|
||||
self.request.response.redirect(self.url + '/contents.html')
|
||||
|
||||
def moveUp(self, ids, delta=1):
|
||||
self.context.moveSubNodesByDelta(ids, -int(delta))
|
||||
self.request.response.redirect(self.url + '/contents.html')
|
||||
|
||||
def moveToBottom(self, ids):
|
||||
self.context.moveSubNodesByDelta(ids, len(self.context))
|
||||
self.request.response.redirect(self.url + '/contents.html')
|
||||
|
||||
def moveToTop(self, ids):
|
||||
self.context.moveSubNodesByDelta(ids, -len(self.context))
|
||||
self.request.response.redirect(self.url + '/contents.html')
|
||||
|
||||
|
||||
|
|
|
@ -169,9 +169,13 @@ class IView(Interface):
|
|||
required=False)
|
||||
|
||||
target = Attribute('Target object that is referenced by this view')
|
||||
|
||||
class IBaseNode(IOrderedContainer):
|
||||
""" Common abstract base class for different types of nodes
|
||||
"""
|
||||
|
||||
|
||||
class INode(IView, IOrderedContainer):
|
||||
class INode(IView, IBaseNode):
|
||||
""" A node is a view that may contain other views, thus building a
|
||||
menu or folder hierarchy.
|
||||
|
||||
|
@ -227,8 +231,14 @@ class INode(IView, IOrderedContainer):
|
|||
a menu).
|
||||
"""
|
||||
|
||||
def moveSubNodesByDelta(names, delta):
|
||||
""" Move the sub-nodes specified by the list of names up
|
||||
(negative delta) or down (positive delta) by the number of places
|
||||
specified by delta.
|
||||
"""
|
||||
|
||||
class IViewManager(IContainer):
|
||||
|
||||
class IViewManager(IBaseNode):
|
||||
""" A manager/container for views.
|
||||
"""
|
||||
contains(IView)
|
||||
|
|
46
util.py
Normal file
46
util.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
"""
|
||||
Utility functions.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
def moveByDelta(objs, toMove, delta):
|
||||
""" Return the list given by objs resorted in a way that the elements
|
||||
of toMove (which must be in the objs list) have been moved by delta.
|
||||
"""
|
||||
result = [obj for obj in objs if obj not in toMove]
|
||||
if delta < 0:
|
||||
objs = list(reversed(objs))
|
||||
result.reverse()
|
||||
toMove = sorted(toMove, lambda x,y: cmp(objs.index(x), objs.index(y)))
|
||||
for element in toMove:
|
||||
newPos = min(len(result), objs.index(element) + abs(delta))
|
||||
result.insert(newPos, element)
|
||||
if delta < 0:
|
||||
result.reverse()
|
||||
return result
|
||||
|
||||
def nl2br(text):
|
||||
if not text: return text
|
||||
if '\n' in text: # Unix or DOS line endings
|
||||
return '<br />\n'.join(x.replace('\r', '') for x in text.split('\n'))
|
||||
else: # gracefully handle Mac line endigns
|
||||
return '<br />\n'.join(text.split('\r'))
|
5
view.py
5
view.py
|
@ -34,6 +34,7 @@ from cybertools.relation.registry import IRelationsRegistry, getRelations
|
|||
from interfaces import IView, INode
|
||||
from interfaces import IViewManager, INodeContained
|
||||
from interfaces import ILoopsContained
|
||||
from util import moveByDelta
|
||||
|
||||
|
||||
class View(object):
|
||||
|
@ -122,8 +123,10 @@ class Node(View, OrderedContainer):
|
|||
def getTextItems(self):
|
||||
return self.getChildNodes(['text'])
|
||||
|
||||
def moveSubNodesByDelta(self, names, delta):
|
||||
self.updateOrder(moveByDelta(self.keys(), names, delta))
|
||||
|
||||
class ViewManager(BTreeContainer):
|
||||
class ViewManager(OrderedContainer):
|
||||
|
||||
implements(IViewManager, ILoopsContained)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue