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:
parent
83947823df
commit
3c158f2f93
10 changed files with 150 additions and 35 deletions
16
README.txt
16
README.txt
|
@ -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
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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"
|
||||||
|
|
17
helpers.txt
17
helpers.txt
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
38
resource.py
38
resource.py
|
@ -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
23
type.py
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue