DocTest fails when creating an object - plone

I'm having a problem with a doctest because I'm trying to rename the IDs of a content type object in an IObjectAddedEvent handler. My requirement is to have IDs that are sequential and context specific eg CAM-001, CAM-002, BLK-001, BLK-002, etc
When I add a object manually in the browser the event handler renames the id correctly but when I try create it in a doctest it fails soon after it is added to it's container. plone.dexterity addContentToContainer calls _setObject with the original id, then the event handler kicks in and renames the id, and then when _getObject uses original id it obviously can't find the object so it bomb with an attribute error.
I created a product to illustrate this here https://github.com/mikejmets/wt.testrig.
I also tried using plone.api in the doctest but that also fails.
All ideas welcome.

Since you are using dexterity, the best solution would be to write your own NameGenerator behavior.
I guess your DX content has the following behavior activated:
<element value="plone.app.content.interfaces.INameFromTitle" />
This bahavior is responsible to rename the item after creation.
You should remove this and add your own.
Example:
Register behavior with zcml.
<plone:behavior
title="Special name(id) generator"
description=""
provides="dotted.name.to.your.INameGenerator"
factory="dotted.name.to.your.name_generator.NameGenerator"
for="dotted.name.to.content.interface"
/>
Corresponding python code.
from plone.app.content.interfaces import INameFromTitle
from zope.component import getUtility
from zope.interface import implements
class INameGenerator(INameFromTitle):
"""Behavior interface.
"""
class NameGenerator(object):
"""Customized name from title behavior."
"""
implements(INameGenerator)
def __init__(self, context):
self.context = context
#property
def title(self):
# YOUR IMPLEMENTATION
title = ...
return title
IMPORTANT:
Inherit your interface from INameFromTitle
Now add <element value="dotted.name.to.your.INameGenerator" /> to your contents behaviors.
Probably removing the INameFromTitle behavior from your content type could be enough, but implement explicitly your own behavior would be better.

Related

How to use annotations with z3c.form's DictionaryField

There is documentation on using Python dict with z3c.form (loading & storing form data).
However, the z3c.form datamanager used for dicts is not registered for other types or interfaces (see reference), whereas annotations typically use something like PersistentDict.
How can I use the DictionaryField datamanager in this scenario? Ie. so that in my form's getContent method I merely return the PersistentDictannotation.
Well, unfortunately there seems no simple solution for this requirement.
I once faced the same problem using the datagrid field in a z3c form.
The following instruction solves the problem for the datagrid field, which is a list (PersistentList of dicts (PersistentMappings).
I guess you may adapt this solution for your case.
First you need to add the following code to the getContent method:
from plone.directives import form
class MyForm(form.SchemaEditForm):
schema = IMyFormSchema
ignoreContext = False
def getContent(self):
annotations = IAnnotations(self.context)
if ANNOTATION_KEY not in annotations:
annotations[ANNOTATION_KEY] = PersistentMapping()
return YourStorageConfig(annotations[ANNOTATION_KEY])
Important note: I wrap the annotation storage to satisfy the get/set behavior of the z3c form. Check the following YourStorageConfig implementation and you will see why :-).
class YourStorageConfig(object):
implements(IMyFormSchema)
def __init__(self, storage):
self.storage = storage
def __getattr__(self, name):
if name == 'storage':
return object.__getattr__(self, name)
value = self.storage.get(name)
return value
def __setattr__(self, name, value):
if name == 'storage':
return object.__setattr__(self, name, value)
if name == 'yourfieldname':
self.storage[name] = PersistentList(map(PersistentMapping, value))
return
raise AttributeError(name)
yourfieldname should be the field name you are using in the form schema.
To implement the a datagrid field, there is some more work to do, but this may be enough for your case.
Please post comments, or tracebacks, so I can provide further help. I'll gonna add more details/explanation if necessary ;-)
It turns out the answer is as easy as the following ZCML adapter registration:
<adapter
for="persistent.dict.PersistentDict zope.schema.interfaces.IField"
provides="z3c.form.interfaces.IDataManager"
factory="z3c.form.datamanager.DictionaryField"
/>
With that, the following customization of a form is sufficient to use (PersistentDict) annotations for loading & storing form data:
def getContent(self):
"return the object the form will manipulate (load from & store to)"
annotations = IAnnotations(self.context)
return annotations[SOME_ANNOTATIONS_KEY_HERE]
This is assuming that a PersistentDict has been previously stored at annotations[SOME_ANNOTATIONS_KEY_HERE] - otherwise the above code will result in KeyError. It would probably be a good idea to change above getContent so that if the annotation does not yet exist, it is created and initialized with some default values.
Finally, note that for some reason, z3c.form warns against enabling DictionaryField for every mapping type, so it may be prudent to for example subclass PersistentDict for form storage, rather than use it directly. I submitted an issue to z3c.form asking for clarification of that warning.

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

How to add the is_folderish attribute to Dexterity objects?

The .is_folderish attribute is used in many places. For example when setting an object as the default view or when activating discussions on an object.
My first question is how to check is an object has that attribute set. I tried using the bin/instance debug with something like this:
>>> app.site.news.is_folderish
...
AttributeError: is_folderish
I imagine that I can't reach attributes on that way because app.site.news is a wrapper to the object that has that attribute.
My second question is how to add that attribute to a new Dexterity object. I suppose I can do it using the code below (but I can't test it before the my first question is resolved).
from zope import schema
from plone.dexterity.content import Item
class IHorse(form.Schema):
...
class Horse(Item):
def __init__(self):
super(Horse, self).__init__(id)
is_folderish = False
But I'm not sure about how both classes could be linked.
You don't need to add is_folderish to your types; it is an index in the catalog, and Dexterity types already have the proper attribute for that index, isPrincipiaFolderish.
If you do need to add attributes to content types, you can either use an event subscriber or create a custom subclass of plone.dexterity.content.Item:
subscribers can listen for the IObjectCreatedEvent event:
from zope.app.container.interfaces import IObjectAddedEvent
#grok.subscribe(IYourDexterityType, IObjectCreatedEvent)
def add_foo_attribute(obj, event):
obj.foo = 'baz'
custom content classes need to be registered in your type XML:
from plone.dexterity.content import Item
class MyItem(Item):
"""A custom content class"""
...
then in your Dexterity FTI XML file, add a klass property:
<property name="klass">my.package.myitem.MyItem</property>

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.

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.

Resources