2015-03-17

Kotti CMS events - insert subobjects automatically

Yet another small recipe for Kotti CMS: how to initialize automatically a new object once inserted with events, for example adding a subobject.

Use case? When someone creates a UserProfile object /users/name-surname, an event should create automatically a profile image in /users/name-surname/photo (a Kotti image instance).

It is quite simple. Let's see our your_package/events.py module, where IMAGE_ID is equals to 'photo':
import os
from kotti.events import (
    ObjectInsert,
    subscribe,
    notify,
    )
from kotti.resources import Image
from your_package.resources import UserProfile
from your_package.config import IMAGE_ID


@subscribe(ObjectInsert, UserProfile)
def user_profile_added(event):
    obj = event.object

    if IMAGE_ID not in obj.keys():
        image_path = os.path.join(
            os.path.dirname(__file__),
            'data', 'fallback.png'
            )
        with open(image_path, 'rb') as image_file:
            obj[IMAGE_ID] = image_obj = Image(
                title='Image',
                in_navigation=False,
                data=image_file.read(),
                filename=u'fallback.png',
                mimetype=u'image/png',
                )
            notify(ObjectInsert(image_obj, event.request)) 
Notes:
  1. the subscribe decorator will register our handler when a UserProfile resource will be inserted
  2. we should check if IMAGE_ID is already instanciated (prevent errors on paste)
  3. if you want your code will work both for Unix-like or under Windows, use os.path.join instead of a plain data/fallback.png path (path separator issues)
  4. the final b in the open is important if you want to write code that works under Windows, see https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files. On Unix, it doesn’t hurt to append a 'b' to the mode, so you can use it platform-independently for all binary files
  5. notify the image insertion
And your_package/__init__.py (in this example I've used the scan method but you could also register your event handlers imperatively):
...
def includeme(config):
    """ Don't add this to your ``pyramid_includes``, but add the
    ``kotti_configure`` above to your ``kotti.configurators`` instead.

    :param config: Pyramid configurator object.
    :type config: :class:`pyramid.config.Configurator`
    """
    ...
    config.scan(__name__)
And... tests of course (your_package/tests/test_events.py):
from pytest import fixture

from kotti.testing import DummyRequest


@fixture
def user_profile(db_session, config, root):
    """ returns dummy UserProfile.
    """
    config.include('your_package')

    from your_package.resources import UserProfile
    root['userprofile'] = userprofile = UserProfile(title='UserProfile')
    from kotti.events import notify
    from kotti.events import ObjectInsert
    notify(ObjectInsert(course, DummyRequest()))    return userprofile


class TestUserProfileWithEvents:

    def test_assert_userprofile_image(self, user_profile):
        from your_package.config import IMAGE_ID
        assert IMAGE_ID in user_profile.keys()
    ... 
You can test also if everything goes right after a copy/paste action (see the Kotti's tests).

Done!

All posts about Kotti

All Kotti posts published by @davidemoro:


No comments:

Post a Comment