Provide standard Form and CreateForm view classes in composer.schema

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2087 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-10-07 08:28:34 +00:00
parent 69af3d37cb
commit 1747ce67e2
9 changed files with 244 additions and 12 deletions

View file

@ -62,6 +62,8 @@ Creating a schema from an interface
>>> class Person(object):
... implements(IPerson)
... def __init__(self, firstName=u'', lastName=u'', age=None):
... self.firstName, self.lastName, self.age = firstName, lastName, age
>>> from cybertools.composer.schema.interfaces import ISchemaFactory
>>> factory = ISchemaFactory(Person())
@ -77,9 +79,10 @@ Using a more specialized schema factory
---------------------------------------
>>> class PersonSchemaFactory(SchemaFactory):
... def __call__(self, manager=None):
... schema = super(PersonSchemaFactory, self).__call__(manager)
... del schema.fields['firstName'] # don't show first name
... def __call__(self, interface, **kw):
... schema = super(PersonSchemaFactory, self).__call__(interface)
... if 'firstName' in schema.fields.keys():
... del schema.fields['firstName'] # don't show first name
... return schema
>>> component.provideAdapter(PersonSchemaFactory, (IPerson,))
@ -90,3 +93,71 @@ Using a more specialized schema factory
lastName Last name textline
age Age number
Access and update a context object using a schema-based form
------------------------------------------------------------
>>> from zope.publisher.browser import TestRequest
>>> from cybertools.composer.schema.browser.form import Form
We first have to provide adapters for special field types ('number' in
this case) and an instance adapter that manages the access to the
context object.
>>> from cybertools.composer.schema.field import NumberFieldInstance
>>> component.provideAdapter(NumberFieldInstance, name='number')
>>> from cybertools.composer.schema.instance import Instance
>>> component.provideAdapter(Instance)
>>> person = Person(u'John', u'Miller', 33)
Note that the first name is not shown as we excluded it via the schema
factory above. The age field is a number, but is shown here as a
string as the instance is accessed using 'edit' mode, i.e. provide
data suitable for showing on an HTML form.
>>> form = Form(person, TestRequest())
>>> form.interface = IPerson
>>> form.data
{'lastName': u'Miller', 'age': '33'}
For editing we have to provide another instance adapter.
>>> from cybertools.composer.schema.instance import Editor
>>> component.provideAdapter(Editor, name='editor')
>>> input = dict(lastName='Miller', age='40', action='update')
>>> request = TestRequest(form=input)
>>> form = Form(person, request)
>>> form.interface = IPerson
>>> form.nextUrl = 'dummy_url' # avoid hassle with IAbsoluteURL view...
>>> form.update()
False
>>> person.age
40
Create a new object using a schema-based form
---------------------------------------------
>>> from cybertools.composer.schema.browser.form import CreateForm
>>> container = dict()
>>> input = dict(lastName=u'Smith', age='28', action='update')
>>> form = CreateForm(container, TestRequest(form=input))
>>> form.interface = IPerson
>>> form.factory = Person
>>> form.nextUrl = 'dummy_url' # avoid hassle with IAbsoluteURL view...
>>> form.getName = lambda x: x.lastName.lower()
>>> form.data
{'lastName': u'Smith', 'age': '28'}
>>> form.update()
False
>>> p2 = container['smith']
>>> p2.lastName, p2.age
(u'Smith', 28)

View file

@ -124,8 +124,8 @@ class BaseView(object):
submit=getCheckoutView,
)
#@Lazy
def nextUrl(self):
#@Lazy # must be method for Zope 2.9 compatibility :-( ???
def getNextUrl(self):
#viewName = 'thankyou.html'
viewName = ''
url = ''

View file

@ -0,0 +1,153 @@
#
# Copyright (c) 2007 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
#
"""
View(s) for forms based on composer.schema.
$Id$
"""
from zope import component
from zope.app.container.interfaces import INameChooser
from zope.cachedescriptors.property import Lazy
from zope.interface import Interface
from zope.event import notify
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.traversing.browser.absoluteurl import absoluteURL
from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.browser.common import schema_macros, schema_edit_macros
from cybertools.composer.schema.interfaces import ISchemaFactory
from cybertools.composer.schema.schema import FormState
class Form(object):
interface = Interface
fieldHandlers = {} # default, don't update!
formState = FormState() # dummy, don't update!
message = u'Object changed.'
def __init__(self, context, request):
self.context = context
self.request = request
@property
def schemaMacros(self):
return schema_macros.macros
@property
def schemaEditMacros(self):
return schema_edit_macros.macros
@Lazy
def object(self):
return self.context
@Lazy
def schema(self):
schemaFactory = component.getAdapter(self.object, ISchemaFactory)
return schemaFactory(self.interface, manager=self,
request=self.request)
@Lazy
def fields(self):
return self.schema.fields
@Lazy
def data(self):
""" Provide data based on context object.
May be overwritten by subclass.
"""
instance = self.instance
instance.template = self.schema
data = instance.applyTemplate(mode='edit')
form = self.request.form
for k, v in data.items():
#overwrite data with values from request.form
if k in form:
data[k] = form[k]
return data
@Lazy
def instance(self):
return IInstance(self.object)
def update(self):
""" Process form data - store in context object.
May be overwritten by subclass.
"""
form = self.request.form
if not form.get('action'):
return True
obj = self.object
instance = component.getAdapter(obj, IInstance, name='editor')
instance.template = self.schema
self.formState = formState = instance.applyTemplate(data=form,
fieldHandlers=self.fieldHandlers)
if formState.severity > 0:
# show form again
return True
if formState.changed:
notify(ObjectModifiedEvent(obj))
url = '%s?messsage=%s' % (self.nextUrl, self.message)
self.request.response.redirect(url)
return False
@Lazy
def nextUrl(self):
return absoluteURL(self.object, self.request)
class CreateForm(Form):
factory = None # overwrite!
message = u'Object created.'
@Lazy
def object(self):
return self.factory()
@Lazy
def container(self):
return self.context
def update(self):
form = self.request.form
if not form.get('action'):
return True
obj = self.object
instance = component.getAdapter(obj, IInstance, name='editor')
instance.template = self.schema
self.formState = formState = instance.applyTemplate(data=form,
fieldHandlers=self.fieldHandlers)
if formState.severity > 0:
# show form again
return True
container = self.container
name = self.getName(obj)
container[name] = obj
notify(ObjectCreatedEvent(obj))
notify(ObjectModifiedEvent(obj))
url = '%s?messsage=%s' % (self.nextUrl, self.message)
self.request.response.redirect(url)
return False
def getName(self, obj):
name = getattr(obj, 'name', getattr(obj, 'title'))
return INameChooser(container).chooseName(name, obj)

View file

@ -32,6 +32,8 @@ from cybertools.composer.schema.schema import FormState
class SchemaView(BaseView):
""" View for schema objects.
"""
formState = FormState()
@ -94,6 +96,6 @@ class SchemaView(BaseView):
if newClient:
clientName = manager.addClient(client)
self.setClientName(clientName)
self.request.response.redirect(self.nextUrl())
self.request.response.redirect(self.getNextUrl())
return False

View file

@ -117,7 +117,7 @@ class FieldInstance(object):
def unmarshall(self, value):
return toUnicode(value) or u''
def validate(self, value):
def validate(self, value, data=None):
if not value and self.context.required:
self.setError('required_missing')
@ -144,7 +144,7 @@ class NumberFieldInstance(FieldInstance):
return None
return int(value)
def validate(self, value):
def validate(self, value, data=None):
if value in ('', None):
if self.context.required:
self.setError('required_missing')

View file

@ -102,7 +102,7 @@ class Editor(BaseInstance):
for f in self.template.fields:
fi = f.getFieldInstance()
value = data.get(f.name)
fi.validate(value)
fi.validate(value, data)
formState.fieldInstances.append(fi)
formState.severity = max(formState.severity, fi.severity)
return formState
@ -202,7 +202,7 @@ class ClientInstanceEditor(ClientInstance):
fi = f.getFieldInstance()
#value = fi.unmarshall(data.get(f.name))
value = data.get(f.name)
fi.validate(value)
fi.validate(value, data)
formState.fieldInstances.append(fi)
formState.severity = max(formState.severity, fi.severity)
return formState

View file

@ -202,9 +202,12 @@ class IFieldInstance(Interface):
value given.
"""
def validate(value):
def validate(value, data=None):
""" Check if the value given is valid. Return an object implementing
IFieldState.
Optionally, in addition the full data set may be given to
allow for checking more than one data element.
"""

View file

@ -80,6 +80,9 @@ class FormError(object):
def __str__(self):
return self.title
def __repr__(self):
return "FormError('%s')" % self.title
formErrors = dict(
required_missing=FormError(u'Missing data for required field',

View file

@ -340,5 +340,5 @@ class RegistrationTemplateView(BaseView):
if s in allServices and s not in newServices]
regs.unregister(toDelete)
#return True
self.request.response.redirect(self.nextUrl())
self.request.response.redirect(self.getNextUrl())
return False