storage improvements; resource listings + 'author(s)' field; start with user_registration.html
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1671 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
878c2f58c8
commit
1e115ed0e2
19 changed files with 169 additions and 61 deletions
|
@ -651,7 +651,7 @@ on data provided in this form:
|
|||
>>> from loops.type import TypeConcept
|
||||
>>> from loops.resource import NoteAdapter
|
||||
>>> component.provideAdapter(TypeConcept)
|
||||
>>> component.provideAdapter(NoteAdapter)
|
||||
>>> component.provideAdapter(NoteAdapter, provides=INote)
|
||||
>>> note_tc = concepts['note'] = Concept('Note')
|
||||
>>> note_tc.conceptType = typeObject
|
||||
>>> ITypeConcept(note_tc).typeInterface = INote
|
||||
|
|
|
@ -25,6 +25,7 @@ $Id$
|
|||
from zope.app import zapi
|
||||
from zope import component
|
||||
from zope.app.form.browser.interfaces import ITerms
|
||||
from zope.app.security.interfaces import IAuthentication
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.dottedname.resolve import resolve
|
||||
from zope.dublincore.interfaces import IZopeDublinCore
|
||||
|
@ -118,6 +119,17 @@ class BaseView(GenericView):
|
|||
d = dc.modified or dc.created
|
||||
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
||||
|
||||
@Lazy
|
||||
def creators(self):
|
||||
cr = IZopeDublinCore(self.context).creators or []
|
||||
pau = component.getUtility(IAuthentication)
|
||||
creators = []
|
||||
for c in cr:
|
||||
principal = pau.getPrincipal(c)
|
||||
if principal is not None:
|
||||
creators.append(principal.title)
|
||||
return ', '.join(creators)
|
||||
|
||||
@Lazy
|
||||
def loopsRoot(self):
|
||||
return self.context.getLoopsRoot()
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
tal:condition="view/useVersioning">V</th>
|
||||
<th i18n:translate="label_size">Size</th>
|
||||
<th i18n:translate="label_modifdate">Modification Date</th>
|
||||
<th i18n:translate="label_authors">Author(s)</th>
|
||||
</tr>
|
||||
<tal:items repeat="related resources">
|
||||
<tal:item define="class python: repeat['related'].odd() and 'even' or 'odd';
|
||||
|
@ -111,13 +112,8 @@
|
|||
<td style="text-align: right">
|
||||
<span tal:replace="related/context/sizeForDisplay">Type</span>
|
||||
</td>
|
||||
<td><span tal:replace="related/modified">Type</span></td>
|
||||
</tr>
|
||||
<tr tal:condition="nothing"
|
||||
tal:attributes="class class">
|
||||
<td colspan="4" style="padding-left: 7em">
|
||||
<i tal:content="description">describing...</i>
|
||||
</td>
|
||||
<td><span tal:replace="related/modified">2007-03-30</span></td>
|
||||
<td><span tal:replace="related/creators">John</span></td>
|
||||
</tr>
|
||||
</tal:item>
|
||||
</tal:items>
|
||||
|
|
|
@ -77,10 +77,10 @@
|
|||
for="zope.interface.Interface"
|
||||
name="loops.pageform" />
|
||||
|
||||
<zope:adapter
|
||||
<!--<zope:adapter
|
||||
factory="loops.browser.util.dataform"
|
||||
for="zope.interface.Interface"
|
||||
name="loops.dataform" />
|
||||
name="loops.dataform" />-->
|
||||
|
||||
<zope:adapter
|
||||
factory="loops.browser.util.concept_macros"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
<div metal:fill-slot="body">
|
||||
|
||||
<div metal:define-macro="form">
|
||||
<div metal:define-macro="content">
|
||||
|
||||
<form action="." metal:define-macro="master"
|
||||
tal:attributes="action request/URL" method="post"
|
||||
|
|
|
@ -282,7 +282,7 @@ class EditObject(FormController):
|
|||
self.collectConcepts(fn[len(self.conceptPrefix):], value)
|
||||
else:
|
||||
if not value and fn == 'data' and IFile.providedBy(adapted):
|
||||
# empty file data - don' change
|
||||
# empty file data - don't change
|
||||
continue
|
||||
if isinstance(value, FileUpload):
|
||||
filename = getattr(value, 'filename', '')
|
||||
|
@ -345,6 +345,9 @@ class CreateObject(EditObject):
|
|||
data = form.get('form.data')
|
||||
if data and isinstance(data, FileUpload):
|
||||
name = getattr(data, 'filename', None)
|
||||
# strip path from IE uploads:
|
||||
if '\\' in name:
|
||||
name = name.rsplit('\\', 1)[-1]
|
||||
else:
|
||||
name = None
|
||||
name = INameChooser(container).chooseName(name, obj)
|
||||
|
|
|
@ -62,7 +62,8 @@ class NodeView(BaseView):
|
|||
|
||||
_itemNum = 0
|
||||
|
||||
template = NamedTemplate('loops.node_macros')
|
||||
#template = NamedTemplate('loops.node_macros')
|
||||
template = node_macros
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
|
@ -74,7 +75,7 @@ class NodeView(BaseView):
|
|||
media='all', position=3)
|
||||
cm.register('js', 'loops.js', resourceName='loops.js')
|
||||
cm.register('portlet_left', 'navigation', title='Navigation',
|
||||
subMacro=self.template.macros['menu'])
|
||||
subMacro=node_macros.macros['menu'])
|
||||
#if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
||||
if canWrite(self.context, 'title'):
|
||||
#cm.register('portlet_right', 'clipboard', title='Clipboard',
|
||||
|
@ -84,7 +85,7 @@ class NodeView(BaseView):
|
|||
# see controller / configurator: use multiple configurators;
|
||||
# register additional configurators (adapters) from within package.
|
||||
cm.register('portlet_right', 'actions', title='Actions',
|
||||
subMacro=self.template.macros['actions'])
|
||||
subMacro=node_macros.macros['actions'])
|
||||
|
||||
@Lazy
|
||||
def view(self):
|
||||
|
|
|
@ -123,7 +123,8 @@
|
|||
</div><br />
|
||||
<div tal:repeat="item item/pageItems">
|
||||
<a href="#"
|
||||
tal:attributes="href item/url"
|
||||
tal:attributes="href item/url;
|
||||
title item/description"
|
||||
tal:content="item/title">Item</a>
|
||||
</div>
|
||||
</metal:body>
|
||||
|
@ -142,13 +143,9 @@
|
|||
tal:condition="nocall:target">
|
||||
<div tal:repeat="related item/resources">
|
||||
<a href="#"
|
||||
tal:attributes="href string:${view/url}/.target${related/uniqueId}"
|
||||
tal:attributes="href string:${view/url}/.target${related/uniqueId};
|
||||
title related/description"
|
||||
tal:content="related/title">Resource Title</a>
|
||||
<div style="margin-left: 5em"
|
||||
tal:define="description related/description"
|
||||
tal:condition="description">
|
||||
<i tal:content="description">Description</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</metal:resources>
|
||||
|
@ -169,13 +166,9 @@
|
|||
tal:condition="nocall:target">
|
||||
<div tal:repeat="related item/children">
|
||||
<a href="#"
|
||||
tal:attributes="href string:${view/url}/.target${related/uniqueId}"
|
||||
tal:attributes="href string:${view/url}/.target${related/uniqueId};
|
||||
title related/description"
|
||||
tal:content="related/title">Resource Title</a>
|
||||
<div style="margin-left: 5em"
|
||||
tal:define="description related/description"
|
||||
tal:condition="description">
|
||||
<i tal:content="description">Description</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</metal:children>
|
||||
|
|
|
@ -27,11 +27,13 @@ from zope import component
|
|||
from zope.app import zapi
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.dublincore.interfaces import ICMFDublinCore
|
||||
from zope.app.form.browser.textwidgets import FileWidget
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
||||
from zope.formlib.form import FormFields
|
||||
from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
|
||||
from zope.proxy import removeAllProxies
|
||||
from zope.schema.interfaces import IBytes
|
||||
from zope.security import canAccess, canWrite
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
||||
|
@ -54,6 +56,14 @@ renderingFactories = {
|
|||
}
|
||||
|
||||
|
||||
class CustomFileWidget(FileWidget):
|
||||
|
||||
def hasInput(self):
|
||||
print 'hasInput', self.request.form.get(self.name)
|
||||
if not self.request.form.get(self.name):
|
||||
return False
|
||||
|
||||
|
||||
class ResourceEditForm(EditForm):
|
||||
|
||||
@Lazy
|
||||
|
@ -67,6 +77,9 @@ class ResourceEditForm(EditForm):
|
|||
if typeInterface is not None:
|
||||
omit = [f for f in typeInterface if f in IBaseResource]
|
||||
fields = FormFields(fields.omit(*omit), typeInterface)
|
||||
dataField = fields['data']
|
||||
if IBytes.providedBy(dataField):
|
||||
dataField.customWidget = CustomFileWidget
|
||||
return fields
|
||||
|
||||
def setUpWidgets(self, ignore_request=False):
|
||||
|
|
|
@ -30,7 +30,8 @@ from zope.formlib.namedtemplate import NamedTemplateImplementation
|
|||
|
||||
|
||||
pageform = NamedTemplateImplementation(ViewPageTemplateFile('pageform.pt'))
|
||||
dataform = NamedTemplateImplementation(ViewPageTemplateFile('dataform.pt'))
|
||||
#dataform = NamedTemplateImplementation(ViewPageTemplateFile('dataform.pt'))
|
||||
dataform = ViewPageTemplateFile('dataform.pt')
|
||||
|
||||
concept_macros = NamedTemplateImplementation(ViewPageTemplateFile('concept_macros.pt'))
|
||||
node_macros = NamedTemplateImplementation(ViewPageTemplateFile('node_macros.pt'))
|
||||
|
|
|
@ -29,6 +29,8 @@ from zope.dublincore.zopedublincore import ScalarProperty
|
|||
from zope.component import adapts
|
||||
from zope.interface import implements
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.storage.interfaces import IStorageInfo
|
||||
from loops.interfaces import ILoopsObject, ILoopsContained, IConcept, IResource
|
||||
from loops.interfaces import IResourceAdapter
|
||||
|
||||
|
@ -72,10 +74,15 @@ class AdapterBase(object):
|
|||
|
||||
class ResourceAdapterBase(AdapterBase):
|
||||
|
||||
implements(IStorageInfo)
|
||||
adapts(IResource)
|
||||
|
||||
_adapterAttributes = ('storageName', 'storageParams', ) + AdapterBase._adapterAttributes
|
||||
_contextAttributes = list(IResourceAdapter)
|
||||
|
||||
storageName = None
|
||||
storageParams = None
|
||||
|
||||
|
||||
# other adapters
|
||||
|
||||
|
|
|
@ -344,7 +344,9 @@
|
|||
set_schema="loops.query.IQueryConcept" />
|
||||
</class>
|
||||
|
||||
<adapter factory="loops.resource.FileAdapter" trusted="True" />
|
||||
<adapter factory="loops.resource.FileAdapter"
|
||||
provides="loops.interfaces.IFile"
|
||||
trusted="True" />
|
||||
<class class="loops.resource.FileAdapter">
|
||||
<require permission="zope.View"
|
||||
interface="loops.interfaces.IFile" />
|
||||
|
@ -361,7 +363,9 @@
|
|||
set_schema="loops.interfaces.IFile" />
|
||||
</class>
|
||||
|
||||
<adapter factory="loops.resource.NoteAdapter" trusted="True" />
|
||||
<adapter factory="loops.resource.NoteAdapter"
|
||||
provides="loops.interfaces.INote"
|
||||
trusted="True" />
|
||||
<class class="loops.resource.NoteAdapter">
|
||||
<require permission="zope.View"
|
||||
interface="loops.interfaces.INote" />
|
||||
|
|
|
@ -207,6 +207,10 @@ Now let's have a look at resources.
|
|||
Using the type machinery we can also specify options that may be used
|
||||
for controlling e.g. storage for external files.
|
||||
|
||||
>>> from loops.interfaces import IFile
|
||||
>>> from loops.resource import FileAdapter
|
||||
>>> component.provideAdapter(FileAdapter, provides=IFile)
|
||||
|
||||
>>> extfile = concepts['extfile'] = Concept(u'External File')
|
||||
>>> ef1 = resources['ef1'] = Resource(u'Extfile #1')
|
||||
>>> ef1.resourceType = extfile
|
||||
|
|
|
@ -41,6 +41,7 @@ from loops.browser.concept import ConceptRelationView
|
|||
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
|
||||
from loops.organize.interfaces import IMemberRegistration
|
||||
from loops.organize.party import getPersonForUser
|
||||
import loops.browser.util
|
||||
|
||||
_ = MessageFactory('zope')
|
||||
|
||||
|
@ -75,11 +76,23 @@ class PasswordWidget(BasePasswordWidget):
|
|||
class MemberRegistration(Form, NodeView):
|
||||
|
||||
form_fields = FormFields(IMemberRegistration).omit('age')
|
||||
template = NamedTemplate('loops.dataform')
|
||||
template = loops.browser.util.dataform
|
||||
label = _(u'Member Registration')
|
||||
|
||||
def __init__(self, context, request):
|
||||
NodeView.__init__(self, context, request)
|
||||
#NodeView.__init__(self, context, request)
|
||||
super(MemberRegistration, self).__init__(context, request)
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return self.template.macros['content']
|
||||
|
||||
@Lazy
|
||||
def item(self):
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
return NodeView.__call__(self, *args, **kw)
|
||||
|
||||
@action(_(u'Register'))
|
||||
def handle_register_action(self, action, data):
|
||||
|
|
|
@ -77,10 +77,12 @@
|
|||
interface="loops.organize.interfaces.IMemberRegistrationManager" />
|
||||
</zope:class>
|
||||
|
||||
<browser:page name="registration.html"
|
||||
for="loops.interfaces.IView"
|
||||
<browser:page
|
||||
for="loops.interfaces.INode"
|
||||
name="register_user.html"
|
||||
class="loops.organize.browser.MemberRegistration"
|
||||
permission="zope.Public" />
|
||||
permission="zope.Public"
|
||||
/>
|
||||
|
||||
<zope:view
|
||||
type="zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
|
|
96
resource.py
96
resource.py
|
@ -34,6 +34,7 @@ from zope.component import adapts
|
|||
from zope.i18nmessageid import MessageFactory
|
||||
from zope.interface import implements
|
||||
from zope.size.interfaces import ISized
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from zope.traversing.api import getName, getParent
|
||||
from persistent import Persistent
|
||||
from cStringIO import StringIO
|
||||
|
@ -52,6 +53,7 @@ from loops.interfaces import IFile, IExternalFile, INote
|
|||
from loops.interfaces import IDocument, ITextDocument, IDocumentSchema, IDocumentView
|
||||
from loops.interfaces import IMediaAsset, IMediaAssetView
|
||||
from loops.interfaces import IResourceManager, IResourceManagerContained
|
||||
from loops.interfaces import ITypeConcept
|
||||
from loops.interfaces import ILoopsContained
|
||||
from loops.interfaces import IIndexAttributes
|
||||
from loops.concept import ResourceRelation
|
||||
|
@ -77,10 +79,13 @@ class Resource(Image, Contained):
|
|||
|
||||
# TODO: remove dependency on Image
|
||||
|
||||
implements(IBaseResource, IResource, IResourceManagerContained, IRelatable, ISized)
|
||||
implements(IBaseResource, IResource, IResourceManagerContained,
|
||||
IRelatable, ISized)
|
||||
|
||||
proxyInterface = IMediaAssetView # obsolete!
|
||||
|
||||
storageName = None
|
||||
|
||||
_size = _width = _height = 0
|
||||
|
||||
def __init__(self, title=u''):
|
||||
|
@ -106,10 +111,16 @@ class Resource(Image, Contained):
|
|||
# TODO (?): check for multiple types (->Error)
|
||||
return concepts and concepts[0] or cm.get('file', None)
|
||||
def setResourceType(self, concept):
|
||||
if concept is None:
|
||||
if concept is None: # this should not happen
|
||||
return
|
||||
current = self.getResourceType()
|
||||
if current != concept:
|
||||
# change storage if necessary, and migrate data
|
||||
oldType = IType(self)
|
||||
from loops.type import ConceptTypeInfo
|
||||
newType = ConceptTypeInfo(concept)
|
||||
self.migrateStorage(oldType, newType)
|
||||
# assign new type parent
|
||||
typePred = self.getLoopsRoot().getConceptManager().getTypePredicate()
|
||||
if typePred is None:
|
||||
raise ValueError('No type predicate found for ' + getName(self))
|
||||
|
@ -122,6 +133,8 @@ class Resource(Image, Contained):
|
|||
return self.resourceType
|
||||
|
||||
def _setData(self, data):
|
||||
#if not data:
|
||||
# return
|
||||
dataFile = StringIO(data) # let File tear it into pieces
|
||||
super(Resource, self)._setData(dataFile)
|
||||
if not self.contentType:
|
||||
|
@ -130,6 +143,7 @@ class Resource(Image, Contained):
|
|||
data = property(Image._getData, _setData)
|
||||
|
||||
def guessContentType(self, data):
|
||||
# probably obsolete, use zope.contenttype.guess_content_type()
|
||||
if not isinstance(data, str): # seems to be a file object
|
||||
data = data.read(20)
|
||||
if data.startswith('%PDF'):
|
||||
|
@ -204,6 +218,32 @@ class Resource(Image, Contained):
|
|||
return '%.1f %s' % (size, unit)
|
||||
#return '%s %s' % (util.getNiceNumber(size), unit)
|
||||
|
||||
# storage migration
|
||||
|
||||
def migrateStorage(self, oldType, newType):
|
||||
oldType = removeSecurityProxy(oldType)
|
||||
newType = removeSecurityProxy(newType)
|
||||
context = removeSecurityProxy(self)
|
||||
oldAdapted = newAdapted = context
|
||||
oldTi = removeSecurityProxy(oldType.typeInterface)
|
||||
if oldTi is not None:
|
||||
oldAdapted = oldTi(context)
|
||||
newTi = removeSecurityProxy(newType.typeInterface)
|
||||
newOptions = {}
|
||||
if newTi is not None:
|
||||
newAdapted = newTi(context)
|
||||
# make sure we use options of new type:
|
||||
newOptions = newType.optionsDict
|
||||
object.__setattr__(newAdapted, 'options', newOptions)
|
||||
#print 'migrateStorage:', newAdapted, newOptions, oldAdapted, oldAdapted.storageName
|
||||
if newOptions.get('storage') != oldAdapted.storageName:
|
||||
data = oldAdapted.data
|
||||
#print 'data', data
|
||||
oldAdapted.data = '' # clear old storage
|
||||
context._storageName = None # let's take storage from new type options
|
||||
context._storageParams = None # "
|
||||
newAdapted.data = data
|
||||
|
||||
|
||||
# Document and MediaAsset are legacy classes, will become obsolete
|
||||
|
||||
|
@ -240,15 +280,41 @@ class FileAdapter(ResourceAdapterBase):
|
|||
_contextAttributes = list(IFile) + list(IBaseResource)
|
||||
_adapterAttributes = ResourceAdapterBase._adapterAttributes + ('data',)
|
||||
|
||||
def setData(self, data): self.context.data = data
|
||||
def setData(self, data):
|
||||
#if self.storageName is None:
|
||||
# self.storageName = 'zopefile'
|
||||
self.storageName = None
|
||||
self.context.data = data
|
||||
def getData(self): return self.context.data
|
||||
data = property(getData, setData)
|
||||
|
||||
@Lazy
|
||||
def options(self):
|
||||
return IType(self.context).optionsDict
|
||||
|
||||
def getStorageName(self):
|
||||
return (getattr(self.context, '_storageName', None)
|
||||
or self.options.get('storage', None))
|
||||
def setStorageName(self, value):
|
||||
self.context._storageName = value
|
||||
storageName = property(getStorageName, setStorageName)
|
||||
|
||||
|
||||
class ExternalFileAdapter(FileAdapter):
|
||||
|
||||
implements(IExternalFile)
|
||||
|
||||
def getStorageParams(self):
|
||||
params = getattr(self.context, '_storageParams', None)
|
||||
if params is not None:
|
||||
return params
|
||||
else:
|
||||
value = self.options.get('storage_parameters') or 'extfiles'
|
||||
return dict(subdirectory=value)
|
||||
def setStorageParams(self, value):
|
||||
self.context._storageParams = value
|
||||
storageParams = property(getStorageParams, setStorageParams)
|
||||
|
||||
@Lazy
|
||||
def externalAddress(self):
|
||||
# or is this an editable attribute?
|
||||
|
@ -256,23 +322,17 @@ class ExternalFileAdapter(FileAdapter):
|
|||
# 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):
|
||||
params = self.options.get('storage_parameters') or 'extfiles'
|
||||
return dict(subdirectory=params)
|
||||
|
||||
def setData(self, data):
|
||||
storage = component.getUtility(IExternalStorage, name=self.storageName)
|
||||
storage.setData(self.externalAddress, data, params=self.storageParams)
|
||||
if not data:
|
||||
return
|
||||
storageParams = self.storageParams
|
||||
storageName = self.storageName
|
||||
storage = component.getUtility(IExternalStorage, name=storageName)
|
||||
storage.setData(self.externalAddress, data, params=storageParams)
|
||||
self.context._size = len(data)
|
||||
# remember storage settings:
|
||||
self.storageParams = storageParams
|
||||
self.storageName = storageName
|
||||
|
||||
def getData(self):
|
||||
storage = component.getUtility(IExternalStorage, name=self.storageName)
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
tal:condition="view/useVersioning">V</th>
|
||||
<th i18n:translate="label_size">Size</th>
|
||||
<th i18n:translate="label_modifdate">Modification Date</th>
|
||||
<th i18n:translate="label_authors">Author(s)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -80,12 +81,7 @@
|
|||
<span tal:replace="row/context/sizeForDisplay|string:">Size</span>
|
||||
</td>
|
||||
<td><span tal:replace="row/modified">modified</span></td>
|
||||
</tr>
|
||||
<tr tal:condition="nothing"
|
||||
tal:attributes="class class">
|
||||
<td colspan="3" style="padding-left: 7em">
|
||||
<i tal:content="description">describing...</i>
|
||||
</td>
|
||||
<td><span tal:replace="row/creators">John</span></td>
|
||||
</tr>
|
||||
</tal:item>
|
||||
</tal:items>
|
||||
|
|
1
setup.py
1
setup.py
|
@ -90,6 +90,7 @@ class SetupManager(object):
|
|||
#ITypeConcept(image).typeInterface = IImage
|
||||
ITypeConcept(textdocument).typeInterface = ITextDocument
|
||||
ITypeConcept(note).typeInterface = INote
|
||||
#ITypeConcept(note).viewName = 'note.html'
|
||||
hasType.conceptType = predicate
|
||||
standard.conceptType = predicate
|
||||
|
||||
|
|
|
@ -141,8 +141,10 @@ Resources
|
|||
---------
|
||||
|
||||
>>> from loops.resource import TextDocumentAdapter
|
||||
>>> from loops.interfaces import IResource, ITextDocument
|
||||
>>> from loops.interfaces import IResource, ITextDocument, IFile
|
||||
>>> component.provideAdapter(TextDocumentAdapter, (IResource,), ITextDocument)
|
||||
>>> from loops.resource import FileAdapter
|
||||
>>> component.provideAdapter(FileAdapter, provides=IFile)
|
||||
|
||||
>>> zope3Id = xrf.getObjectByName('zope3')['id']
|
||||
>>> td01 = resources['td01'] = Resource(u'Doc1')
|
||||
|
|
Loading…
Add table
Reference in a new issue