[Solved] Adding Event Listner for a behavior

I am hopelessly trying to add a eventlistner for a behavior.

I have folders content types with 'dates' behavior, defined like

  <plone:behavior
    title="Dates behavior"
    description="Add multiple dates"
    provides=".behaviors.IDatesBehavior"
    for="plone.dexterity.interfaces.IDexterityContent"
    />


  class IDatesBehavior(Interface):
    """List of Datetime fields"""
    dates = schema.List(
        title=_(u'Additional Dates'),
        value_type=schema.Datetime(),
        required=False,
    )

  alsoProvides(IDatesBehavior, IFormFieldProvider)

The plan is that these dates shall be saved inside the Folder(ish content type) on save / edit, so I added:

    <subscriber
    for=".behaviors.behaviors.IDatesBehavior
    zope.lifecycleevent.interfaces.IObjectModifiedEvent"
    handler=".behaviors.make_dates"
    />

  <subscriber
    for=".behaviors.behaviors.IDatesBehavior
    zope.lifecycleevent.interfaces.IObjectAddedEvent"
    handler=".behaviors.make_dates"
    />

This works for the content with Datesbehavior but it seems like it also gets called when manually adding a Date (content type) to the folders object.

To try to work around this I added a check to see if that helped).
But no matter how I try to add or delete object, I keep getting the following errors:

 RuntimeError: maximum recursion depth exceeded while calling a Python object
 INFO ExtendedPathIndex Attempt to unindex nonexistent object with id -1803674900
 Zope.ZCatalog uncatalogObject unsuccessfully attempted to uncatalog an object with a uid of /Plone/News/my-item/date-10

After some googling it seems like plone.api is not the right way, what could I try to add/delete objects ?


alsoProvides(IDatesBehavior, IFormFieldProvider)

def make_dates(self, context):
    """Make date content items"""

    if self.portal_type == 'movie':
        event_time = self.end - self.start
        title = self.Title

        #old_dates = self.listFolderContents(contentFilter={"portal_type" : "date"})

        ids = self.keys()   

        if len(ids) > 0:
            # manage_delObject will mutate the list
            # so we cannot give it tuple returned by objectIds()

            ids = list(ids)
            self.manage_delObjects(ids)

            #if len(old_dates) > 0:
            #    for old_date in old_dates:
            #        #plone api gives maximum rec error
            #        import pdb;pdb.set_trace()
            #        self.manage_delObjects(old_date)
            #        #api.content.delete(old_date)

        if self.dates:
            for new_date in self.dates:
                import pdb;pdb.set_trace()
                item = createContentInContainer(self, "date", title=title, start=new_date, end=new_date+event_time)
                #self.invokeFactory("date", "date")
                #api.content.create(container=self, type="date", title='title1')

Have those folders your behaviour? Then, it would be natural that you get a loop should your "modified" handler modify the object.

I see two options for you:

  • events are notified by fairly high level API calls; there are ways to modify an object without notifying events.
  • your handler could set a marker on your object (e.g. using a so called volatile attribute (their name has the prefix _v_)) and thereby recognize a recursive call (and then do nothing).

The main folder content type 'Movie' has the behavior, but the 'Dato' (I changed the name incase Date conflicts with the catalog') does not.

I added a check (for 'content type movie'), but it still does not work.

If I remove the 'add part', the remove part works (so all items in the container (Movie) gets deleted.

I will look into the volatile attribute

Thank you.

I am a bit confused about how the event listener is called:

I made a listener that does this:

def make_dates(object, event):
    if object.Title == '5':
        object.Title = '6'

    if object.Title == '4':
        object.Title = '5'

    if object.Title == '3':
        object.Title = '4'

    if object.Title == '2':
        object.Title = '3'

    if object.Title == '1':
        object.Title = '2'

If I add content and set the title to '1', the title ends up as '6'.

Is the event listener supposed to get fired several times ?

After hours and hours…

To me, this is quite confusing

What happens is that if something is added to a folderish content type, the modified event fires.

So, for example: in my 'Movie' content type I planned to make a 'gallery' with images added to the folderish content type. The movie content type is based on wildcard.media, and every time I add an image, it will fetch the preview thumbnail from youtube.

After discovering this, I made an ugly 'check'. This seems to work (but there is still an error about 'Attempt to unindex nonexistent object'

My code now is this:

def make_dates(object, event):
    """Make date content items"""

    if str(event).startswith("<zope.lifecycleevent.ObjectModifiedEvent"):
        #check how long the event lasts
        event_time = object.end - object.start

        old_dates = object.listFolderContents(contentFilter={"portal_type" : "dato"})

        if len(old_dates) > 0:
            api.content.delete(objects=old_dates)

        if object.dates:
            _create_dates(object, object.dates, object.title, event_time)


def _create_dates(object, dates, title, event_time):
    for date in dates:
        #all events are calculated as same lenght
        my_date = api.content.create(
            type='dato',
            container=object,
            title=date.isoformat(),
            start=date,
            end= date+event_time,
            )
        #api.content.transition(obj=my_date, transition='publish')

UPDATE: the dates need to be 'localized' to show up in the calendar, maybe something like:

pytz.timezone(default_timezone()).localize(date)

If you add something to a folderish content type, you obviously modify this folderish content type. Thus, it is natural that an IObjectModified event is notified for it. It would be a bug, however, if such an event is notified for the object added to the folder.

I hope this is not the standard way for you to check that some object is implemented by a specific class. Typically, you would use isinstance or obj.__class__ in such tests. Looking at the text representation of an object for this is expensive and unreliable (in general).

Thanks.

for some reason I dont understand: Isinstance always returned 'True', so I ended up with the same errors.

But class works:
if event.class == ObjectModifiedEvent:

isinstance(event, ObjectModifiedEvent) will return True, if event.__class__ is either ObjectModifiedEvent or a subclass thereof.

Thanks for all the help

Espen, did you give up on the "Attempt to unindex nonexistent object" errors?

I got it to work. I am currently in Mozambique so I can tell you more)... and I have basically left this forum

Did you guys figure out the "Attempt to unindex nonexistent object" errors?
I've been fighting with that all morning. Any help would be appreciated.

From my experience this happens if you create an object with an id and rename it immediately. That happens, for example, if you use api.content.create without enforcing the id parameter.
Quick solution: create the object immediately with the proper id. Anyway it should be safe to ignore the warning.

2 Likes

Just a view hints:

  • you can use a second interface for the modified event, so that you only match it on specific objects. Like you did for the IObjectAddedEvent
  • If you want to check for a type, better use IObjectModifiedEvent.prodivedBy(event) or just use diffent handler for the two events you have.
1 Like

a typo:

ObjectModifiedEvent.providedBy(event)