How do I enable behaviors on existing objects?

I have a simple behavior that just adds one field to IMyType. The field name is 'program' and the schema is at IRFAProgram. It's a behavior because some people need it and some don't.

   <plone:behavior
     name="RFAProgram"
     title="RFA Program | Content field"
     description="Adds RFA Program field to the content type"
     provides="rfasite.content.behaviors.themes.IRFAProgram"
   />


@provider(IFormFieldProvider)
class IRFAProgram(model.Schema):
  
    directives.order_before(program="IThemes.theme")  
    directives.widget(program=RadioFieldWidget)
    program = schema.Choice(
        title="Program",
        values=['RFA', 'PRC'],
        required=True,
    )

I also have a large collection of objects of IMyType that I'd like to import data into that field, but the behavior was just enabled now, and none of the existing objects know about it:

>>> obj.program
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'RequestContainer' object has no attribute 'program'

When I create or edit an existing object by visiting the edit form in a browser, the field appears and I can set it. Once I 'save', then the object has the property.

But I have thousands of objects to import.
How can I programmatically add the behavior to existing objects, and set the property on the object?

I've messed about quite a bit with IBehaviorAssignable and tried to do obj = IProgram(obj) but I keep getting 'Can Not Adapt'.

Can someone point me in the right direction?

Hey,

I don't know if something like this would help:

BEHAVIORS.setdefault(SomeContext, set()).add(ILockingSupport)

It is from the docs of the behavior-egg way down in the "Examples"-Section: plone.behavior/plone/behavior/behaviors.rst at master · plone/plone.behavior · GitHub

Hope this can help.

I am usually using a context_property in behavior together with an optional default value

Thanks @zopyx Unfortunately, in my case, I can't use a default value. I've got a big import of data to actually set these fields to their correct values.

I got here by doing an import and not assigning the behaviors to the content type first. the fields were ignored on import. We would like to enable the behaviors and then write a quick script to set all the values.

well, then write a script that would iterate over all existing content object and set the property as needed...

That's already done. The script is written.

One of the lines is:

obj.program = program

and that's where we get an attribute error, because the behavior is not assigned to the context.

@wkbkhard was on the right track, but this BEHAVIORS global is simulating the plone registry that keeps track of what behavior is assigned to which Type. at least, that's what I understand. It's simulating the behavior being enabled or disabled. In my case, it's enabled, just not affecting existing content.

I have some doubts here. Did you commit your transaction?

what part of AttributeError do you not understand?

A completely shot in the dark:

If you plan to set the field (program) for all items of that content type, could it be a workaround to add the field to portal_catalog / index / metadata ( and remove the index when you are done )

Sorry but I am not getting your point. When you enable a behavior to a type globally (which is the standard unless you use collective.instancebehavior), then none of the object instance will have this attribute program in its instance dict. That's why you need the context_property() if the types with the enabled behavior are your own or you need to iterate over all existing content and add "program" to the instance dict yourself (obj.program = <some default>).

You can either set a default on the behavior, or during import do something like:

for obj in my_objects:
    if 'program' in obj.__dict__:
        continue
    obj.program = 'my sharona'  # or obj.__dict__['program'] = 'my sharona'

@zopyx First, I'm sorry for the flippant response. Your suggestion 'just write a script' was actually the right answer.

I was typing 'obj.program' and getting attribute error, and ASSUMING assignment would give me the same issue.

obj.program = 'foo' worked correctly, and the transaction.commit() saves it.

I was way, way overthinking the problem.

2 Likes

This happens sometimes :nerd_face: