    ===========
    Group Forms
    ===========

    Group forms allow you to split up a form into several logical units without
    much overhead. To the parent form, groups should be only dealt with during
    coding and be transparent on the data extraction level.

    For the examples to work, we have to bring up most of the form framework:

      >>> from z3c.form import testing
      >>> testing.setupFormDefaults()

    So let's first define a complex content component that warrants setting up
    multiple groups:

      >>> import zope.interface
      >>> import zope.schema

      >>> class IVehicleRegistration(zope.interface.Interface):
      ...     firstName = zope.schema.TextLine(title=u'First Name')
      ...     lastName = zope.schema.TextLine(title=u'Last Name')
      ...
      ...     license = zope.schema.TextLine(title=u'License')
      ...     address = zope.schema.TextLine(title=u'Address')
      ...
      ...     model = zope.schema.TextLine(title=u'Model')
      ...     make = zope.schema.TextLine(title=u'Make')
      ...     year = zope.schema.TextLine(title=u'Year')

      >>> class VehicleRegistration(object):
      ...     zope.interface.implements(IVehicleRegistration)
      ...
      ...     def __init__(self, **kw):
      ...         for name, value in kw.items():
      ...             setattr(self, name, value)

    The schema above can be separated into basic, license, and car information,
    where the latter two will be placed into groups. First we create the two
    groups:

      >>> from z3c.form import field, group

      >>> class LicenseGroup(group.Group):
      ...     label = u'License'
      ...     fields = field.Fields(IVehicleRegistration).select(
      ...         'license', 'address')

      >>> class CarGroup(group.Group):
      ...     label = u'Car'
      ...     fields = field.Fields(IVehicleRegistration).select(
      ...         'model', 'make', 'year')

    Most of the group is setup like any other (sub)form. Additionally, you can
    specify a label, which is a human-readable string that can be used for layout
    purposes.

    Let's now create an add form for the entire vehicle registration. In
    comparison to a regular add form, you only need to add the ``GroupForm`` as
    one of the base classes. The groups are specified in a simple tuple:

      >>> import os
      >>> from z3c.form.ptcompat import ViewPageTemplateFile
      >>> from z3c.form import form, tests

      >>> class RegistrationAddForm(group.GroupForm, form.AddForm):
      ...     fields = field.Fields(IVehicleRegistration).select(
      ...         'firstName', 'lastName')
      ...     groups = (LicenseGroup, CarGroup)
      ...
      ...     template = ViewPageTemplateFile(
      ...         'simple_groupedit.pt', os.path.dirname(tests.__file__))
      ...
      ...     def create(self, data):
      ...         return VehicleRegistration(**data)
      ...
      ...     def add(self, object):
      ...         self.getContent()['obj1'] = object
      ...         return object

    Note: The order of the base classes is very important here. The ``GroupForm``
    class must be left of the ``AddForm`` class, because the ``GroupForm`` class
    overrides some methods of the ``AddForm`` class.

    Now we can instantiate the form:

      >>> request = testing.TestRequest()

      >>> add = RegistrationAddForm(None, request)
      >>> add.update()

    After the form is updated the tuple of group classes is converted to group
    instances:

      >>> add.groups
      (
, ) If we happen to update the add form again, the groups that have already been converted to instances ares skipped. >>> add.update() >>> add.groups (,
    )

    We can now render the form:

      >>> print add.render()
First Name
Last Name
License
License
Address
Car
Model
Make
Year
    Let's now submit the form, but forgetting to enter the address:

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.firstName': u'Stephan',
      ...     'form.widgets.lastName': u'Richter',
      ...     'form.widgets.license': u'MA 40387',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.add': u'Add'
      ...     })

      >>> add = RegistrationAddForm(None, request)
      >>> add.update()
      >>> print testing.render(add, './/xmlns:i')
There were some errors. >>> print testing.render(add, './/xmlns:fieldset[1]/xmlns:ul')

- Address:
  Required input is missing.

    As you can see, the template is clever enough to just report the errors at the
    top of the form, but still report the actual problem within the group. So what
    happens, if errors happen inside and outside a group?

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.firstName': u'Stephan',
      ...     'form.widgets.license': u'MA 40387',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.add': u'Add'
      ...     })

      >>> add = RegistrationAddForm(None, request)
      >>> add.update()
      >>> print testing.render(add, './/xmlns:i')
There were some errors. >>> print testing.render(add, './/xmlns:ul[1]')

- Last Name:
  Required input is missing.

- Address:
  Required input is missing.

>>> print testing.render(add, './/xmlns:fieldset[1]/xmlns:ul')

- Address:
  Required input is missing.

    Let's now successfully complete the add form.

      >>> from zope.container import btree
      >>> context = btree.BTreeContainer()

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.firstName': u'Stephan',
      ...     'form.widgets.lastName': u'Richter',
      ...     'form.widgets.license': u'MA 40387',
      ...     'form.widgets.address': u'10 Main St, Maynard, MA',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.add': u'Add'
      ...     })

      >>> add = RegistrationAddForm(context, request)
      >>> add.update()

    The object is now added to the container and all attributes should be set:

      >>> reg = context['obj1']
      >>> reg.firstName
      u'Stephan'
      >>> reg.lastName
      u'Richter'
      >>> reg.license
      u'MA 40387'
      >>> reg.address
      u'10 Main St, Maynard, MA'
      >>> reg.model
      u'BMW'
      >>> reg.make
      u'325'
      >>> reg.year
      u'2005'

    Let's now have a look at an edit form for the vehicle registration:

      >>> class RegistrationEditForm(group.GroupForm, form.EditForm):
      ...     fields = field.Fields(IVehicleRegistration).select(
      ...         'firstName', 'lastName')
      ...     groups = (LicenseGroup, CarGroup)
      ...
      ...     template = ViewPageTemplateFile(
      ...         'simple_groupedit.pt', os.path.dirname(tests.__file__))

      >>> request = testing.TestRequest()

      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()

    After updating the form, we can render the HTML:

      >>> print edit.render()
First Name
Last Name
License
License
Address
Car
Model
Make
Year
    The behavior when an error occurs is identical to that of the add form:

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.firstName': u'Stephan',
      ...     'form.widgets.lastName': u'Richter',
      ...     'form.widgets.license': u'MA 40387',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.apply': u'Apply'
      ...     })

      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()
      >>> print testing.render(edit, './/xmlns:i')
There were some errors. >>> print testing.render(edit, './/xmlns:ul')

- Address:
  Required input is missing.

>>> print testing.render(edit, './/xmlns:fieldset/xmlns:ul')

- Address:
  Required input is missing.

    When an edit form with groups is successfully committed, a detailed
    object-modified event is sent out telling the system about the changes.
    To see the error, let's create an event subscriber for object-modified events:

      >>> eventlog = []
      >>> import zope.lifecycleevent
      >>> @zope.component.adapter(zope.lifecycleevent.ObjectModifiedEvent)
      ... def logEvent(event):
      ...     eventlog.append(event)
      >>> zope.component.provideHandler(logEvent)

    Let's now complete the form successfully:

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.firstName': u'Stephan',
      ...     'form.widgets.lastName': u'Richter',
      ...     'form.widgets.license': u'MA 4038765',
      ...     'form.widgets.address': u'11 Main St, Maynard, MA',
      ...     'form.widgets.model': u'Ford',
      ...     'form.widgets.make': u'F150',
      ...     'form.widgets.year': u'2006',
      ...     'form.buttons.apply': u'Apply'
      ...     })

      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()

    The success message will be shown on the form, ...

      >>> print testing.render(edit, './/xmlns:i')
Data successfully updated.
    and the data are correctly updated:

      >>> reg.firstName
      u'Stephan'
      >>> reg.lastName
      u'Richter'
      >>> reg.license
      u'MA 4038765'
      >>> reg.address
      u'11 Main St, Maynard, MA'
      >>> reg.model
      u'Ford'
      >>> reg.make
      u'F150'
      >>> reg.year
      u'2006'

    Let's look at the event:

      >>> event = eventlog[-1]
      >>> event
The event's description contains the changed Interface and the names of all changed fields, even if they where in different groups: >>> attrs = event.descriptions[0] >>> attrs.interface
      >>> attrs.attributes
      ('license', 'address', 'model', 'make', 'year')

    Group form as instance
    ----------------------

    It is also possible to use group instances in forms. Let's setup our previous
    form and assing a group instance:

      >>> class RegistrationEditForm(group.GroupForm, form.EditForm):
      ...     fields = field.Fields(IVehicleRegistration).select(
      ...         'firstName', 'lastName')
      ...
      ...     template = ViewPageTemplateFile(
      ...         'simple_groupedit.pt', os.path.dirname(tests.__file__))

      >>> request = testing.TestRequest()

      >>> edit = RegistrationEditForm(reg, request)

    Instanciate the form and use a group class and a group instance:

      >>> carGroupInstance = CarGroup(edit.context, request, edit)
      >>> edit.groups = (LicenseGroup, carGroupInstance)
      >>> edit.update()
      >>> print edit.render()
First Name
Last Name
License
License
Address
Car
Model
Make
Year
    Groups with Different Content
    -----------------------------

    You can customize the content for a group by overriding a group's
    ``getContent`` method.  This is a very easy way to get around not
    having object widgets.  For example, suppose we want to maintain the
    vehicle owner's information in a separate class than the vehicle.  We
    might have an ``IVehicleOwner`` interface like so.

      >>> class IVehicleOwner(zope.interface.Interface):
      ...     firstName = zope.schema.TextLine(title=u'First Name')
      ...     lastName = zope.schema.TextLine(title=u'Last Name')

    Then out ``IVehicleRegistration`` interface would include an object
    field for the owner instead of the ``firstName`` and ``lastName``
    fields.

      >>> class IVehicleRegistration(zope.interface.Interface):
      ...     owner = zope.schema.Object(title=u'Owner', schema=IVehicleOwner)
      ...
      ...     license = zope.schema.TextLine(title=u'License')
      ...     address = zope.schema.TextLine(title=u'Address')
      ...
      ...     model = zope.schema.TextLine(title=u'Model')
      ...     make = zope.schema.TextLine(title=u'Make')
      ...     year = zope.schema.TextLine(title=u'Year')

    Now let's create simple implementations of these two interfaces.

      >>> class VehicleOwner(object):
      ...     zope.interface.implements(IVehicleOwner)
      ...
      ...     def __init__(self, **kw):
      ...         for name, value in kw.items():
      ...             setattr(self, name, value)

      >>> class VehicleRegistration(object):
      ...     zope.interface.implements(IVehicleRegistration)
      ...
      ...     def __init__(self, **kw):
      ...         for name, value in kw.items():
      ...             setattr(self, name, value)

    Now we can create a group just for the owner with its own
    ``getContent`` method that simply returns the ``owner`` object field
    of the ``VehicleRegistration`` instance.

      >>> class OwnerGroup(group.Group):
      ...     label = u'Owner'
      ...     fields = field.Fields(IVehicleOwner, prefix='owner')
      ...
      ...     def getContent(self):
      ...         return self.context.owner

    When we create an Edit form for example, we should omit the ``owner``
    field which is taken care of with the group.

      >>> class RegistrationEditForm(group.GroupForm, form.EditForm):
      ...     fields = field.Fields(IVehicleRegistration).omit(
      ...         'owner')
      ...     groups = (OwnerGroup,)
      ...
      ...     template = ViewPageTemplateFile(
      ...         'simple_groupedit.pt', os.path.dirname(tests.__file__))

      >>> reg = VehicleRegistration(
      ...               license=u'MA 40387',
      ...               address=u'10 Main St, Maynard, MA',
      ...               model=u'BMW',
      ...               make=u'325',
      ...               year=u'2005',
      ...               owner=VehicleOwner(firstName=u'Stephan',
      ...                                  lastName=u'Richter'))
      >>> request = testing.TestRequest()

      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()

    When we render the form, the group appears as we would expect but with
    the ``owner`` prefix for the fields.

      >>> print edit.render()
License
Address
Model
Make
Year
Owner
First Name
Last Name
    Now let's try and edit the owner.  For example, suppose that Stephan
    Richter gave his BMW to Paul Carduner because he is such a nice guy.

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.owner.firstName': u'Paul',
      ...     'form.widgets.owner.lastName': u'Carduner',
      ...     'form.widgets.license': u'MA 4038765',
      ...     'form.widgets.address': u'Berkeley',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.apply': u'Apply'
      ...     })
      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()

    We'll see if everything worked on the form side.

      >>> print testing.render(edit, './/xmlns:i')
Data successfully updated.
    Now the owner object should have updated fields.

      >>> reg.owner.firstName
      u'Paul'
      >>> reg.owner.lastName
      u'Carduner'
      >>> reg.license
      u'MA 4038765'
      >>> reg.address
      u'Berkeley'
      >>> reg.model
      u'BMW'
      >>> reg.make
      u'325'
      >>> reg.year
      u'2005'

    Nested Groups
    -------------

    The group can contains groups. Let's adapt the previous RegistrationEditForm:

      >>> class OwnerGroup(group.Group):
      ...     label = u'Owner'
      ...     fields = field.Fields(IVehicleOwner, prefix='owner')
      ...
      ...     def getContent(self):
      ...         return self.context.owner

      >>> class VehicleRegistrationGroup(group.Group):
      ...     label = u'Registration'
      ...     fields = field.Fields(IVehicleRegistration).omit(
      ...         'owner')
      ...     groups = (OwnerGroup,)
      ...
      ...     template = ViewPageTemplateFile(
      ...         'simple_groupedit.pt', os.path.dirname(tests.__file__))

      >>> class RegistrationEditForm(group.GroupForm, form.EditForm):
      ...     groups = (VehicleRegistrationGroup,)
      ...
      ...     template = ViewPageTemplateFile(
      ...         'simple_nested_groupedit.pt', os.path.dirname(tests.__file__))

      >>> reg = VehicleRegistration(
      ...               license=u'MA 40387',
      ...               address=u'10 Main St, Maynard, MA',
      ...               model=u'BMW',
      ...               make=u'325',
      ...               year=u'2005',
      ...               owner=VehicleOwner(firstName=u'Stephan',
      ...                                  lastName=u'Richter'))
      >>> request = testing.TestRequest()

      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()

    Now let's try and edit the owner.  For example, suppose that Stephan
    Richter gave his BMW to Paul Carduner because he is such a nice guy.

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.owner.firstName': u'Paul',
      ...     'form.widgets.owner.lastName': u'Carduner',
      ...     'form.widgets.license': u'MA 4038765',
      ...     'form.widgets.address': u'Berkeley',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.apply': u'Apply'
      ...     })
      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()

    We'll see if everything worked on the form side.

      >>> print testing.render(edit, './/xmlns:i')
Data successfully updated.
    Now the owner object should have updated fields.

      >>> reg.owner.firstName
      u'Paul'
      >>> reg.owner.lastName
      u'Carduner'
      >>> reg.license
      u'MA 4038765'
      >>> reg.address
      u'Berkeley'
      >>> reg.model
      u'BMW'
      >>> reg.make
      u'325'
      >>> reg.year
      u'2005'

    So what happens, if errors happen inside a nested group? Let's use an empty
    invalid object for the test missing input errors:

      >>> reg = VehicleRegistration(owner=VehicleOwner())

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.owner.firstName': u'',
      ...     'form.widgets.owner.lastName': u'',
      ...     'form.widgets.license': u'',
      ...     'form.widgets.address': u'',
      ...     'form.widgets.model': u'',
      ...     'form.widgets.make': u'',
      ...     'form.widgets.year': u'',
      ...     'form.buttons.apply': u'Apply'
      ...     })

      >>> edit = RegistrationEditForm(reg, request)
      >>> edit.update()
      >>> data, errors = edit.extractData()
      >>> print testing.render(edit, './/xmlns:i')
There were some errors. >>> print testing.render(edit, './/xmlns:fieldset[1]/xmlns:ul')

- License:
  Required input is missing.
- Address:
  Required input is missing.
- Model:
  Required input is missing.
- Make:
  Required input is missing.
- Year:
  Required input is missing.

- First Name:
  Required input is missing.
- Last Name:
  Required input is missing.

    Group instance in nested group
    ------------------------------

    Let's also test if the Group class can handle group objects as instances:

      >>> reg = VehicleRegistration(
      ...               license=u'MA 40387',
      ...               address=u'10 Main St, Maynard, MA',
      ...               model=u'BMW',
      ...               make=u'325',
      ...               year=u'2005',
      ...               owner=VehicleOwner(firstName=u'Stephan',
      ...                                  lastName=u'Richter'))
      >>> request = testing.TestRequest()

      >>> edit = RegistrationEditForm(reg, request)
      >>> vrg = VehicleRegistrationGroup(edit.context, request, edit)
      >>> ownerGroup = OwnerGroup(edit.context, request, edit)

    Now build the group instance object chain:

      >>> vrg.groups = (ownerGroup,)
      >>> edit.groups = (vrg,)

    Also use refreshActions which is not needed but will make coverage this
    additional line of code in the update method:

      >>> edit.refreshActions = True

    Update and render:

      >>> edit.update()
      >>> print edit.render()
Registration
License
Address
Model
Make
Year
Owner
First Name
Last Name
    Now test the error handling if just one missing value is given in a group:

      >>> request = testing.TestRequest(form={
      ...     'form.widgets.owner.firstName': u'Paul',
      ...     'form.widgets.owner.lastName': u'',
      ...     'form.widgets.license': u'MA 4038765',
      ...     'form.widgets.address': u'Berkeley',
      ...     'form.widgets.model': u'BMW',
      ...     'form.widgets.make': u'325',
      ...     'form.widgets.year': u'2005',
      ...     'form.buttons.apply': u'Apply'
      ...     })

      >>> edit = RegistrationEditForm(reg, request)
      >>> vrg = VehicleRegistrationGroup(edit.context, request, edit)
      >>> ownerGroup = OwnerGroup(edit.context, request, edit)
      >>> vrg.groups = (ownerGroup,)
      >>> edit.groups = (vrg,)

      >>> edit.update()
      >>> data, errors = edit.extractData()
      >>> print testing.render(edit, './/xmlns:i')
There were some errors. >>> print testing.render(edit, './/xmlns:fieldset[1]/xmlns:ul')

- Last Name:
  Required input is missing.

    Just check whether we fully support the interface:

      >>> from z3c.form import interfaces
      >>> from zope.interface.verify import verifyClass
      >>> verifyClass(interfaces.IGroup, group.Group)
      True