[Zope-CVS] CVS: Products/CompositePage - __init__.py:1.1 composite.py:1.1 interfaces.py:1.1 rawfile.py:1.1 slot.py:1.1 tool.py:1.1 transformers.py:1.1

Shane Hathaway shane at zope.com
Fri Sep 26 17:21:06 EDT 2003


Update of /cvs-repository/Products/CompositePage
In directory cvs.zope.org:/tmp/cvs-serv25375

Added Files:
	__init__.py composite.py interfaces.py rawfile.py slot.py 
	tool.py transformers.py 
Log Message:
Added the CompositePage product.

CompositePage is like the PageDesign product, but simplified, and now based
on PDLib, a Javascript drag and drop / context menu library.



=== Added File Products/CompositePage/__init__.py ===
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""CompositePage product initialization.

$Id: __init__.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

import tool, composite, slot, transformers


def initialize(context):

    tool.registerTransformer("common", transformers.CommonTransformer())
    tool.registerTransformer("zmi", transformers.ZMITransformer())

    context.registerClass(
        tool.CompositeTool,
        constructors=(tool.manage_addCompositeTool,),
        icon="www/comptool.gif",
        )

    context.registerClass(
        composite.Composite,
        constructors=(composite.addCompositeForm,
                      composite.manage_addComposite,
                      ),
        icon="www/composite.gif",
        )

    context.registerClass(
        slot.Slot,
        constructors=(slot.addSlotForm,
                      slot.manage_addSlot,
                      slot.manage_generateSlots,
                      ),
        visibility=None,
        )



=== Added File Products/CompositePage/composite.py ===
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Composite class and supporting code.

$Id: composite.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

import os
import re

import Globals
import Acquisition
from Acquisition import aq_base, aq_inner, aq_parent, aq_get
from OFS.Folder import Folder
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
from AccessControl import ClassSecurityInfo
from AccessControl.ZopeGuards import guarded_getattr

from interfaces import ISlot, CompositeError
from slot import Slot

_www = os.path.join(os.path.dirname(__file__), "www")


class SlotGenerator (Acquisition.Explicit):
    """Automatically makes slots available to the template.
    """
    def __getitem__(self, name):
        composite = aq_parent(aq_inner(self))
        slots = composite.filled_slots
        try:
            return slots[name]
        except (KeyError, AttributeError):
            # Generate a new slot.
            s = Slot(name)
            if composite._v_generating:
                # Persist the slot immediately.
                slots._setObject(s.getId(), s)
            else:
                # Persist automatically if the slot changes
                jar = AddOnChangeJar(slots)
                s._p_jar = jar
            return s.__of__(slots)



class Composite(Folder):
    """An HTML fragment composed from a template and fragments.
    """
    meta_type = "Composite"

    security = ClassSecurityInfo()

    manage_options = (
        Folder.manage_options[:1]
        + ({"label": "Design", "action": "manage_designForm",},
           {"label": "View", "action": "view",},)
        + Folder.manage_options[2:]
        )

    template_path = "template"
    _v_editing = 0
    _v_rendering = 0
    _v_generating = 0

    security.declarePublic("slots")
    slots = SlotGenerator()

    _properties = Folder._properties + (
        {"id": "template_path", "mode": "w", "type": "string",
         "label": "Path to template"},
        )

    def __init__(self):
        f = SlotCollection()
        f._setId("filled_slots")
        self._setObject(f.getId(), f)

    def getTemplate(self):
        return self.restrictedTraverse(self.template_path)

    def generateSlots(self):
        """Creates the slots defined by the template.
        """
        self._v_generating = 1
        try:
            self()
        finally:
            self._v_generating = 0

    def __call__(self):
        """Renders the composite.
        """
        if self._v_rendering:
            raise CompositeError("Circular composite reference")
        self._v_rendering = 1
        try:
            template = self.getTemplate()
            return template()
        finally:
            self._v_rendering = 0

    view = __call__

    index_html = None

    def design(self, transformer="common"):
        """Renders the composite with editing features.
        """
        tool = aq_get(self, "composite_tool", None, 1)
        if tool is None:
            raise CompositeError("No composite_tool found")

        # Never cache a design view.
        req = getattr(self, "REQUEST", None)
        if req is not None:
            req["RESPONSE"].setHeader("Cache-Control", "no-cache")

        self._v_editing = 1
        try:
            text = self()
        finally:
            self._v_editing = 0
        tf = guarded_getattr(tool.transformers, transformer)
        return tf.transform(self, text)

    def manage_designForm(self):
        """Renders the composite with editing and ZMI features.
        """
        return self.design("zmi")

    def isEditing(self):
        return self._v_editing

Globals.InitializeClass(Composite)



class SlotCollection(Folder):
    """Collection of composite slots.
    """
    meta_type = "Slot Collection"

    def all_meta_types(self):
        return Folder.all_meta_types(self, interfaces=(ISlot,))



class AddOnChangeJar:
    """Adds an object to a folder if the object changes.
    """

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

    def register(self, obj):
        obj._p_jar = None
        self.parent._setObject(obj.getId(), obj)


addCompositeForm = PageTemplateFile("addCompositeForm", _www)

def manage_addComposite(dispatcher, id, title="", create_sample=0,
                        REQUEST=None):
    """Adds a composite to a folder.
    """
    ob = Composite()
    ob._setId(id)
    ob.title = string(title)
    dispatcher._setObject(ob.getId(), ob)
    if create_sample:
        ob = dispatcher.this()._getOb(ob.getId())
        f = open(os.path.join(_www, "sample_template.zpt"), "rt")
        try:
            text = f.read()
        finally:
            f.close()
        pt = ZopePageTemplate(id="template", text=text,
                              content_type="text/html")
        ob._setObject(pt.getId(), pt)
    if REQUEST is not None:
        return dispatcher.manage_main(dispatcher, REQUEST)


def string(s):
    """Ensures an object is either a string or a unicode.
    """
    try:
        return str(s)
    except UnicodeEncodeError:
        return unicode(s)



=== Added File Products/CompositePage/interfaces.py ===
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Interfaces and exceptions in the CompositePage product.

$Id: interfaces.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

from Interface import Interface

class ISlot(Interface):
    """A slot in a composite.
    """

class CompositeError(Exception):
    """An error in constructing a composite
    """



=== Added File Products/CompositePage/rawfile.py ===
##############################################################################
#
# Copyright (c) 2002 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.
# 
##############################################################################
"""Binary data that is stored in a file.

$Id: rawfile.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

import os
from os import stat
from time import time

import Acquisition
import Globals
from Globals import package_home
from App.Common import rfc1123_date
from DateTime import DateTime


class RawFile(Acquisition.Explicit):
    """Binary data stored in external files."""

    def __init__(self, path, content_type, _prefix=None):
        if _prefix is None:
            _prefix = SOFTWARE_HOME
        elif type(_prefix) is not type(''):
            _prefix = package_home(_prefix)
        path = os.path.join(_prefix, path)
        self.path = path
        if Globals.DevelopmentMode:
            # In development mode, a shorter time is handy
            max_age = 60 # One minute
        else:
            # A longer time reduces latency in production mode
            max_age = 3600 # One hour
        self.cch = 'public,max-age=%d' % max_age

        file = open(path, 'rb')
        data = file.read()
        file.close()
        self.content_type = content_type
        self.__name__ = path.split('/')[-1]
        self.lmt = float(stat(path)[8]) or time()
        self.lmh = rfc1123_date(self.lmt)


    def __call__(self, REQUEST=None, RESPONSE=None):
        """Default rendering"""
        # HTTP If-Modified-Since header handling. This is duplicated
        # from OFS.Image.Image - it really should be consolidated
        # somewhere...
        if RESPONSE is not None:
            RESPONSE.setHeader('Content-Type', self.content_type)
            RESPONSE.setHeader('Last-Modified', self.lmh)
            RESPONSE.setHeader('Cache-Control', self.cch)
            if REQUEST is not None:
                header = REQUEST.get_header('If-Modified-Since', None)
                if header is not None:
                    header = header.split(';')[0]
                    # Some proxies seem to send invalid date strings for this
                    # header. If the date string is not valid, we ignore it
                    # rather than raise an error to be generally consistent
                    # with common servers such as Apache (which can usually
                    # understand the screwy date string as a lucky side effect
                    # of the way they parse it).
                    try:
                        mod_since = long(DateTime(header).timeTime())
                    except:
                        mod_since = None
                    if mod_since is not None:
                        if getattr(self, 'lmt', None):
                            last_mod = long(self.lmt)
                        else:
                            last_mod = long(0)
                        if last_mod > 0 and last_mod <= mod_since:
                            RESPONSE.setStatus(304)
                            return ''

        f = open(self.path, 'rb')
        data = f.read()
        f.close()
        return data

    index_html = None  # Tells ZPublisher to use __call__

    HEAD__roles__ = None
    def HEAD(self, REQUEST, RESPONSE):
        """ """
        RESPONSE.setHeader('Content-Type', self.content_type)
        RESPONSE.setHeader('Last-Modified', self.lmh)
        return ''



=== Added File Products/CompositePage/slot.py ===
##############################################################################
#
# Copyright (c) 2002 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.
# 
##############################################################################
"""Slot class and supporting code.

$Id: slot.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

import os
import sys
from cgi import escape

from Acquisition import aq_base, aq_inner, aq_parent
from ZODB.POSException import ConflictError
from OFS.SimpleItem import SimpleItem
from OFS.OrderedFolder import OrderedFolder
from DocumentTemplate.DT_Util import safe_callable
from Products.PageTemplates.PageTemplateFile import PageTemplateFile

from interfaces import ISlot

_www = os.path.join(os.path.dirname(__file__), "www")


class NullElement(SimpleItem):
    """Temporary slot content
    """
    meta_type = "Temporary Null Page Element"

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


class Slot (OrderedFolder):
    """A slot in a composite.
    """
    meta_type = "Composite Slot"

    __implements__ = ISlot, OrderedFolder.__implements__

    null_element = NullElement("null_element")


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

    def single(self):
        """Renders as a single-element slot."""
        allow_add = (not self._objects)
        return "".join(self.renderToList(allow_add))

    def multiple(self):
        return self.renderToList(1)

    def reorder(self, name, new_index):
        if name not in self.objectIds():
            raise KeyError, name
        objs = [info for info in self._objects if info['id'] != name]
        objs.insert(new_index,
                    {'id': name, 'meta_type': getattr(self, name).meta_type})
        self._objects = tuple(objs)

    def nullify(self, name):
        res = self[name]
        objs = list(self._objects)
        # Replace the item with a pointer to the null element.
        for info in objs:
            if info["id"] == name:
                info["id"] = "null_element"
        delattr(self, name)
        return res

    def pack(self):
        objs = [info for info in self._objects if info["id"] != "null_element"]
        self._objects = tuple(objs)

    def renderToList(self, allow_add):
        """Renders the items to a list.
        """
        res = []
        composite = aq_parent(aq_inner(aq_parent(aq_inner(self))))
        editing = composite.isEditing()
        items = self.objectItems()
        if editing:
            mypath = escape('/'.join(self.getPhysicalPath()))
        for index in range(len(items)):
            name, obj = items[index]

            if editing and allow_add:
                tag = ('<div class="slot_target" target_path="%s" '
                       'target_index="%d"></div>' % (mypath, index))
                res.append(tag)

            try:
                if safe_callable(obj):
                    text = obj()
                else:
                    text = str(obj)
            except ConflictError:
                # Ugly ZODB requirement: don't catch ConflictErrors
                raise
            except:
                t, v = sys.exc_info()[:2]
                t = getattr(t, '__name__', t)
                text = "<code>%s</code>" % (
                    escape(('%s: %s' % (t, v))[:80]))

            if editing:
                path = escape('/'.join(obj.getPhysicalPath()))
                tag = '<div class="slot_element" source_path="%s">' % path
            else:
                # Output a <div> just to ensure that the element
                # is rendered as an HTML block in both editing mode
                # and rendering mode.
                tag = "<div>"
            res.append("%s\n%s\n</div>" % (tag, text))

        if editing and allow_add:
            index = len(items)
            tag = ('<div class="slot_target" target_path="%s" '
                   'target_index="%d"></div>' % (mypath, index))
            res.append(tag)

        return res




addSlotForm = PageTemplateFile("addSlotForm", _www)

def manage_addSlot(dispatcher, id, REQUEST=None):
    """Adds a slot to a composite.
    """
    ob = Slot(id)
    dispatcher._setObject(ob.getId(), ob)
    if REQUEST is not None:
        return dispatcher.manage_main(dispatcher, REQUEST)

def manage_generateSlots(dispatcher, REQUEST=None):
    """Adds all slots requested by a template to a composite.
    """
    dispatcher.this().generateSlots()
    if REQUEST is not None:
        return dispatcher.manage_main(dispatcher, REQUEST)
    


=== Added File Products/CompositePage/tool.py ===
##############################################################################
#
# Copyright (c) 2002 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.
# 
##############################################################################
"""Composite tool.

$Id: tool.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

import Globals
from Acquisition import aq_base, aq_parent, aq_inner
from OFS.SimpleItem import SimpleItem
from AccessControl import ClassSecurityInfo

from interfaces import ISlot, CompositeError


_transformers = {}

def registerTransformer(name, obj):
    """Registers a transformer for use with the composite tool.
    """
    if _transformers.has_key(name):
        raise RuntimeError("There is already a transformer named %s" % name)
    obj._setId(name)
    _transformers[name] = obj



class Transformers(SimpleItem):
    """Makes page transformers accessible through URL traversal.
    """

    def __init__(self, id):
        self._setId(id)

    def __getattr__(self, name):
        try:
            return _transformers[name]
        except KeyError:
            raise AttributeError, name



class CompositeTool(SimpleItem):
    """Page composition helper tool.
    """
    meta_type = "Composite Tool"
    id = "composite_tool"

    security = ClassSecurityInfo()

    security.declarePublic("transformers")
    transformers = Transformers("transformers")


    security.declarePublic("moveElements")
    def moveElements(self, source_paths, target_path, target_index):
        """Moves elements to a slot.
        """
        target_index = int(target_index)
        # Coerce the paths to sequences of path elements.
        if hasattr(target_path, "split"):
            target_path = target_path.split('/')
        sources = []
        for p in source_paths:
            if hasattr(p, "split"):
                p = p.split('/')
            if p:
                sources.append(p)

        # Ignore descendants when an ancestor is already listed.
        i = 1
        sources.sort()
        while i < len(sources):
            prev = sources[i - 1]
            if sources[i][:len(prev)] == prev:
                del sources[i]
            else:
                i = i + 1

        # Prevent parents from becoming their own descendants.
        for source in sources:
            if target_path[:len(source)] == source:
                raise CompositeError(
                    "Can't make an object a descendant of itself")

        # Gather the sources, replacing with nulls to avoid changing
        # indexes while moving.
        root = self.getPhysicalRoot()
        orig_slots = {}  # id(aq_base(slot)) -> slot
        elements = []
        try:
            for source in sources:
                slot = root.restrictedTraverse(source[:-1])
                assert ISlot.isImplementedBy(slot), repr(slot)
                slot_id = id(aq_base(slot))
                if not orig_slots.has_key(slot_id):
                    orig_slots[slot_id] = slot
                # validate(slot, "nullify")
                element = slot.nullify(source[-1])
                elements.append(element)

            # Add the elements and reorder.
            slot = root.restrictedTraverse(target_path)
            assert ISlot.isImplementedBy(slot), repr(slot)
            for element in elements:
                # verifyObjectPaste(element, container=slot)
                new_id = slot._get_id(element.getId())
                element._setId(new_id)
                slot._setObject(new_id, element)
                slot.reorder(new_id, target_index)
                target_index += 1
        finally:
            # Clear the nulls just added.
            for slot in orig_slots.values():
                slot.pack()


    security.declarePublic("deleteElements")
    def deleteElements(self, source_paths):
        sources = []
        for p in source_paths:
            if hasattr(p, "split"):
                p = p.split('/')
            if p:
                sources.append(p)

        # Replace with nulls to avoid changing indexes while deleting.
        orig_slots = {}
        try:
            for source in sources:
                slot = self.restrictedTraverse(source[:-1])
                assert ISlot.isImplementedBy(slot), repr(slot)
                slot_id = id(aq_base(slot))
                if not orig_slots.has_key(slot_id):
                    orig_slots[slot_id] = slot
                # validate(slot, "nullify")
                slot.nullify(source[-1])
        finally:
            # Clear the nulls just added.
            for slot in orig_slots.values():
                slot.pack()


    security.declarePublic("moveAndDelete")
    def moveAndDelete(self, move_source_paths="", move_target_path="",
                      move_target_index="", delete_source_paths="",
                      REQUEST=None):
        """Move and delete elements.
        """
        if move_source_paths:
            p = move_source_paths.split(':')
            self.moveElements(p, move_target_path, int(move_target_index))
        if delete_source_paths:
            p = delete_source_paths.split(':')
            self.deleteElements(p)
        if REQUEST is not None:
            # Return to the page the user was looking at.
            REQUEST["RESPONSE"].redirect(REQUEST["HTTP_REFERER"])

Globals.InitializeClass(CompositeTool)


def manage_addCompositeTool(dispatcher, REQUEST=None):
    """Adds a composite tool to a folder.
    """
    ob = CompositeTool()
    dispatcher._setObject(ob.getId(), ob)
    if REQUEST is not None:
        return dispatcher.manage_main(dispatcher, REQUEST)




=== Added File Products/CompositePage/transformers.py ===
##############################################################################
#
# Copyright (c) 2002 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.
# 
##############################################################################
"""Editable page transformation classes.

$Id: transformers.py,v 1.1 2003/09/26 21:21:05 shane Exp $
"""

import os
import re

import Globals
from Acquisition import aq_base, aq_inner, aq_parent
from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from AccessControl import ClassSecurityInfo

from rawfile import RawFile

_common = os.path.join(os.path.dirname(__file__), "common")
_zmi = os.path.join(os.path.dirname(__file__), "zmi")

start_of_head_search = re.compile("(<head[^>]*>)", re.IGNORECASE).search
start_of_body_search = re.compile("(<body[^>]*>)", re.IGNORECASE).search
end_of_body_search = re.compile("(</body[^>]*>)", re.IGNORECASE).search

DEFAULT_HTML_PAGE = """
<html>
<head>
<title>Composite Page</title>
</head>
<body>
%s
</body>
</html>
"""


class CommonTransformer (SimpleItem):
    """Basic page transformer.

    Adds editing features to a rendered composite.
    """

    security = ClassSecurityInfo()

    security.declarePublic(
        "pdlib_js", "design_js", "pdstyles_css", "designstyles_css",
        "transform")
    pdlib_js = RawFile("pdlib.js", "text/javascript", _common)
    edit_js = RawFile("edit.js", "text/javascript", _common)
    pdstyles_css = RawFile("pdstyles.css", "text/css", _common)
    editstyles_css = RawFile("editstyles.css", "text/css", _common)

    header_templates = (PageTemplateFile("header.pt", _common),)
    top_templates = ()
    bottom_templates = (PageTemplateFile("bottom.pt", _common),)


    def transform(self, composite, text):
        """Adds scripts to a rendered composite.
        """
        params = {
            "tool": aq_parent(aq_inner(self)),
            "transformer": self,
            "composite": composite,
            }
        header = ""
        top = ""
        bottom = ""
        for t in self.header_templates:
            header += t.__of__(self)(**params)
        for t in self.top_templates:
            top += t.__of__(self)(**params)
        for t in self.bottom_templates:
            bottom += t.__of__(self)(**params)
            
        match = start_of_head_search(text)
        if match is None:
            # Turn it into a page.
            text = DEFAULT_HTML_PAGE % text
            match = start_of_head_search(text)
            if match is None:
                raise CompositeError("Could not find header")
        if header:
            index = match.end(0)
            text = "%s%s%s" % (text[:index], header, text[index:])
        if top:
            match = start_of_body_search(text)
            if match is None:
                raise CompositeError("No 'body' tag found")
            index = match.end(0)
            text = "%s%s%s" % (text[:index], top, text[index:])
        if bottom:
            match = end_of_body_search(text)
            if match is None:
                raise CompositeError("No 'body' end tag found")
            m = match
            while m is not None:
                # Find the *last* occurrence of "</body>".
                match = m
                m = end_of_body_search(text, match.end(0))
            index = match.start(0)
            text = "%s%s%s" % (text[:index], bottom, text[index:])

        return text

Globals.InitializeClass(CommonTransformer)



class ZMITransformer (CommonTransformer):
    """Zope management interface page transformer.

    Adds editing features to a rendered composite.
    """
    security = ClassSecurityInfo()

    security.declarePublic("zmi_edit_js")
    zmi_edit_js = RawFile("zmi_edit.js", "text/javascript", _zmi)

    header_templates = CommonTransformer.header_templates + (
        PageTemplateFile("header.pt", _zmi),)
    top_templates = CommonTransformer.top_templates + (
        PageTemplateFile("top.pt", _zmi),)
    bottom_templates = (PageTemplateFile("bottom.pt", _zmi),
                        ) + CommonTransformer.bottom_templates

    def showElement(self, path, RESPONSE):
        """Redirects to the workspace for an element.
        """
        root = self.getPhysicalRoot()
        obj = root.restrictedTraverse(path)
        RESPONSE.redirect(obj.absolute_url() + "/manage_workspace")

    def showSlot(self, path, RESPONSE):
        """Redirects to (and possibly creates) the workspace for a slot.
        """
        from composite import Composite

        obj = self.getPhysicalRoot()
        parts = path.split('/')
        for name in parts:
            obj = obj.restrictedTraverse(name)
            try:
                is_comp = isinstance(obj, Composite)
            except TypeError:
                is_comp = 0  # Python 2.1 bug
            if is_comp:
                obj.generateSlots()
        RESPONSE.redirect(obj.absolute_url() + "/manage_workspace")

Globals.InitializeClass(ZMITransformer)





More information about the Zope-CVS mailing list