use new SchemaFactory for fine-grained control of field properties
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2080 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
c2807ce730
commit
5fd6f75a60
6 changed files with 107 additions and 168 deletions
|
@ -580,14 +580,6 @@
|
||||||
permission="zope.ManageContent"
|
permission="zope.ManageContent"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<zope:view
|
|
||||||
type="zope.publisher.interfaces.browser.IBrowserRequest"
|
|
||||||
for="loops.interfaces.IFile"
|
|
||||||
provides="zope.app.form.interfaces.IInputWidget"
|
|
||||||
factory="loops.browser.form.UploadWidget"
|
|
||||||
permission="zope.Public"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- inner HTML views -->
|
<!-- inner HTML views -->
|
||||||
|
|
||||||
<page
|
<page
|
||||||
|
|
188
browser/form.py
188
browser/form.py
|
@ -41,8 +41,8 @@ from zope.security.proxy import isinstance, removeSecurityProxy
|
||||||
from cybertools.ajax import innerHtml
|
from cybertools.ajax import innerHtml
|
||||||
from cybertools.browser.form import FormController
|
from cybertools.browser.form import FormController
|
||||||
from cybertools.composer.interfaces import IInstance
|
from cybertools.composer.interfaces import IInstance
|
||||||
|
from cybertools.composer.schema.interfaces import ISchemaFactory
|
||||||
from cybertools.composer.schema.browser.common import schema_macros, schema_edit_macros
|
from cybertools.composer.schema.browser.common import schema_macros, schema_edit_macros
|
||||||
from cybertools.composer.schema.util import getSchemaFromInterface
|
|
||||||
from cybertools.typology.interfaces import IType, ITypeManager
|
from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.concept import Concept, ResourceRelation
|
from loops.concept import Concept, ResourceRelation
|
||||||
|
@ -58,26 +58,6 @@ from loops.util import _
|
||||||
from loops.versioning.interfaces import IVersionable
|
from loops.versioning.interfaces import IVersionable
|
||||||
|
|
||||||
|
|
||||||
# special widgets
|
|
||||||
|
|
||||||
class UploadWidget(FileWidget):
|
|
||||||
|
|
||||||
def _toFieldValue(self, input):
|
|
||||||
# not used at the moment as the context object is updated
|
|
||||||
# via EditObject.updateFields()
|
|
||||||
fn = getattr(input, 'filename', '') # zope.publisher.browser.FileUpload
|
|
||||||
self.request.form['filename'] = fn
|
|
||||||
if input:
|
|
||||||
self.request.form['_tempfilename'] = input.headers.get('_tempfilename')
|
|
||||||
# f = self.context
|
|
||||||
# f.extfiledata = tempfilename # provide for rename
|
|
||||||
if fn:
|
|
||||||
contentType = guess_content_type(fn)
|
|
||||||
if contentType:
|
|
||||||
request.form['form.contentType'] = contentType
|
|
||||||
return super(UploadWidget, self)._toFieldValue(input)
|
|
||||||
|
|
||||||
|
|
||||||
# forms
|
# forms
|
||||||
|
|
||||||
class ObjectForm(NodeView):
|
class ObjectForm(NodeView):
|
||||||
|
@ -91,7 +71,13 @@ class ObjectForm(NodeView):
|
||||||
def __init__(self, context, request):
|
def __init__(self, context, request):
|
||||||
super(ObjectForm, self).__init__(context, request)
|
super(ObjectForm, self).__init__(context, request)
|
||||||
|
|
||||||
# cybertools.composer.schema support
|
@Lazy
|
||||||
|
def adapted(self):
|
||||||
|
return adapted(self.context)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def typeInterface(self):
|
||||||
|
return IType(self.context).typeInterface or ITextDocument
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schemaMacros(self):
|
def schemaMacros(self):
|
||||||
|
@ -103,10 +89,13 @@ class ObjectForm(NodeView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return getSchemaFromInterface(self.typeInterface, manager=self)
|
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
|
||||||
|
return schemaFactory(self.typeInterface, manager=self,
|
||||||
|
request=self.request)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def fields(self):
|
def fields(self):
|
||||||
|
return self.schema.fields
|
||||||
fields = self.schema.fields
|
fields = self.schema.fields
|
||||||
fields.data.height = 10
|
fields.data.height = 10
|
||||||
ifc = self.typeInterface
|
ifc = self.typeInterface
|
||||||
|
@ -130,41 +119,12 @@ class ObjectForm(NodeView):
|
||||||
def instance(self):
|
def instance(self):
|
||||||
return IInstance(adapted(self.context))
|
return IInstance(adapted(self.context))
|
||||||
|
|
||||||
# zope.formlib support
|
|
||||||
|
|
||||||
@property
|
|
||||||
def form_fields(self):
|
|
||||||
ifc = self.typeInterface
|
|
||||||
ff = FormFields(ifc)
|
|
||||||
if ifc in widgetControllers:
|
|
||||||
wc = widgetControllers[ifc](self.context, self.request)
|
|
||||||
ff = wc.modifyFormFields(ff)
|
|
||||||
return ff
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
if self._isSetUp:
|
|
||||||
return
|
|
||||||
self.setUpWidgets()
|
|
||||||
desc = self.widgets.get('description')
|
|
||||||
if desc:
|
|
||||||
desc.height = 2
|
|
||||||
if self.typeInterface in widgetControllers:
|
|
||||||
wc = widgetControllers[self.typeInterface](self.context, self.request)
|
|
||||||
wc.modifyWidgetSetup(self.widgets)
|
|
||||||
self._isSetUp = True
|
|
||||||
|
|
||||||
# general methods
|
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
response = self.request.response
|
response = self.request.response
|
||||||
response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT')
|
response.setHeader('Expires', 'Sat, 1 Jan 2000 00:00:00 GMT')
|
||||||
response.setHeader('Pragma', 'no-cache')
|
response.setHeader('Pragma', 'no-cache')
|
||||||
return innerHtml(self)
|
return innerHtml(self)
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def typeInterface(self):
|
|
||||||
return IType(self.context).typeInterface or ITextDocument
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def defaultPredicate(self):
|
def defaultPredicate(self):
|
||||||
return self.loopsRoot.getConceptManager().getDefaultPredicate()
|
return self.loopsRoot.getConceptManager().getDefaultPredicate()
|
||||||
|
@ -194,55 +154,6 @@ class ObjectForm(NodeView):
|
||||||
for o in result])
|
for o in result])
|
||||||
|
|
||||||
|
|
||||||
class WidgetController(object):
|
|
||||||
|
|
||||||
def __init__(self, context, request):
|
|
||||||
self.context = context
|
|
||||||
self.request = request
|
|
||||||
|
|
||||||
def modifySchemaFields(self, fields):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def modifyFormFields(self, formFields):
|
|
||||||
return formFields
|
|
||||||
|
|
||||||
def modifyWidgetSetup(self, widgets):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoteWidgetController(WidgetController):
|
|
||||||
|
|
||||||
def modifySchemaFields(self, fields):
|
|
||||||
del fields['description']
|
|
||||||
fields.data.height = 5
|
|
||||||
|
|
||||||
def modifyFormFields(self, formFields):
|
|
||||||
return formFields.omit('description')
|
|
||||||
|
|
||||||
def modifyWidgetSetup(self, widgets):
|
|
||||||
widgets['data'].height = 5
|
|
||||||
|
|
||||||
|
|
||||||
class FileWidgetController(WidgetController):
|
|
||||||
|
|
||||||
def modifySchemaFields(self, fields):
|
|
||||||
if self.request.principal.id != 'rootadmin':
|
|
||||||
del fields['contentType']
|
|
||||||
|
|
||||||
|
|
||||||
def modifyFormFields(self, formFields):
|
|
||||||
if self.request.principal.id == 'rootadmin':
|
|
||||||
return formFields
|
|
||||||
return formFields.omit('contentType')
|
|
||||||
|
|
||||||
|
|
||||||
widgetControllers = {
|
|
||||||
INote: NoteWidgetController,
|
|
||||||
IFile: FileWidgetController,
|
|
||||||
IExternalFile: FileWidgetController,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EditObjectForm(ObjectForm, EditForm):
|
class EditObjectForm(ObjectForm, EditForm):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -275,13 +186,13 @@ class CreateObjectForm(ObjectForm, Form):
|
||||||
form_action = 'create_resource'
|
form_action = 'create_resource'
|
||||||
dialog_name = 'create'
|
dialog_name = 'create'
|
||||||
|
|
||||||
# cybertools.composer.schema support
|
@Lazy
|
||||||
|
def adapted(self):
|
||||||
|
return self.typeInterface(Resource())
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def instance(self):
|
def instance(self):
|
||||||
return IInstance(Concept())
|
return IInstance(Resource())
|
||||||
|
|
||||||
# general methods
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def typeInterface(self):
|
def typeInterface(self):
|
||||||
|
@ -307,13 +218,16 @@ class CreateObjectForm(ObjectForm, Form):
|
||||||
class InnerForm(CreateObjectForm):
|
class InnerForm(CreateObjectForm):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
#def macro(self): return self.template.macros['fields']
|
|
||||||
def macro(self): return self.schemaMacros['fields']
|
def macro(self): return self.schemaMacros['fields']
|
||||||
|
|
||||||
|
|
||||||
# processing form input
|
# processing form input
|
||||||
|
|
||||||
class EditObject(FormController):
|
class EditObject(FormController):
|
||||||
|
""" Note that ``self.context`` of this adapter may be different from
|
||||||
|
``self.object``, the object it acts upon, e.g. when this object
|
||||||
|
is created during the update processing.
|
||||||
|
"""
|
||||||
|
|
||||||
prefix = 'form.'
|
prefix = 'form.'
|
||||||
conceptPrefix = 'assignments.'
|
conceptPrefix = 'assignments.'
|
||||||
|
@ -321,9 +235,18 @@ class EditObject(FormController):
|
||||||
old = None
|
old = None
|
||||||
selected = None
|
selected = None
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def adapted(self):
|
||||||
|
return adapted(self.object)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def typeInterface(self):
|
||||||
|
return IType(self.object).typeInterface or ITextDocument
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return getSchemaFromInterface(self.typeInterface, manager=self)
|
schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
|
||||||
|
return schemaFactory(self.typeInterface)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def fields(self):
|
def fields(self):
|
||||||
|
@ -331,12 +254,7 @@ class EditObject(FormController):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def instance(self):
|
def instance(self):
|
||||||
return component.getAdapter(adapted(self.object), IInstance, name='editor')
|
return component.getAdapter(self.adapted, IInstance, name='editor')
|
||||||
#return IInstance(adapted(self.object), name='editor')
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def typeInterface(self):
|
|
||||||
return IType(self.object).typeInterface or ITextDocument
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def loopsRoot(self):
|
def loopsRoot(self):
|
||||||
|
@ -398,50 +316,6 @@ class EditObject(FormController):
|
||||||
def fieldHandlers(self):
|
def fieldHandlers(self):
|
||||||
return dict(fileupload=self.handleFileUpload)
|
return dict(fileupload=self.handleFileUpload)
|
||||||
|
|
||||||
def xupdateFields(self): # obsolete
|
|
||||||
obj = self.object
|
|
||||||
errors = {}
|
|
||||||
form = self.request.form
|
|
||||||
ti = IType(obj).typeInterface
|
|
||||||
if ti is not None:
|
|
||||||
adapted = ti(obj)
|
|
||||||
else:
|
|
||||||
adapted = obj
|
|
||||||
for k in form.keys():
|
|
||||||
# TODO: use self.view.form_fields or better: IInstance(adapted)
|
|
||||||
if k.startswith(self.prefix):
|
|
||||||
fn = k[len(self.prefix):]
|
|
||||||
if fn in ('action', 'type', 'data.used') or fn.endswith('-empty-marker'):
|
|
||||||
continue
|
|
||||||
value = form[k]
|
|
||||||
if fn.startswith(self.conceptPrefix) and value:
|
|
||||||
self.collectConcepts(fn[len(self.conceptPrefix):], value)
|
|
||||||
else:
|
|
||||||
if not value and fn == 'data' and IFile.providedBy(adapted):
|
|
||||||
# empty file data - don't change
|
|
||||||
continue
|
|
||||||
if isinstance(value, FileUpload):
|
|
||||||
filename = getattr(value, 'filename', '')
|
|
||||||
value = value.read()
|
|
||||||
if filename:
|
|
||||||
#self.request.form['filename'] = filename
|
|
||||||
contentType = guess_content_type(filename, value[:100])
|
|
||||||
if contentType:
|
|
||||||
ct = contentType[0]
|
|
||||||
self.request.form['form.contentType'] = ct
|
|
||||||
adapted.contentType = ct
|
|
||||||
adapted.localFilename = filename
|
|
||||||
if fn == 'title' and not value:
|
|
||||||
# TODO: provide general validation mechanism
|
|
||||||
errors[fn] = 'Field %s must not be empty' % fn
|
|
||||||
else:
|
|
||||||
# TODO: provide unmarshalling depending on field type
|
|
||||||
setattr(adapted, fn, value)
|
|
||||||
if self.old or self.selected:
|
|
||||||
self.assignConcepts(obj)
|
|
||||||
notify(ObjectModifiedEvent(obj))
|
|
||||||
return errors
|
|
||||||
|
|
||||||
def collectConcepts(self, fieldName, value):
|
def collectConcepts(self, fieldName, value):
|
||||||
if self.old is None: self.old = []
|
if self.old is None: self.old = []
|
||||||
if self.selected is None: self.selected = []
|
if self.selected is None: self.selected = []
|
||||||
|
|
|
@ -392,7 +392,6 @@
|
||||||
interface="zope.filerepresentation.interfaces.IFileFactory" />
|
interface="zope.filerepresentation.interfaces.IFileFactory" />
|
||||||
</class>
|
</class>
|
||||||
|
|
||||||
|
|
||||||
<adapter factory="cybertools.composer.schema.instance.Instance" />
|
<adapter factory="cybertools.composer.schema.instance.Instance" />
|
||||||
<adapter factory="cybertools.composer.schema.instance.Editor"
|
<adapter factory="cybertools.composer.schema.instance.Editor"
|
||||||
name="editor" />
|
name="editor" />
|
||||||
|
@ -402,6 +401,11 @@
|
||||||
<adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance"
|
<adapter factory="cybertools.composer.schema.field.FileUploadFieldInstance"
|
||||||
name="fileupload" />
|
name="fileupload" />
|
||||||
|
|
||||||
|
<adapter factory="loops.schema.ResourceSchemaFactory" />
|
||||||
|
<adapter factory="loops.schema.ResourceSchemaFactory"
|
||||||
|
for="loops.interfaces.IResource" />
|
||||||
|
<adapter factory="loops.schema.FileSchemaFactory" />
|
||||||
|
<adapter factory="loops.schema.NoteSchemaFactory" />
|
||||||
|
|
||||||
<adapter factory="loops.setup.SetupManager" />
|
<adapter factory="loops.setup.SetupManager" />
|
||||||
<adapter factory="loops.external.NodesLoader" />
|
<adapter factory="loops.external.NodesLoader" />
|
||||||
|
|
|
@ -375,8 +375,8 @@ class TextDocumentAdapter(DocumentAdapter):
|
||||||
""" A type adapter for providing text document functionality for resources.
|
""" A type adapter for providing text document functionality for resources.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
implements(IDocument)
|
implements(ITextDocument)
|
||||||
_contextAttributes = list(IDocument) + list(IBaseResource)
|
_contextAttributes = list(ITextDocument) + list(IBaseResource)
|
||||||
|
|
||||||
|
|
||||||
class NoteAdapter(DocumentAdapter):
|
class NoteAdapter(DocumentAdapter):
|
||||||
|
|
62
schema.py
Normal file
62
schema.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Specialized schema factories
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.component import adapts
|
||||||
|
|
||||||
|
from cybertools.composer.schema.factory import SchemaFactory
|
||||||
|
from loops.interfaces import IResourceAdapter, IFile, INote
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceSchemaFactory(SchemaFactory):
|
||||||
|
|
||||||
|
adapts(IResourceAdapter)
|
||||||
|
|
||||||
|
def __call__(self, interface, **kw):
|
||||||
|
schema = super(ResourceSchemaFactory, self).__call__(interface, **kw)
|
||||||
|
schema.fields.data.height = 10
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
class FileSchemaFactory(SchemaFactory):
|
||||||
|
|
||||||
|
adapts(IFile)
|
||||||
|
|
||||||
|
def __call__(self, interface, **kw):
|
||||||
|
schema = super(FileSchemaFactory, self).__call__(interface, **kw)
|
||||||
|
if 'request' in kw and kw['request'].principal.id != 'rootadmin':
|
||||||
|
del schema.fields['contentType']
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
class NoteSchemaFactory(SchemaFactory):
|
||||||
|
|
||||||
|
adapts(INote)
|
||||||
|
|
||||||
|
def __call__(self, interface, **kw):
|
||||||
|
schema = super(NoteSchemaFactory, self).__call__(interface, **kw)
|
||||||
|
del schema.fields['description']
|
||||||
|
schema.fields.data.height = 5
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ from zope.app.security.interfaces import IAuthentication
|
||||||
from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
|
from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
|
||||||
from zope.dublincore.interfaces import IZopeDublinCore
|
from zope.dublincore.interfaces import IZopeDublinCore
|
||||||
|
|
||||||
|
from cybertools.composer.schema.factory import SchemaFactory
|
||||||
from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance
|
from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance
|
||||||
from cybertools.composer.schema.instance import Instance, Editor
|
from cybertools.composer.schema.instance import Instance, Editor
|
||||||
from cybertools.relation.tests import IntIdsStub
|
from cybertools.relation.tests import IntIdsStub
|
||||||
|
@ -34,6 +35,7 @@ from loops.concept import Concept
|
||||||
from loops.concept import IndexAttributes as ConceptIndexAttributes
|
from loops.concept import IndexAttributes as ConceptIndexAttributes
|
||||||
from loops.resource import Resource, FileAdapter
|
from loops.resource import Resource, FileAdapter
|
||||||
from loops.resource import IndexAttributes as ResourceIndexAttributes
|
from loops.resource import IndexAttributes as ResourceIndexAttributes
|
||||||
|
from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory
|
||||||
from loops.setup import SetupManager, addObject
|
from loops.setup import SetupManager, addObject
|
||||||
from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept
|
from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept
|
||||||
|
|
||||||
|
@ -67,6 +69,11 @@ class TestSite(object):
|
||||||
component.provideAdapter(FieldInstance)
|
component.provideAdapter(FieldInstance)
|
||||||
component.provideAdapter(NumberFieldInstance, name='number')
|
component.provideAdapter(NumberFieldInstance, name='number')
|
||||||
|
|
||||||
|
component.provideAdapter(SchemaFactory)
|
||||||
|
component.provideAdapter(ResourceSchemaFactory)
|
||||||
|
component.provideAdapter(FileSchemaFactory)
|
||||||
|
component.provideAdapter(NoteSchemaFactory)
|
||||||
|
|
||||||
component.getSiteManager().registerHandler(invalidateRelations,
|
component.getSiteManager().registerHandler(invalidateRelations,
|
||||||
(ILoopsObject, IObjectRemovedEvent))
|
(ILoopsObject, IObjectRemovedEvent))
|
||||||
component.getSiteManager().registerHandler(removeRelation,
|
component.getSiteManager().registerHandler(removeRelation,
|
||||||
|
|
Loading…
Add table
Reference in a new issue