diff --git a/README.txt b/README.txt index 033a5e6..6a38c05 100755 --- a/README.txt +++ b/README.txt @@ -17,12 +17,12 @@ with lower-level aspects like type or state management. >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) - + >>> from zope.app import zapi >>> from zope.app.tests import ztapi >>> from zope.interface import Interface >>> from zope.publisher.browser import TestRequest - + Concepts and Relations ====================== @@ -73,7 +73,7 @@ also need a default predicate concept; the default name for this is Now we can assign the concept c2 as a child to c1 (using the standard ConceptRelation): - + >>> cc1.assignChild(cc2) We can now ask our concepts for their related child and parent concepts: @@ -163,7 +163,7 @@ The token attribute provided with the items returned by the children() and parents() methods identifies identifies not only the item itself but also the relationship to the context object using a combination of URIs to item and the predicate of the relationship: - + >>> [c.token for c in children] ['.loops/concepts/cc2:.loops/concepts/standard'] @@ -207,11 +207,11 @@ types and predicates. >>> from zope.schema.interfaces import IIterableSource >>> ztapi.provideAdapter(IIterableSource, ITerms, LoopsTerms, ... with=(IBrowserRequest,)) - + >>> sorted((t.title, t.token) for t in view.conceptTypes()) [(u'Topic', '.loops/concepts/topic'), (u'Type', '.loops/concepts/type'), (u'Unknown Type', '.loops/concepts/unknown')] - + >>> sorted((t.title, t.token) for t in view.predicates()) [(u'subconcept', '.loops/concepts/standard')] @@ -222,7 +222,7 @@ Index attributes adapter >>> idx = IndexAttributes(cc2) >>> idx.text() u'cc2 Zope 3' - + >>> idx.title() u'cc2 Zope 3' @@ -230,16 +230,16 @@ Index attributes adapter Resources and what they have to do with Concepts ================================================ - >>> from loops.interfaces import IDocument, IMediaAsset + >>> from loops.interfaces import IResource, IDocument, IMediaAsset We first need a resource manager: - + >>> from loops.resource import ResourceManager >>> loopsRoot['resources'] = ResourceManager() >>> resources = loopsRoot['resources'] A common type of resource is a document: - + >>> from loops.interfaces import IDocument >>> from loops.resource import Document >>> doc1 = Document(u'Zope Info') @@ -247,9 +247,9 @@ A common type of resource is a document: >>> doc1.title u'Zope Info' >>> doc1.data - u'' - >>> doc1.contentType '' + >>> doc1.contentType + u'' Another one is a media asset: @@ -258,7 +258,7 @@ Another one is a media asset: >>> img = MediaAsset(u'A png Image') For testing we use some simple files from the tests directory: - + >>> from loops import tests >>> import os >>> path = os.path.join(*tests.__path__) @@ -312,7 +312,7 @@ These relations may also be managed starting from a resource using the resource configuration view: >>> from loops.browser.resource import ResourceConfigureView - + Index attributes adapter ------------------------ @@ -320,7 +320,7 @@ Index attributes adapter >>> idx = IndexAttributes(doc1) >>> idx.text() u'doc1 Zope Info' - + >>> idx.title() u'doc1 Zope Info' @@ -340,7 +340,7 @@ the views or nodes, however, present informations coming from the concepts or resources they are related to. We first need a view manager: - + >>> from loops.view import ViewManager, Node >>> from zope.security.checker import NamesChecker, defineChecker >>> nodeChecker = NamesChecker(('body',)) @@ -351,7 +351,7 @@ We first need a view manager: The view space is typically built up with nodes; a node may be a top-level menu that may contain other nodes as menu or content items: - + >>> m1 = Node(u'Menu') >>> views['m1'] = m1 >>> m11 = Node(u'Zope') @@ -426,7 +426,7 @@ out - this is usually done through ZCML.) >>> from cybertools.relation.interfaces import IRelationInvalidatedEvent >>> ztapi.subscribe([ITargetRelation, IRelationInvalidatedEvent], None, ... removeTargetRelation) - + >>> m111.target = cc1 >>> m111.target is cc1 True @@ -487,10 +487,10 @@ accessing a target via a node view it is usually wrapped in a corresponding view; these views we have to provide as multi-adapters: >>> from loops.browser.node import ConfigureView - >>> from loops.browser.resource import DocumentView, MediaAssetView + >>> from loops.browser.resource import DocumentView, ResourceView >>> ztapi.provideAdapter(IDocument, Interface, DocumentView, ... with=(IBrowserRequest,)) - >>> ztapi.provideAdapter(IMediaAsset, Interface, MediaAssetView, + >>> ztapi.provideAdapter(IResource, Interface, ResourceView, ... with=(IBrowserRequest,)) >>> form = {'action': 'create', 'create.title': 'New Resource', @@ -592,10 +592,10 @@ Let's add some more nodes and reorder them: >>> m11['m114'] = m114 >>> m11.keys() ['m111', 'm112', 'm113', 'm114'] - + A special management view provides methods for moving objects down, up, to the bottom, and to the top. - + >>> from cybertools.container.ordered import OrderedContainerView >>> view = OrderedContainerView(m11, TestRequest()) >>> view.move_bottom(('m113',)) @@ -641,7 +641,7 @@ instance to another. >>> exporter.dumpData() Load them again from the exported file: - + >>> importer = NodesImporter(views) >>> importer.filename = dumpname >>> imported = importer.getData() diff --git a/browser/common.py b/browser/common.py index 252b188..0fc769b 100644 --- a/browser/common.py +++ b/browser/common.py @@ -117,7 +117,7 @@ class BaseView(object): @Lazy def loopsRoot(self): return self.context.getLoopsRoot() - + @Lazy def url(self): return zapi.absoluteURL(self.context, self.request) @@ -199,9 +199,10 @@ class LoopsTerms(object): @Lazy def loopsRoot(self): return self.context.getLoopsRoot() - + def getTerm(self, value): - #return BaseView(value, self.request) + #if value is None: + # return SimpleTerm(None, '', u'not assigned') title = value.title or zapi.getName(value) token = self.loopsRoot.getLoopsUri(value) return SimpleTerm(value, token, title) diff --git a/browser/concept.py b/browser/concept.py index 4d2aac5..7d6b398 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -66,14 +66,14 @@ class ConceptEditForm(EditForm): class ConceptView(BaseView): template = NamedTemplate('loops.concept_macros') - + @Lazy def macro(self): return self.template.macros['conceptdata'] def fieldData(self): ti = IType(self.context).typeInterface - if not ti: return + if not ti: return adapter = ti(self.context) for n, f in schema.getFieldsInOrder(ti): value = getattr(adapter, n, '') diff --git a/browser/configure.zcml b/browser/configure.zcml index 3606c04..8f2af13 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -233,6 +233,21 @@ + + + + - + + + @@ -263,33 +285,6 @@ class=".resource.DocumentView" attribute="show" /> - - - - - - - - - - - + />--> - - - - - + + + + + + + + + + + + + + + + @@ -141,19 +165,12 @@ - - + interface=".interfaces.IBaseResource" /> + set_schema=".interfaces.IBaseResource" /> @@ -249,12 +266,7 @@ trusted="True" /> - - @@ -264,16 +276,30 @@ - + + + + + - + + - + + + + + @@ -283,6 +309,14 @@ set_schema="loops.query.IQueryConcept" /> + + + + + + @@ -302,7 +336,7 @@ /> diff --git a/interfaces.py b/interfaces.py index 9ab4bfa..5229138 100644 --- a/interfaces.py +++ b/interfaces.py @@ -29,6 +29,7 @@ from zope.app.container.constraints import contains, containers from zope.app.container.interfaces import IContainer, IOrderedContainer from zope.app.file.interfaces import IImage as IBaseAsset from zope.app.folder.interfaces import IFolder +from zope.app.size.interfaces import ISized from cybertools.relation.interfaces import IRelation import util @@ -62,7 +63,7 @@ class IPotentialTarget(Interface): class IConcept(ILoopsObject, IPotentialTarget): """ The concept is the central element of the loops framework. - + A concept is related to other concepts, may have resources associated with it and may be referenced by views. """ @@ -130,7 +131,7 @@ class IConcept(ILoopsObject, IPotentialTarget): def getResources(predicates=None): """ Return a sequence of resources assigned to self, optionally restricted to the predicates given. - """ + """ def getResourceRelations(predicates=None, resource=None): """ Return a sequence of relations to resources assigned to self, @@ -143,12 +144,12 @@ class IConcept(ILoopsObject, IPotentialTarget): The relationship defaults to ConceptResourceRelation. """ - + def deassignResource(resource, predicates=None): """ Remove the relations to the resource given from self, optionally restricting them to the predicates given. """ - + class IConceptView(Interface): """ Used for accessing a concept via a node's target attribute""" @@ -178,12 +179,12 @@ class IConceptManagerContained(Interface): # resource interfaces -class IBaseResource(Interface): +class IBaseResource(ILoopsObject): """ New base interface for resources. Functionality beyond this simple interface is provided by adapters that are chosen via the resource type's typeInterface. """ - + title = schema.TextLine( title=_(u'Title'), description=_(u'Title of the resource'), @@ -199,6 +200,26 @@ class IBaseResource(Interface): source="loops.resourceTypeSource", required=False) + data = schema.Bytes( + title=_(u'Data'), + description=_(u'Resource raw data'), + default='', + missing_value='', + required=False) + + contentType = schema.BytesLine( + title=_(u'Content Type'), + description=_(u'Content type (format) of the data field'), + default='', + missing_value='', + required=False) + + +class IBaseResourceSchema(Interface): + """ New schema for resources; to be used by sub-interfaces that will + be implemented by type adapters. + """ + class IResourceSchema(Interface): @@ -224,29 +245,6 @@ class IResourceSchema(Interface): required=False) -# the next two interfaces are probably obsolete: - -class IFileSystemResource(Interface): - - fsPath = schema.BytesLine( - title=_(u'Filesystem Path'), - description=_(u'Optional path to a file in the filesystem ' - 'to be used for storing the resource'), - default='', - missing_value='', - required=False) - - -class IControlledResource(Interface): - - readOnly = schema.Bool( - title=_(u'Read only'), - description=_(u'Check this if resource may not be modified ' - 'after being first filled with non-empty content'), - default=False, - required=False) - - class IResource(ILoopsObject, IPotentialTarget): """ A resource is an atomic information element that is made available via a view or a concept. @@ -323,10 +321,10 @@ class IMediaAssetView(IMediaAssetSchema): class IMediaAsset(IMediaAssetSchema, IResource, IBaseAsset): """ A resource containing a (typically binary) file-like content - or an image. + or an image. """ - - + + class IResourceManager(ILoopsObject, IContainer): """ A manager/container for resources. """ @@ -475,7 +473,7 @@ class ILoops(ILoopsObject): """ Retrieve object specified by the loops uri (starting with '.loops/') given. """ - + def getConceptManager(): """ Return the (default) concept manager. """ @@ -545,7 +543,7 @@ class ITypeConcept(Interface): # storage = schema.Choice() -class IResourceAdapter(Interface): +class IResourceAdapter(IBaseResourceSchema): """ Base interface for adapters for resources. This is the base interface of the interfaces to be used as typeInterface attribute on type concepts specifying resource types. @@ -556,15 +554,15 @@ class IFile(IResourceAdapter): """ A media asset that is not shown on a (web) page like an image but may be downloaded instead. """ - - + + class IImage(IResourceAdapter): """ A media asset that may be embedded in a (web) page as an image. """ - - + + class ITextDocument(IResourceAdapter): - """ A resource containing some sort of plain text that may be rendered and + """ A resource containing some sort of plain text that may be rendered and edited without necessarily involving a special external application (like e.g. OpenOffice); typical content types are text/html, text/xml, text/restructured, etc. @@ -581,3 +579,27 @@ class IViewConfiguratorSchema(Interface): default=u'', required=False) + +# the next two interfaces are obsolete, they will be replaced by IResourceStorage: + +class IFileSystemResource(Interface): + + fsPath = schema.BytesLine( + title=_(u'Filesystem Path'), + description=_(u'Optional path to a file in the filesystem ' + 'to be used for storing the resource'), + default='', + missing_value='', + required=False) + + +class IControlledResource(Interface): + + readOnly = schema.Bool( + title=_(u'Read only'), + description=_(u'Check this if resource may not be modified ' + 'after being first filled with non-empty content'), + default=False, + required=False) + + diff --git a/knowledge/knowledge.py b/knowledge/knowledge.py index cefe24b..b6d5189 100644 --- a/knowledge/knowledge.py +++ b/knowledge/knowledge.py @@ -36,7 +36,8 @@ from loops.interfaces import IConcept, IResource from loops.knowledge.interfaces import IPerson, ITask from loops.organize.party import Person as BasePerson from loops.organize.task import Task as BaseTask -from loops.type import TypeInterfaceSourceList, AdapterBase +from loops.common import AdapterBase +from loops.type import TypeInterfaceSourceList # register type interfaces - (TODO: use a function for this) diff --git a/organize/party.py b/organize/party.py index f3ce4b3..3a39d42 100644 --- a/organize/party.py +++ b/organize/party.py @@ -41,7 +41,8 @@ from cybertools.typology.interfaces import IType from loops.concept import Concept from loops.interfaces import IConcept from loops.organize.interfaces import IPerson, ANNOTATION_KEY -from loops.type import TypeInterfaceSourceList, AdapterBase +from loops.common import AdapterBase +from loops.type import TypeInterfaceSourceList # register type interfaces - (TODO: use a function for this) diff --git a/organize/task.py b/organize/task.py index f728e0f..b0ba59b 100644 --- a/organize/task.py +++ b/organize/task.py @@ -26,7 +26,8 @@ from zope.interface import implements from cybertools.organize.interfaces import ITask from loops.interfaces import IConcept -from loops.type import TypeInterfaceSourceList, AdapterBase +from loops.common import AdapterBase +from loops.type import TypeInterfaceSourceList TypeInterfaceSourceList.typeInterfaces += (ITask,) diff --git a/process/definition.py b/process/definition.py index 4179b45..6e1958d 100644 --- a/process/definition.py +++ b/process/definition.py @@ -33,7 +33,8 @@ from cybertools.typology.interfaces import IType from cybertools.process.interfaces import IProcess from cybertools.process.definition import Process as BaseProcess from loops.interfaces import IConcept -from loops.type import TypeInterfaceSourceList, AdapterBase +from loops.common import AdapterBase +from loops.type import TypeInterfaceSourceList # register type interfaces - (TODO: use a function for this) diff --git a/query.py b/query.py index 1efd03e..814a3b6 100644 --- a/query.py +++ b/query.py @@ -32,7 +32,8 @@ from zope.security.proxy import removeSecurityProxy from cybertools.typology.type import BaseType, TypeManager from loops.interfaces import IConcept -from loops.type import AdapterBase, TypeInterfaceSourceList +from loops.common import AdapterBase +from loops.type import TypeInterfaceSourceList _ = MessageFactory('loops') diff --git a/resource.py b/resource.py index 4ef1ef3..1dc9904 100644 --- a/resource.py +++ b/resource.py @@ -25,13 +25,15 @@ $Id$ from zope.app import zapi from zope.app.container.btree import BTreeContainer from zope.app.container.contained import Contained -from zope.app.file.image import Image as BaseMediaAsset +from zope.app.file.image import Image from zope.app.file.interfaces import IFile from zope.app.filerepresentation.interfaces import IReadFile, IWriteFile from zope.app.size.interfaces import ISized +from zope.cachedescriptors.property import Lazy from zope.component import adapts from zope.i18nmessageid import MessageFactory from zope.interface import implements +from zope import schema from persistent import Persistent from cStringIO import StringIO @@ -39,36 +41,42 @@ from textindexng.interfaces import IIndexableContent from textindexng.content import IndexContentCollector from cybertools.relation.registry import getRelations from cybertools.relation.interfaces import IRelatable +from cybertools.typology.interfaces import ITypeManager from interfaces import IBaseResource, IResource +from interfaces import IFile from interfaces import IDocument, IDocumentSchema, IDocumentView from interfaces import IMediaAsset, IMediaAssetSchema, IMediaAssetView -from interfaces import IFileSystemResource, IControlledResource from interfaces import IResourceManager, IResourceManagerContained from interfaces import ILoopsContained from interfaces import IIndexAttributes from concept import ResourceRelation +from common import ResourceAdapterBase from view import TargetRelation _ = MessageFactory('loops') -class Resource(Contained, Persistent): +class Resource(Image, Contained): + + implements(IBaseResource, IResource, IResourceManagerContained, IRelatable, ISized) - implements(IBaseResource, IResource, IFileSystemResource, IControlledResource, - IResourceManagerContained, IRelatable) - proxyInterface = IMediaAssetView _size = _width = _height = 0 + def __init__(self, title=u''): + super(Resource, self).__init__() + self.title = title + def getResourceType(self): - typePred = self.getLoopsRoot().getConceptManager().getTypePredicate() + cm = self.getLoopsRoot().getConceptManager() + typePred = cm.getTypePredicate() if typePred is None: return None concepts = self.getConcepts([typePred]) # TODO (?): check for multiple types (->Error) - return concepts and concepts[0] or None + return concepts and concepts[0] or cm.get('file', None) def setResourceType(self, concept): current = self.getResourceType() if current != concept: @@ -80,32 +88,32 @@ class Resource(Contained, Persistent): self.deassignConcept(current, [typePred]) self.assignConcept(concept, typePred) resourceType = property(getResourceType, setResourceType) - + _title = u'' def getTitle(self): return self._title def setTitle(self, title): self._title = title title = property(getTitle, setTitle) - _contentType = '' + def _setData(self, data): + dataFile = StringIO(data) # let File tear it into pieces + super(Resource, self)._setData(dataFile) + if not self.contentType: + self.guessContentType(data) + data = property(Image._getData, _setData) + + def guessContentType(self, data): + if not isinstance(data, str): # seems to be a file object + data = data.read(20) + if data.startswith('%PDF'): + self.contentType = 'application/pdf' + + _contentType = u'' def setContentType(self, contentType): if contentType: self._contentType = contentType def getContentType(self): return self._contentType contentType = property(getContentType, setContentType) - _fsPath = '' - def setFsPath(self, fsPath): self._fsPath = fsPath - def getFsPath(self): return self._fsPath - fsPath = property(getFsPath, setFsPath) - - _readOnly = '' - def setReadOnly(self, readOnly): self._readOnly = readOnly - def getReadOnly(self): return self._readOnly - readOnly = property(getReadOnly, setReadOnly) - - def __init__(self, title=u''): - self.title = title - def getLoopsRoot(self): return zapi.getParent(self).getLoopsRoot() @@ -122,7 +130,7 @@ class Resource(Contained, Persistent): relationships = [ResourceRelation(None, self, p) for p in predicates] # TODO: sort... return getRelations(first=concept, second=self, relationships=relationships) - + def getConcepts(self, predicates=None): return [r.first for r in self.getConceptRelations(predicates)] @@ -132,17 +140,7 @@ class Resource(Contained, Persistent): def deassignConcept(self, concept, predicates=None): concept.deassignResource(self, predicates) - -class Document(Resource): - - implements(IDocument, ISized) - - proxyInterface = IDocumentView - - _data = u'' - def setData(self, data): self._data = data - def getData(self): return self._data - data = property(getData, setData) + # ISized interface def getSize(self): return len(self.data) @@ -154,29 +152,24 @@ class Document(Resource): return '%i Bytes' % self.getSize() -class MediaAsset(Resource, BaseMediaAsset): +class Document(Resource): - implements(IMediaAsset) + implements(IDocument) - proxyInterface = IMediaAssetView + proxyInterface = IDocumentView def __init__(self, title=u''): - super(MediaAsset, self).__init__() self.title = title - def _setData(self, data): - dataFile = StringIO(data) # let File tear it into pieces - super(MediaAsset, self)._setData(dataFile) - if not self.contentType: - self.guessContentType(data) + _data = '' + def setData(self, data): self._data = data + def getData(self): return self._data + data = property(getData, setData) - data = property(BaseMediaAsset._getData, _setData) - def guessContentType(self, data): - if not isinstance(data, str): # seems to be a file object - data = data.read(20) - if data.startswith('%PDF'): - self.contentType = 'application/pdf' +class MediaAsset(Resource): + + implements(IMediaAsset) class ResourceManager(BTreeContainer): @@ -188,17 +181,19 @@ class ResourceManager(BTreeContainer): def getViewManager(self): return self.getLoopsRoot().getViewManager() - - + + # adapters and similar stuff -class FileAdapter(object): +class FileAdapter(ResourceAdapterBase): """ A type adapter for providing file functionality for resources. """ - - def __init__(self, context): - self.context = context + + implements(IFile) + + # TODO: provide specialized access to data attribute analog to zope.app.file; + # automatically set contentType... class DocumentWriteFileAdapter(object): @@ -259,3 +254,23 @@ class IndexableResource(object): icc.addBinary(fields[0], context.data, context.contentType, language='de') return icc + +class ResourceTypeSourceList(object): + + implements(schema.interfaces.IIterableSource) + + def __init__(self, context): + self.context = context + + def __iter__(self): + return iter(self.resourceTypes) + + @Lazy + def resourceTypes(self): + types = ITypeManager(self.context).listTypes(include=('resource',)) + return [t.typeProvider for t in types if t.typeProvider is not None] + + def __len__(self): + return len(self.resourceTypes) + + diff --git a/type.py b/type.py index ba7c5bf..160bfa9 100644 --- a/type.py +++ b/type.py @@ -36,6 +36,7 @@ from loops.interfaces import ITypeConcept from loops.interfaces import IResourceAdapter, IFile, IImage, ITextDocument from loops.concept import Concept from loops.resource import Resource, Document, MediaAsset +from loops.common import AdapterBase class LoopsType(BaseType): @@ -65,7 +66,7 @@ class LoopsType(BaseType): def typeInterface(self): adapter = zapi.queryAdapter(self.typeProvider, ITypeConcept) if adapter is not None: - return adapter.typeInterface + return removeSecurityProxy(adapter.typeInterface) else: conceptType = self.typeProvider typeConcept = self.root.getConceptManager().getTypeConcept() @@ -130,7 +131,7 @@ class ResourceType(LoopsType): type concepts as is already the case for concepts. """ - adapts(IResource) + #adapts(IResource) typeTitles = {'MediaAsset': u'Media Asset'} @@ -214,15 +215,13 @@ class LoopsTypeManager(TypeManager): for cls in (Document, MediaAsset)]) -class TypeConcept(object): +class TypeConcept(AdapterBase): """ typeInterface adapter for concepts of type 'type'. """ implements(ITypeConcept) - adapts(IConcept) - def __init__(self, context): - self.context = removeSecurityProxy(context) + _schemas = list(ITypeConcept) + list(IConcept) def getTypeInterface(self): ti = getattr(self.context, '_typeInterface', None) @@ -251,56 +250,3 @@ class TypeInterfaceSourceList(object): def __len__(self): return len(self.typeInterfaces) - -class ResourceTypeSourceList(object): - - implements(schema.interfaces.IIterableSource) - - def __init__(self, context): - self.context = context - - def __iter__(self): - return iter(self.resourceTypes) - - @Lazy - def resourceTypes(self): - types = ITypeManager(self.context).listTypes(include=('resource',)) - return [t.typeProvider for t in types] - - def __len__(self): - return len(self.resourceTypes) - - -class AdapterBase(object): - """ (Mix-in) Class for concept adapters that provide editing of fields - defined by the type interface. - """ - - adapts(IConcept) - - _attributes = ('context', '__parent__', ) - _schemas = list(IConcept) - - def __init__(self, context): - self.context = context # to get the permission stuff right - self.__parent__ = context - - def __getattr__(self, attr): - self.checkAttr(attr) - return getattr(self.context, '_' + attr, None) - - def __setattr__(self, attr, value): - if attr in self._attributes: - object.__setattr__(self, attr, value) - else: - self.checkAttr(attr) - setattr(self.context, '_' + attr, value) - - def checkAttr(self, attr): - if attr not in self._schemas: - raise AttributeError(attr) - - def __eq__(self, other): - return self.context == other.context - -