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:
		
							parent
							
								
									69af3d37cb
								
							
						
					
					
						commit
						1747ce67e2
					
				
					 9 changed files with 244 additions and 12 deletions
				
			
		|  | @ -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,8 +79,9 @@ Using a more specialized schema factory | |||
| --------------------------------------- | ||||
| 
 | ||||
|   >>> class PersonSchemaFactory(SchemaFactory): | ||||
|   ...     def __call__(self, manager=None): | ||||
|   ...         schema = super(PersonSchemaFactory, self).__call__(manager) | ||||
|   ...     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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 = '' | ||||
|  |  | |||
							
								
								
									
										153
									
								
								composer/schema/browser/form.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								composer/schema/browser/form.py
									
										
									
									
									
										Normal 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) | ||||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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') | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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. | ||||
|         """ | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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', | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 helmutm
						helmutm