Question

I'd like to be able to chain several z3c forms in Plone one after the other. For example, once form#1 finishes processing and completes error checking, it passes the results (preferably through a GET variables) into form#2, which in turn does the same to form#3 etc... I would also like to be able to use the same URL for all the forms.

My current implementation is to have a browser view that then dispatches appropriate forms, i.e DispatcherView checks for self.request variables and then determines which one of form#1, form#2, form#3 to call.

I have this code but it seems that z3c forms are abstracted as several calls to BrowserView, and trying to trigger multiple calls from it to z3c.form interferes with processing of the latter. For example when user press 'submit' button once, error checking of form#1 takes place, and when I try the solution in example below, form#2 returns showing all the required fields being incorrect, meaning that form#2 received values from form#1. I tried to trigger form#2 from different places such as, DispatcherView(BrowserView) call() method, call() method of the form#1, also update() and render() of the latter but all of these overrides lead to the same issue.

Where is the appropriate place to piggyback consecutive calls so this thing would work, or do I need to create separate pages and explicitly redirect to each other using self.request.RESPONSE.redicrect ?

from Products.Five import BrowserView
from zope import interface, schema
from z3c.form import form, field, group, button
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm

countries = SimpleVocabulary([SimpleTerm(value="not_selected", title=_("Chose your region")),
                            SimpleTerm(value="canada", title=_("Canada")),
                            SimpleTerm(value="us", title=_("United States")),
                            SimpleTerm(value="belgium", title=_("Belgium"))])
products =  SimpleVocabulary([SimpleTerm(value="product1", title=_("Product1")),
                            SimpleTerm(value="product2", title=_("Product2")),
                            SimpleTerm(value="product3", title=_("Product2"))
                            ])
class DispatcherView(BrowserView):
    def __call__(self):
        if 'form.widgets.region' in self.request.keys():
            step2 = Step2(self.context, self.request)
            return step2.__call__()
        else:
            step1 = Step1(self.context, self.request)
            return step1.__call__() 
    def update(self):
        pass

class IStep1(interface.Interface):
    region = schema.Choice(title=_("Select your region"),
                        vocabulary=countries, required=True,
                        default="not_selected")
class IStep2(interface.Interface):
    product = schema.Choice(title=_("Pick a product"),
                        vocabulary=products, required=True)

class Step1(form.Form):
    fields = field.Fields(IStep1)
    def __init__(self,context, request):
        self.ignoreContext = True
        super(self.__class__, self).__init__(context, request)
    def update(self):
        super(self.__class__, self).update()
    @button.buttonAndHandler(u'Next >>')
    def handleNext(self, action):
        data, errors = self.extractData()
        if errors:
            print "Error occured"

class Step2(form.Form):
    fields = field.Fields(IStep2)
    def __init__(self,context, request):
        self.ignoreContext = True
        super(self.__class__, self).__init__(context, request)
    def update(self):
        super(self.__class__, self).update()
    @button.buttonAndHandler(_('<< Previous'))
    def handleBack(self, action):
        data, errors = self.extractData()
        if errors:
            print "Error occured"
            #handle input errors here

    @button.buttonAndHandler(_('Next >>'))
    def handleNext(self, action):
        data, errors = self.extractData()
        if errors:
            print "Error occured"

EDIT: Cris Ewing gave answer for this, here's how sample code looks like after it's been rewritten using collective.z3cformwizard:

from zope import schema, interface
from zope.interface import implements
from z3c.form import field, form
from collective.z3cform.wizard import wizard
from plone.z3cform.fieldsets import group
from plone.z3cform.layout import FormWrapper
from Products.statusmessages.interfaces import IStatusMessage
from Products.statusmessages.adapter import _decodeCookieValue

from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
from z3c.form.browser.checkbox import CheckBoxFieldWidget

from Products.Five import BrowserView

countries = SimpleVocabulary([SimpleTerm(value="not_selected", title=_("Chose your region")),
                            SimpleTerm(value="belgium", title=_("Belgium")),
                            SimpleTerm(value="canada", title=_("Canada")),
                            SimpleTerm(value="us", title=_("United States")),
                            ])
products = SimpleVocabulary([SimpleTerm(value="product1", title=_("Product1")),
                            SimpleTerm(value="product2", title=_("Product2")),
                            SimpleTerm(value="product3", title=_("Product3")),
                            SimpleTerm(value="product4", title=_("Product4")),
                            SimpleTerm(value="product5", title=_("Product5"))
                            ])
class Step1(wizard.Step):
    prefix = 'one'
    fields = field.Fields(schema.Choice(__name__="region",
                                title=_("Select your region"), vocabulary=countries,
                                required=True, default="not_selected")
                        )
class Step2(wizard.Step):
    prefix = 'two'
    fields = field.Fields(schema.List(__name__="product",
                                value_type=schema.Choice(
                                    title=_("Select your product"),
                                    vocabulary=products),
                                    required=True
                                    )
                        )
    for fv in fields.values():
        fv.widgetFactory = CheckBoxFieldWidget


class WizardForm(wizard.Wizard):
    label= _("Find Product")
    steps = Step1, Step2
    def finish(self):
        ##check answer here
        import pdb; pdb.set_trace()

class DispatcherView(FormWrapper):
    form = WizardForm
    def __init__(self, context, request):
        FormWrapper.__init__(self, context, request)
    def absolute_url(self):
        return '%s/%s' % (self.context.absolute_url(), self.__name__)

Also don't forget browser:view slug in configure.zcml:

<browser:page
    name="view"
    for="Products.myproduct.DispatcherView"
    class=".file.DispatcherView"
    permission="zope2.View"
/>
Was it helpful?

Solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top