work in progress: confiburable storage for resources; + some minor tweaks

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1405 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-10-16 09:27:44 +00:00
parent 83947823df
commit 3c158f2f93
10 changed files with 150 additions and 35 deletions

View file

@ -549,10 +549,10 @@ view for rendering.)
>>> component.provideAdapter(LoopsType) >>> component.provideAdapter(LoopsType)
>>> view = NodeView(m112, TestRequest()) >>> view = NodeView(m112, TestRequest())
>>> view.renderTarget() >>> view.renderTarget()
u'' u'<pre></pre>'
>>> doc1.data = u'Test data\n\nAnother paragraph' >>> doc1.data = u'Test data\n\nAnother paragraph'
>>> view.renderTarget() >>> view.renderTarget()
u'Test data\n\nAnother paragraph' u'<pre>Test data\n\nAnother paragraph</pre>'
>>> doc1.contentType = 'text/restructured' >>> doc1.contentType = 'text/restructured'
>>> view.renderTarget() >>> view.renderTarget()
u'<p>Test data</p>\n<p>Another paragraph</p>\n' u'<p>Test data</p>\n<p>Another paragraph</p>\n'
@ -667,6 +667,18 @@ created object:
>>> sorted(t.__name__ for t in note.getConcepts()) >>> sorted(t.__name__ for t in note.getConcepts())
[u'note', u'topic'] [u'note', u'topic']
When creating an object its name is automatically generated using the title
of the object. Let's make sure that the name chooser also handles special
and possibly critcal cases:
>>> nc = ResourceNameChooser(resources)
>>> nc.chooseName(u'abc: (cde)', None)
u'abc_cde'
>>> nc.chooseName(u'\xdcml\xe4ut', None)
u'uemlaeut'
>>> nc.chooseName(u'A very very loooooong title', None)
u'a_title'
Editing an object Editing an object
----------------- -----------------

View file

@ -235,12 +235,36 @@ class CreateObject(EditObject):
return True return True
specialCharacters = {
'\xc4': 'Ae', '\xe4': 'ae', '\xd6': 'Oe', '\xf6': 'oe',
'\xdc': 'Ue', '\xfc': 'ue', '\xdf': 'ss'}
class ResourceNameChooser(NameChooser): class ResourceNameChooser(NameChooser):
adapts(IResourceManager) adapts(IResourceManager)
def chooseName(self, title, obj): def chooseName(self, title, obj):
name = title.replace(' ', '_').lower() result = []
name = super(ResourceNameChooser, self).chooseName(name, obj) if len(title) > 15:
return name words = title.split()
if len(words) > 1:
title = '_'.join((words[0], words[-1]))
for c in title:
try:
c = c.encode('ISO8859-15')
except UnicodeEncodeError:
continue
if c in specialCharacters:
result.append(specialCharacters[c].lower())
continue
if ord(c) > 127:
c = chr(ord(c) & 127)
if c in ('_., '):
result.append('_')
elif not c.isalpha() and not c.isdigit():
continue
else:
result.append(c.lower())
name = unicode(''.join(result))
return super(ResourceNameChooser, self).chooseName(name, obj)

View file

@ -288,6 +288,8 @@ class NodeView(BaseView):
targetId = self.targetId targetId = self.targetId
if targetId is not None: if targetId is not None:
return '%s/.target%s' % (self.url, targetId) return '%s/.target%s' % (self.url, targetId)
else:
return self.url
@Lazy @Lazy
def realTargetUrl(self): def realTargetUrl(self):

View file

@ -34,6 +34,7 @@ from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
from zope.proxy import removeAllProxies from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.documenttemplate.dt_util import html_quote
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.interfaces import IBaseResource, IDocument, IMediaAsset from loops.interfaces import IBaseResource, IDocument, IMediaAsset
@ -112,10 +113,14 @@ class ResourceView(BaseView):
return self return self
def show(self): def show(self):
data = self.context.data context = self.context
data = context.data
response = self.request.response response = self.request.response
response.setHeader('Content-Type', self.context.contentType) response.setHeader('Content-Type', context.contentType)
response.setHeader('Content-Length', len(data)) response.setHeader('Content-Length', len(data))
if not context.contentType.startswith('image/'):
response.setHeader('Content-Disposition',
'attachment; filename=%s' % zapi.getName(context))
return data return data
def concepts(self): def concepts(self):
@ -202,9 +207,12 @@ class DocumentView(ResourceView):
""" Return the rendered content (data) of the context object. """ Return the rendered content (data) of the context object.
""" """
text = self.context.data text = self.context.data
typeKey = renderingFactories.get(self.context.contentType, None) contentType = self.context.contentType
typeKey = renderingFactories.get(contentType, None)
if typeKey is None: if typeKey is None:
return text if contentType == 'text/html':
return text
return u'<pre>%s</pre>' % html_quote(text)
source = zapi.createObject(typeKey, text) source = zapi.createObject(typeKey, text)
view = zapi.getMultiAdapter((removeAllProxies(source), self.request)) view = zapi.getMultiAdapter((removeAllProxies(source), self.request))
return view.render() return view.render()

View file

@ -340,6 +340,11 @@
<adapter factory="loops.target.ConceptProxy" <adapter factory="loops.target.ConceptProxy"
permission="zope.ManageContent" /> permission="zope.ManageContent" />
<adapter for="loops.interfaces.IFile"
provides="cybertools.text.interfaces.ITextTransform"
name="application/pdf"
factory="cybertools.text.pdf.PdfTransform" />
<vocabulary <vocabulary
factory="loops.concept.ConceptTypeSourceList" factory="loops.concept.ConceptTypeSourceList"
name="loops.conceptTypeSource" name="loops.conceptTypeSource"

View file

@ -206,6 +206,23 @@ Now let's have a look at resources.
>>> img1_type.title >>> img1_type.title
u'File' u'File'
Using the type machinery we can also specify options that may be used
for controlling e.g. storage for external files.
>>> extfile = concepts['extfile'] = Concept(u'External File')
>>> ef1 = resources['ef1'] = Resource(u'Extfile #1')
>>> ef1.resourceType = extfile
>>> ef1_type = IType(ef1)
>>> IType(ef1).options
[]
>>> extfile_ad = TypeConcept(extfile)
>>> extfile_ad.options = ['dummy', 'storage:varsubdir',
... 'storage_parameters:extfiles']
>>> IType(ef1).options
['dummy', 'storage:varsubdir', 'storage_parameters:extfiles']
>>> IType(ef1).optionsDict
{'default': ['dummy'], 'storage_parameters': 'extfiles', 'storage': 'varsubdir'}
Can we find out somehow which types are available? This is the time to look Can we find out somehow which types are available? This is the time to look
for a type manager. This could be a utility; but in the loops package it for a type manager. This could be a utility; but in the loops package it
is again an adapter, now for the loops root object. Nevertheless one can is again an adapter, now for the loops root object. Nevertheless one can

View file

@ -539,6 +539,13 @@ class ITypeConcept(Interface):
default=u'', default=u'',
required=False) required=False)
options = schema.List(
title=_(u'Options'),
description=_(u'Additional settings.'),
value_type=schema.TextLine(),
default=[],
required=False)
# storage = schema.Choice() # storage = schema.Choice()
@ -555,6 +562,12 @@ class IFile(IResourceAdapter, IResourceSchema):
""" """
class IExternalFile(IFile):
""" A file whose content (data attribute) is not stored in the ZODB
but somewhere else, typically in the file system.
"""
class IImage(IResourceAdapter): class IImage(IResourceAdapter):
""" A media asset that may be embedded in a (web) page as an image. """ A media asset that may be embedded in a (web) page as an image.
""" """
@ -590,26 +603,3 @@ class IViewConfiguratorSchema(Interface):
required=False) 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)

View file

@ -22,6 +22,7 @@ Definition of the Concept class.
$Id$ $Id$
""" """
from zope import component
from zope.app import zapi from zope.app import zapi
from zope.app.container.btree import BTreeContainer from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained from zope.app.container.contained import Contained
@ -42,6 +43,7 @@ from zope.event import notify
from cybertools.relation.registry import getRelations from cybertools.relation.registry import getRelations
from cybertools.relation.interfaces import IRelatable from cybertools.relation.interfaces import IRelatable
from cybertools.storage.interfaces import IExternalStorage
from cybertools.text.interfaces import ITextTransform from cybertools.text.interfaces import ITextTransform
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
@ -208,6 +210,38 @@ class FileAdapter(ResourceAdapterBase):
data = property(getData, setData) data = property(getData, setData)
class ExternalFileAdapter(FileAdapter):
@Lazy
def externalAddress(self):
# or is this an editable attribute?
# or some sort of subpath set during import?
# anyway: an attribute of the context object.
return self.context.__name__
@Lazy
def options(self):
return IType(self.context).optionsDict
@Lazy
def storageName(self):
return self.options.get('storage')
@Lazy
def storageParams(self):
return self.options.get('storage_parameters')
def setData(self, data):
storage = component.getUtility(IExternalStorage, name=self.storageName)
storage.setData(self.externalAddress, data, params=self.storageParams)
def getData(self):
storage = component.getUtility(IExternalStorage)
return storage.getData(self.externalAddress, params=self.storageParams)
data = property(getData, setData)
class DocumentAdapter(ResourceAdapterBase): class DocumentAdapter(ResourceAdapterBase):
""" Common base class for all resource types with a text-like """ Common base class for all resource types with a text-like
data attribute. data attribute.
@ -280,8 +314,8 @@ class IndexAttributes(object):
ti = IType(context).typeInterface ti = IType(context).typeInterface
if ti is not None: if ti is not None:
adapted = ti(context) adapted = ti(context)
transform = component.queryAdapter( transform = component.queryAdapter(adapted, ITextTransform,
adapted, ITextTransform, name=context.contentType) name=context.contentType)
if transform is not None: if transform is not None:
rfa = component.queryAdapter(IReadFile, adapted) rfa = component.queryAdapter(IReadFile, adapted)
if rfa is None: if rfa is None:

23
type.py
View file

@ -99,6 +99,21 @@ class LoopsType(BaseType):
# TODO: unify this type attribute naming... # TODO: unify this type attribute naming...
return self.context.resourceType return self.context.resourceType
@Lazy
def options(self):
return ITypeConcept(self.typeProvider).options or []
@Lazy
def optionsDict(self):
result = {'default': []}
for opt in self.options:
if ':' in opt:
key, value = opt.split(':', 1)
result[key] = value
else:
result['default'].append(opt)
return result
class LoopsTypeInfo(LoopsType): class LoopsTypeInfo(LoopsType):
""" The type info class used by the type manager for listing types. """ The type info class used by the type manager for listing types.
@ -237,6 +252,14 @@ class TypeConcept(AdapterBase):
self.context._typeInterface = ifc self.context._typeInterface = ifc
typeInterface = property(getTypeInterface, setTypeInterface) typeInterface = property(getTypeInterface, setTypeInterface)
def getOptions(self):
return getattr(self.context, '_options', [])
#return super(TypeConcept, self).options or []
def setOptions(self, value):
self.context._options = value
#super(TypeConcept, self).options = value
options = property(getOptions, setOptions)
class TypeInterfaceSourceList(object): class TypeInterfaceSourceList(object):