diff --git a/README.txt b/README.txt index 7703b04..142096e 100755 --- a/README.txt +++ b/README.txt @@ -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 diff --git a/browser/common.py b/browser/common.py index f90bff4..74730d0 100644 --- a/browser/common.py +++ b/browser/common.py @@ -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() diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index 38c11be..3e2fed5 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -86,6 +86,7 @@ tal:condition="view/useVersioning">V Size Modification Date + Author(s) Type - Type - - - - describing... - + 2007-03-30 + John diff --git a/browser/configure.zcml b/browser/configure.zcml index 08b6042..c65efa5 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -77,10 +77,10 @@ for="zope.interface.Interface" name="loops.pageform" /> - + name="loops.dataform" />--> -
+

@@ -142,13 +143,9 @@ tal:condition="nocall:target">
Resource Title -
- Description -
@@ -169,13 +166,9 @@ tal:condition="nocall:target">
Resource Title -
- Description -
diff --git a/browser/resource.py b/browser/resource.py index 1ed3ad4..fc20f2b 100644 --- a/browser/resource.py +++ b/browser/resource.py @@ -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): diff --git a/browser/util.py b/browser/util.py index 129ca60..5e1d818 100644 --- a/browser/util.py +++ b/browser/util.py @@ -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')) diff --git a/common.py b/common.py index f1ab5e4..b955c0a 100644 --- a/common.py +++ b/common.py @@ -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 diff --git a/configure.zcml b/configure.zcml index 05687ee..d482317 100644 --- a/configure.zcml +++ b/configure.zcml @@ -344,7 +344,9 @@ set_schema="loops.query.IQueryConcept" /> - + @@ -361,7 +363,9 @@ set_schema="loops.interfaces.IFile" /> - + diff --git a/helpers.txt b/helpers.txt index 9b0e35c..144b8f4 100755 --- a/helpers.txt +++ b/helpers.txt @@ -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 diff --git a/organize/browser.py b/organize/browser.py index 7df85a8..5174825 100644 --- a/organize/browser.py +++ b/organize/browser.py @@ -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): diff --git a/organize/configure.zcml b/organize/configure.zcml index 1b2611e..91331a2 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -77,10 +77,12 @@ interface="loops.organize.interfaces.IMemberRegistrationManager" /> - + permission="zope.Public" + /> 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) diff --git a/search/search.pt b/search/search.pt index 0faf31f..4b483bf 100644 --- a/search/search.pt +++ b/search/search.pt @@ -54,6 +54,7 @@ tal:condition="view/useVersioning">V Size Modification Date + Author(s) @@ -80,12 +81,7 @@ Size modified - - - - describing... - + John diff --git a/setup.py b/setup.py index 172a098..9946f46 100644 --- a/setup.py +++ b/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 diff --git a/xmlrpc/README.txt b/xmlrpc/README.txt index b5db9b2..4a99d2f 100755 --- a/xmlrpc/README.txt +++ b/xmlrpc/README.txt @@ -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')