[Zope-CVS] CVS: Products/PageDesign - ElementReference.py:1.1 IElementReference.py:1.1 SimpleItemAsElement.py:1.1 IClipboardSource.py:1.2 IClipboardTarget.py:1.2 IIndexedSlot.py:1.3 ISlot.py:1.2 PageDesign.py:1.7 Slot.py:1.4 SlotElementClipboardSource.py:1.3 SlotElementClipboardTarget.py:1.3 ReferencedElement.py:NONE

Shane Hathaway shane@cvs.zope.org
Thu, 15 Aug 2002 20:29:35 -0400


Update of /cvs-repository/Products/PageDesign
In directory cvs.zope.org:/tmp/cvs-serv15742

Modified Files:
	IClipboardSource.py IClipboardTarget.py IIndexedSlot.py 
	ISlot.py PageDesign.py Slot.py SlotElementClipboardSource.py 
	SlotElementClipboardTarget.py 
Added Files:
	ElementReference.py IElementReference.py 
	SimpleItemAsElement.py 
Removed Files:
	ReferencedElement.py 
Log Message:
Converted Slots to store references rather than page elements that contain
references, since the new strategy provides a better way to:

- Show broken references without littering the code with bare "except:"s

- Implement relative references with a "re-seat" capability, for when you move
  elements between page designs.


=== Added File Products/PageDesign/ElementReference.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.
# 
##############################################################################

import Acquisition
from Persistence import Persistent
from OFS.Traversable import Traversable

from IElementReference import IElementReference
from IPageElement import IPageElement
from SimpleItemAsElement import SimpleItemAsElement


class ElementReference (Persistent, Acquisition.Explicit, Traversable):

    __implements__ = IElementReference

    def __init__(self, ob):
        self._path = ob.getPhysicalPath()

    def dereference(self):
        root = self.getPhysicalRoot()
        try:
            ob = root.restrictedTraverse(self._path)
        except:
            return BrokenPageElement(self._path)
        # The code below is essentially adapter lookup
        if IPageElement.isImplementedBy(ob):
            return ob
        else:
            return SimpleItemAsElement(ob)

    def beforeDelete(self):
        # Turn self._path into an absolute path
        pass

    def afterAdd(self):
        # Turn self._path into a relative path
        pass



=== Added File Products/PageDesign/IElementReference.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.
# 
##############################################################################

from Interface import Interface

class IElementReference (Interface):

    def dereference():
        """Returns an IPageElement."""

    def afterAdd():
        """Updates the reference after adding or moving."""

    def beforeDelete():
        """Updates the reference before deleting or moving."""



=== Added File Products/PageDesign/SimpleItemAsElement.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.
# 
##############################################################################

from Acquisition import aq_base, aq_get, aq_inner, aq_parent
from OFS.Application import Application

from IPageElement import IPageElement


class SimpleItemAsElement:

    __implements__ = IPageElement

    def __init__(self, ob):
        self._ob = ob

    def render(self, design, editable, index):
        """Returns a string.
        """
        ob = self._ob
        if getattr(aq_base(ob), 'isDocTemp', 0):
            # DTML
            s = ob(self, aq_get(self, 'REQUEST'),
                   design=design, editable=editable, index=index)
        elif hasattr(ob, '__call__'):
            # Other kinds of callable objects
            s = ob(design=design, editable=editable, index=index)
        else:
            s = str(ob)
        return s

    def getIconURL(self):
        """Returns an URL to an icon for this element if available.
        """
        ob = self._ob
        icon = None
        if hasattr(aq_base(ob), 'getIcon'):
            icon = ob.getIcon()
        elif hasattr(aq_base(ob), 'icon'):
            icon = ob.icon
        if icon:
            return '/' + icon
        else:
            return ''

    def getTitle(self):
        """Returns the title of this element.
        """
        ob = self._ob
        # It's altogether silly to have to look at four attributes. :-)
        if hasattr(aq_base(ob), 'Title'):
            s = ob.Title()
        else:
            s = getattr(ob, 'title', '')
        if s:
            return str(s)
        if hasattr(aq_base(ob), 'getId'):
            return ob.getId()
        return str(ob.id)

    def getEditURL(self):
        """Returns the URL for editing this element.

        Returns None or a blank string if it is not editable.
        """
        if hasattr(Application, 'externalEdit_'):
            ob = self._ob
            folder = aq_parent(aq_inner(ob))
            if hasattr(aq_base(ob), 'getId'):
                id = ob.getId()
            else:
                id = str(ob.id)
            
            url = '%s/externalEdit_/%s' % (folder.absolute_url(), id)
            return url
        return None



=== Products/PageDesign/IClipboardSource.py 1.1 => 1.2 ===
--- Products/PageDesign/IClipboardSource.py:1.1	Sat Aug  3 15:46:10 2002
+++ Products/PageDesign/IClipboardSource.py	Thu Aug 15 20:29:35 2002
@@ -18,7 +18,8 @@
 class IClipboardSource(Interface):
 
     def cut():
-        """Removes this item from its container and returns the element."""
+        """Removes object(s) from containers, returning the list of objects.
+        """
 
     #def copy():
     #    """Belongs here too but I don't need it yet. ;-)"""


=== Products/PageDesign/IClipboardTarget.py 1.1 => 1.2 ===
--- Products/PageDesign/IClipboardTarget.py:1.1	Sat Aug  3 15:46:10 2002
+++ Products/PageDesign/IClipboardTarget.py	Thu Aug 15 20:29:35 2002
@@ -17,6 +17,6 @@
 
 class IClipboardTarget(Interface):
 
-    def paste(elements):
-        """Adds the given elements to the container."""
+    def paste(objects):
+        """Adds the given objects to the container."""
 


=== Products/PageDesign/IIndexedSlot.py 1.2 => 1.3 ===
--- Products/PageDesign/IIndexedSlot.py:1.2	Mon Aug 12 10:39:29 2002
+++ Products/PageDesign/IIndexedSlot.py	Thu Aug 15 20:29:35 2002
@@ -21,8 +21,8 @@
         """Returns the number of elements currently in the slot.
         """
 
-    def getElement(index):
-        """Returns the IPageElement at the specified index.
+    def getElementReference(index):
+        """Returns the IElementReference at the specified index.
         """
 
     def track(index):
@@ -37,16 +37,16 @@
         """Disconnects an insertion point tracker, returning its index.
         """
 
-    def insertElements(index, elements):
-        """Inserts IPageElements at the specified index.
+    def insert(index, refs):
+        """Inserts IElementReferences at the specified index.
 
         If index is an insertion point tracker (as returned by track()),
         the tracker will be disconnected and its current value will be
         used as the index.
 
-        If index is None, the elements will be appended to the end.
+        If index is None, the references will be appended to the end.
         """
 
-    def remove(element):
-        """Removes an element and returns the element removed.
+    def remove(refs):
+        """Removes element references.
         """


=== Products/PageDesign/ISlot.py 1.1 => 1.2 ===
--- Products/PageDesign/ISlot.py:1.1	Sat Aug  3 15:46:10 2002
+++ Products/PageDesign/ISlot.py	Thu Aug 15 20:29:35 2002
@@ -19,8 +19,8 @@
 class ISlot (Interface):
 
     def single():
-        """Renders as a single-element slot."""
+        """Renders as a single-element slot, returning a string."""
 
     def multiple():
-        """Renders as a multiple-element slot."""
+        """Renders as a multiple-element slot, returning a list of strings."""
 


=== Products/PageDesign/PageDesign.py 1.6 => 1.7 ===
--- Products/PageDesign/PageDesign.py:1.6	Wed Aug 14 23:15:46 2002
+++ Products/PageDesign/PageDesign.py	Thu Aug 15 20:29:35 2002
@@ -37,7 +37,7 @@
 
 from RawFile import RawFile
 from SlotProvider import SlotProvider
-from ReferencedElement import ReferencedElement
+from ElementReference import ElementReference
 from ISlotProvider import ISlotProvider
 from IClipboardSource import IClipboardSource
 from IClipboardTarget import IClipboardTarget
@@ -156,19 +156,21 @@
     def moveElement(self, source_path, target_path, RESPONSE=None):
         """Moves an element from one slot to another.
         """
+        # In the future: adapt source to IClipboardSource
         source = self.getPhysicalRoot().restrictedTraverse(source_path)
         if not IClipboardSource.isImplementedBy(source):
             raise DesignError, 'moveElement: bad or missing source_path'
         getSecurityManager().validate(source, source, 'cut', source.cut)
 
+        # In the future: adapt target to IClipboardTarget
         target = self.getPhysicalRoot().restrictedTraverse(target_path)
         if not IClipboardTarget.isImplementedBy(target):
             raise DesignError, 'moveElement: bad or missing target_path'
         getSecurityManager().validate(target, target,
                                       'paste', target.paste)
 
-        element = source.cut()
-        target.paste([element])
+        objects = source.cut()
+        target.paste(objects)
 
         if RESPONSE is not None:
             RESPONSE.redirect(self.absolute_url() + '/editForm')
@@ -177,24 +179,27 @@
     security.declareProtected(change_page_designs, 'addElements')
     def addElements(self, source_paths=(), target_path='', REQUEST=None):
         """Add elements to the page design."""
-        elements = []
+        refs = []
         for source_path in source_paths:
             source = self.getPhysicalRoot().restrictedTraverse(source_path)
+            # In the future: adapt source to IClipboardSource
+            # Also, use makeReference() (or "link()"?) instead of cut().
             if IClipboardSource.isImplementedBy(source):
                 getSecurityManager().validate(
                     source, source, 'cut', source.cut)
-                element = source.cut()
+                r = source.cut()
+                refs.extend(r)
             else:
-                element = ReferencedElement(source)
-            elements.append(element)
+                refs.append(ElementReference(source))
 
         target = self.getPhysicalRoot().restrictedTraverse(target_path)
+        # In the future: adapt target to IClipboardTarget
         if not IClipboardTarget.isImplementedBy(target):
             raise DesignError, 'moveElement: bad or missing target_path'
         getSecurityManager().validate(target, target,
                                       'paste', target.paste)
 
-        target.paste(elements)
+        target.paste(refs)
         if REQUEST is not None:
             return self.dialog_done_html
 
@@ -204,6 +209,7 @@
         """Removes an element.
         """
         source = self.getPhysicalRoot().restrictedTraverse(source_path)
+        # In the future: adapt source to IClipboardSource
         if not IClipboardSource.isImplementedBy(source):
             raise DesignError, 'removeElement: bad or missing source_path'
         getSecurityManager().validate(source, source, 'cut', source.cut)


=== Products/PageDesign/Slot.py 1.3 => 1.4 ===
--- Products/PageDesign/Slot.py:1.3	Mon Aug 12 22:42:39 2002
+++ Products/PageDesign/Slot.py	Thu Aug 15 20:29:35 2002
@@ -22,13 +22,14 @@
 
 from Persistence import Persistent
 import Acquisition
-from Acquisition import aq_inner, aq_parent
+from Acquisition import aq_base, aq_inner, aq_parent
 import Globals
 from AccessControl import ClassSecurityInfo
 from OFS.Traversable import Traversable
 
 from IIndexedSlot import IIndexedSlot
 from IPageElement import IPageElement
+from IElementReference import IElementReference
 from SlotElementClipboardSource import SlotElementClipboardSource
 from SlotElementClipboardTarget \
      import SlotElementClipboardTarget, SlotElementClipboardLastTarget
@@ -84,11 +85,12 @@
     </tr></table>
     <div class="design-element-body">%(text)s</div></div>'''
 
-    _trackers = ()
+    _refs = ()      # List of IElementReference
+    _trackers = ()  # List of InsertionPointTracker
+
 
     def __init__(self, id):
         self.id = id
-        self.contents = ()
 
 
     def getId(self):
@@ -125,7 +127,7 @@
     security.declarePublic('single')
     def single(self):
         """Renders as a single-element slot."""
-        allow_add = (not self.contents)
+        allow_add = (not self._refs)
         res = self.renderToList(allow_add)
         if not res:
             layered_provider = aq_parent(aq_inner(self))._getLayeredProvider()
@@ -160,9 +162,11 @@
         res = []
         phys_path = '/'.join(self.getPhysicalPath())
 
-        clen = len(self.contents)
+        clen = len(self._refs)
         for index in range(clen):
-            e = self.contents[index].__of__(self)
+            e = self._refs[index].__of__(self).dereference()
+            assert IPageElement.isImplementedBy(e), repr(e)
+
             if editable and allow_add:
                 path = '%s/elementTargets/%d' % (phys_path, index)
                 res.append(self.renderTargetHTML(
@@ -181,23 +185,21 @@
                     escape(('%s: %s' % (t, v))[:80]))
 
             if editable:
-                design_url = design.absolute_url()
                 path = '%s/elementSources/%d' % (phys_path, index)
                 move_up_url = move_down_url = ''
                 if index > 0:
                     # Can move up.
                     move_up_url = (
-                        '%s/moveElement?source_path=%s'
+                        'moveElement?source_path=%s'
                         '&target_path=%s/elementTargets/%d' % (
-                        design_url, path, phys_path, index - 1))
+                        path, phys_path, index - 1))
                 if index < (clen - 1):
                     # Can move down.
                     move_down_url = (
-                        '%s/moveElement?source_path=%s'
+                        'moveElement?source_path=%s'
                         '&target_path=%s/elementTargets/%d' % (
-                        design_url, path, phys_path, index + 2))
-                remove_url = '%s/removeElement?source_path=%s' % (
-                    design_url, path)
+                        path, phys_path, index + 2))
+                remove_url = 'removeElement?source_path=%s' % path
 
                 text = self.renderSourceHTML(
                     element=e,
@@ -222,10 +224,10 @@
 
 
     def getElementCount(self):
-        return len(self.contents)
+        return len(self._refs)
 
-    def getElement(self, index):
-        return self.contents[index]
+    def getElementReference(self, index):
+        return self._refs[index].__of__(self)
 
     def beforeChange(self):
         aq_parent(aq_inner(self))._persistSlot(self)
@@ -243,34 +245,39 @@
         self._trackers = tuple(ipts)
         return ipt.index
 
-    def insertElements(self, index, elements):
-        # Verify none of the elements are already inserted.
-        # An element can only be in one place at a time.
+    def insert(self, index, refs):
+        # Verify none of the references are already inserted.
+        # A reference can only be in one place at a time.
+        self.beforeChange()
         if isinstance(index, InsertionPointTracker):
             index = self.untrack(index)
-        for e in elements:
-            if not IPageElement.isImplementedBy(e):
-                raise ValueError, 'Not a page element'
-            if e in self.contents:
+        for ref in refs:
+            if not IElementReference.isImplementedBy(ref):
+                raise ValueError, 'Not an element reference'
+            if ref in self._refs:
                 raise ValueError, 'Already inserted'
-        self.beforeChange()
         if index is None:
             # append.
-            self.contents = self.contents + tuple(elements)
+            self._refs = self._refs + tuple(refs)
         else:
-            self.contents = (self.contents[:index] + tuple(elements)
-                             + self.contents[index:])
+            self._refs = (self._refs[:index] + tuple(refs)
+                             + self._refs[index:])
         for ipt in self._trackers:
-            ipt.afterInsert(index, len(elements))
+            ipt.afterInsert(index, len(refs))
+        for ref in refs:
+            ref.__of__(self).afterAdd()
 
-    def remove(self, element):
+    def remove(self, refs):
         self.beforeChange()
-        lst = list(self.contents)
-        index = lst.index(element)
-        del lst[index]
-        self.contents = tuple(lst)
-        for ipt in self._trackers:
-            ipt.afterRemove(index, 1)
+        lst = list(self._refs)
+        for ref in refs:
+            ref = aq_base(ref)
+            ref.__of__(self).beforeDelete()
+            index = lst.index(ref)
+            del lst[index]
+            for ipt in self._trackers:
+                ipt.afterRemove(index, 1)
+        self._refs = tuple(lst)
 
 Globals.InitializeClass(Slot)
 


=== Products/PageDesign/SlotElementClipboardSource.py 1.2 => 1.3 ===
--- Products/PageDesign/SlotElementClipboardSource.py:1.2	Mon Aug 12 10:39:29 2002
+++ Products/PageDesign/SlotElementClipboardSource.py	Thu Aug 15 20:29:35 2002
@@ -19,7 +19,7 @@
 from AccessControl import ClassSecurityInfo
 from IClipboardSource import IClipboardSource
 from IIndexedSlot import IIndexedSlot
-from IPageElement import IPageElement
+from IElementReference import IElementReference
 
 # Permission name
 change_page_designs = 'Change Page Designs'
@@ -35,16 +35,16 @@
         index = int(index)
         slot = aq_parent(aq_inner(self))
         assert IIndexedSlot.isImplementedBy(slot), `slot`
-        element = aq_base(slot.getElement(index))
-        assert IPageElement.isImplementedBy(element), `element`
-        self.element = aq_base(element)
+        ref = aq_base(slot.getElementReference(index))
+        assert IElementReference.isImplementedBy(ref), `ref`
+        self.refs = (aq_base(ref),)
         return self
 
     security.declareProtected(change_page_designs, 'cut')
     def cut(self):
         slot = aq_parent(aq_inner(self))
-        element = aq_base(self.element)
-        slot.remove(element)
-        return element
+        slot.remove(self.refs)
+        return self.refs
 
 Globals.InitializeClass(SlotElementClipboardSource)
+


=== Products/PageDesign/SlotElementClipboardTarget.py 1.2 => 1.3 ===
--- Products/PageDesign/SlotElementClipboardTarget.py:1.2	Mon Aug 12 10:39:29 2002
+++ Products/PageDesign/SlotElementClipboardTarget.py	Thu Aug 15 20:29:35 2002
@@ -39,9 +39,9 @@
         return self
 
     security.declareProtected(change_page_designs, 'paste')
-    def paste(self, elements):
+    def paste(self, refs):
         slot = aq_parent(aq_inner(self))
-        slot.insertElements(self.ipt, elements)
+        slot.insert(self.ipt, refs)
 
 Globals.InitializeClass(SlotElementClipboardTarget)
 
@@ -55,10 +55,10 @@
     security = ClassSecurityInfo()
 
     security.declareProtected(change_page_designs, 'paste')
-    def paste(self, elements):
+    def paste(self, refs):
         slot = aq_parent(aq_inner(self))
         assert IIndexedSlot.isImplementedBy(slot), `slot`
-        slot.insertElements(None, elements)
+        slot.insert(None, refs)
 
 Globals.InitializeClass(SlotElementClipboardLastTarget)
 

=== Removed File Products/PageDesign/ReferencedElement.py ===