diff --git a/configure.zcml b/configure.zcml
index ef93674..8910dcc 100644
--- a/configure.zcml
+++ b/configure.zcml
@@ -272,9 +272,9 @@
provides="loops.interfaces.IExternalFile" />
+ interface="loops.interfaces.IExternalFile" />
+ set_schema="loops.interfaces.IExternalFile" />
+
diff --git a/external/README.txt b/external/README.txt
index 4be1f63..c2fedda 100644
--- a/external/README.txt
+++ b/external/README.txt
@@ -74,6 +74,19 @@ Working with nodes
>>> elements = reader.read(input)
>>> loader.load(elements)
+Sub-elements
+------------
+
+ >>> from loops.external import annotation
+
+ >>> input = """concept('myquery', u'My Query', 'query', viewName='mystuff.html')[
+ ... annotations(creators='john')]"""
+ >>> elements = reader.read(input)
+ >>> elements[0].subElements
+ [{'creators': 'john'}]
+
+ >>> loader.load(elements)
+ [('creators', 'john')]
Exporting loops Objects
=======================
@@ -107,6 +120,20 @@ Writing object information to the external storage
node('home', u'Home', '', u'menu', body=u'Welcome')
node('myquery', u'My Query', 'home', u'page', target=u'concepts/myquery')...
+Writing subElements
+-------------------
+
+ >>> input = """concept('myquery', u'My Query', 'query', viewName='mystuff.html')[
+ ... annotations(creators='john'),
+ ... annotations(modified='2007-08-12')]"""
+ >>> elements = reader.read(input)
+ >>> output = StringIO()
+ >>> writer.write(elements, output)
+ >>> print output.getvalue()
+ concept('myquery', u'My Query', 'query', viewName='mystuff.html')[
+ annotations(creators='john'),
+ annotations(modified='2007-08-12')]...
+
The Export/Import View
======================
diff --git a/external/annotation.py b/external/annotation.py
new file mode 100644
index 0000000..49ffc24
--- /dev/null
+++ b/external/annotation.py
@@ -0,0 +1,57 @@
+#
+# Copyright (c) 2008 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
+#
+
+"""
+Export/import of annotations.
+
+$Id$
+"""
+
+from zope.component import adapts
+from zope.interface import implements
+
+from loops.external.element import Element, elementTypes
+from loops.external.interfaces import ISubExtractor
+from loops.interfaces import ILoopsObject
+
+
+class AnnotationsElement(Element):
+
+ elementType = 'annotations'
+
+ def __init__(self, **kw):
+ for k, v in kw.items():
+ self[k] = v
+
+ def __call__(self, loader):
+ print self.items()
+
+
+class AnnotationsExtractor(object):
+
+ implements(ISubExtractor)
+ adapts(ILoopsObject)
+
+ def extract(self):
+ return []
+
+
+elementTypes.update(dict(
+ annotations=AnnotationsElement,
+))
+
diff --git a/external/base.py b/external/base.py
index f271c85..3be6522 100644
--- a/external/base.py
+++ b/external/base.py
@@ -36,7 +36,7 @@ from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.interfaces import ISchemaFactory
from cybertools.typology.interfaces import IType
from loops.common import adapted
-from loops.external.interfaces import ILoader, IExtractor
+from loops.external.interfaces import ILoader, IExtractor, ISubExtractor
from loops.external.element import elementTypes
from loops.interfaces import IConceptSchema, IResourceSchema
from loops.resource import Document, MediaAsset
@@ -81,6 +81,8 @@ class Loader(Base, SetupManager):
def load(self, elements):
for element in elements:
element(self)
+ if element.subElements is not None:
+ self.load(element.subElements)
# TODO: care for setting attributes via Instance (Editor)
# instead of using SetupManager methods:
@@ -103,7 +105,9 @@ class Extractor(Base):
typeElement = elementTypes['type']
for obj in self.typeConcept.getChildren([self.typePredicate]):
data = self.getObjectData(obj)
- yield typeElement(getName(obj), obj.title, **data)
+ element = typeElement(getName(obj), obj.title, **data)
+ self.provideSubElements(obj, element)
+ yield element
def extractConcepts(self):
conceptElement = elementTypes['concept']
@@ -112,7 +116,9 @@ class Extractor(Base):
if obj.conceptType != typeConcept:
data = self.getObjectData(obj)
tp = getName(obj.conceptType)
- yield conceptElement(name, obj.title, tp, **data)
+ element = conceptElement(name, obj.title, tp, **data)
+ self.provideSubElements(obj, element)
+ yield element
def extractResources(self):
elementClass = elementTypes['resource']
@@ -123,26 +129,9 @@ class Extractor(Base):
tp = 'textdocument'
element = elementClass(name, obj.title, tp, **data)
element.processExport(self)
+ self.provideSubElements(obj, element)
yield element
- def getObjectData(self, obj, defaultInterface=IConceptSchema):
- aObj = adapted(obj)
- schemaFactory = component.getAdapter(aObj, ISchemaFactory)
- ti = IType(obj).typeInterface or defaultInterface
- schema = schemaFactory(ti, manager=self) #, request=self.request)
- instance = IInstance(aObj)
- instance.template = schema
- # TODO: use ``_not_exportable`` attribute of adapter to control export;
- # this should also convert object attributes like e.g. typeInterface
- #data = instance.applyTemplate(mode='export')
- data = instance.applyTemplate(mode='edit')
- if 'title' in data:
- del data['title']
- data['description'] = obj.description
- if not data['description']:
- del data['description']
- return data
-
def extractChildren(self):
childElement = elementTypes['child']
typePredicate = self.typePredicate
@@ -172,7 +161,7 @@ class Extractor(Base):
def extractNodes(self, parent=None, path=''):
if parent is None:
parent = self.views
- element = elementTypes['node']
+ elementClass = elementTypes['node']
for name, obj in parent.items():
data = {}
for attr in ('description', 'body', 'viewName'):
@@ -182,8 +171,36 @@ class Extractor(Base):
target = obj.target
if target is not None:
data['target'] = '/'.join((getName(getParent(target)), getName(target)))
- yield element(name, obj.title, path, obj.nodeType, **data)
+ elem = elementClass(name, obj.title, path, obj.nodeType, **data)
+ self.provideSubElements(obj, elem)
+ yield elem
childPath = path and '/'.join((path, name)) or name
for elem in self.extractNodes(obj, childPath):
+ self.provideSubElements(obj, elem)
yield elem
+ # helper methods
+
+ def getObjectData(self, obj, defaultInterface=IConceptSchema):
+ aObj = adapted(obj)
+ schemaFactory = component.getAdapter(aObj, ISchemaFactory)
+ ti = IType(obj).typeInterface or defaultInterface
+ schema = schemaFactory(ti, manager=self) #, request=self.request)
+ instance = IInstance(aObj)
+ instance.template = schema
+ # TODO: use ``_not_exportable`` attribute of adapter to control export;
+ # this should also convert object attributes like e.g. typeInterface
+ #data = instance.applyTemplate(mode='export')
+ data = instance.applyTemplate(mode='edit')
+ if 'title' in data:
+ del data['title']
+ data['description'] = obj.description
+ if not data['description']:
+ del data['description']
+ return data
+
+ def provideSubElements(self, obj, element):
+ for name, extractor in component.getAdapters((obj,), ISubExtractor):
+ for sub in extractor.extract():
+ element.add(sub)
+
diff --git a/external/browser.py b/external/browser.py
index 9e242f0..5c0c4b3 100644
--- a/external/browser.py
+++ b/external/browser.py
@@ -89,7 +89,7 @@ class ExportImport(object):
return False
reader = component.getUtility(IReader)
elements = reader.read(data)
- loader = Loader(self.context, )
+ loader = Loader(self.context)
loader.load(elements)
self.message = u'Content uploaded and imported.'
return False
diff --git a/external/element.py b/external/element.py
index 1ae93d8..fed833f 100644
--- a/external/element.py
+++ b/external/element.py
@@ -37,6 +37,10 @@ class Element(dict):
implements(IElement)
elementType = ''
+ posArgs = ()
+ object = None
+ parent = None
+ subElements = None
def __init__(self, name, title, type=None, *args, **kw):
self['name'] = name
@@ -46,9 +50,24 @@ class Element(dict):
for k, v in kw.items():
self[k] = v
+ def __getitem__(self, key):
+ if isinstance(key, Element):
+ key = (key,)
+ if isinstance(key, tuple):
+ for item in key:
+ item.parent = self
+ self.add(item)
+ return key
+ return super(Element, self).__getitem__(key)
+
def processExport(self, extractor):
pass
+ def add(self, element):
+ if self.subElements is None:
+ self.subElements = []
+ self.subElements.append(element)
+
def __call__(self, loader):
pass
@@ -62,7 +81,7 @@ class ConceptElement(Element):
type = loader.concepts[self['type']]
kw = dict((k, v) for k, v in self.items()
if k not in self.posArgs)
- loader.addConcept(self['name'], self['title'], type, **kw)
+ self.object = loader.addConcept(self['name'], self['title'], type, **kw)
class TypeElement(ConceptElement):
@@ -83,7 +102,8 @@ class TypeElement(ConceptElement):
ti = self.get('typeInterface')
if ti:
kw['typeInterface'] = resolve(ti)
- loader.addConcept(self['name'], self['title'], loader.typeConcept, **kw)
+ self.object = loader.addConcept(self['name'], self['title'],
+ loader.typeConcept, **kw)
class ChildElement(Element):
@@ -128,7 +148,7 @@ class ResourceElement(Element):
content = content.decode('UTF-8')
kw['data'] = content
f.close()
- loader.addResource(self['name'], self['title'], type, **kw)
+ self.object = loader.addResource(self['name'], self['title'], type, **kw)
class ResourceRelationElement(ChildElement):
@@ -160,6 +180,7 @@ class NodeElement(Element):
if target is not None:
targetObject = traverse(loader.context, target, None)
node.target = targetObject
+ self.object = node
# element registry
@@ -172,3 +193,5 @@ elementTypes = dict(
resourceRelation=ResourceRelationElement,
node=NodeElement,
)
+
+toplevelElements = ('type', 'concept', 'resource', 'resourceRelation', 'node')
diff --git a/external/interfaces.py b/external/interfaces.py
index 5b3996a..cb1828d 100644
--- a/external/interfaces.py
+++ b/external/interfaces.py
@@ -36,6 +36,21 @@ class IElement(Interface):
or IElement objects.
"""
+ elementType = Attribute('A string denoting the element type.')
+ object = Attribute('The object that has been created from this '
+ 'element during import.')
+ parent = Attribute('An optional parent element that this element is part of.')
+ subElements = Attribute('An optional list of sub-elements; initially None.')
+
+ def processExport(extractor):
+ """ Will be called by the extractor during export to allow for
+ special handling e.g. of certain attributes.
+ """
+
+ def add(element):
+ """ Add a sub-element, may be called by the extractor during export.
+ """
+
def __call__(loader):
""" Create the object that is specified by the element in the
context of the loader and return it.
@@ -75,7 +90,8 @@ class IWriter(Interface):
class IExtractor(Interface):
""" Extracts information from loops objects and provides them as
- IElement objects. Will typically be used as an adapter.
+ IElement objects. Will typically be used as an adapter on the
+ loops root object.
"""
def extract():
@@ -83,3 +99,7 @@ class IExtractor(Interface):
the content of the context object.
"""
+class ISubExtractor(IExtractor):
+ """ Used for extracting special informations from individual objects
+ that will be represented by sub-elements.
+ """
diff --git a/external/pyfunc.py b/external/pyfunc.py
index 000e4a4..8a74091 100644
--- a/external/pyfunc.py
+++ b/external/pyfunc.py
@@ -27,7 +27,7 @@ from zope.cachedescriptors.property import Lazy
from zope.interface import implements
from loops.external.interfaces import IReader, IWriter
-from loops.external.element import elementTypes
+from loops.external.element import elementTypes, toplevelElements
class PyReader(object):
@@ -50,7 +50,8 @@ class InputProcessor(dict):
def __getitem__(self, key):
def factory(*args, **kw):
element = elementTypes[key](*args, **kw)
- self.elements.append(element)
+ if key in toplevelElements:
+ self.elements.append(element)
return element
return factory
@@ -59,8 +60,8 @@ class PyWriter(object):
implements(IWriter)
- def write(self, elements, output):
- for element in elements:
+ def write(self, elements, output, level=0):
+ for idx, element in enumerate(elements):
args = []
for arg in element.posArgs:
if arg in element:
@@ -68,7 +69,18 @@ class PyWriter(object):
for k, v in element.items():
if k not in element.posArgs:
args.append("%s=%s" % (str(k), repr(v)))
- output.write('%s(%s)\n' % (element.elementType, ', '.join(args)))
+ if not element.subElements:
+ output.write('%s%s(%s)'
+ % (level*' ', element.elementType, ', '.join(args)))
+ else:
+ output.write('%s%s(%s)[\n'
+ % (level*' ', element.elementType, ', '.join(args)))
+ self.write(element.subElements, output, level+1)
+ output.write(']')
+ if level == 0:
+ output.write('\n')
+ elif idx < len(elements) - 1:
+ output.write(',\n')
def toStr(value):
diff --git a/interfaces.py b/interfaces.py
index 266f454..de09b97 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -662,7 +662,35 @@ class IFile(IResourceAdapter, IResourceSchema):
localFilename = Attribute('Filename provided during upload.')
+class IStorageInfo(Interface):
+
+ storageName = schema.BytesLine(
+ title=_(u'Storage Name'),
+ description=_(u'The name of a storage utility used for this '
+ 'object.'),
+ default='',
+ missing_value='',
+ required=False)
+
+ storageParams = schema.BytesLine(
+ title=_(u'Storage Parameters'),
+ description=_(u'Information used to address the external '
+ 'storage, e.g. a filename or path.'),
+ default='',
+ missing_value='',
+ required=False)
+
+ externalAddress = schema.BytesLine(
+ title=_(u'External Address'),
+ description=_(u'The full address for accessing the object '
+ 'on the external storage, e.g. a filename or path.'),
+ default='',
+ missing_value='',
+ required=False)
+
+
class IExternalFile(IFile):
+#class IExternalFile(IFile, IStorageInfo):
""" A file whose content (data attribute) is not stored in the ZODB
but somewhere else, typically in the file system.
"""
diff --git a/stateful/configure.zcml b/stateful/configure.zcml
index 38aaca8..d541a00 100644
--- a/stateful/configure.zcml
+++ b/stateful/configure.zcml
@@ -6,7 +6,7 @@
i18n_domain="loops">