How to use annotations with z3c.form's DictionaryField - plone

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.

Related

Why does FreeMarkers built-in "?is_string" return true for an Object?

If I pass an Object into the model and test it with the "?is_string" built-in, it will falsely return a true value.
Is it possible (without checking the class name) to have a proper type checks on Objects?
FreeMarker: 2.3.28
Code to reproduce:
public class Test {}
// In Test Controller
ModelAndView mv = new ModelAndView("test");
mv.addObject("test", new Test());
// In test.ftl
<#if test?is_string>
${test} - is a string!
</#if>
// Result
Test#455b31c - is a string
The problem is that that approach isn't really supported by FreeMarker. The Java objects are mapped to some template language values via Configuration.objectWrapper, and the template only sees the result of that mapping. Furthermore the template language has a different type system than Java, a simplistic one, without classes. (It was a design goal back then that the data-model is just some simple tree, and the templates will work no mater what objects are behind, as far as it still gives the same tree.) ?is_... doesn't check the Java type, but the type according the template language. As with the usual ObjectWrapper-s a "generic" object (means, nothing recognized like List, Map, Date, etc.) can be used as a strings whose value is whatever toString() returns, it's a string as far as the template language is concerned. It's kind of duck typed...
A workaround I can think of is that first check the value with ?is_hash, as that will catch the said generic objects (as they support ., they are hashes as well, not just strings). Or instead just check the property you expect to be present in a Test. Then on the "else" branch you can continue with ?is_string.

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.

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.

How to set values of a Plone autoform in update methode with ignoreContext = True

I'm trying to make a form where I'm storing values outside of the context. Storing is done and working well but now I would like the update method to fill the form on rendering process. So I'm overriding update method of the Form class that way:
def update(self):
super(ConfigurationForm,self).update()
form = self.request.form
if not form:
#We are on a rendering process
provider = self.getProvider()
settings = provider.get()
#TODO: update widget values !?
settings is a dict where keys are equals to Interface fields's names.
So I have tried many ways to update widgets values:
Using dataconverter (too much complex and don't know if this is the only way
Updating the self.request.form dict and call again the update method
playing with field objects
What is the good way to achieve this ? (supporting all kind of field ?)
Don't do ignoreContext. Override getContent() to return a dict instead. The dict will be used as a pseudo context.

Anonymous users creating content types: which approaches instead of invokeFactory can be used?

I've come across this little function that let anonymous users call invoke factory.
security.declarePrivate('anonymousInvokeFactory')
def anonymousInvokeFactory(self, container, type_name, id,
REQUEST=None, *args, **kw):
"""
Anonymous cannot add objects with InvokeFactory, so this is a
special
method to do it with. Must be called from other function to limit
possibillities of abuse.
"""
# remember original user
mtool = getToolByName(self, 'portal_membership')
originalUser = mtool.getAuthenticatedMember()
# wrap the request in new security to be able to add content
user = self.getWrappedOwner()
newSecurityManager(REQUEST, user)
container.invokeFactory(type_name, id, REQUEST=REQUEST, *args, **kw)
# set original user again
newSecurityManager(REQUEST, originalUser)
return id
I seems perfect for a situation where I'm using some proxyManager metadata. But I haven't seen this little snippet anywhere besides this nabble entry - is it safe? Which disadvantages can you see in this approach? EDIT: I've found now in official community plone docs effort some references.
My scenario: the anonymous user is creating an Archetype object on ZODB, in a specific context only, that only accepts this type of object. He can not see any objects, he is just calling a form that is going to create these objects. These objects are going to be created, and their attributes (fields) need to be populated as well. The _createObjectType approach creates the object but it doesn't add the fields even using **kwargs. EDIT2: It's possible to edit using default acessors like obj.setTitle. I'm now using this approach, and it works flawlessly.
I would be weary of using anything that sets up a new security manager. A better way to do this would be to bypass security when creating the object.
You can do something like:
pt = getToolByName(context, 'portal_types')
type_info = pt.getTypeInfo('portal_type')
ob = type_info._constructInstance(context, id)
# CMFCore compatibility
if hasattr(type_info, '_finishConstruction'):
return type_info._finishConstruction(ob)
else:
return ob
source: uwosh.pfg.d2c

Resources