How to access z3c.form widget settings from browser view - plone

Given following widget based on z3c.form https://github.com/collective/Products.UserAndGroupSelectionWidget/blob/z3cform-widget/src/Products/UserAndGroupSelectionWidget/z3cform/widget.py
I would like in some browserview to access its settings and corresponding field. Since Widget does not know the schema and field upfront, I'm interested in what information do I need to get widget and field. Currently I have available the fieldname and context, which seemed to be enough for archtypes https://github.com/collective/Products.UserAndGroupSelectionWidget/blob/z3cform-widget/src/Products/UserAndGroupSelectionWidget/browser.py#L60
EDIT: To simplify the question, I would like to access a field that is defined in some z3c form and its widget. I could not find other way except passing request and context to form init and then accessing the field. Is there a multiadapter?
The idea is to have a z3c.form widget that people hook into whatever field which does an ajax call. That ajax request needs to pass parameters and response will lookup where widget was used and with what settings. The question is, how to lookup the z3c.form field and which information is needed to do so?

Getting the Field
If you can get the schema, you can get the field.
For a dexterity content type, if you know the field name and the type's portal_type, you can get the schema from the type's Factory Type Information (FTI).
So, if we know portal_type and field_name:
from zope.component import getUtility
from plone.dexterity.interfaces import IDexterityFTI
fti = getUtility(IDexterityFTI, name=portal_type)
schema = fti.lookupSchema()
field = schema.get(field_name)
Getting the Widget
From the z3c.form documentation: http://packages.python.org/z3c.form/widget.html
The widget is a multiadapter, so if you have the field, you can get it like so:
ageWidget = zope.component.getMultiAdapter((field, request),
interfaces.IFieldWidget)
Important: If you have however specified a widget via plone.autoform, then that widget won't be fetched. plone.autoform manually sets a widgetFactory on the z3c.form.field.Field object (which is not the same as the zope.schema Field!). The best way to get the widget then, is what you have already done, by manually calling initiating a FieldWidget.
So for example if you want the UserAndGroupSelectionWidget:
widget = FieldWidget(field, UserAndGroupSelectionWidget(field, request))
P.S Since I'm also in the collective and use the picker widget, I've updated the code for you ;)

Related

Extending SearchableText using collective.dexteritytextindexer

I am trying to extend the SearchableText index for my content type.
I have succeeded in getting multiple fields to be included by marking them as indexer:searchable="true" in the model file.
However I can't extend the SearchableText from my type's py as follows:
class IMyBehavior(form.Schema):
dexteritytextindexer.searchable('description')
description = schema.Text(title=u'Precis')
alsoProvides(IMyBehavior, IFormFieldProvider)
class MySearchableTextExtender(object):
adapts(IMyBehavior)
implements(dexteritytextindexer.IDynamicTextIndexExtender)
def __init__(self, context):
self.context = context
def __call__(self):
"""Extend the searchable text with a custom string"""
return 'some more searchable words'
I have to admit, I don't really know how the first class works. Do I have to set the searchable fields in this class to be able to extend the SearchableText in the second?
If I remove all the indexer:searchable="true" from the model, then the SearchableText is just empty.
Is the first class trying to register the schema at the same time? If so what should this look like if it's just extending the SearchableText?
The collective.dexteritytextindexer provides two important features:
As you already achieved, dexteritytextindexer gives you the ability to put values into Plone's SearchableText index. By adding dexteritytextindexer.searchable(FIELDNAME) to your form, the value of the field will appear in the SearchableText. In Archetypes you have the same feature, by adding searchable=True to the field definition.
collective.dexteritytextindexer gives you also the ability to extend the searchableText manually by registering an IDynamicTextIndexExtender adapter. It extends the values from part 1 with the values from your adapter.
I guess the Problem in your case is, that you have missed to register the adapter: https://github.com/collective/collective.dexteritytextindexer#extending-indexed-data
Example:
<adapter
factory=".yourbehavior.MySearchableTextExtender"
provides="collective.dexteritytextindexer.IDynamicTextIndexExtender"
name="IMyBehavior"
/>
Here's a working example:
This code extends the SearchableText of a container with the searchableText of it's children.
IDynamicTextIndexExtender adapter:
https://github.com/4teamwork/ftw.simplelayout/blob/a7d631de3984b8c1747506b9411045fdf83bc908/ftw/simplelayout/indexer.py
Register the adapter with zcml:
https://github.com/4teamwork/ftw.simplelayout/blob/a7d631de3984b8c1747506b9411045fdf83bc908/ftw/simplelayout/behaviors.zcml#L21
And the most important part - test the implementation:
https://github.com/4teamwork/ftw.simplelayout/blob/a7d631de3984b8c1747506b9411045fdf83bc908/ftw/simplelayout/tests/test_indexer.py#L31

Querying adapters against plone.directives.form.Schema

I have a form model created as following:
from plone.app.directives import Form
class IFormSchema(form.Schema):
foobar = schema.Bool(title=u"Just another field")
I'd like to register an adapter against this definition:
#component.adapter(IFormSchema)
#interface.implementer(ITreeSelectURLProvider)
def TreeSourceURL():
"""
"""
return "http://foobar"
The registration goes correctly.
However, there is an issue that I don't know if IFormSchema is directly provided by any object in any point of z3c.form processing chain, so that I could call:
provider = ITreeSelectURLProvider(someObject)
Does IFormSchema get directly applied to some object (zope.interface.directlyProvides?) in any point of z3c.form or plone.autoform chain
If not, what is the recommended practice so that I can register adapters against the model? What classes I should make to implement this interface?
To make matters worse, the context in the question is not a real content item but a subform object.
Dexterity make sure that the schema interface (be that defined on the filesystem and referenced in the FTI, or defined through the web or in an XML file) is provided by instances of the content type.
This isn't really about forms, it's about Dexterity. The form.Schema base class is just a marker that extends Interface, and which allows some of plone.autoform's processing to take place at configuration time.

Setting field defaults on programmatically created Dexterity items

I have a Dexterity content type based on plone.directives.form.Schema which has a number of form hints for assigning defaults:
#form.default_value(field=ITrial['start_on'])
def default_start_on(data):
return datetime.now()
Some of the defaults are more complex, passing back objects that are themselves instances of Dexterity types. These objects are essential for the main type's setup, which is triggered by various events.
I'm now in the process of testing. Ideally, I'd like to be able to use something like:
item = createContentInContainer(folder, 'ctcc.model.trial', 'item')
That is, I'd like the defaults to be picked up by the item without having to be manually passed into the constructor.
If I was using zope.schema I could use FieldProperty to set up proxies to the schema fields. Is there something equivalent for Dexterity, or perhaps a function for pushing an object through form creation?
Solution: I ended up going with David's option #1, intercepting ObjectCreatedEvent.
#grok.subscribe(ITrial, IObjectCreatedEvent)
def create_trial(trial, event):
if getattr(trial, 'start_on', None) is None:
trial.start_on = default_start_on(None)
It stills feels like I'm replicating part of form behaviour, but at least it's using the same functions that are providing the form defaults.
As you've discovered, the #form.default_value decorator is respected by z3c.form forms, but not when items are created in other ways. You have several options:
Write a handler for the ObjectCreatedEvent for your content type which sets up the necessary default values. This is the simplest approach to implement, but may not work if there are other handlers of this event that need the values in place.
Create your own subclass of Dexterity's generic Item class, and use it instead of Item as the basis for your content type. Then you can customize the __init__ method to set whatever you want. This would require migration of existing content items though, if you already have some.
This is a more complicated option. Replace the factory utility used to construct the content type. createContentInContainer ends up looking for an IFactory utility with a name equal to the factory attribute of the content type's FTI. The default implementation is in plone.dexterity.factory but you could replace it with a different one that does more.

datagridfield not visible in dexterity through-the-web content type editor

I've added collective.z3cform.datagridfield to my buildout, see it as active in my site settings; however, I cannot add a field of type datagridfield via the through-the-web editor for a dexterity content type. What am I missing?
Extending vangheem's answer: You can provide support for collective.z3cform.datagridfield by providing a field factory, but it will be a hack.
Reason being is, that the collective.z3cform.datagridfield.row.DictRow expects a schema, defining the table rows. This becomes a subform once rendered. The schemaeditor in this instance would need to ask you depending on the field type also for the (table-) schema.
Depending on what solution you are after, you might be able to get away by implementing a field factory with a fixed table schema like this:
from five import grok
from zope import schema
import collective.z3cform.datagridfield.row
import plone.schemaeditor.interfaces
import zope.interface
# example from http://pypi.python.org/pypi/collective.z3cform.datagridfield
class ITableRowSchema(zope.interface.Interface):
one = schema.TextLine(title=u"One")
two = schema.TextLine(title=u"Two")
three = schema.TextLine(title=u"Three")
# new field factory for the zope.schema.interfaces.IObject
class DataGridFieldFactory(grok.GlobalUtility):
grok.provides(plone.schemaeditor.interfaces.IFieldFactory)
# this will show up in the schema editor vocabulary
title = "DataGridField"
def __call__(self, *args, **kwargs):
# that's the horrid part as it will nail your field to this
# specific schema
kw = dict(value_type=collective.z3cform.datagridfield.row.DictRow(
schema=ITableRowSchema))
kwargs.update(kw)
return zope.schema.List(*args, **kwargs)
Please have a look into: plone.schemaeditor.fields.py for more information about the field factory.
This will get you a basic datagrid for your content type. What's missing is the widget, which you currently can't be declared AFAIK.

How to check constraints on dexterity content types fields

I want to check during edit form saving process the value of a field verify some constraints
(understand calling a method where I can invalidate the form action)
The field must be defined via schema (not supermodel), otherwise the field isn't visible in the schema. Once the field is defined in the schema, you may use a decorated function like the following to set a field validator:
#form.validator(field=IMySchema['title'])
def validateTitle(value):
if value == value.upper():
raise schema.ValidationError(u"Please don't shout")
I'm pretty sure you can do this with a filesystem code dexterity type using zope.interface invariants.
Take a look at the Dexterity Developer Manual, on the chapter dedicated to validators.

Resources