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)
|
||||
>>> view = NodeView(m112, TestRequest())
|
||||
>>> view.renderTarget()
|
||||
u''
|
||||
u'<pre></pre>'
|
||||
>>> doc1.data = u'Test data\n\nAnother paragraph'
|
||||
>>> view.renderTarget()
|
||||
u'Test data\n\nAnother paragraph'
|
||||
u'<pre>Test data\n\nAnother paragraph</pre>'
|
||||
>>> doc1.contentType = 'text/restructured'
|
||||
>>> view.renderTarget()
|
||||
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())
|
||||
[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
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -235,12 +235,36 @@ class CreateObject(EditObject):
|
|||
return True
|
||||
|
||||
|
||||
specialCharacters = {
|
||||
'\xc4': 'Ae', '\xe4': 'ae', '\xd6': 'Oe', '\xf6': 'oe',
|
||||
'\xdc': 'Ue', '\xfc': 'ue', '\xdf': 'ss'}
|
||||
|
||||
class ResourceNameChooser(NameChooser):
|
||||
|
||||
adapts(IResourceManager)
|
||||
|
||||
def chooseName(self, title, obj):
|
||||
name = title.replace(' ', '_').lower()
|
||||
name = super(ResourceNameChooser, self).chooseName(name, obj)
|
||||
return name
|
||||
result = []
|
||||
if len(title) > 15:
|
||||
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
|
||||
if targetId is not None:
|
||||
return '%s/.target%s' % (self.url, targetId)
|
||||
else:
|
||||
return self.url
|
||||
|
||||
@Lazy
|
||||
def realTargetUrl(self):
|
||||
|
|
|
@ -34,6 +34,7 @@ from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
|
|||
from zope.proxy import removeAllProxies
|
||||
from zope.security import canAccess, canWrite
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.documenttemplate.dt_util import html_quote
|
||||
|
||||
from cybertools.typology.interfaces import IType
|
||||
from loops.interfaces import IBaseResource, IDocument, IMediaAsset
|
||||
|
@ -112,10 +113,14 @@ class ResourceView(BaseView):
|
|||
return self
|
||||
|
||||
def show(self):
|
||||
data = self.context.data
|
||||
context = self.context
|
||||
data = context.data
|
||||
response = self.request.response
|
||||
response.setHeader('Content-Type', self.context.contentType)
|
||||
response.setHeader('Content-Type', context.contentType)
|
||||
response.setHeader('Content-Length', len(data))
|
||||
if not context.contentType.startswith('image/'):
|
||||
response.setHeader('Content-Disposition',
|
||||
'attachment; filename=%s' % zapi.getName(context))
|
||||
return data
|
||||
|
||||
def concepts(self):
|
||||
|
@ -202,9 +207,12 @@ class DocumentView(ResourceView):
|
|||
""" Return the rendered content (data) of the context object.
|
||||
"""
|
||||
text = self.context.data
|
||||
typeKey = renderingFactories.get(self.context.contentType, None)
|
||||
contentType = self.context.contentType
|
||||
typeKey = renderingFactories.get(contentType, 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)
|
||||
view = zapi.getMultiAdapter((removeAllProxies(source), self.request))
|
||||
return view.render()
|
||||
|
|
|
@ -340,6 +340,11 @@
|
|||
<adapter factory="loops.target.ConceptProxy"
|
||||
permission="zope.ManageContent" />
|
||||
|
||||
<adapter for="loops.interfaces.IFile"
|
||||
provides="cybertools.text.interfaces.ITextTransform"
|
||||
name="application/pdf"
|
||||
factory="cybertools.text.pdf.PdfTransform" />
|
||||
|
||||
<vocabulary
|
||||
factory="loops.concept.ConceptTypeSourceList"
|
||||
name="loops.conceptTypeSource"
|
||||
|
|
17
helpers.txt
17
helpers.txt
|
@ -206,6 +206,23 @@ Now let's have a look at resources.
|
|||
>>> img1_type.title
|
||||
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
|
||||
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
|
||||
|
|
|
@ -539,6 +539,13 @@ class ITypeConcept(Interface):
|
|||
default=u'',
|
||||
required=False)
|
||||
|
||||
options = schema.List(
|
||||
title=_(u'Options'),
|
||||
description=_(u'Additional settings.'),
|
||||
value_type=schema.TextLine(),
|
||||
default=[],
|
||||
required=False)
|
||||
|
||||
# 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):
|
||||
""" A media asset that may be embedded in a (web) page as an image.
|
||||
"""
|
||||
|
@ -590,26 +603,3 @@ class IViewConfiguratorSchema(Interface):
|
|||
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$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.app import zapi
|
||||
from zope.app.container.btree import BTreeContainer
|
||||
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.interfaces import IRelatable
|
||||
from cybertools.storage.interfaces import IExternalStorage
|
||||
from cybertools.text.interfaces import ITextTransform
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
|
||||
|
@ -208,6 +210,38 @@ class FileAdapter(ResourceAdapterBase):
|
|||
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):
|
||||
""" Common base class for all resource types with a text-like
|
||||
data attribute.
|
||||
|
@ -280,8 +314,8 @@ class IndexAttributes(object):
|
|||
ti = IType(context).typeInterface
|
||||
if ti is not None:
|
||||
adapted = ti(context)
|
||||
transform = component.queryAdapter(
|
||||
adapted, ITextTransform, name=context.contentType)
|
||||
transform = component.queryAdapter(adapted, ITextTransform,
|
||||
name=context.contentType)
|
||||
if transform is not None:
|
||||
rfa = component.queryAdapter(IReadFile, adapted)
|
||||
if rfa is None:
|
||||
|
|
23
type.py
23
type.py
|
@ -99,6 +99,21 @@ class LoopsType(BaseType):
|
|||
# TODO: unify this type attribute naming...
|
||||
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):
|
||||
""" The type info class used by the type manager for listing types.
|
||||
|
@ -237,6 +252,14 @@ class TypeConcept(AdapterBase):
|
|||
self.context._typeInterface = ifc
|
||||
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):
|
||||
|
||||
|
|
2
view.py
2
view.py
|
@ -196,4 +196,4 @@ class NodeTraverser(ItemTraverser):
|
|||
request.annotations['loops.view'] = viewAnnotations
|
||||
return self.context
|
||||
return super(NodeTraverser, self).publishTraverse(request, name)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue