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">