When the meta.zcml of pyramid_formish is included within your
pyramid application, you can make use of the formish:form ZCML
directive. A formish:form configures one or more special
pyramid “view” callables that render a form, making use of a
user-defined “form controller”.

The ZCML directive requires Python code in the form of a form
controller.

You refer to the form controller within a formish:form ZCML
directive using the controller attribute, which is a dotted Python
name referring to the form controller class:

The above example assumes that there is a forms module which lives
in the same directory as the configure.zcml of your application,
and that it contains a form controller class named
AddCommunityCommunityController. The controller attribute
names a controller for this form definition.

The for, name, and renderer attributes of a
formish:form tag mirror the meaning of the meanings of these names
in pyramidview ZCML directive.

for represents the class or interface which the context must
implement for this view to be invoked. It is optional. If it is not
supplied, the form will be invokable against any context.

name is the view name. It is optional. If it is not supplied, it
defaults to the empty string '', which implies that the form will
be the default view for its context.

renderer is the path to a Chameleon ZPT template which will be used to
render the form when it is first presented, or redisplay the form with errors
when form validation fails. The template is either a pyramid
“resource specification” or an absolute or ZCML-package-relative path to an
on-disk template. It is optional. If it is not supplied, the __call__
method of the form controller must return a webob.Response object
rather than a dictionary.

The form_id tag represents the HTML id attribute value that
the form will use when rendered.

method indicates the form submission method (the method
attribute of the HTML tag representing the form). It must be one of
GET or POST. It is optional. If it is not provided, POST
is assumed.

The template in templates/form_template.pt might look something
like this:

Any number of formish:action tags can be present within a
formish:form tag.

Each formish:action tag represents a submit button at the bottom
of a rendered form that will be given an HTML “value” matching the
name attribute. When this button is pressed, the value of
name will be present in the request.params dictionary. The
value of the button (the text visible to the user) will be the value
of the title attribute.

The name attribute of an action tag also represents the name of a
handler for an action. Handlers are defined on form controller
classes as a method of the form controller class named
handle_<actionname>. A handler method is invoked only when the
value of the param attribute for its action is present as a key in
the request.params dictionary and when the submission validates
properly (or when validate="false" is present in the action
definition).

A form controller is a Python class which has the following
responsibilities:

Provide the default values for the form’s fields.

Provide the fields used by the form.

Provide the widgets used to render the form’s fields.

Provide a display method for the form.

Provide one or more handlers for the form’s actions that are
invoked by pyramid_formish after succesful validation.

A form controller may also (but commonly does not) provide a method
that does custom validation of a form submission.

Each responsibility of a form controller is fulfilled by a method of
the form controller. This is of course not the only way to factor
this particular problem (for example, it would have been possible to
have a single method responsible for both returning fields and
widgets), but the division seems to be the “least worst” way to factor
the problem. The division makes the form controller testable; in
particular, the only conditions in form controller methods are pure
business logic conditions, not “framework meta” conditions (such as
“is this a POST request?”).

The constructor of a form controller class should accept two arguments:
context and request. The context is the pyramid context
of the view which creates the form controller, and the request is the
WebOb request object. For example:

The constructor for a form controller is called whenever a request that
displays or validates a form is handled. Like a pyramid view, a form
controller’s lifecycle is no longer than the lifecycle of a single
pyramid request.

The imports and associated APIs defined in the examples above and
below are fictional, but for purposes of example, we’ll assume that
the my.package.security module offers an API which allows the
developer to determine whether a “workflow” is available for the
current context representing a dynamic set of choices based on the
current state of the context; furthermore it offers an API to see if
there are any valid security transitions for the current user
associated with this workflow. This sort of thing is typical in a
content management system. Although it is purely fictional, this
example hopefully demonstrates that we can influence both the form and
the schema as necessary based on a set of conditions in the handler’s
initialization.

The form controller provides default values to a Formish form via
its form_defaults method. The form_defaults method of a form
controller accepts no arguments, and should return a dictionary
mapping a form field name to a Python value.

A form controller provides Formish with the fields of a form via its
form_fields method. If defined, it must return a sequence of
two-tuples. Each tuple in the returned value should be of a certain
composition: the first value in the tuple should be a string
containing the field name, the second value should a a
schemaish.Structure object representing a data type. The first
value in the tuple should match the name supplied as a dictionary key
in the form_defaults method. The second value in the tuple should
be a schemaish Structure object, such as a schemish.String or
another data type. These types of objects often make use of
validatish validators. For example:

frommy.packageimportsecurityimportschemaishfromvalidatishimportvalidatortags_field=schemaish.Sequence(schemaish.String())description_field=schemaish.String(description=('This description will appear in search results and ''on the community listing page. Please limit your ''description to 100 words or less'),validator=validator.All(validator.Length(max=500),validator.Required()))text_field=schemaish.String(description=('This text will appear on the Overview page for this ''community. You can use this to describe the ''community or to make a special announcement.'))security_field=schemaish.String(description=('Items marked as private can only be seen by ''members of this community.'))classAddCommunityFormController(object):def__init__(self,context,request):self.context=contextself.request=requestself.workflow=security.get_workflow(context)defform_fields(self):fields=[('title',title_field),('tags',tags_field),('description',description_field),('text',text_field),]ifself.workflowisnotNoneandself.workflow.states:fields.append(('security_state',security_field))returnfields

The structure returned by form_fields is the ordered set of data
types of fields associated with a form, as well as any validation
constraints for individual fields on the form. Note that the actual
field objects it returns don’t need to be reconstructed on every
request; they can be shared between requests, as in the above example.

A result of form_fields does not describe the user interface
elements associated with the fields it describes (this is the job of
widgets).

If a form controller does not supply a form_fields method, an
error is raised.

THe schemaish package allows you to define a set of fields in a
schema, which is spelled as a Python class definition with
class-level attributes as named structure objects. This spelling is
not directly supported by pyramid_formish, largely
because it doesn’t match the idea of conditional fields very well.

Widgets are associated with fields via the form_widgets method of
a form controller. The form_widgets method accepts a list of
fields (this is really just the return value of the form_fields
method of your form controller), and should return a dictionary. Each
of the keys in the dictionary should be a field name, and the value
should be a Formish widget. For example:

The display method of a form controller is its __call__ method.
The __call__ method accepts no arguments. It must return either a
dictionary or a WebOb response object. If the display method
returns a dictionary, the renderer associated with the form
controller’s ZCML renderer attribute (typically a template) will
be used to render the dictionary to a response. Here’s an example of
a form controller with a display method on it.

If there is no key in in request.params dictionary which matches
the param value of a particular formish:action associated with
a form, the __call__ of the controller is called and the form is
displayed. Likewise, if a form is submitted, and validation fails,
the __call__ of the controller is called and the form is
redisplayed with errors.

For example, if the form we’re defining above is invoked with a
request that has a params dict that has the value cancel as a key,
the handle_cancel method of the .forms.AddCommunityController
handler will be called after validation is performed. But if neither
submit nor cancel is present in request.params, the
__call__ method of the controller is called, and no validation is
performed.

If a form controller does not supply a __call__ method, an error
is raised at form controller display time.

Each handler of a form controller is responsible for returning a
response or a dictionary. A handler of a form controller is called
after validation is performed successfully for an action. Note
that these handlers are not called when form validation is
unsuccessful: when form validation is not successful the form display
method is called and the form is redisplayed with error messages.

Each handler has the method name handle_<action_name>. If the
validate flag of a formish:action tag is true (the
default), the associated handler will accept a single argument named
converted. If the validate tag is false, it will accept no
arguments.

For example, the cancel action of a formish:form ZCML
definition for a form controller (which is defined in ZCML as
validate="false" might be defined as so:

The return value of the above example’s handler is a “response” object
(an object which has the attributes app_iter, headerlist and
status). A handler is permitted to return a response or a
dictionary. If it returns a dictionary, the template associated
with the form is rendered with the result of the dictionary in its
global namespace.

If a handle_<actionname> method for a form action does not exist
on a form controller as necessary, an error is raised at form
submission time.

A handler may also raise a pyramid_formish.ValidationError
exception if it detects a post-validation error. This permits
“whole-form” validation that requires data that may only be known by
the handler at runtime. When a handler raises such an error, the form
is rerendered with the error present in the rendering. The error
should be raised with keyword arguments matching field names that map
to error messages, e.g.:

frommy.packageimportsecurityfrommy.packageimportwidgetsfrommy.packageimportapifrompyramid.securityimportauthenticated_useridfrompyramid.traversalimportmodel_urlfromwebob.excimportHTTPFoundimportschemaishimportformishfromvalidatishimportvalidatortags_field=schemaish.Sequence(schemaish.String())description_field=schemaish.String(description=('This description will appear in search results and ''on the community listing page. Please limit your ''description to 100 words or less'),validator=validator.All(validator.Length(max=500),validator.Required()))text_field=schemaish.String(description=('This text will appear on the Overview page for this ''community. You can use this to describe the ''community or to make a special announcement.'))security_field=schemaish.String(description=('Items marked as private can only be seen by ''members of this community.'))classAddCommunityFormController(object):def__init__(self,context,request):self.context=contextself.request=requestself.workflow=security.get_workflow(context)defform_defaults(self):defaults={'title':'','tags':[],'description':'','text':'',}ifself.workflowisnotNone:defaults['security_state']=self.workflow.initial_statereturndefaultsdefform_fields(self):fields=[('title',title_field),('tags',tags_field),('description',description_field),('text',text_field),]ifself.workflowisnotNoneandself.workflow.states:fields.append(('security_state',security_field))returnfieldsdefform_widgets(self,fields):widgets={'title':formish.Input(),'description':formish.TextArea(cols=60,rows=10),'text':widgets.RichTextWidget(),}widgets['tags']=widgets.TagsAddWidget()schema=dict(fields)if'security_state'inschema:security_states=self.workflow.stateswidgets['security_state']=formish.RadioChoice(options=[(s['name'],s['title'])forsinsecurity_states],none_option=None)returnwidgetsdef__call__(self):api=api.TemplateAPI(self.context,self.request)return{'api':api,'page_title':'Edit %s'%self.context.title}defhandle_cancel(self):returnHTTPFound(location=model_url(self.context,self.request))defhandle_submit(self,converted):request=self.requestcontext=self.contextuserid=authenticated_userid(request)community=create_content(ICommunity,converted['title'],converted['description'],converted['text'],userid,)# required to use moderators_group_name and# members_group_namecommunity.__name__=converted['title']community.tags=converted['tags']context[name]=communityifself.workflowisnotNone:if'security_state'inconverted:self.workflow.transition_to_state(community,request,converted['security_state'])location=model_url(community,request,'members','add_existing.html',query={'status_message':'Community added'})returnHTTPFound(location=location)

And assuming the remainder of the dotted names in the above
configuration can be resolved, the resulting page will contain two
forms. The appropriate handler will be called upon a submission of
either.

In this mode, the <formish:form> tags accept only two attributes:
controller and form_id. Both are required, and the
form_id attribute should be unique for each form within a forms
group. These attributes have the same meaning as when they are used
in a non-multiform context.

The remainder of the arguments that are normally associated with the
<formish:form> tag when the non-multiform mode is used (such as
for_, name, renderer, permission, containment,
route_name, and wrapper) must be placed on the
<formish:forms> tag instead.

Along with the attributes that normally belong to the
<formish:form> tag, the <formish:forms> tag also accepts a
view argument. This argument should be a dotted name to a
pyramid view function or class that is willing to render a
template that renders all the forms in the sequence of forms implied
by the <formish:forms> directive. These forms will be available
as a sequence named request.forms as this template is rendered;
each can be called to render a single form, e.g.:

Note that because there are multiple forms to render, the same
template cannot currently be used to render multiple forms as is used
to render a single form (the single-form template expects
request.form, but a multiform view template will expect
request.forms).

The __call__ method (“display method”) of a form controller that
is part of a forms group is never invoked. Instead, the callable
named by the view attribute attached to the forms tag is used
as a display method.

The action subtag of <formish:form> tags in this mode operate
the same way as they do when multiple forms are not involved.

Prior iterations of pyramid_formish were released as a package named
repoze.bfg.formish. repoze.bfg.formish users are encouraged to
upgrade their deployments to pyramid_formish, as, after the first
final release of pyramid_formish, further feature development on
repoze.bfg.formish will cease.

Most existing repoze.bfg.formish applications can be converted to a
pyramid_formish application in a mostly automated fashion. Here’s how
to convert a repoze.bfg.formish application to a
pyramid_formish application:

Ensure that your application works under repoze.bfg.formish version
0.3 or better. If your application has an automated test suite, run it
while your application is using repoze.bfg.formish 0.3+.
Otherwise, test it manually. It is only safe to proceed to the next step
once your application works under repoze.bfg.formish 0.3+.

If your application has a proper set of dependencies, and a standard
automated test suite, you might test your repoze.bfg.formish
application against repoze.bfg.formish 0.3 like so:

$ bfgenv/bin/python setup.py test

bfgenv above will be the virtualenv into which you’ve installed
repoze.bfg.formish 0.3.

Install pyramid_formish into a separate virtualenv as per the
instructions in the Pyramid installation documentation. The
pyramid_formish virtualenv should be separate from the one you’ve
used to install repoze.bfg.formish. A quick way to do this:

Put a copy of your repoze.bfg.formish application into a temporary
location (perhaps by checking a fresh copy of the application out
of a version control repository). For example:

$ cd /tmp
$ svn co http://my.server/my/bfg/application/trunk bfgapp

Use the bfgformish2pyramidformish script present in the bin
directory of the pyramid_formish virtualenv to convert all
repoze.bfg.formish Python import statements into compatible
pyramid_formish import statements. bfg2pyramid will also fix
ZCML directive usages of common repoze.bfg.formish directives. You
invoke bfg2pyramid by passing it the path of the copy of your
application. The path passed should contain a “setup.py” file,
representing your repoze.bfg.formish application’s setup script.
bfgformish2pyramidformish will change the copy of the application in
place.

$ ~/pyramidenv/bfgformish2pyramidformish /tmp/bfgapp

bfgformish2pyramidformish will convert the following
repoze.bfg.formish application aspects to pyramid_formish
compatible analogues:

Python import statements naming repoze.bfg.formish APIs will
be converted to pyramid_formish compatible import statements.
Every Python file beneath the top-level path will be visited and
converted recursively, except Python files which live in
directories which start with a . (dot).

Each ZCML file found (recursively) within the path will have the default
xmlns:formish attribute attached to the configure tag changed
from http://namespaces.repoze.org/formish to
http://pylonshq.com/pyramid_formish. Every ZCML file beneath the
top-level path (files ending with .zcml) will be visited and
converted recursively, except ZCML files which live in directories which
start with a . (dot).

ZCML files which contain directives that have attributes which name a
repoze.bfg.formish API module or attribute of an API module will be
converted to pyramid_formish compatible ZCML attributes. Every
ZCML file beneath the top-level path (files ending with .zcml) will
be visited and converted recursively, except ZCML files which live in
directories which start with a . (dot).

Edit the setup.py file of the application you’ve just converted (if
you’ve been using the example paths, this will be
/tmp/bfgapp/setup.py) to depend on the pyramid_formish
distribution instead the of repoze.bfg.formish distribution in its
install_requires list. The original may look like this:

requires = ['repoze.bfg.formish', ... other dependencies ...]

Edit the setup.py so it has:

requires = ['pyramid_formish', ... other dependencies ...]

All other install-requires and tests-requires dependencies save for the
one on repoze.bfg.formish can remain the same.

Retest your application using pyramid_formish. This might be as
easy as: