How to create a new contenttreewidget for z3cform - plone

I'm trying to override the widget used for relateditems (dexterity so z3cform) which is the from plone.formwidget.contenttree.widget import MultiContentTreeWidget
The issue I have is I don't understand why my example try to find a component to IDataConverter where there is no IDataConverter for contenttree widget and it's parent.
The code is:
#zope
from zope import interface
import z3c.form.interfaces
import z3c.form.widget
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
#plone
from plone.formwidget.contenttree.interfaces import IContentTreeWidget
from plone.formwidget.contenttree.widget import MultiContentTreeWidget
from plone.app.relationfield.widget import RelationListDataManager
#internal
class DatalistManager(RelationListDataManager):
pass
class IMultiDatalistWidget(IContentTreeWidget):
"""Datalist widget marker for z3c.form """
class MultiDatalistWidget(MultiContentTreeWidget):
interface.implementsOnly(IMultiDatalistWidget)
input_template = ViewPageTemplateFile('templates/datalist_input.pt')
klass = u'html5-datalist-multiselection-widget'
js_template = """\
(function($) {
$().ready(function() {
console.log('autocomplete ready ?');
});
})(jQuery);
"""
def js_extra(self):
return ""
#interface.implementer(z3c.form.interfaces.IFieldWidget)
def MultiDatalistFieldWidget(field, request):
"""IFieldWidget factory for DatalistWidget."""
return z3c.form.widget.FieldWidget(field, MultiDatalistWidget(request))
And the zcml:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:z3c="http://namespaces.zope.org/z3c"
i18n_domain="collective.z3cform.html5widgets">
<include package="plone.app.relationfield" />
<!-- TRY TO OVERRIDE IRelationList default from plone.app.relationfield -->
<adapter factory=".widget_datalist.MultiDatalistFieldWidget"
for="z3c.relationfield.interfaces.IRelationList
.layer.Layer"
/>
</configure>
If I'm trying the widget I have the following traceback:
Traceback (innermost last):
Module ZPublisher.Publish, line 126, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 46, in call_object
Module plone.z3cform.layout, line 66, in __call__
Module plone.z3cform.layout, line 50, in update
Module z3c.form.form, line 208, in update
Module plone.z3cform.patch, line 21, in BaseForm_update
Module z3c.form.form, line 150, in update
Module z3c.form.form, line 134, in updateWidgets
Module z3c.form.field, line 277, in update
Module z3c.formwidget.query.widget, line 183, in update
Module z3c.formwidget.query.widget, line 230, in updateQueryWidget
Module z3c.form.browser.checkbox, line 45, in update
Module z3c.form.browser.widget, line 170, in update
Module z3c.form.widget, line 221, in update
Module z3c.form.widget, line 130, in update
TypeError: ('Could not adapt', <MultiDatalistWidget 'form.widgets.relatedItems'>, <InterfaceClass z3c.form.interfaces.IDataConverter>)
You can find the repository on github: https://github.com/toutpt/collective.z3cform.html5widgets

I once overrode the widget with this class to enable an upload ability for a webmailer. I think you want to override the generation of the json to make this jQuery compatible. Thanks a lot for that work.
Here is my class: http://pastie.org/7923172
Hope it helps.

Related

How to fix KeyError in zc.relation catalog

One of our sites has a broken relations catalog and I don't know how to fix it.
This is what I see in the log:
2015-11-20T09:27:43 ERROR Zope.SiteErrorLog 1448018863.240.913599974037 http://www.example.com/folder/news-item/##edit
Traceback (innermost last):
Module ZPublisher.Publish, line 138, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 48, in call_object
Module z3c.form.form, line 218, in __call__
Module collective.nitf.browser, line 64, in update
Module plone.dexterity.browser.edit, line 62, in update
Module plone.z3cform.fieldsets.extensible, line 59, in update
Module plone.z3cform.patch, line 30, in GroupForm_update
Module z3c.form.group, line 145, in update
Module plone.app.z3cform.csrf, line 21, in execute
Module z3c.form.action, line 98, in execute
Module z3c.form.button, line 315, in __call__
Module z3c.form.button, line 170, in __call__
Module plone.dexterity.browser.edit, line 26, in handleApply
Module z3c.form.group, line 126, in applyChanges
Module zope.event, line 31, in notify
Module zope.component.event, line 24, in dispatch
Module zope.component._api, line 136, in subscribers
Module zope.component.registry, line 321, in subscribers
Module zope.interface.adapter, line 585, in subscribers
Module zope.component.event, line 32, in objectEventNotify
Module zope.component._api, line 136, in subscribers
Module zope.component.registry, line 321, in subscribers
Module zope.interface.adapter, line 585, in subscribers
Module z3c.relationfield.event, line 76, in updateRelations
Module zc.relation.catalog, line 546, in unindex
Module zc.relation.catalog, line 556, in unindex_doc
Module zc.relation.catalog, line 622, in _remove
KeyError: 304600783
I already tried the code in The dreaded plone.relations IntId KeyError, written by #martijn-pieters some years ago, but seems is no longer valid as I can't find any interfaces named IComplexRelationshipContainer.
Any hints?
Verify if plone.relations are installed.
See here http://davidjb.com/blog/2010/10/bad-relationships-relationchoice-relationcatalog-and-removed-dexterity-content-in-plone, may be the solution for this problem.
Eg.
from zope.component.hooks import setSite
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManager import setSecurityPolicy
from Testing.makerequest import makerequest
from Products.CMFCore.tests.base.security import PermissiveSecurityPolicy, OmnipotentUser
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
from zc.relation.interfaces import ICatalog
_policy=PermissiveSecurityPolicy()
_oldpolicy=setSecurityPolicy(_policy)
newSecurityManager(None, OmnipotentUser().__of__(app.acl_users))
portal = makerequest(app['Plone'])
setSite(portal)
intids = getUtility(IIntIds)
catalog = getUtility(ICatalog)
print [x.from_object for x in sorted(catalog.findRelations({}))]
I think some years ago I stumpled upon something similar.
I launched this and everything worked fine later:
from Products.Five.browser import BrowserView
from Products.CMFCore.utils import getToolByName
from z3c.relationfield.event import updateRelations
from z3c.relationfield.interfaces import IHasRelations
from zc.relation.interfaces import ICatalog
from zope.component import getUtility
class View(BrowserView):
def __call__(self):
rcatalog = getUtility(ICatalog)
# Clear the relation catalog to fix issues with interfaces that don't exist anymore.
# This actually fixes the from_interfaces_flattened and to_interfaces_flattened indexes.
rcatalog.clear()
pc = getToolByName(self.context, 'portal_catalog')
brains = pc.searchResults(object_provides=IHasRelations.__identifier__)
for brain in brains:
obj = brain.getObject()
updateRelations(obj, None)
return "Catalog rebuilt for %s objects" % len(brains)

How to clean up old interfaces on zc.relation catalog?

I was using plone.directives.form version 1.0 with Plone 4.2.5 and after upgrading to 4.2.6 I started seeing the following traceback and I guess its due to plone.directives.form being upgraded to version 1.1.
How can I avoid this error? The only line of code that is not from default Plone on the traceback is on der.freitag.handlers where it does a transaction.commit() and the content type is just a regular dexterity content type.
1385740390.020.496977141203 http://10.100.0.207:8081/website/front-page/atomkraft/++add++der.freitag.customizablearticlelink
Traceback (innermost last):
Module ZPublisher.Publish, line 138, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 48, in call_object
Module plone.z3cform.layout, line 70, in __call__
Module plone.z3cform.layout, line 54, in update
Module plone.dexterity.browser.add, line 112, in update
Module plone.z3cform.fieldsets.extensible, line 59, in update
Module plone.z3cform.patch, line 30, in GroupForm_update
Module z3c.form.group, line 138, in update
Module z3c.form.action, line 99, in execute
Module z3c.form.button, line 315, in __call__
Module z3c.form.button, line 170, in __call__
Module plone.dexterity.browser.add, line 99, in handleAdd
Module z3c.form.form, line 247, in createAndAdd
Module plone.dexterity.browser.add, line 78, in add
Module plone.dexterity.utils, line 152, in addContentToContainer
Module Products.BTreeFolder2.BTreeFolder2, line 455, in _setObject
Module zope.event, line 31, in notify
Module zope.component.event, line 24, in dispatch
Module zope.component._api, line 136, in subscribers
Module zope.component.registry, line 321, in subscribers
Module zope.interface.adapter, line 585, in subscribers
Module zope.component.event, line 32, in objectEventNotify
Module zope.component._api, line 136, in subscribers
Module zope.component.registry, line 321, in subscribers
Module zope.interface.adapter, line 585, in subscribers
Module der.freitag.handlers, line 126, in set_customizable_article_link_id
Module transaction._manager, line 89, in commit
Module transaction._transaction, line 329, in commit
Module transaction._transaction, line 443, in _commitResources
Module ZODB.Connection, line 567, in commit
Module ZODB.Connection, line 623, in _commit
Module ZODB.Connection, line 658, in _store_objects
Module ZODB.serialize, line 422, in serialize
Module ZODB.serialize, line 431, in _dump
PicklingError: Can't pickle <class 'plone.directives.form.schema.Schema'>: attribute lookup plone.directives.form.schema.Schema failed
EDIT: the object that is being created has a relation field (a z3c.relationfield.schema.RelationChoice) and it turns out that zc.relation keeps a list of all interfaces provided by each member of any relation. Thus, after upgrading from plone.directives.form version 1.0 to version 1.1 the interfaces on plone.directives.form can no longer be resolved.
From z3c.relationfield documentation I don't see any option to update relations, so the only solution would be to get all relations and recreate them?
Just for reference that's how I fixed it:
While still on plone.directives.form 1.0 update your objects so that they do no longer provide the plone.directives.form.schema.Schema interface.
Then re-create the relations:
from z3c.relationfield import RelationValue
from zc.relation.interfaces import ICatalog
from zope.app.intid.interfaces import IIntIds
from zope.component import getUtility
from zope.event import notify
from zope.lifecycleevent import ObjectModifiedEvent
logger = logging.getLogger(LOGGER)
relations_catalog = getUtility(ICatalog)
intids = getUtility(IIntIds)
relations = [rel for rel in relations_catalog.findRelations()]
len_relations = len(relations)
logger.info('Relations needed to update: {0}'.format(len_relations))
for relation in relations:
# get the object link and the object linked
object_with_link = relation.from_object
object_linked_to = relation.to_object
# remove the broken relation
object_with_link.reference = None
# let the catalog remove the old relation
notify(ObjectModifiedEvent(object_with_link))
# create a new relation
object_linked_to_intid = intids.getId(object_linked_to)
new_relation = RelationValue(object_linked_to_intid)
object_with_link.reference = new_relation
# let the catalog know about this new relation
notify(ObjectModifiedEvent(object_with_link))
After this, stop the instance, run buildout again to update plone.directives.form to version 1.1 and voilĂ !
The Schema class is now in plone.supermodel.model, not plone.directives.form.schema.
However, the real problem you should try to fix is that the code is for some reason trying to store a schema in the ZODB. Pickling/unpickling Zope interfaces is not supported.
In case someone runs into this type of problem and cannot bring the old package back, here's another approach:
import transaction
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.User import system
from Testing.makerequest import makerequest
from zope.component.hooks import setSite
from zope.globalrequest import setRequest
from zc.relation.interfaces import ICatalog
from z3c.relationfield.event import _relations
from z3c.relationfield.event import _setRelation
from zope.component import getUtility
app = makerequest(app)
newSecurityManager(None, system)
portal = app.Plone
setSite(portal)
portal.REQUEST['PARENTS'] = [portal]
portal.REQUEST.setVirtualRoot('/')
setRequest(portal.REQUEST)
THRESHOLD = 100
relations_catalog = getUtility(ICatalog)
paths = ['/'.join(r.from_object.getPhysicalPath())
for r in relations_catalog.findRelations() if r.from_object]
relations_catalog.clear()
counter = 0
for path in paths:
obj = app.unrestrictedTraverse(path)
for name, relation in _relations(obj):
_setRelation(obj, name, relation)
counter += 1
if counter % THRESHOLD == 0:
transaction.savepoint()
transaction.commit()
One more option, I developed a package called collective.diversion that is designed to ease the pain of pickling errors when moving a class. Neither of the above scripts worked for me, however using collective.diversion did.
Adding the package to the buildout and including the following ZCML caused the items to be loaded, and they'll be persisted back in the correct place on write, so reindexing the catalogue should be sufficient.
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:diversion="http://namespaces.plone.org/diversion">
<diversion:class
old="plone.directives.form.schema.Schema"
new="plone.supermodel.model.Schema"
/>
</configure>

Vocabulary do not work on collective.z3cform.datagridfield's sub object schema because of the context is NO_VALUE

I'm working on a content type that uses datagridfield.
Within the subobject, I have a choice field with a custom vocabulary.
The vocabulary work outside the datagrid (If I use it directly in the content type's schema). But when I add it to the subobject, it doesn't work anymore.
Looking at the debug note, I see the vocabluary's context is NO_VALUE.
Any hint/direction to make this works ?
This is the simplest schema that won't work (I think):
# Import
from zope import schema
from zope.interface import Interface
from plone.directives import form
from collective.z3cform.datagridfield import DataGridFieldFactory, DictRow
# The container
class IMenu(Interface):
dishes = schema.List(value_type=schema.TextLine())
# The vocabulary
#grok.provider(IContextSourceBinder)
def getDishes(context):
terms = [SimpleVocabulary.createTerm(dish, dish, dish) for dish in context.dishes]
return SimpleVocabulary(terms)
# The object line
class IOrderLine(Interface):
dish = schema.Choice(source=getDishes)
quantity = schema.Int()
# The object
class IOrder(Interface):
form.widget(dishes=DataGridFieldFactory)
dishes = schema.List(value_type=DictRow(schema=IOrderLine))
Now I register Menu, Order as dexterity content type, add a menu and tried to add an order within it, I got an error on Order add view and this is the traceback:
Traceback (innermost last):
Module ZPublisher.Publish, line 126, in publish
Module ZPublisher.mapply, line 77, in mapply
Module Products.PDBDebugMode.runcall, line 70, in pdb_runcall
Module ZPublisher.Publish, line 46, in call_object
Module plone.z3cform.layout, line 66, in __call__
Module plone.z3cform.layout, line 50, in update
Module plone.dexterity.browser.add, line 112, in update
Module plone.z3cform.fieldsets.extensible, line 59, in update
Module plone.z3cform.patch, line 30, in GroupForm_update
Module z3c.form.group, line 128, in update
Module z3c.form.form, line 134, in updateWidgets
Module z3c.form.field, line 277, in update
Module z3c.form.browser.multi, line 63, in update
Module z3c.form.browser.widget, line 171, in update
Module z3c.form.widget, line 477, in update
Module collective.z3cform.datagridfield.datagridfield, line 107, in updateWidgets
Module collective.z3cform.datagridfield.datagridfield, line 91, in getWidget
Module z3c.form.browser.widget, line 171, in update
Module z3c.form.object, line 217, in update
Module z3c.form.object, line 208, in updateWidgets
Module z3c.form.object, line 87, in update
Module plone.z3cform.patch, line 21, in BaseForm_update
Module z3c.form.form, line 150, in update
Module z3c.form.form, line 134, in updateWidgets
Module z3c.form.field, line 277, in update
Module z3c.form.browser.select, line 51, in update
Module z3c.form.browser.widget, line 171, in update
Module z3c.form.widget, line 220, in update
Module z3c.form.widget, line 214, in updateTerms
Module zope.component._api, line 107, in getMultiAdapter
Module zope.component._api, line 120, in queryMultiAdapter
Module zope.component.registry, line 238, in queryMultiAdapter
Module zope.interface.adapter, line 532, in queryMultiAdapter
Module z3c.form.term, line 96, in ChoiceTerms
Module zope.schema._field, line 349, in bind
Module waga.game.core.content.interfaces, line 202, in getDishes
AttributeError: 'NO_VALUE' object has no attribute 'dishes'
> /home/quyetnd/Projects/waga.game.core/src/waga/game/core/content/interfaces.py(202)getDishes()
-> terms = [SimpleVocabulary.createTerm(dish, dish, dish) for dish in context.dishes]
The context object is an instance.
This is limitation in z3c.form (at least some versions, I think it has been fixed in the newer ones).
My ugly workaround in vocab source function:
if not context:
context = getSite()
if not context:
# Form is rendered from console without HTTP request traversing, etc.
# and thread local site variable is not set
raise RuntimeError("Oh snap. Beer time.")
...
http://developer.plone.org/serving/traversing.html#using-getsite

Add "tag" method to ATEvent

I use archetypes.schemaextender to extend the schema of ATEvent in order to add an ImageField.
That works.
Now, I want to add the "tag" method to the ATEvent in order to use the "new" way of scaling images.
So I do this :
In a file extender.py :
from Products.CMFCore.permissions import View
from AccessControl import ClassSecurityInfo
from zope.interface import Interface
class IImageExtender(Interface):
""" """
def tag():
""" """
class ImageExtender(object):
""" """
implements(IImageExtender)
security = ClassSecurityInfo()
def __init__(self, context):
self.context = context
security.declareProtected(View, 'tag')
def tag(self, **kwargs):
"""Generate image tag using the api of the ImageField
"""
return self.getField('image').tag(self, **kwargs)
After that in the configure.zcml:
<adapter for="Products.ATContentTypes.interface.IATEvent"
provides=".extender.IImageExtender"
factory=".extender.ImageExtender" />
But it doesn't work.
I get those errors :
Module zope.tales.pythonexpr, line 59, in __call__
- __traceback_info__: ( path('nocall:item_object/tag')(scale=size, css_class='tileImage'))
Module <string>, line 1, in <module>
Module zope.tales.pythonexpr, line 77, in __call__
Module zope.tales.expressions, line 217, in __call__
Module zope.tales.expressions, line 194, in _eval
Module zope.tales.expressions, line 217, in __call__
Module zope.tales.expressions, line 194, in _eval
Module zope.tales.expressions, line 124, in _eval
Module zope.pagetemplate.engine, line 66, in __call__
Module zope.traversing.adapters, line 136, in traversePathElement
- __traceback_info__: (<ATEvent at /Plone/manifestations/visites-conferences/conf>, 'tag')
Module zope.traversing.adapters, line 50, in traverse
- __traceback_info__: (<ATEvent at /Plone/manifestations/visites-conferences/conf>, 'tag', [])
LocationError: (<ATEvent at /Plone/manifestations/visites-conferences/conf>, 'tag')
Any clues ?
Thanks.
I believe the problem here is that you are trying to call 'tag' on the ATEvent
Object, but the method is defined on an adapter for the ATEvent Object. You need to be able to call IImageExtender(item).tag()
Unfortunately, restricted python prevents you from doing this directly in a page template, so your best bet will be to create a BrowserView for your extended ATEvent that provides access to the adapter.

Vocabulary source, function is not iterable, with dexterity in Plone 4.1

I have a custom Dexterity content type with collective.z3c.datagridfield define in the following way:
class ILanguageRow(Interface):
# Interface that defines a datagrid row.
lang = schema.Choice(
title=_(u'Language'), required=True,
source=my_languages,
default=u'en',
)
(...)
This it the function that returns the vocabulary, as in http://plone.org/products/dexterity/documentation/manual/schema-driven-forms/customising-form-behaviour/vocabularies
#grok.provider(IContextSourceBinder)
def languages(context):
"""
Return a vocabulary of language codes and
translated language names.
"""
# z3c.form KSS inline validation hack
if not ISiteRoot.providedBy(context):
for item in getSite().aq_chain:
if ISiteRoot.providedBy(item):
context = item
# retrieve the localized language names.
request = getRequest()
portal_state = getMultiAdapter((context, request), name=u'plone_portal_state')
lang_items = portal_state.locale().displayNames.languages.items()
# build the dictionary
return SimpleVocabulary(
[SimpleTerm(value=lcode, token=lcode, title=lname)\
for lcode, lname in sorted(lang_items) if lcode in config.CV_LANGS]
)
Inside the Edit and Add Form, the Choice field works correctly. But when I attempt to save the content:
TypeError: argument of type 'function' is not iterable
2011-07-08 13:37:40 ERROR Zope.SiteErrorLog 1310125060.840.103138625259 http://localhost:8081/Plone/++add++my.content.types.curriculum
Traceback (innermost last):
Module ZPublisher.Publish, line 126, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 46, in call_object
Module plone.z3cform.layout, line 70, in __call__
Module plone.z3cform.layout, line 54, in update
Module my.content.types.curriculum, line 356, in update
Module plone.z3cform.fieldsets.extensible, line 59, in update
Module plone.z3cform.patch, line 30, in GroupForm_update
Module z3c.form.group, line 134, in update
Module z3c.form.group, line 47, in update
Module z3c.form.group, line 43, in updateWidgets
Module z3c.form.field, line 275, in update
Module z3c.form.browser.multi, line 61, in update
Module z3c.form.browser.widget, line 70, in update
Module z3c.form.widget, line 396, in update
Module z3c.form.widget, line 88, in update
Module z3c.form.widget, line 390, in set
Module collective.z3cform.datagridfield.datagridfield, line 112, in updateWidgets
Module collective.z3cform.datagridfield.datagridfield, line 90, in getWidget
Module z3c.form.browser.widget, line 70, in update
Module z3c.form.object, line 213, in update
Module z3c.form.widget, line 88, in update
Module collective.z3cform.datagridfield.datagridfield, line 216, in set
Module z3c.form.object, line 229, in applyValue
Module z3c.form.validator, line 67, in validate
Module zope.schema._bootstrapfields, line 153, in validate
Module zope.schema._field, line 325, in _validate
TypeError: argument of type 'function' is not iterable
Why is this happening?
This happens when the field hasn't been bound or is missing the context. Normally validation will happen against a "bound" field (bound = field.bind(context)) so that your context-aware vocabulary can be turned into a static vocabulary for this context. It'll still be a function (not called with the context) when this didn't take place.
I am not familiar enough with the datagrid widget setup to pinpoint where this goes wrong, but it appears that it generates widgets on-the-fly and I suspect it doesn't bind the fields for these correctly. Take a look at DataGridField.getWidget method of the collective.z3cform.datagridfield.datagridfield module and try to figure out what's going on there with a debugger and / or file a bug with the authors of the package.
I solved the problem by providing my custom vocabulary as a Named Vocabulary, in this way:
from Products.CMFCore.interfaces import ISiteRoot
from zope.component import getMultiAdapter
from zope.site.hooks import getSite
from zope.globalrequest import getRequest
from my.content import config
class LanguagesVocabulary(object):
grok.implements(IVocabularyFactory)
def __call__(self, context):
# z3c.form KSS inline validation hack
if not ISiteRoot.providedBy(context):
for item in getSite().aq_chain:
if ISiteRoot.providedBy(item):
context = item
# retrieve the localized language names.
request = getRequest()
portal_state = getMultiAdapter((context, request), name=u'plone_portal_state')
lang_items = portal_state.locale().displayNames.languages.items()
# build the dictionary
terms = [SimpleTerm(value=lcode, token=lcode, title=lname)\
for lcode, lname in sorted(lang_items) if lcode in config.CV_LANGS]
return SimpleVocabulary(terms)
grok.global_utility(LanguagesVocabulary, name=u"my.content.LanguagesVocabulary")
and in my Dexterity content type schemata:
class ILanguageRow(Interface):
# Interface that defines a datagrid row.
lang = schema.Choice(
title=_(u'Language'), required=True,
vocabulary=u"my.content.LanguagesVocabulary",
)
This way it works.

Resources