Esempio n. 1
0
class AddForm(form.Form):
    template = viewpagetemplatefile.ViewPageTemplateFile('crud-add.pt')

    label = _(u"Add")
    ignoreContext = True
    ignoreRequest = True

    @property
    def prefix(self):
        parent_prefix = getattr(self.context, 'prefix', '')
        return 'crud-add.' + parent_prefix

    @property
    def fields(self):
        return field.Fields(self.context.add_schema)

    @button.buttonAndHandler(_('Add'), name='add')
    def handle_add(self, action):
        data, errors = self.extractData()
        if errors:
            self.status = form.AddForm.formErrorsMessage
            return
        try:
            item = self.context.add(data)
        except zope.schema.ValidationError, e:
            self.status = e
        else:
Esempio n. 2
0
class DefaultOrderReviewStep(wizard.Step):
    implements(IOrderReviewStep)
    prefix = 'order-review'
    label = _(u'label_order_review_step', default="Order Review")
    title = _(u"title_default_order_review_step",
              default="Default Order Review Step")
    description = _(u'help_order_review_step', default=u'')
    index = zvptf.ViewPageTemplateFile('templates/checkout/order_review.pt')

    def __init__(self, context, request, wiz):
        super(wizard.Step, self).__init__(context, request)
        self.wizard = wiz
Esempio n. 3
0
class IntroStep(wizard.Step):
    prefix = 'intro'
    fields = {}
    label = _(u'Introduction')
    index = viewpagetemplatefile.ViewPageTemplateFile('templates/intro.pt')

    def __init__(self, context, request, wizard):
        # Use collective.beaker for session managment
        session = ISession(request, None)
        self.sessionmanager = session

        super(IntroStep, self).__init__(context, request, wizard)
Esempio n. 4
0
class CrudForm(AbstractCrudForm, form.Form):
    template = viewpagetemplatefile.ViewPageTemplateFile('crud-master.pt')
    description = u''

    editform_factory = EditForm
    addform_factory = AddForm

    def update(self):
        super(CrudForm, self).update()

        addform = self.addform_factory(self, self.request)
        editform = self.editform_factory(self, self.request)
        addform.update()
        editform.update()
        self.subforms = [editform, addform]
class Step(utils.OverridableTemplate, form.Form):
    """
    Base class for a wizard step implementing the IStep interface.

    Subclasses will typically want to override at least the fields attribute.
    """
    implements(IStep)

    index = viewpagetemplatefile.ViewPageTemplateFile('wizard-step.pt')
    subforms = ()
    label = u""
    description = u""
    wizard = None
    completed = True
    condition = None

    @property
    def available(self):
        if self.prefix in self.request.SESSION[self.wizard.sessionKey]:
            return True
        return False

    def __init__(self, context, request, wizard):
        super(Step, self).__init__(context, request)
        self.wizard = wizard

    def getContent(self):
        return self.request.SESSION[self.wizard.sessionKey].setdefault(
            self.prefix, {})

    def applyChanges(self, data):
        content = self.getContent()
        applyChanges(self, content, data)
        self.wizard.sync()

    def load(self, context, **kw):
        pass

    def apply(self, context, **kw):
        pass

    def update(self):
        session = self.wizard.session
        data = session.get(self.prefix, None)
        if data is None:
            self.load(self.wizard.context)
            self.wizard.sync()
        super(Step, self).update()
Esempio n. 6
0
class OutroStep(wizard.Step):
    prefix = 'outro'
    fields = {}
    label = _(u'Thanks!')
    index = viewpagetemplatefile.ViewPageTemplateFile('templates/outro.pt')

    def __init__(self, context, request, wizard):
        # Use collective.beaker for session managment
        session = ISession(request, None)
        self.sessionmanager = session

        super(OutroStep, self).__init__(context, request, wizard)

    def get_url(self):

        url = self.wizard.get_finish_url()
        return url
Esempio n. 7
0
class CheckoutWizard(wizard.Wizard):
    label = _(u"label_checkout_wizard", default="Checkout")
    index = zvptf.ViewPageTemplateFile('templates/checkout-wizard.pt')

    def __init__(self, context, request):
        super(CheckoutWizard, self).__init__(context, request)
        self.context = context
        self.request = request

    def getSelectedPaymentProcessor(self):
        payment_processor = None
        if 'payment_processor_choice' not in self.session.keys():
            return None
        try:
            pp_name = self.session.get('payment_processor_choice').get(
                'payment_processor')
        except KeyError:
            try:
                pp_name = self.request.SESSION.get(
                    'payment_processor_choice').get('payment_processor')
            except KeyError:
                # No payment processor step activated
                pp_name = "none"
        for name, adapter in getAdapters(
            (self.context, self.request, self.context), IPaymentProcessor):
            if name == pp_name:
                payment_processor = adapter
        return payment_processor

    @property
    def steps(self):
        contact_info_steps = ()
        shipping_address_steps = ()
        contact_info_step_groups = getAdapters(
            (self.context, self.request, self), IContactInformationStepGroup)
        shipping_address_step_groups = getAdapters(
            (self.context, self.request, self), IShippingAddressStepGroup)
        payment_processor_step_groups = getAdapters(
            (self.context, self.request, self), IPaymentProcessorStepGroup)
        order_review_step_groups = getAdapters(
            (self.context, self.request, self), IOrderReviewStepGroup)
        registry = getUtility(IRegistry)
        shop_config = registry.forInterface(IShopConfiguration)

        # Get steps for selected Contact Info Step Group
        selected_contact_info_step_group = shop_config.contact_info_step_group
        for name, step_group_adapter in contact_info_step_groups:
            if name == selected_contact_info_step_group:
                contact_info_steps = step_group_adapter.steps

        # Get steps for selected Shipping Address Step Group
        selected_shipping_address_step_group = shop_config.shipping_address_step_group
        for name, step_group_adapter in shipping_address_step_groups:
            if name == selected_shipping_address_step_group:
                shipping_address_steps = step_group_adapter.steps

        # Get steps for selected Payment Processor Step Group
        selected_pp_step_group = shop_config.payment_processor_step_group
        for name, step_group_adapter in payment_processor_step_groups:
            if name == selected_pp_step_group:
                payment_processor_steps = step_group_adapter.steps

        # Get steps for selected Order Review Step Group
        selected_order_review_step_group = shop_config.order_review_step_group
        for name, step_group_adapter in order_review_step_groups:
            if name == selected_order_review_step_group:
                order_review_steps = step_group_adapter.steps

        return contact_info_steps + \
               shipping_address_steps + \
               payment_processor_steps + \
               order_review_steps

    @button.buttonAndHandler(_(u'btn_back', default="Back"),
                             name='back',
                             condition=lambda form: not form.onFirstStep)
    def handleBack(self, action):
        data, errors = self.currentStep.extractData()
        if errors:
            errorMessage = '<ul>'
            for error in errors:
                if errorMessage.find(error.message):
                    errorMessage += '<li>' + error.message + '</li>'
            errorMessage += '</ul>'
            self.status = errorMessage

        else:
            self.currentStep.applyChanges(data)
            self.updateCurrentStep(self.currentIndex - 1)

            # Back can change the conditions for the finish button,
            # so we need to reconstruct the button actions, since we
            # do not redirect.
            self.updateActions()

    @button.buttonAndHandler(_(u'btn_continue', default='Next'),
                             name='continue',
                             condition=lambda form: not form.onLastStep)
    def handleContinue(self, action):
        data, errors = self.currentStep.extractData()
        if errors:
            errorMessage = '<ul>'
            for error in errors:
                if errorMessage.find(error.message):
                    errorMessage += '<li>' + error.message + '</li>'
            errorMessage += '</ul>'
            self.status = errorMessage
        else:

            self.currentStep.applyChanges(data)
            self.updateCurrentStep(self.currentIndex + 1)

            # Proceed can change the conditions for the finish button,
            # so we need to reconstruct the button actions, since we
            # do not redirect.
            self.updateActions()

    @button.buttonAndHandler(
        _(u'btn_finish', default='Finish'),
        name='finish',
        condition=lambda form: form.allStepsFinished or form.onLastStep)
    def handleFinish(self, action):
        data, errors = self.currentStep.extractData()
        if errors:
            self.status = self.formErrorsMessage
            return
        else:
            self.status = self.successMessage
            self.finished = True
        self.currentStep.applyChanges(data)
        self.finish()

        address_data = {}
        address_data.update(self.session['contact_information'])
        self.request.SESSION[SESSION_ADDRESS_KEY] = address_data

        shipping_data = {}
        shipping_data.update(self.session['shipping_address'])
        self.request.SESSION[SESSION_SHIPPING_KEY] = shipping_data

        self.request.SESSION['order_confirmation'] = True

        payment_processor_choice = {}
        if 'payment_processor_choice' in self.session.keys():
            payment_processor_choice.update(
                self.session['payment_processor_choice'])
        self.request.SESSION[
            'payment_processor_choice'] = payment_processor_choice

        # Save contact information in a cookie in order to prefill
        # form if customer returns
        cookie_value = base64.b64encode(
            simplejson.dumps(self.session['contact_information']))
        expiry_date = (
            datetime.now() +
            timedelta(days=90)).strftime("%a, %d-%b-%Y %H:%M:%S GMT")
        self.request.RESPONSE.setCookie(COOKIE_ADDRESS_KEY,
                                        cookie_value,
                                        path='/',
                                        expires=expiry_date)

        self.request.SESSION[self.sessionKey] = {}
        self.sync()

        pp = self.getSelectedPaymentProcessor()
        if pp is not None and pp.external:
            self.request.SESSION['external-processor-url'] = pp.url
            self.request.SESSION[
                'external-processor-url'] = "http://localhost:8077/"

        self.request.response.redirect('checkout')
Esempio n. 8
0
class EditForm(form.Form):
    label = _(u"Edit")
    template = viewpagetemplatefile.ViewPageTemplateFile('crud-table.pt')

    #exposes the edit sub form for your own derivatives
    editsubform_factory = EditSubForm

    @property
    def prefix(self):
        parent_prefix = getattr(self.context, 'prefix', '')
        return 'crud-edit.' + parent_prefix

    def update(self):
        self._update_subforms()
        super(EditForm, self).update()

    def _update_subforms(self):
        self.subforms = []
        for id, item in self.batch:
            subform = self.editsubform_factory(self, self.request)
            subform.content = item
            subform.content_id = id
            subform.update()
            self.subforms.append(subform)

    @property
    def batch(self):
        items = self.context.get_items()
        batch_size = self.context.batch_size or sys.maxint
        page = int(self.request.get('%spage' % self.prefix, 0))
        return Batch.fromPagenumber(items, batch_size, page + 1)

    def render_batch_navigation(self):
        bv = CrudBatchView(self.context, self.request)
        bv.prefix = self.prefix
        return bv(self.batch)

    @button.buttonAndHandler(_('Apply changes'),
                             name='edit',
                             condition=lambda form: form.context.update_schema)
    def handle_edit(self, action):
        success = _(u"Successfully updated")
        partly_success = _(u"Some of your changes could not be applied.")
        status = no_changes = _(u"No changes made.")
        for subform in self.subforms:
            # With the ``extractData()`` call, validation will occur,
            # and errors will be stored on the widgets amongst other
            # places.  After this we have to be extra careful not to
            # call (as in ``__call__``) the subform again, since
            # that'll remove the errors again.  With the results that
            # no changes are applied but also no validation error is
            # shown.
            data, errors = subform.extractData()
            if errors:
                if status is no_changes:
                    status = subform.formErrorsMessage
                elif status is success:
                    status = partly_success
                continue
            del data['select']
            self.context.before_update(subform.content, data)
            changes = subform.applyChanges(data)
            if changes:
                if status is no_changes:
                    status = success
                elif status is subform.formErrorsMessage:
                    status = partly_success

                # If there were changes, we'll update the view widgets
                # again, so that they'll actually display the changes
                for widget in subform.widgets.values():
                    if widget.mode == DISPLAY_MODE:
                        widget.update()
                        zope.event.notify(
                            z3c.form.widget.AfterWidgetUpdateEvent(widget))
        self.status = status

    @button.buttonAndHandler(_('Delete'), name='delete')
    def handle_delete(self, action):
        selected = self.selected_items()
        if selected:
            self.status = _(u"Successfully deleted items.")
            for id, item in selected:
                try:
                    self.context.remove((id, item))
                except ConflictError:
                    raise
                except:
                    # In case an exception is raised, we'll catch it
                    # and notify the user; in general, this is
                    # unexpected behavior:
                    self.status = _(u'Unable to remove one or more items.')
                    break

            # We changed the amount of entries, so we update the subforms again.
            self._update_subforms()
        else:
            self.status = _(u"Please select items to delete.")

    def selected_items(self):
        tuples = []
        for subform in self.subforms:
            data = subform.widgets['select'].extract()
            if not data or data is NOVALUE:
                continue
            else:
                tuples.append((subform.content_id, subform.content))
        return tuples
Esempio n. 9
0
class EditSubForm(form.EditForm):
    template = viewpagetemplatefile.ViewPageTemplateFile('crud-row.pt')

    @property
    def prefix(self):
        return 'crud-edit.%s.' % self.content_id

    # These are set by the parent form
    content = None
    content_id = None

    @property
    def fields(self):
        fields = field.Fields(self._select_field())

        crud_form = self.context.context

        update_schema = crud_form.update_schema
        if update_schema is not None:
            fields += field.Fields(update_schema)

        view_schema = crud_form.view_schema
        if view_schema is not None:
            view_fields = field.Fields(view_schema)
            for f in view_fields.values():
                f.mode = DISPLAY_MODE
                # This is to allow a field to appear in both view
                # and edit mode at the same time:
                if not f.__name__.startswith('view_'):
                    f.__name__ = 'view_' + f.__name__
            fields += view_fields

        return fields

    def getContent(self):
        return self.content

    def _select_field(self):
        select_field = field.Field(
            zope.schema.Bool(__name__='select',
                             required=False,
                             title=_(u'select')))
        select_field.widgetFactory[INPUT_MODE] = singlecheckboxwidget_factory
        return select_field

    # XXX: The three following methods, 'getCombinedWidgets',
    # 'getTitleWidgets', and 'getNiceTitles' are hacks to support the
    # page template.  Let's get rid of them.
    def getCombinedWidgets(self):
        """Returns pairs of widgets to improve layout"""
        widgets = self.widgets.items()
        combined = []
        seen = set()
        for name, widget in list(widgets):
            if widget.mode == INPUT_MODE:
                view_widget = self.widgets.get('view_%s' % name)
                if view_widget is not None:
                    combined.append((widget, view_widget))
                    seen.add(view_widget)
                else:
                    combined.append((widget, ))
            else:
                if widget not in seen:
                    combined.append((widget, ))
        return combined

    def getTitleWidgets(self):
        combinedWidgets = self.getCombinedWidgets()
        widgetsForTitles = [w[0] for w in combinedWidgets]
        return widgetsForTitles

    def getNiceTitles(self):
        widgetsForTitles = self.getTitleWidgets()
        freakList = []
        for item in widgetsForTitles:
            freakList.append(item.field.title)
        return freakList
Esempio n. 10
0
class SocialNetworkStep(wizard.GroupStep):
    prefix = 'Social'
    label = _(u'Social Network accounts')
    description = _(u'Input your social networks details')

    template = viewpagetemplatefile.ViewPageTemplateFile(
        'templates/socialnetwork.pt')
    fields = field.Fields()
    groups = [ChatGroup, SocialNetworkGroup]

    def __init__(self, context, request, wizard):
        # Use collective.beaker for session managment
        session = ISession(request, None)
        self.sessionmanager = session

        super(SocialNetworkStep, self).__init__(context, request, wizard)

    def load(self, context):
        member = api.user.get_current()
        data = self.getContent()

        # Chats group
        if not data.get('irc', None):
            irc = member.getProperty('irc')
            if type(irc).__name__ == 'object':
                irc = None
            data['irc'] = irc

        if not data.get('telegram', None):
            telegram = member.getProperty('telegram')
            if type(telegram).__name__ == 'object':
                telegram = None
            data['telegram'] = telegram

        if not data.get('skype', None):
            skype = member.getProperty('skype')
            if type(skype).__name__ == 'object':
                skype = None
            data['skype'] = skype

        # Social Network group
        if not data.get('twitter', None):
            twitter = member.getProperty('twitter')
            if type(twitter).__name__ == 'object':
                twitter = None
            data['twitter'] = twitter

        if not data.get('instagram', None):
            instagram = member.getProperty('instagram')
            if type(instagram).__name__ == 'object':
                instagram = None
            data['instagram'] = instagram

        if not data.get('facebook', None):
            facebook = member.getProperty('facebook')
            if type(facebook).__name__ == 'object':
                facebook = None
            data['facebook'] = facebook

    def apply(self, context, initial_finish=False):
        data = self.getContent()
        return data

    def applyChanges(self, data):
        member = api.user.get_current()
        member.setMemberProperties(
            mapping={
                'irc': data['irc'],
                'telegram': data['telegram'],
                'skype': data['skype'],
                'twitter': data['twitter'],
                'instagram': data['instagram'],
                'facebook': data['facebook']
            })
class PersonalDataStep(wizard.GroupStep):
    prefix = 'Address'
    label = _(u'Personal data, address and work details')
    description = _(u'Input your personal data address and work details')

    template = viewpagetemplatefile.ViewPageTemplateFile(
        'templates/personaldata.pt')
    fields = field.Fields()
    groups = [PersonalInfoGroup, AddressGroup, WorkGroup]

    def __init__(self, context, request, wizard):
        # Use collective.beaker for session managment
        session = ISession(request, None)
        self.sessionmanager = session

        super(PersonalDataStep, self).__init__(context, request, wizard)

    def load(self, context):
        member = api.user.get_current()
        data = self.getContent()

        # Personal info group
        if not data.get('fullname', None):
            fullname = member.getProperty('fullname')
            if type(fullname).__name__ == 'object':
                fullname = None
            data['fullname'] = fullname

        if not data.get('gender', None):
            gender = member.getProperty('gender')
            if type(gender).__name__ == 'object':
                gender = None
            data['gender'] = gender

        if not data.get('birthdate', None):
            birthdate = member.getProperty('birthdate')
            if type(birthdate).__name__ == 'object':
                birthdate = None
            data['birthdate'] = birthdate

        if not data.get('mobile', None):
            mobile = member.getProperty('mobile')
            if type(mobile).__name__ == 'object':
                mobile = None
            data['mobile'] = mobile

        # Address group
        if not data.get('address1', None):
            address1 = member.getProperty('address1')
            if type(address1).__name__ == 'object':
                address1 = None
            data['address1'] = address1

        if not data.get('address2', None):
            address2 = member.getProperty('address2')
            if type(address2).__name__ == 'object':
                address2 = None
            data['address2'] = address2

        if not data.get('country', None):
            country = member.getProperty('country')
            if type(country).__name__ == 'object':
                country = None
            data['country'] = country

        if not data.get('city', None):
            city = member.getProperty('city')
            if type(city).__name__ == 'object':
                city = None
            data['city'] = city

        # Office group
        if not data.get('institution', None):
            institution = member.getProperty('institution')
            if type(institution).__name__ == 'object':
                institution = None
            data['institution'] = institution

        if not data.get('instadd', None):
            instadd = member.getProperty('instadd')
            if type(instadd).__name__ == 'object':
                instadd = None
            data['instadd'] = instadd

        if not data.get('officephone', None):
            officephone = member.getProperty('officephone')
            if type(officephone).__name__ == 'object':
                officephone = None
            data['officephone'] = officephone

        if not data.get('profession', None):
            profession = member.getProperty('profession')
            if type(profession).__name__ == 'object':
                profession = None
            data['profession'] = profession

    def apply(self, context, initial_finish=False):
        data = self.getContent()
        return data

    def applyChanges(self, data):
        member = api.user.get_current()
        member.setMemberProperties(
            mapping={
                'fullname': data['fullname'],
                'gender': data['gender'],
                'birthdate': data['birthdate'],
                'mobile': data['mobile'],
                'address1': data['address1'],
                'address2': data['address2'],
                'country': data['country'],
                'city': data['city'],
                'institution': data['institution'],
                'instadd': data['instadd'],
                'officephone': data['officephone'],
                'profession': data['profession']
            })
class Wizard(utils.OverridableTemplate, form.Form):
    """
    Abstract class for a wizard implementing the IWizard interface.

    Subclasses must provide at least the finish method.
    """

    implements(IWizard)

    successMessage = _(u"Information submitted successfully.")
    formErrorsMessage = _(u"There were errors.")
    clearMessage = _(u"Form cleared.")

    index = viewpagetemplatefile.ViewPageTemplateFile('wizard.pt')

    steps = ()  # Set this to be form classes
    label = u""
    description = u""
    ignoreContext = True
    fields = field.Fields()

    currentStep = None
    currentIndex = None
    finished = False
    validate_back = True

    @property
    def sessionKey(self):
        try:
            path = list(self.context.getPhysicalPath())
        except:
            path = []
        path.append(self.__name__)
        return (WIZARD_SESSION_KEY, tuple(path))

    def update(self):
        # initialize session
        sessionKey = self.sessionKey
        # PEP8 complaints but TransientObject does not implement __contains__
        if not self.request.SESSION.has_key(sessionKey):  # NOQA
            self.request.SESSION[sessionKey] = {}
        # Reset session if we came from a URL different from that of the wizard
        # unless it's the URL that's used during z3cform inline validation.
        referer = self.request.get('HTTP_REFERER', '')
        url = self.request.get('ACTUAL_URL', '')
        if referer.startswith('http') and ('kss_z3cform_inline_validation'
                                           not in url):
            if not utils.location_is_equal(url, referer):
                self.request.SESSION[sessionKey] = {}
        self.session = self.request.SESSION[sessionKey]

        self.updateActiveSteps()

        # if this wizard hasn't been loaded yet in this session,
        # load the data
        if not len(self.session):
            self.initialize()
            self.sync()

        self.jumpToCurrentStep()

        self.updateActions()
        self.actions.execute()
        self.updateWidgets()

    def updateActiveSteps(self):
        self.activeSteps = []
        for step in self.steps:
            if step.condition is not None:
                condition = getMultiAdapter(
                    (self.context, self.request, self),
                    IStepCondition,
                    name=step.condition,
                )
                if condition.validate() is False:
                    continue
            step = step(self.context, self.request, self)
            self.activeSteps.append(step)

    def jumpToCurrentStep(self):
        self.updateCurrentStep(self.session.setdefault('step', 0))
        if 'step' in self.request.form:
            self.jump(self.request.form['step'])

    def updateActions(self):
        """
        Allow the current step to determine whether the wizard navigation
        is enabled.
        """
        form.Form.updateActions(self)
        if not self.currentStep.completed:
            if self.onLastStep:
                self.actions['finish'].disabled = 'disabled'
            else:
                self.actions['continue'].disabled = 'disabled'

    def updateCurrentStep(self, index):
        self.currentIndex = index
        self.session['step'] = self.currentIndex
        self.sync()
        self.currentStep = self.activeSteps[self.currentIndex]
        self.currentStep.update()

    @property
    def onLastStep(self):
        return self.currentIndex == len(self.activeSteps) - 1

    def showContinue(self):
        return not self.onLastStep

    @button.buttonAndHandler(_(u'Continue'),
                             name='continue',
                             condition=lambda form: form.showContinue())
    def handleContinue(self, action):
        messages = IStatusMessage(self.request)
        data, errors = self.currentStep.extractData()
        if errors:
            self.status = self.formErrorsMessage
            messages.addStatusMessage(self.status, type="error")
        else:
            self.currentStep.applyChanges(data)
            self.updateCurrentStep(self.currentIndex + 1)

            # Proceed can change the conditions for the finish button,
            # so we need to reconstruct the button actions, since we
            # do not redirect.
            self.updateActions()

    @property
    def allStepsFinished(self):
        for step in self.activeSteps:
            if not step.available:
                return False
        return True

    def showFinish(self):
        return self.allStepsFinished or self.onLastStep

    @button.buttonAndHandler(_(u'Finish'),
                             name='finish',
                             condition=lambda form: form.showFinish())
    def handleFinish(self, action):
        messages = IStatusMessage(self.request)
        data, errors = self.currentStep.extractData()
        if errors:
            self.status = self.formErrorsMessage
            messages.addStatusMessage(self.status, type="error")
            return
        else:
            self.status = self.successMessage
            self.finished = True
            messages.addStatusMessage(self.status, type="info")
        self.currentStep.applyChanges(data)
        self.finish()
        # clear out the session
        self.request.SESSION[self.sessionKey] = {}
        self.sync()

    @property
    def onFirstStep(self):
        return self.currentIndex == 0

    def showBack(self):
        return not self.onFirstStep

    @button.buttonAndHandler(_(u'Back'),
                             name='back',
                             condition=lambda form: form.showBack())
    def handleBack(self, action):
        messages = IStatusMessage(self.request)

        if self.validate_back:
            # if true, only allow navigating back if the current
            # step validates
            data, errors = self.currentStep.extractData()
            if errors:
                self.status = self.formErrorsMessage
                messages.addStatusMessage(self.status, type="error")
                return
            self.currentStep.applyChanges(data)

        self.updateCurrentStep(self.currentIndex - 1)

        # Back can change the conditions for the finish button,
        # so we need to reconstruct the button actions, since we
        # do not redirect.
        self.updateActions()

    def showClear(self):
        values = [v for v in self.session.values() if isinstance(v, dict)]
        if len(values) > 1:
            return True
        for value in values:
            if value:
                return True
        return False

    @button.buttonAndHandler(_(u'Clear'),
                             name='clear',
                             condition=lambda form: form.showClear())
    def handleClear(self, action):
        self.session.clear()
        self.sync()
        self.status = self.clearMessage
        self.updateActiveSteps()
        self.updateCurrentStep(0)
        self.updateActions()
        self.currentStep.ignoreRequest = True
        self.currentStep.update()

    def jump(self, step_idx):
        # make sure target is available
        try:
            target_step = self.activeSteps[step_idx]
        except (KeyError, TypeError):
            return
        if not target_step.available:
            return

        self.updateCurrentStep(step_idx)
        self.updateActions()

    def initialize(self):
        self.loadSteps(self.context)

    def loadSteps(self, context):
        for step in self.activeSteps:
            if hasattr(step, 'load'):
                step.load(context)

    def finish(self):
        self.applySteps(self.context)

    def applySteps(self, context):
        for step in self.activeSteps:
            if hasattr(step, 'apply'):
                step.apply(context)

    def sync(self):
        self.request.SESSION._p_changed = True

    @property
    def absolute_url(self):
        return self.context.absolute_url() + '/' + (self.__name__ or '')