[Solved] How can i use collective.instancebehavior correctly?

My Behavior Definition:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    i18n_domain="my.addon">

  <plone:behavior
      name="my.addon.pincode"
      title="PIN Code Behavior"
      description="Adds PIN Code Behavior"
      provides=".pincode.IPinCodeBehavior"
      factory=".pincode.PinCode"
      marker=".pincode.IPinCode"
      for="plone.app.contenttypes.interfaces.IFile" />

</configure>
# pincode.py
from my.addon import _
from plone.app.contenttypes.interfaces import IFile
from plone.autoform.interfaces import IFormFieldProvider
from plone.dexterity.interfaces import IDexterityContent
from plone.supermodel import model
from zope import schema
from zope.component import adapter
from zope.interface import implementer
from zope.interface import Interface
from zope.interface import provider

import random

def create_pincode():
    return random.randint(1000, 9999)


class IPinCode(Interface):
    pass

@provider(IFormFieldProvider)
class IPinCodeBehavior(model.Schema):

    pin = schema.Int(
        title=_("Pin Code"),
        description=_("the PIN Code to protect the File"),
        defaultFactory=create_pincode,
        readonly=False,
        min=1000,
        max=9999,
        required=False,
    )

@implementer(IPinCodeBehavior)
@adapter(IFile)
class PinCode(object):
    def __init__(self, context):
        self.context = context

    @property
    def pin(self):
        return self.context.pin

    @pin.setter
    def pin(self, value):
        self.context.pin = value

Here a little Test:

def test_instance_behavior(self):
  self.portal.invokeFactory("File", id="file1", title="File 1")
  file1 = self.portal.file1

  from my.addon.behaviors.pincode import IPinCodeBehavior
  from collective.instancebehavior import enable_behaviors
  from collective.instancebehavior import IInstanceBehaviorAssignableContent
  from collective.instancebehavior import instance_behaviors_of

  enable_behaviors(
    file1,
    ["my.addon.pincode"],
    [IPinCodeBehavior], # The marker interface or the schema ???
  )
  
  self.assertIn(
    "my.addon.pincode",
    instance_behaviors_of(file1),
    "Instance Behavior IPinCode not enabled",
  )

  self.assertTrue(
    IPinCodeBehavior.providedBy(file1),
    "IPinCodeBehavior not provided by File1",
  )

  aspect = IPinCodeBehavior(file1, None)
  if aspect is not None:
    print(aspect.pin)  # don't work
    # AttributeError: 'RequestContainer' object has no attribute 'pin'

If i use the Behavior in the normal way via FTI all is fine. But if i use collective.instancebehavior, my behavior is not available, i get an error. I think i forgot something. The Note in the Readme of collective.instancebehavior say:

Note: the targeted object must implement collective.instancebehavior.IInstanceBehaviorAssignableContent.

How can i achieve this? Has anyone a hint?

I did this for a filterable folder (add collection behavior for regular folders):

def activate_filtered_folder(context=None):
    """Acticate the filtered folder support."""
    if not context:
        return

    alsoProvides(context, IInstanceBehaviorAssignableContent)
    enable_behaviors(
        context,
        ["plone.collection", "my.filtersettingsbehavior"],
        [ISyndicatableCollection],
        reindex=False,
    )
    set_collection_defaults(context)

def deactivate_filtered_folder(context=None):
    """Deacticate the filtered folder support."""
    if not context:
        return

    disable_behaviors(
        context,
        ["plone.collection", "my.filtersettingsbehavior"],
        [ISyndicatableCollection],
        reindex=False,
    )
    noLongerProvides(context, IInstanceBehaviorAssignableContent)

I think you just missed the alsoProvides() part.

Ok, i add the alsoProvides method like you, but the error is present. Hmm...

This should be

  enable_behaviors(
    file1,
    ["my.addon.pincode"],
    [IPinCode], # <-- The marker interfaces!
  )

From the code:

Behavior marker interfaces belonging to the behaviors to be enabled. This is a list of interface classes.

That should be all to get it working. Do you load all required add-ons for your tests?

Context has to provide IInstanceBehaviorAssignableContent. You can inherit it to some marker interface used by the behavior or, if you use base classes, let them implement it.
In the product shop we use it to dynamically assign variants. A base behavior says: this product may have variants:

and in the next step one can activate variant aspects as instance behaviors to it.

@jensens @tmassman Thank's for help. Now it runs like expected.

class InstanceBehaviorFunctionalTest(unittest.TestCase):
    """basic use cases and tests """

    layer = MY_ADDON_FUNCTIONAL_TESTING

    def setUp(self):
        app = self.layer["app"]
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        setRoles(self.portal, TEST_USER_ID, ["Manager"])
        self.portal_url = self.portal.absolute_url()
        
        api.content.create(
            container=self.portal,
            type="File",
            id="file",
            title="File",
        )
        transaction.commit()

        self.browser = Browser(app)
        self.browser.handleErrors = False
        self.browser.addHeader(
            "Authorization",
            "Basic {0}:{1}".format(
                SITE_OWNER_NAME,
                SITE_OWNER_PASSWORD,
            ),
        )

    def test_instance_behavior(self):

        obj = self.portal.file

        alsoProvides(obj, IInstanceBehaviorAssignableContent)

        enable_behaviors(
            obj,
            ("my.addon.pincode",),
            (IPinCode,),
        )

        self.assertIn(
            "my.addon.pincode",
            instance_behaviors_of(obj),
            "Instance Behavior my.addon.pincode not enabled",
        )

        self.assertTrue(
            IInstanceBehaviorAssignableContent.providedBy(obj),
            "IInstanceBehaviorAssignableContent not provided by object",
        )

        self.assertTrue(
            IPinCode.providedBy(obj),
            "IPinCode not providedBy object",
        )

        # the custom PIN
        PIN = 1953

        # the Aspect
        aspect = IPinCode(obj, None)

        # init the PIN Code
        pincode = IPinCodeBehavior(obj, None)
        pincode.pin = PIN

        # here the object has the property
        self.assertEqual(
            aspect.pin,
            PIN,
            "The Aspect PIN Code has not the right value",
        )
        self.assertEqual(
            pincode.pin,
            PIN,
            "The PIN Code has not the right value",
        )
        self.assertEqual(
            aspect.pin,
            pincode.pin,
            "Assign value via Behavior fails",
        )
        self.assertEqual(
            obj.pin,
            PIN,
            "Object has no valid PIN Code",
        )

1 Like

Plone Foundation Code of Conduct