======================
Sub-form setup details
======================

PLEASE NOTE: This doctest tests SubForm generation for zope.schema.Object fields.

IMPORTANT: SubFormAdapter and ISubformFactory was backported to plone.z3cform
since this got removed in z3c.form >= 4.x

The more general use-case is tested in autoform.txt, which also contains a more
thorough explanation of the concepts and mechanisms concerned.

To handle hints for zope.schema.Object sub-forms, the special base class
AutoObjectSubForm is required, to set up the 'fields' property on the subform
instance.

We now import the keys under which the tagged values will be stored.

    >>> from plone.autoform.interfaces import OMITTED_KEY
    >>> from plone.autoform.interfaces import WIDGETS_KEY
    >>> from plone.autoform.interfaces import MODES_KEY
    >>> from plone.autoform.interfaces import ORDER_KEY

For the purposes of this test, we'll set the form data manually. See
autoform.txt for more info on how the data could be set.

Test setup
----------

First, let's load this package's ZCML so that we can run the tests:
    >>> configuration = """\
    ... <configure xmlns="http://namespaces.zope.org/zope">
    ...
    ...     <include package="Products.Five" file="configure.zcml" />
    ...     <include package="plone.autoform" />
    ...
    ... </configure>
    ... """
    >>> from io import StringIO
    >>> from zope.configuration import xmlconfig
    >>> xmlconfig.xmlconfig(StringIO(configuration))

We need a sample Schema with an Object field. The Object field will have it's
own schema, ITestSubObjectSchema, which will be the schema for the
ObjectSubForm.

    >>> from zope.interface import Interface
    >>> import zope.schema

    >>> vocab = zope.schema.vocabulary.SimpleVocabulary.fromValues([1, 2])

    >>> class ITestSubObjectSchema(Interface):
    ...     """ This is the schema for the Object field.
    ...
    ...         It will be used to render the ObjectSubForm.
    ...     """
    ...     foofield = zope.schema.TextLine(title=u"Foo")
    ...     barfield = zope.schema.Choice(title=u"Bar", vocabulary=vocab)
    ...     bazfield = zope.schema.Int(title=u"Baz")
    ...     quxfield = zope.schema.TextLine(title=u"Qux")

    >>> class ITestSchema(Interface):
    ...     """ This is the form schema that contains the Object field.
    ...     """
    ...     subobject = zope.schema.Object(
    ...                     title=u'my object widget',
    ...                     schema=ITestSubObjectSchema)

We need a test context and request, marked with the ``IFormLayer`` interface to
make z3c.form happy:

    >>> from zope.publisher.browser import TestRequest
    >>> from z3c.form import interfaces
    >>> context = object()
    >>> request = TestRequest(environ={'AUTHENTICATED_USER': 'user1'}, skin=interfaces.IFormLayer)

Note that we need to pretend that we have authenticated a user. Without this,
the permission checks will be turned off.

Now we define a TestForm, and a TestObjectSubForm.

    >>> from plone.autoform.form import AutoExtensibleForm
    >>> from plone.autoform.form import AutoObjectSubForm
    >>> from plone.z3cform.subform import ObjectSubForm
    >>> from z3c.form import form
    >>> from z3c.form.interfaces import IForm

    >>> class TestForm(AutoExtensibleForm, form.Form):
    ...     schema = ITestSchema
    ...     additionalSchemata = ()
    ...     ignoreContext = True

    >>> TestForm.mode
    'input'

    >>> class TestObjectSubForm(AutoObjectSubForm, ObjectSubForm):
    ...     pass

Finally we need an adapter that acts as the ObjectSubForm's factory.

    >>> from plone.z3cform.interfaces import ISubformFactory
    >>> from plone.z3cform.subform import SubformAdapter
    >>> from z3c.form.browser.object import ObjectWidget
    >>> import zope.component

    >>> @zope.interface.implementer(ISubformFactory)
    ... @zope.component.adapter(zope.interface.Interface,  # widget value
    ...                         interfaces.IFormLayer,       # request
    ...                         zope.interface.Interface,    # widget context
    ...                         zope.interface.Interface,    # form
    ...                         ObjectWidget,                # widget
    ...                         zope.interface.Interface,    # field
    ...                         zope.interface.Interface)    # field.schema
    ... class TestSubformAdapter(SubformAdapter):
    ...     """ """
    ...     factory = TestObjectSubForm

    >>> zope.component.provideAdapter(TestSubformAdapter)

Now we can set some form data on the ObjectSubForm.

Omitted fields are listed like this:

    >>> ITestSubObjectSchema.setTaggedValue(OMITTED_KEY,
    ...                            ((IForm, 'quxfield', True),
    ...                             (IForm, 'barfield', False),
    ...                             (IForm, 'foofield', False),
    ...                           ))

Widgets can be specified either by a dotted name string or an actual instance:

    >>> from z3c.form.browser.radio import RadioFieldWidget
    >>> ITestSubObjectSchema.setTaggedValue(WIDGETS_KEY, {'barfield': RadioFieldWidget})

Fields can be moved like this:

    >>> ITestSubObjectSchema.setTaggedValue(ORDER_KEY, [('foofield', 'after', 'barfield')])

Field modes can be set like this:

    >>> ITestSubObjectSchema.setTaggedValue(MODES_KEY,
    ...                            ((Interface, 'foofield', 'display'),
    ...                             (IForm, 'barfield', 'hidden'))
    ...                           )

The ObjectSubForm is not a GroupForm, so it doesn't support Fieldsets. Fieldset
hints would thus be ignored.

Lets test this now.
-------------------

First we instantiate and update the TestForm.

    >>> test_form = TestForm(context, request)
    >>> test_form.update()

Now we instantiate and update the ObjectSubForm for the object in TestForm's schema.

    >>> widget = test_form.widgets.get('subobject')
    >>> from z3c.form.object import makeDummyObject
    >>> test_subform = zope.component.getMultiAdapter(
    ...     (None, request, context, test_form, widget, widget.field,
    ...      makeDummyObject(ITestSubObjectSchema)),
    ...      ISubformFactory)()

    >>> test_subform.update()

Lets check that foofield is after barfield and that quxfield is not shown.

    >>> list(test_subform.fields.keys())
    ['barfield', 'foofield', 'bazfield']

And that the barfield has a RadioWidget.

    >>> test_subform.widgets['barfield']
    <RadioWidget 'form.widgets.subobject.widgets.barfield'>

Now the modes, barfield was hidden and foofield was set to display:

    >>> test_subform.widgets['barfield'].mode
    'hidden'
    >>> test_subform.widgets['foofield'].mode
    'display'
