work in progress: sub-elements, e.g. for annotations and state information, for export/import
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2477 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
d944aca0f5
commit
9dec6887b6
10 changed files with 221 additions and 36 deletions
|
@ -272,9 +272,9 @@
|
|||
provides="loops.interfaces.IExternalFile" />
|
||||
<class class="loops.resource.ExternalFileAdapter">
|
||||
<require permission="zope.View"
|
||||
interface="loops.interfaces.IFile" />
|
||||
interface="loops.interfaces.IExternalFile" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="loops.interfaces.IFile" />
|
||||
set_schema="loops.interfaces.IExternalFile" />
|
||||
</class>
|
||||
|
||||
<adapter factory="loops.resource.TextDocumentAdapter" trusted="True"
|
||||
|
@ -409,6 +409,7 @@
|
|||
<include package=".rest" />
|
||||
<include package=".search" />
|
||||
<include package=".security" />
|
||||
<include package=".stateful" />
|
||||
<include package=".versioning" />
|
||||
<include package=".xmlrpc" />
|
||||
|
||||
|
|
27
external/README.txt
vendored
27
external/README.txt
vendored
|
@ -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
|
||||
======================
|
||||
|
|
57
external/annotation.py
vendored
Normal file
57
external/annotation.py
vendored
Normal file
|
@ -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,
|
||||
))
|
||||
|
63
external/base.py
vendored
63
external/base.py
vendored
|
@ -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)
|
||||
|
||||
|
|
2
external/browser.py
vendored
2
external/browser.py
vendored
|
@ -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
|
||||
|
|
29
external/element.py
vendored
29
external/element.py
vendored
|
@ -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')
|
||||
|
|
22
external/interfaces.py
vendored
22
external/interfaces.py
vendored
|
@ -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.
|
||||
"""
|
||||
|
|
22
external/pyfunc.py
vendored
22
external/pyfunc.py
vendored
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
i18n_domain="loops">
|
||||
|
||||
<zope:utility
|
||||
factory="cybertools.stateful.publishing.simplePublishing"
|
||||
factory="cybertools.stateful.publishing.simplePublishingFactory"
|
||||
name="loops.simple_publishing" />
|
||||
|
||||
<zope:adapter
|
||||
|
|
Loading…
Add table
Reference in a new issue