[Zope3-checkins] CVS: Zope3/src/zope/products/statictree - CHANGES.txt:1.1 MAINTAINER.txt:1.1 README.txt:1.1 TODO.txt:1.1 __init__.py:1.1 adapters.py:1.1 browser.py:1.1 configure.zcml:1.1 filters.py:1.1 interfaces.py:1.1 node.py:1.1 utils.py:1.1 version.txt:1.1

Philipp von Weitershausen philikon at philikon.de
Fri Jan 16 07:39:03 EST 2004


Update of /cvs-repository/Zope3/src/zope/products/statictree
In directory cvs.zope.org:/tmp/cvs-serv18109

Added Files:
	CHANGES.txt MAINTAINER.txt README.txt TODO.txt __init__.py 
	adapters.py browser.py configure.zcml filters.py interfaces.py 
	node.py utils.py version.txt 
Log Message:
Checked the 'statictree' product in. This is a port of my original ZopeTree
product for Zope2, which was meant to replace the inflexible and
irreliable ZTUtils.Tree. This version for Zope 3 is released under the ZPL
and now added to the main distribution on people's request.

See README.txt for more infos.


=== Added File Zope3/src/zope/products/statictree/CHANGES.txt ===
CHANGES
=======

v1.0.1 (2004-01-16) -- 'Nibbler'

  - Added last remaining pieces for unit tests

  - Updated documentation

  - Integrated it into the Zope3 distribution below the zope.products
    package

v1.0 (2003-11-24) -- 'Lur'

  - Ported to Zope 3

  - Renamed it to 'statictree'

  - Much more unit tests

  - Added filter functionality

  - Provided sample implementations as well as an alternate
    rotterdam-like skin using the static tree


=== Added File Zope3/src/zope/products/statictree/MAINTAINER.txt ===
This package is maintained by:
Philipp "philiKON" von Weitershausen, philikon at philikon.de


=== Added File Zope3/src/zope/products/statictree/README.txt ===
Static tree for Zope3
=====================


What is Static tree?
--------------------

Static tree is a port of Philipp's Zope2 product called
ZopeTree. ZopeTree was meant to be a light-weight and easy-to-use
static tree implementation, mainly designed for use in ZPTs. It was
originally written because Zope2's ZTUtils.Tree was found to be too
complicated and inflexible.

The ZTUtils package has not been ported to Zope3. Parts of it, like
batching, have found their way into Zope3, though. Only support for
static tree generation is not in the core.


How to use it
-------------

Using the skin
--------------

Static tree comes with a pre-defined skin, StaticTree. It looks just
like Zope3's default skin, Rotterdam, except that it displays a static
tree in the navigation box instead of the Javascript/XML based dynamic
tree.

Using predefined views on objects
---------------------------------

Static tree comes with several predefined views:

- static_cookie_tree: simple view using cookies for tree state
  storage.

- folder_cookie_tree: same as above, however only showing folders.

- site_cookie_tree: same as above, with the nearest site as root node.

- root_cookie_tree: same as above, with the root container as root
  node.

The example page template(s) in the 'example' directory give an idea
how to use these views for HTML templating.

Customization
-------------

The best way to customize static tree is to define your own view for
objects (usually *). If you want to use the cookie functionality,
simply extend the cookie browser view::

  from zope.products.statictree.browser import StaticTreeView
  from zope.products.statictree.filters import OnlyInterfacesFilter

  class BendableStaticTreeView(StaticTreeView):

      def bendablesCookieTree(self):
          # tree with only IBendables, but also show the folder
          # they're in
          filter = OnlyInterfacesFilter(IBendable, IFolder)
          return self.cookieTree(filter)

You can also write your own filters. All you have to do is implement
the IObjectFindFilter interface (which is trivial)::

  from zope.interface import implements
  from zope.app.interfaces.find import IObjectFindFilter

  class BendableFilter:
      implements(IObjectFindFilter)

      def matches(self, obj)
          # only allow bendable objects
          return obj.isBendable()


License and Copyright
---------------------

This product is released under the terms of the Zope Public License
(ZPL) v2.0. See the 'ZopePublicLicense.txt' file at the root of your
Zope distribution.

Copyright (c) 2003 Philipp "philiKON" von Weitershausen
Copyright (c) 2004 Zope Corporation and Contributors


Credits
-------

Thanks to ZopeMag (http://zopemag.com) for sponsoring development of
the original ZopeTree product.

Thanks to Runyaga LLC (http://runyaga.com) for sponsoring the Zope3
port.


=== Added File Zope3/src/zope/products/statictree/TODO.txt ===

- BUG: site manager does not show up in StaticTree skin

v1.1

- allow sorting: would work similar to filter

- add XML representation so it's compatible with rotterdam's
  xmltree.js, but stateful


=== Added File Zope3/src/zope/products/statictree/__init__.py ===
# make this directory a package


=== Added File Zope3/src/zope/products/statictree/adapters.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Object adapters

This module contains adapters necessary to use common objects with
statictree. The most prominent ones are those for ILocation and
IContainer. We also provide adapters for any object, so we don't end
up with ComponentLookupErrors whenever encounter unknown
objects. Explicit is better than implement EXCEPT WHEN IT'S NOT...

$Id: adapters.py,v 1.1 2004/01/16 12:39:00 philikon Exp $
"""

from zope.interface import Interface, implements
from zope.component.exceptions import ComponentLookupError

from zope.app import zapi
from zope.app.interfaces.location import ILocation
from zope.app.interfaces.container import IReadContainer
from zope.app.interfaces.services.service import ISite

from interfaces import IUniqueId, IChildObjects

__metaclass__ = type

class StubUniqueId:
    implements(IUniqueId)
    __used_for__ = Interface

    def __init__(self, context):
        self.context = context

    def getId(self):
        # this does not work for persistent objects
        return str(id(self.context))

class StubChildObjects:
    implements(IChildObjects)
    __used_for__ = Interface

    def __init__(self, context):
        pass

    def hasChildren(self):
        return False

    def getChildObjects(self):
        return ()

class LocationUniqueId:
    implements(IUniqueId)
    __used_for__ = ILocation

    def __init__(self, context):
        self.context = context

    def getId(self):
        context = self.context
        if not context.__name__:
            # always try to be unique
            return str(id(context))
        parents = [context.__name__]
        parents += [parent.__name__ for parent in zapi.getParents(context)
                    if parent.__name__]
        return '\\'.join(parents)

class ContainerChildObjects:
    implements(IChildObjects)
    __used_for__ = IReadContainer

    def __init__(self, context):
        self.context = context

    def hasChildren(self):
        return bool(len(self.context))

    def getChildObjects(self):
        return self.context.values()

class ContainerSiteChildObjects(ContainerChildObjects):
    """Adapter for read containers which are sites as well. The site
    manager will be treated as just another child object.
    """
    __used_for__ = ISite

    def hasChildren(self):
        if super(ContainerSiteChildObjects, self).hasChildren():
            return True
        try:
            self.context.getSiteManager()
            return True
        except ComponentLookupError:
            return False

    def getChildObjects(self):
        values = super(ContainerSiteChildObjects, self).getChildObjects()
        try:
            return [self.context.getSiteManager()] + list(values)
        except ComponentLookupError:
            return values


=== Added File Zope3/src/zope/products/statictree/browser.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser views

$Id: browser.py,v 1.1 2004/01/16 12:39:00 philikon Exp $
"""

from zope.app import zapi
from zope.app.publisher.browser import BrowserView
from zope.app.interfaces.content.folder import IFolder
from zope.app.interfaces.services.service import ISite
from zope.app.interfaces.traversing import IContainmentRoot

from interfaces import ITreeStateEncoder
from node import Node
from filters import OnlyInterfacesFilter

class StaticTreeView(BrowserView):

    request_variable = 'tree-state'

    def cookieTree(self, root=None, filter=None):
        """Build a tree with tree state information from a request.
        """
        if root is None:
            root = self.context
        request = self.request
        expanded_nodes = []
        tree_state = request.get(self.request_variable, "")
        tree_state = str(tree_state)
        if tree_state:
            # set a cookie right away
            request.response.setCookie(self.request_variable,
                                       tree_state)
            encoder = zapi.getUtility(root, ITreeStateEncoder)
            expanded_nodes = encoder.decodeTreeState(tree_state)
        node = Node(root, expanded_nodes, filter)
        node.expand()
        return node

    def folderCookieTree(self, root=None):
        """Cookie tree with only folders.
        """
        filter = OnlyInterfacesFilter(IFolder)
        return self.cookieTree(root, filter)

    def siteCookieTree(self):
        """Cookie tree with only folders and the nearest site as root
        node.
        """
        parent = self.context
        for parent in zapi.getParents(self.context):
            if ISite.isImplementedBy(parent):
                break
        return self.folderCookieTree(parent)

    def rootCookieTree(self):
        """Cookie tree with only folders and the root container as
        root node.
        """
        root = zapi.getRoot(self.context)
        return self.folderCookieTree(root)


=== Added File Zope3/src/zope/products/statictree/configure.zcml ===
<configure
  xmlns="http://namespaces.zope.org/zope"
  xmlns:browser="http://namespaces.zope.org/browser"
  i18n_domain="statictree"
  >

  <class class=".node.Node">
    <allow interface=".interfaces.INode" />
  </class>

  <utility
    provides=".interfaces.ITreeStateEncoder"
    factory=".utils.TreeStateEncoder"
    />

  <!-- stub adapters -->

  <adapter
    provides=".interfaces.IUniqueId"
    for="*"
    factory=".adapters.StubUniqueId"
    />

  <adapter
    provides=".interfaces.IChildObjects"
    for="*"
    factory=".adapters.StubChildObjects"
    />

  <!-- adapters for zope.app.container machinery -->

  <adapter
    provides=".interfaces.IUniqueId"
    for="zope.app.interfaces.location.ILocation"
    factory=".adapters.LocationUniqueId"
    />

  <adapter
    provides=".interfaces.IChildObjects"
    for="zope.app.interfaces.container.IReadContainer"
    factory=".adapters.ContainerChildObjects"
    />

  <adapter
    provides=".interfaces.IChildObjects"
    for="zope.app.interfaces.services.service.ISite"
    factory=".adapters.ContainerSiteChildObjects"
    />

<!-- browser stuff -->

  <browser:pages
    for="*"
    class=".browser.StaticTreeView"
    permission="zope.View"
    >
    <browser:page
      name="static_cookie_tree"
      attribute="cookieTree"
      />
    <browser:page
      name="folder_cookie_tree"
      attribute="folderCookieTree"
      />
    <browser:page
      name="site_cookie_tree"
      attribute="siteCookieTree"
      />
    <browser:page
      name="root_cookie_tree"
      attribute="rootCookieTree"
      />
  </browser:pages>

  <include package=".skins" />

</configure>



=== Added File Zope3/src/zope/products/statictree/filters.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Filters

Child objects can be filtered out by certain criteria which are
defined by a filter. Writing your own filter should be very easy. All
you have to implement is the IObjectFindFilter interface from the
zope.app.interfaces.find package. Already existing filters for the
find machinery may be used with statictree just as well.

Since commonly needed, this module provides two filters that filter by
interface.

$Id: filters.py,v 1.1 2004/01/16 12:39:00 philikon Exp $
"""

from zope.interface import implements
from zope.app.interfaces.find import IObjectFindFilter

__metaclass__ = type

class OnlyInterfacesFilter:
    """Only match objects that implement one of the given interfaces.
    """
    implements(IObjectFindFilter)
    only_interfaces = True

    def __init__(self, *filterby):
        self.ifaces = filterby

    def matches(self, obj):
        ifaces = self.ifaces
        for iface in ifaces:
            if iface.isImplementedBy(obj):
                return self.only_interfaces
        return not self.only_interfaces

class AllButInterfacesFilter(OnlyInterfacesFilter):
    """Match only objects that do not implement one of the given
    interfaces.
    """
    only_interfaces = False


=== Added File Zope3/src/zope/products/statictree/interfaces.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Static tree interfaces

$Id: interfaces.py,v 1.1 2004/01/16 12:39:00 philikon Exp $
"""

from zope.interface import Interface, Attribute
from zope.schema import Bool, Int

class IUniqueId(Interface):
    """Interface that promises to return a unique id within a
    tree.

    Problem: How are implementing objects (most probably adapters)
    supposed to know, whether their id is unique in the context? Well,
    they just have to be damn sure that they are unique.
    """

    def getId():
        """Return a string containing a unique id within a tree
        """

class IChildObjects(Interface):
    """Interface providing methods to retrieve child objects so they
    can be wrapped in tree nodes.
    """

    def hasChildren():
        """Return true if child objects are available
        """

    def getChildObjects():
        """Return a sequence of child objects
        """

class INode(IUniqueId, IChildObjects):
    """A node in the tree
    """

    context = Attribute("""
        The object that is being wrapped.
        """)

    depth = Int(
        title=u"Depth",
        description=u"The positional depth of this node in the tree.",
        )

    expanded = Bool(
        title=u"Expanded",
        description=u"True if this node is expanded.",
        )

    def expand(recursive=False):
        """Expand this node.

        'recursive' can be set to True to expand all child nodes as
        well
        """

    def collapse():
        """Collapse this node.
        """

    def getChildNodes():
        """Return a sequence of children nodes if the node is expanded.
        """

    def getFlatNodes():
        """Return a flat list of nodes in the tree. Children of
        expanded nodes are shown.
        """

    def getFlatDicts():
        """Return a tuple:

          1st element: flat list of dictinaries containing nodes in
          the tree and extra information (depth, toggled tree
          state). Children of expanded nodes are shown.

          2nd element: maximum depth
        """

class ITreeStateEncoder(Interface):
    """This utility can encode and decode the ids of expended nodes
    """

    def encodeTreeState(expanded_nodes):
        """Encode the tree expansion information in 'expanded_nodes'.
        """

    def decodeTreeState(tree_state):
        """Decode the tree expansion information 'tree_state'.
        """


=== Added File Zope3/src/zope/products/statictree/node.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""A node in the treee

$Id: node.py,v 1.1 2004/01/16 12:39:00 philikon Exp $
"""

from zope.interface import implements
from zope.app import zapi

from interfaces import INode, IUniqueId, IChildObjects, \
     ITreeStateEncoder

__metaclass__ = type

class Node:
    """A tree node

    This object represents a node in the tree. It wraps the actual
    object and provides the INode interface to be relied on. In that
    way, it works similar to an adapter.

    This implementation is designed to be as lazy as
    possible. Especially, it will only create child nodes when
    necessary.
    """
    implements(INode)

    __slots__ = (
        'context', 'expanded', 'filter', '_id', '_expanded_nodes',
        '_child_nodes', '_child_objects_adapter',
        )

    def __init__(self, context, expanded_nodes=[], filter=None):
        self.context = context
        self.expanded = False
        self.filter = filter
        self._expanded_nodes = expanded_nodes
        self._id = id = zapi.getAdapter(context, IUniqueId).getId()
        if id in expanded_nodes:
            self.expand()

    def _create_child_nodes(self):
        """Create child nodes and save the result so we don't have
        to create that sequence every time
        """
        nodes = []
        for obj in self.getChildObjects():
            node = Node(obj, self._expanded_nodes, self.filter)
            nodes.append(node)
        self._child_nodes = nodes

    def _get_child_objects_adapter(self):
        """Lazily create the child objects adapter
        """
        if not hasattr(self, '_child_objects_adapter'):
            self._child_objects_adapter = zapi.getAdapter(
                self.context, IChildObjects)
        return self._child_objects_adapter

    def expand(self, recursive=False):
        """See the zope.products.statictree.interfaces.INode interface
        """
        self.expanded = True
        if recursive:
            for node in self.getChildNodes():
                node.expand(True)

    def collapse(self):
        """See the zope.products.statictree.interfaces.INode interface
        """
        self.expanded = False

    def getId(self):
        """See the zope.products.statictree.interfaces.INode interface
        """
        return self._id

    def hasChildren(self):
        """See the zope.products.statictree.interfaces.INode interface
        """
        # we could actually test for the length of the result of
        # getChildObjects(), but we need to watch performance
        return self._get_child_objects_adapter().hasChildren()

    def getChildObjects(self):
        """See the zope.products.statictree.interfaces.INode interface
        """
        filter = self.filter
        children = self._get_child_objects_adapter().getChildObjects()
        if filter:
            return [child for child in children if filter.matches(child)]
        return children
        
    def getChildNodes(self):
        """See the zope.products.statictree.interfaces.INode interface
        """
        if not self.expanded:
            return []
        if not hasattr(self, '_child_nodes'):
            # children nodes are not created until they are explicitly
            # requested through this method
            self._create_child_nodes()
        return self._child_nodes[:]

    def getFlatNodes(self):
        """See the zope.products.statictree.interfaces.INode interface
        """
        nodes = []
        for node in self.getChildNodes():
            nodes.append(node)
            nodes += node.getFlatNodes()
        return nodes

    def getFlatDicts(self, depth=0, maxdepth=0):
        """See the zope.products.statictree.interfaces.INode interface
        """
        nodes = []
        encoder = zapi.getUtility(self.context, ITreeStateEncoder)

        if self.hasChildren() and depth > maxdepth:
            maxdepth = depth

        for node in self.getChildNodes():
            id = node.getId()
            expanded_nodes = self._expanded_nodes[:]
            if id in self._expanded_nodes:
                # if the node is already expanded, the toggle would
                # collapse it
                expanded_nodes.remove(id)
            else:
                # if it isn't expanded, the toggle would expand it
                expanded_nodes += [id]
            flatdict = {
                'depth': depth,
                'node': node,
                'tree-state': encoder.encodeTreeState(expanded_nodes),
                }
            nodes.append(flatdict)
            child_nodes, maxdepth = node.getFlatDicts(depth+1, maxdepth)
            nodes += child_nodes
        return nodes, maxdepth


=== Added File Zope3/src/zope/products/statictree/utils.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Static tree utilities

$Id: utils.py,v 1.1 2004/01/16 12:39:00 philikon Exp $
"""

import zlib

from zope.interface import implements
from interfaces import ITreeStateEncoder

class TreeStateEncoder:
    """Encodes tree state

    >>> expanded_nodes = ['a', 'c', 'foobar']
    >>> encoder = TreeStateEncoder()
    >>> encoded = encoder.encodeTreeState(expanded_nodes)
    >>> decoded = encoder.decodeTreeState(encoded)
    >>> decoded == expanded_nodes
    True
    """
    implements(ITreeStateEncoder)

    # note that this implementation relies on the node ids not
    # containing colons
    def encodeTreeState(self, expanded_nodes):
        tree_state = ":".join(expanded_nodes)
        tree_state = zlib.compress(tree_state)
        return b2a(tree_state)

    def decodeTreeState(self, tree_state):
        tree_state = a2b(tree_state)
        tree_state = zlib.decompress(tree_state)
        return tree_state.split(":")

#
# The following code has been taken unchanged from Zope2's
# ZTUtils.Tree module
#

from binascii import b2a_base64, a2b_base64
from string import translate, maketrans

a2u_map = maketrans('+/=', '-._')
u2a_map = maketrans('-._', '+/=')

def b2a(s):
    '''Encode a value as a cookie- and url-safe string.

    Encoded string use only alpahnumeric characters, and "._-".
    '''
    s = str(s)
    if len(s) <= 57:
        return translate(b2a_base64(s)[:-1], a2u_map)
    frags = []
    for i in range(0, len(s), 57):
        frags.append(b2a_base64(s[i:i + 57])[:-1])
    return translate(''.join(frags), a2u_map)

def a2b(s):
    '''Decode a b2a-encoded string.'''
    s = translate(s, u2a_map)
    if len(s) <= 76:
        return a2b_base64(s)
    frags = []
    for i in range(0, len(s), 76):
        frags.append(a2b_base64(s[i:i + 76]))
    return ''.join(frags)



=== Added File Zope3/src/zope/products/statictree/version.txt ===
statictree 1.0.1



More information about the Zope3-Checkins mailing list