Field Managers

One of the features in zope.formlib that works really well is the syntax used to define the contents of the form. The formlib uses form fields, to describe how the form should be put together. Since we liked this way of working, this package offers this feature as well in a very similar way.

A field manager organizes all fields to be displayed within a form. Each field is associated with additional meta-data. The simplest way to create a field manager is to specify the schema from which to extract all fields.

Thus, the first step is to create a schema:

>>> import zope.interface
>>> import zope.schema
>>> class IPerson(zope.interface.Interface):
...      id = zope.schema.Int(
...          title=u'Id',
...          readonly=True)
...
...      name = zope.schema.TextLine(
...          title=u'Name')
...
...      country = zope.schema.Choice(
...          title=u'Country',
...          values=(u'Germany', u'Switzerland', u'USA'),
...          required=False)

We can now create the field manager:

>>> from z3c.form import field
>>> manager = field.Fields(IPerson)

Like all managers in this package, it provides the enumerable mapping API:

>>> manager['id']
<Field 'id'>
>>> manager['unknown']
Traceback (most recent call last):
...
KeyError: 'unknown'
>>> manager.get('id')
<Field 'id'>
>>> manager.get('unknown', 'default')
'default'
>>> 'id' in manager
True
>>> 'unknown' in manager
False
>>> manager.keys()
['id', 'name', 'country']
>>> [key for key in manager]
['id', 'name', 'country']
>>> manager.values()
[<Field 'id'>, <Field 'name'>, <Field 'country'>]
>>> manager.items()
[('id', <Field 'id'>),
 ('name', <Field 'name'>),
 ('country', <Field 'country'>)]
>>> len(manager)
3

You can also select the fields that you would like to have:

>>> manager = manager.select('name', 'country')
>>> manager.keys()
['name', 'country']

Changing the order is simply a matter of changing the selection order:

>>> manager = manager.select('country', 'name')
>>> manager.keys()
['country', 'name']

Selecting a field becomes a little bit more tricky when field names overlap. For example, let’s say that a person can be adapted to a pet:

>>> class IPet(zope.interface.Interface):
...      id = zope.schema.TextLine(
...          title=u'Id')
...
...      name = zope.schema.TextLine(
...          title=u'Name')

The pet field(s) can only be added to the fields manager with a prefix:

>>> manager += field.Fields(IPet, prefix='pet')
>>> manager.keys()
['country', 'name', 'pet.id', 'pet.name']

When selecting fields, this prefix has to be used:

>>> manager = manager.select('name', 'pet.name')
>>> manager.keys()
['name', 'pet.name']

However, sometimes it is tedious to specify the prefix together with the field; for example here:

>>> manager = field.Fields(IPerson).select('name')
>>> manager += field.Fields(IPet, prefix='pet').select('pet.name', 'pet.id')
>>> manager.keys()
['name', 'pet.name', 'pet.id']

It is easier to specify the prefix as an afterthought:

>>> manager = field.Fields(IPerson).select('name')
>>> manager += field.Fields(IPet, prefix='pet').select(
...     'name', 'id', prefix='pet')
>>> manager.keys()
['name', 'pet.name', 'pet.id']

Alternatively, you can specify the interface:

>>> manager = field.Fields(IPerson).select('name')
>>> manager += field.Fields(IPet, prefix='pet').select(
...     'name', 'id', interface=IPet)
>>> manager.keys()
['name', 'pet.name', 'pet.id']

Sometimes it is easier to simply omit a set of fields instead of selecting all the ones you want:

>>> manager = field.Fields(IPerson)
>>> manager = manager.omit('id')
>>> manager.keys()
['name', 'country']

Again, you can solve name conflicts using the full prefixed name, ...

>>> manager = field.Fields(IPerson).omit('country')
>>> manager += field.Fields(IPet, prefix='pet')
>>> manager.omit('pet.id').keys()
['id', 'name', 'pet.name']

using the prefix keyword argument, ...

>>> manager = field.Fields(IPerson).omit('country')
>>> manager += field.Fields(IPet, prefix='pet')
>>> manager.omit('id', prefix='pet').keys()
['id', 'name', 'pet.name']

or, using the interface:

>>> manager = field.Fields(IPerson).omit('country')
>>> manager += field.Fields(IPet, prefix='pet')
>>> manager.omit('id', interface=IPet).keys()
['id', 'name', 'pet.name']

You can also add two field managers together:

>>> manager = field.Fields(IPerson).select('name', 'country')
>>> manager2 = field.Fields(IPerson).select('id')
>>> (manager + manager2).keys()
['name', 'country', 'id']

Adding anything else to a field manager is not well defined:

>>> manager + 1
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'Fields' and 'int'

You also cannot make any additions that would cause a name conflict:

>>> manager + manager
Traceback (most recent call last):
...
ValueError: ('Duplicate name', 'name')

When creating a new form derived from another, you often want to keep existing fields and add new ones. In order to not change the super-form class, you need to copy the field manager:

>>> manager.keys()
['name', 'country']
>>> manager.copy().keys()
['name', 'country']

More on the Constructor

The constructor does not only accept schemas to be passed in; one can also just pass in schema fields:

>>> field.Fields(IPerson['name']).keys()
['name']

However, the schema field has to have a name:

>>> email = zope.schema.TextLine(title=u'E-Mail')
>>> field.Fields(email)
Traceback (most recent call last):
...
ValueError: Field has no name

Adding a name helps:

>>> email.__name__ = 'email'
>>> field.Fields(email).keys()
['email']

Or, you can just pass in other field managers, which is the feature that the add mechanism uses:

>>> field.Fields(manager).keys()
['name', 'country']

Last, but not least, the constructor also accepts form fields, which are used by select() and omit():

>>> field.Fields(manager['name'], manager2['id']).keys()
['name', 'id']

If the constructor does not recognize any of the types above, it raises a TypeError exception:

>>> field.Fields(object())
Traceback (most recent call last):
...
TypeError: ('Unrecognized argument type', <object object at ...>)

Additionally, you can specify several keyword arguments in the field manager constructor that are used to set up the fields:

  • omitReadOnly

    When set to True all read-only fields are omitted.

    >>> field.Fields(IPerson, omitReadOnly=True).keys()
    ['name', 'country']
    
  • keepReadOnly

    Sometimes you want to keep a particular read-only field around, even though in general you want to omit them. In this case you can specify the fields to keep:

    >>> field.Fields(
    ...     IPerson, omitReadOnly=True, keepReadOnly=('id',)).keys()
    ['id', 'name', 'country']
    
  • prefix

    Sets the prefix of the fields. This argument is passed on to each field.

    >>> manager = field.Fields(IPerson, prefix='myform.')
    >>> manager['myform.name']
    <Field 'myform.name'>
    
  • interface

    Usually the interface is inferred from the field itself. The interface is used to determine whether an adapter must be looked up for a given context.

    But sometimes fields are generated in isolation to an interface or the interface of the field is not the one you want. In this case you can specify the interface:

    >>> class IMyPerson(IPerson):
    ...     pass
    
    >>> manager = field.Fields(email, interface=IMyPerson)
    >>> manager['email'].interface
    <InterfaceClass __builtin__.IMyPerson>
    
  • mode

    The mode in which the widget will be rendered. By default there are two available, “input” and “display”. When mode is not specified, “input” is chosen.

    >>> from z3c.form import interfaces
    >>> manager = field.Fields(IPerson, mode=interfaces.DISPLAY_MODE)
    >>> manager['country'].mode
    'display'
    
  • ignoreContext

    While the ignoreContext flag is usually set on the form, it is sometimes desirable to set the flag for a particular field.

    >>> manager = field.Fields(IPerson)
    >>> manager['country'].ignoreContext
    
    >>> manager = field.Fields(IPerson, ignoreContext=True)
    >>> manager['country'].ignoreContext
    True
    
    >>> manager = field.Fields(IPerson, ignoreContext=False)
    >>> manager['country'].ignoreContext
    False
    

Fields Widget Manager

When a form (or any other widget-using view) is updated, one of the tasks is to create the widgets. Traditionally, generating the widgets involved looking at the form fields (or similar) of a form and generating the widgets using the information of those specifications. This solution is good for the common (about 85%) use cases, since it makes writing new forms very simple and allows a lot of control at a class-definition level.

It has, however, its limitations. It does not, for example, allow for customization without rewriting a form. This can range from omitting fields on a particular form to generically adding a new widget to the form, such as an “object name” button on add forms. This package solves this issue by providing a widget manager, which is responsible providing the widgets for a particular view.

The default widget manager for forms is able to look at a form’s field definitions and create widgets for them. Thus, let’s create a schema first:

>>> import zope.interface
>>> import zope.schema
>>> class LastNameTooShort(zope.schema.interfaces.ValidationError):
...     """The last name is too short."""
>>> def lastNameConstraint(value):
...     if value and value == value.lower():
...         raise zope.interface.Invalid(u"Name must have at least one capital letter")
...     return True
>>> class IPerson(zope.interface.Interface):
...     id = zope.schema.TextLine(
...         title=u'ID',
...         description=u"The person's ID.",
...         readonly=True,
...         required=True)
...
...     lastName = zope.schema.TextLine(
...         title=u'Last Name',
...         description=u"The person's last name.",
...         default=u'',
...         required=True,
...         constraint=lastNameConstraint)
...
...     firstName = zope.schema.TextLine(
...         title=u'First Name',
...         description=u"The person's first name.",
...         default=u'-- unknown --',
...         required=False)
...
...     @zope.interface.invariant
...     def twiceAsLong(person):
...         if len(person.lastName) >= 2 * len(person.firstName):
...             raise LastNameTooShort()

Next we need a form that specifies the fields to be added:

>>> from z3c.form import field
>>> class PersonForm(object):
...     prefix = 'form.'
...     fields = field.Fields(IPerson)
>>> personForm = PersonForm()

For more details on how to define fields within a form, see form.txt. We can now create the fields widget manager. Its discriminators are the form for which the widgets are created, the request, and the context that is being manipulated. In the simplest case the context is None and ignored, as it is true for an add form.

>>> from z3c.form.testing import TestRequest
>>> request = TestRequest()
>>> context = object()
>>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True

Widget Mapping

The main responsibility of the manager is to provide the IEnumerableMapping interface and an update() method. Initially the mapping, going from widget id to widget value, is empty:

>>> from zope.interface.common.mapping import IEnumerableMapping
>>> IEnumerableMapping.providedBy(manager)
True
>>> manager.keys()
[]

Only by “updating” the manager, will the widgets become available; before we can use the update method, however, we have to register the IFieldWidget adapter for the ITextLine field:

>>> from z3c.form import interfaces, widget
>>> @zope.component.adapter(zope.schema.TextLine, TestRequest)
... @zope.interface.implementer(interfaces.IFieldWidget)
... def TextFieldWidget(field, request):
...     return widget.FieldWidget(field, widget.Widget(request))
>>> zope.component.provideAdapter(TextFieldWidget)
>>> from z3c.form import converter
>>> zope.component.provideAdapter(converter.FieldDataConverter)
>>> zope.component.provideAdapter(converter.FieldWidgetDataConverter)
>>> manager.update()

Other than usual mappings in Python, the widget manager’s widgets are always in a particular order:

>>> manager.keys()
['id', 'lastName', 'firstName']

As you can see, if we call update twice, we still get the same amount and order of keys:

>>> manager.update()
>>> manager.keys()
['id', 'lastName', 'firstName']

Let’s make sure that all enumerable mapping functions work correctly:

>>> manager['lastName']
<Widget 'form.widgets.lastName'>
>>> manager['unknown']
Traceback (most recent call last):
...
KeyError: 'unknown'
>>> manager.get('lastName')
<Widget 'form.widgets.lastName'>
>>> manager.get('unknown', 'default')
'default'
>>> 'lastName' in manager
True
>>> 'unknown' in manager
False
>>> [key for key in manager]
['id', 'lastName', 'firstName']
>>> manager.values()
[<Widget 'form.widgets.id'>,
 <Widget 'form.widgets.lastName'>,
 <Widget 'form.widgets.firstName'>]
>>> manager.items()
[('id', <Widget 'form.widgets.id'>),
 ('lastName', <Widget 'form.widgets.lastName'>),
 ('firstName', <Widget 'form.widgets.firstName'>)]
>>> len(manager)
3

It is also possible to delete widgets from the manager:

>>> del manager['firstName']
>>> len(manager)
2
>>> manager.values()
[<Widget 'form.widgets.id'>, <Widget 'form.widgets.lastName'>]
>>> manager.keys()
['id', 'lastName']
>>> manager.items()
[('id', <Widget 'form.widgets.id'>),
('lastName', <Widget 'form.widgets.lastName'>)]

Note that deleting a non-existent widget causes a KeyError to be raised:

>>> del manager['firstName']
Traceback (most recent call last):
...
KeyError: 'firstName'

Properties of widgets within a manager

When a widget is added to the widget manager, it is located:

>>> lname = manager['lastName']
>>> lname.__name__
'lastName'
>>> lname.__parent__
<z3c.form.field.FieldWidgets object at ...>

All widgets created by this widget manager are context aware:

>>> interfaces.IContextAware.providedBy(lname)
True
>>> lname.context is context
True

Determination of the widget mode

By default, all widgets will also assume the mode of the manager:

>>> manager['lastName'].mode
'input'
>>> manager.mode = interfaces.DISPLAY_MODE
>>> manager.update()
>>> manager['lastName'].mode
'display'

The exception is when some fields specifically desire a different mode. In the first case, all “readonly” fields will be shown in display mode:

>>> manager.mode = interfaces.INPUT_MODE
>>> manager.update()
>>> manager['id'].mode
'display'

An exception is made when the flag, “ignoreReadonly” is set:

>>> manager.ignoreReadonly = True
>>> manager.update()
>>> manager['id'].mode
'input'

In the second case, the last name will inherit the mode from the widget manager, while the first name will want to use a display widget:

>>> personForm.fields = field.Fields(IPerson).select('lastName')
>>> personForm.fields += field.Fields(
...     IPerson, mode=interfaces.DISPLAY_MODE).select('firstName')
>>> manager.mode = interfaces.INPUT_MODE
>>> manager.update()
>>> manager['lastName'].mode
'input'
>>> manager['firstName'].mode
'display'

In a third case, the widget will be shown in display mode, if the attribute of the context is not writable. Clearly this can never occur in add forms, since there the context is ignored, but is an important use case in edit forms.

Thus, we need an implementation of the IPerson interface including some security declarations:

>>> from zope.security import checker
>>> class Person(object):
...     zope.interface.implements(IPerson)
...
...     def __init__(self, firstName, lastName):
...         self.id = firstName[0].lower() + lastName.lower()
...         self.firstName = firstName
...         self.lastName = lastName
>>> PersonChecker = checker.Checker(
...     get_permissions = {'id': checker.CheckerPublic,
...                        'firstName': checker.CheckerPublic,
...                        'lastName': checker.CheckerPublic},
...     set_permissions = {'firstName': 'test.Edit',
...                        'lastName': checker.CheckerPublic}
...     )
>>> srichter = checker.ProxyFactory(
...     Person(u'Stephan', u'Richter'), PersonChecker)

In this case the last name is always editable, but for the first name the user will need the edit (“test.Edit”) permission.

We also need to register the data manager and setup a new security policy:

>>> from z3c.form import datamanager
>>> zope.component.provideAdapter(datamanager.AttributeField)
>>> from zope.security import management
>>> from z3c.form import testing
>>> management.endInteraction()
>>> newPolicy = testing.SimpleSecurityPolicy()
>>> oldpolicy = management.setSecurityPolicy(newPolicy)
>>> management.newInteraction()

Now we can create the widget manager:

>>> personForm = PersonForm()
>>> request = TestRequest()
>>> manager = field.FieldWidgets(personForm, request, srichter)

After updating the widget manager, the fields are available as widgets, the first name being in display and the last name is input mode:

>>> manager.update()
>>> manager['id'].mode
'display'
>>> manager['firstName'].mode
'display'
>>> manager['lastName'].mode
'input'

However, explicitly overriding the mode in the field declaration overrides this selection for you:

>>> personForm.fields['firstName'].mode = interfaces.INPUT_MODE
>>> manager.update()
>>> manager['id'].mode
'display'
>>> manager['firstName'].mode
'input'
>>> manager['lastName'].mode
'input'

Required fields

There is a flag for required fields. This flag get set if at least one field is required. This let us render a required info legend in forms if required fields get used.

>>> manager.hasRequiredFields
True

Data extraction and validation

Besides managing widgets, the widget manager also controls the process of extracting and validating extracted data. Let’s start with the validation first, which only validates the data as a whole, assuming each individual value being already validated.

Before we can use the method, we have to register a “manager validator”:

>>> from z3c.form import validator
>>> zope.component.provideAdapter(validator.InvariantsValidator)
>>> personForm.fields = field.Fields(IPerson)
>>> manager.update()
>>> manager.validate(
...     {'firstName': u'Stephan', 'lastName': u'Richter'})
()

The result of this method is a tuple of errors that occurred during the validation. An empty tuple means the validation succeeded. Let’s now make the validation fail:

>>> errors = manager.validate(
...     {'firstName': u'Stephan', 'lastName': u'Richter-Richter'})
>>> [error.doc() for error in errors]
['The last name is too short.']

A special case occurs when the schema fields are not associated with an interface:

>>> name = zope.schema.TextLine(__name__='name')
>>> class PersonNameForm(object):
...     prefix = 'form.'
...     fields = field.Fields(name)
>>> personNameForm = PersonNameForm()
>>> manager = field.FieldWidgets(personNameForm, request, context)

In this case, the widget manager’s validate() method should simply ignore the field and not try to look up any invariants:

>>> manager.validate({'name': u'Stephan'})
()

Let’s now have a look at the widget manager’s extract(), which returns a data dictionary and the collection of errors. Before we can validate, we have to register a validator for the widget:

>>> zope.component.provideAdapter(validator.SimpleFieldValidator)

When all goes well, the data dictionary is complete and the error collection empty:

>>> request = TestRequest(form={
...     'form.widgets.id': u'srichter',
...     'form.widgets.firstName': u'Stephan',
...     'form.widgets.lastName': u'Richter'})
>>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
>>> data, errors = manager.extract()
>>> data['firstName']
u'Stephan'
>>> data['lastName']
u'Richter'
>>> errors
()

Since all errors are immediately converted to error view snippets, we have to provide the adapter from a validation error to an error view snippet first:

>>> from z3c.form import error
>>> zope.component.provideAdapter(error.ErrorViewSnippet)
>>> zope.component.provideAdapter(error.InvalidErrorViewSnippet)

Let’s now cause a widget-level error by not submitting the required last name:

>>> request = TestRequest(form={
...     'form.widgets.firstName': u'Stephan', 'form.widgets.id': u'srichter'})
>>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
>>> manager.extract()
({'firstName': u'Stephan'}, (<ErrorViewSnippet for RequiredMissing>,))

Or, we could violate a constraint. This constraint raises Invalid, which is a convenient way to raise errors where we mainly care about providing a custom error message.

>>> request = TestRequest(form={
...     'form.widgets.firstName': u'Stephan',
...     'form.widgets.lastName': u'richter',
...     'form.widgets.id': u'srichter'})
>>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
>>> extracted = manager.extract()
>>> extracted
({'firstName': u'Stephan'}, (<InvalidErrorViewSnippet for Invalid>,))
>>> extracted[1][0].createMessage()
u'Name must have at least one capital letter'

Finally, let’s ensure that invariant failures are also caught:

>>> request = TestRequest(form={
...     'form.widgets.id': u'srichter',
...     'form.widgets.firstName': u'Stephan',
...     'form.widgets.lastName': u'Richter-Richter'})
>>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
>>> data, errors = manager.extract()
>>> errors[0].error.doc()
'The last name is too short.'

Note that the errors coming from invariants are all error view snippets as well, just as it is the case for field-specific validation errors. And that’s really all there is!

By default, the extract() method not only returns the errors that it catches, but also sets them on individual widgets and on the manager:

>>> manager.errors
(<ErrorViewSnippet for LastNameTooShort>,)

This behavior can be turned off. To demonstrate, let’s make a new request that causes a widget-level error:

>>> request = TestRequest(form={
...     'form.widgets.firstName': u'Stephan', 'form.widgets.id': u'srichter'})
>>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()

We have to set the setErrors property to False before calling extract, we still get the same result from the method call, ...

>>> manager.setErrors = False
>>> manager.extract()
({'firstName': u'Stephan'}, (<ErrorViewSnippet for RequiredMissing>,))

but there are no side effects on the manager and the widgets:

>>> manager.errors
()
>>> manager['lastName'].error is None
True

Customization of Ignoring the Context

Note that you can also manually control ignoring the context per field.

>>> class CustomPersonForm(object):
...     prefix = 'form.'
...     fields = field.Fields(IPerson).select('id')
...     fields += field.Fields(IPerson, ignoreContext=True).select(
...                   'firstName', 'lastName')
>>> customPersonForm = CustomPersonForm()

Let’s now create a manager and update it:

>>> customManager = field.FieldWidgets(customPersonForm, request, context)
>>> customManager.update()
>>> customManager['id'].ignoreContext
False
>>> customManager['firstName'].ignoreContext
True
>>> customManager['lastName'].ignoreContext
True

Fields – Custom Widget Factories

It is possible to declare custom widgets for fields within the field’s declaration.

Let’s have a look at the default form first. Initially, the standard registered widgets are used:

>>> manager = field.FieldWidgets(personForm, request, srichter)
>>> manager.update()
>>> manager['firstName']
<Widget 'form.widgets.firstName'>

Now we would like to have our own custom input widget:

>>> class CustomInputWidget(widget.Widget):
...     pass
>>> def CustomInputWidgetFactory(field, request):
...     return widget.FieldWidget(field, CustomInputWidget(request))

It can be simply assigned as follows:

>>> personForm.fields['firstName'].widgetFactory = CustomInputWidgetFactory
>>> personForm.fields['lastName'].widgetFactory = CustomInputWidgetFactory

Now this widget should be used instead of the registered default one:

>>> manager = field.FieldWidgets(personForm, request, srichter)
>>> manager.update()
>>> manager['firstName']
<CustomInputWidget 'form.widgets.firstName'>

In the background the widget factory assignment really just registered the default factory in the WidgetFactories object, which manages the custom widgets for all modes. Now all modes show this input widget:

>>> manager = field.FieldWidgets(personForm, request, srichter)
>>> manager.mode = interfaces.DISPLAY_MODE
>>> manager.update()
>>> manager['firstName']
<CustomInputWidget 'form.widgets.firstName'>

However, we can also register a specific widget for the display mode:

>>> class CustomDisplayWidget(widget.Widget):
...     pass
>>> def CustomDisplayWidgetFactory(field, request):
...     return widget.FieldWidget(field, CustomDisplayWidget(request))
>>> personForm.fields['firstName']\
...     .widgetFactory[interfaces.DISPLAY_MODE] = CustomDisplayWidgetFactory
>>> personForm.fields['lastName']\
...     .widgetFactory[interfaces.DISPLAY_MODE] = CustomDisplayWidgetFactory

Now the display mode should produce the custom display widget, ...

>>> manager = field.FieldWidgets(personForm, request, srichter)
>>> manager.mode = interfaces.DISPLAY_MODE
>>> manager.update()
>>> manager['firstName']
<CustomDisplayWidget 'form.widgets.firstName'>
>>> manager['lastName']
<CustomDisplayWidget 'form.widgets.lastName'>

... while the input mode still shows the default custom input widget on the lastName field but not on the firstName field since we don’t have the test.Edit permission:

>>> manager = field.FieldWidgets(personForm, request, srichter)
>>> manager.mode = interfaces.INPUT_MODE
>>> manager.update()
>>> manager['firstName']
<CustomDisplayWidget 'form.widgets.firstName'>
>>> manager['lastName']
<CustomInputWidget 'form.widgets.lastName'>

The widgets factories component,

>>> factories = personForm.fields['firstName'].widgetFactory
>>> factories
{'display': <function CustomDisplayWidgetFactory at ...>}

is pretty much a standard dictionary that also manages a default value:

>>> factories.default
<function CustomInputWidgetFactory at ...>

When getting a value for a key, if the key is not found, the default is returned:

>>> factories.keys()
['display']
>>> factories[interfaces.DISPLAY_MODE]
<function CustomDisplayWidgetFactory at ...>
>>> factories[interfaces.INPUT_MODE]
<function CustomInputWidgetFactory at ...>
>>> factories.get(interfaces.DISPLAY_MODE)
<function CustomDisplayWidgetFactory at ...>
>>> factories.get(interfaces.INPUT_MODE)
<function CustomInputWidgetFactory at ...>

If no default is specified,

>>> factories.default = None

then the dictionary behaves as usual:

>>> factories[interfaces.DISPLAY_MODE]
<function CustomDisplayWidgetFactory at ...>
>>> factories[interfaces.INPUT_MODE]
Traceback (most recent call last):
...
KeyError: 'input'
>>> factories.get(interfaces.DISPLAY_MODE)
<function CustomDisplayWidgetFactory at ...>
>>> factories.get(interfaces.INPUT_MODE)