plone.app.event - a calendar framework for Plone

Overview

plone.app.event is a new calendar framework for Plone.

Features:

  • Dexterity behaviors and Archetypes type,
  • Timezone support,
  • Recurring Events,
  • Whole day events,
  • Open end events (End on the same day),
  • Icalendar export,
  • Icalendar import,
  • Better calendar and events portlets,
  • An event listing and event detail view.

It was developed with these goals in mind:

  • Encapsulation and independence: All event related code should reside in a single package. Relevant, re-usable functionality is split to seperate packages. Plone’s dependencies on calendar related code should be reduced to a minimum. plone.app.event should be able to be deinstalled from Plone.
  • Dexterity and Archetypes support: plone.app.event should provide Dexterity behaviors, which can be used in Dexterity types and an ATEvent content type (factored out from ATContentTypes). For a Dexterity event type, use plone.app.contenttypes 1.1 or newer.
  • Standards compliancy: We support the icalendar standard (RFC5545) including recurrence.
  • Recurring events based on the RFC5545 standard.

Documentation

Installation

Compatibility

plone.app.event is tested with latest Plone 4.3 and the upcoming Plone 5.0.

Removed 4.2 compatibility

Since plone.app.event 1.1b1 we depend on changes from plone.app.contenttypes 1.1b1, which depends on plone.dexterity>=2.2.1 which itself (since 2.2) depends on a AccessControl version not provided by the Plone 4.2 version fixes.

You can still experiment with Plone 4.2 compatibility if you need to, but officially it’s support in plone.app.event is removed. There are a number of other compatibility issues to be solved and the tests will fail anyways. If you really need to, use this as a starting point:

plone.app.dexterity = 2.0.10
plone.dexterity = 2.1.3
plone.app.contenttypes = 1.1a1
z3c.form = 3.0.5
plone.app.z3cform = 0.7.5

Buildout files

  • buildout.cfg: plone.app.event base installation.
  • dev.cfg: plone.app.event development configuration, including tests.
  • tests.cfg: plone.app.event tests only.
  • sources.cfg: plone.app.event mr.developer source definitions.
  • versions.cfg: plone.app.event version requirements.

Installation

Depend on one (or both) of these setuptools dependencies:

'plone.app.event [dexterity]'

or:

'plone.app.event [archetypes]'

The zcml dependency is be loaded automatically by z3c.autoinclude.

Then install plone.app.event via the controlpanel or by depending on the following GenericSetup profile in metadata.xml:

plone.app.event:default

For Archetypes, use this one:

plone.app.event.at:default

Don’t use the plone.app.event.dx:default profile, which will be removed in future versions of plone.app.event. Please create your own type based on plone.app.event’s Dexterity behaviors (Through the web or via a GenericSetup profile), or install plone.app.contenttypes for ready-to-use Dexterity types.

Plone 4.3 installation

plone.app.event depends on plone.app.portlets>=2.5a1. This version has the calendar and event portlet removed, which are now in plone.app.event itself. Also, it allows the calendar portlet to do AJAX calls without KSS via standard jQuery. For Plone < 5.0 you have to fix the plone.app.portlets version in your buildout like so:

[buildout]
versions = versions

[versions]
plone.app.portlets = 2.5a1
Upgrading from plone.app.event 1.0

The “ploneintegration” setuptools extra, subpackage and GenericSetup profile have been gone. You just need to remove these dependencies from your setup and use the “plone.app.event.at:default” profile instead, if you plan to use the Archetypes based ATEvent type.

Use the provided upgrade steps to upgrade Dexterity behaviors: Attribute storage (Migrate fields from annotation storage to attribute storage) and New IRichText behavior (Enable the new IRichText instead of the IEventSummary behavior).

Upgrading from Products.ATContentType to plone.app.event

Warning

Please backup before upgrading and check the upgraded contents for validity!

If you want to upgrade Products.ATContentTypes based ATEvents to plone.app.event ones, there is an upgrade step for that: “Upgrades old AT events to plone.app.events” (Metadata version 1 to 2). In order to use it, go to Plone Control Center -> ZMI -> portal_setup -> Upgrades. Select “plone.app.event.at:default” profile and click “Show old upgrades”. Select the upgrade step and run it.

You might also need to “clear and rebuild” the catalog after upgrading. You can do so at Plone Control Center -> ZMI -> portal_catalog -> Advanced (this may take a while)

Upgrading to Dexterity

Upgrade steps to migrate Products.ATContentTypes based ATEvents, plone.app.event based ATEvents or plone.app.event Dexterity example types (plone.app.event.dx.event) to plone.app.contenttypes Dexterity Events can be found within plone.app.contenttypes. This package utilizes plone.app.event’s Dexterity behaviors for it’s Event type.

Configuration

Note

Don’t forget to set the portal timezone!

After installation, please set your timezone in the @@event-settings controlpanel. Otherwise time calculations are based on UTC and likely wrong for your timezone. Also set the first weekday setting for correct display of the first weekday in calendar views.

Architectural Overview

Design goals

The development of plone.app.event was done with following design goals in mind:

[a] Encapsulation and independence: All event related code should reside in a seperate package (splitted into other packages, where appropriate). Plone should be least dependend on plone.app.event. Best would be that one can deinstall this feature completly.

[b] Dexterity and Archetypes support: plone.app.event should provide Dexterity behaviors, which can be used in Dexterity types and an ATEvent content type (factored out from ATContentTypes) as a replacement for the Products.ATContentType ATEvent.

[c] Standards compliancy: the iCalendar / RFC5545 standard is wonderful flexible, so plone.app.event should provide support for it by allowing ical exports. This is also available for the current ATContentType based implementation, but plone.app.event aims to improve it. A future goal is to support CalDAV also.

[d] Recurring events support based on the RFC5545 standard.

[e] A modern dateinput widget.

[f] Features like whole-day-events.

[g] Timezone support.

Encapsulation and independence: plone.app.event provides the Archetypes based type and the Dexterity behaviors via two other subpackages in that package: at and dx. Based on installed features (Products.ATContentTypes or plone.dexterity, respectively), eather of those subpackages are included via the zcml:condition statement. The calendar and event portlets were moved from plone.app.portlets into plone.app.event, where they belong semantically - thus improving encapsulation and independence and reducing interwoven dependencies. The calendar portlet was completly refactored. The functionality of the CalendarTool (portal_calendar) was reimplenented. Important settings from the calendar-controlpanel are now available in the event configlet. Since the calendar portlet was the only consumer of the CalendarTool, the CalendarTool, the calendar controlpanel and the dependency to Products.CMFCalendar can be dropped. The new plone.formwidget.datetime implements archetypes and z3cform based widgets, so the old datetime widget can be dropped. Python-dateutil provides recurrence calculations based on the RFC5545 standard - plone.formwidget.recurrence provides a awidget for recurrence and Products.DateRecurringIndex an appropriate index as a drop-in replacement for Zope’s DateIndex. The iCalendar package was improved and is now used for plone.app.event to provide icalendar serialization. The timezone support is based on the pytz package. Plone now haves a portal timezone, User timezones and every event can define another timezone, if wished. User timezones are planned. Whole day events get their starttime set to 0:00 and endtime set to 23:59:59 - thats should be feasable in most cases

(excluding any scientific events...).

Packages

plone.app.event

Github: https://github.com/plone/plone.app.event

The “at” submodule provides the Archetypes based ATEvent content type as a drop-in replacement of the ATContentType based ATEvent. Ical, recurrence and generic event accessor adapters and some event subscribers related to the ATEvent.

The “dx” submodule provides Dexterity behaviors (some granular ones). Like in the “at” submodule, ical, recurrence and generic event accessor adapters as well as some event subscribers are provided.

Both subpackages are only loaded, if the neccassary features are installed.

plone.app.event does not depend on CMFCalendar and the portal_calendar tool any more. Plone core’s only consumer of this package was the calendar portlet anyways, which was completly rewritten.

base.py provides some basic event related functionality. Many of them need a context in order to get the correct timezone.

The “browser” submodule provides the new “event” controlpanel (the “calendar” controlpanel can be dropped, since we do not need CMFCalendar any more). The settings are stored in plone.registry. The event view is generic to ATEvent and DX based event types.

The ical submodule provides adapters and views for export and import to and from icalendar resources.

The locales directory which holds locale files.

In the portlets subpackage there are portlet_calendar (a complete rewrite) and portlet_events, both from plone.app.portlets, where only BBB imports exist, so that existing installations do not break.

The tests are all ported to plone.app.testing.

plone.event

Github: https://github.com/plone/plone.event

Date/time related utilities, recurrence calculations based on python-dateutil.

plone.formwidget.datetime

Github: https://github.com/plone/plone.formwidget.datetime

Derived from collective.z3cform.datetimewidget and archetypes.datetimewidget (which itself was derived from the former). It is splitted into “at” and “z3cform” subpackages, like plone.app.event.

plone.formwidget.recurrence

Github: https://github.com/plone/plone.formwidget.recurrence

Recurrence widget based on jquery.recurrenceinput.js. Supports complex recurrence rules with exclusion and inclusion dates, automatically updated occurrences display within the widget and a nicely formatted string which explains the recurrence rule. The recurrence rule is stored as a RFC5545/icalendar compatible recurrence string.

Products.DateRecurringIndex

Github: https://github.com/collective/Products.DateRecurringIndex

A drop-in replacement for Zope’s DateIndex with support for recurring events. Each recurrence get’s an index entry.

icalendar

Github: https://github.com/collective/icalendar

icalendar parser/generator framework.

Other, external packages

plone.app.eventindex

Github: https://github.com/regebro/plone.app.eventindex

A possible alternative to Products.DateRecurringindex, which supports late indexing and which does not have problems with unlimited occurrences. This eventindex is currently not used by plone.app.event.

Python-dateutil

Documentation: http://labix.org/python-dateutil Repository: https://launchpad.net/dateutil

Useful extensions to the standard Python datetime features. plone.app.event uses it mainly for recurrence calculations.

Pytz

Documentation: http://pytz.sourceforge.net/ Pypi page: https://pypi.python.org/pypi/pytz/

World timezone definitions, modern and historical. Based on the Olson database.

Developer documentation

The IEvent interface

All event types should implement the IEvent interface from plone.event.interfaces, in order that some functionality of plone.app.event can be used. For example, catalog searches for event objects ask for the IEvent interface in the object_provides index:

from plone.event.interfaces import IEvent
assert(IEvent.providedBy(obj)==True)

Custom event content types

Using Dexterity behaviors to build new content types with IEvent support

For Dexterity use the plone.app.event.dx.behaviors.IEventBasic and optionally any other event related behavior from there.

You can just enable the behaviors you want to use for your custom content type in the FTI via GenericSetup or through the web. This types/Event.xml GenericSetup FTI configuration snippet from plone.app.contenttypes shows an example. The only behavior, which is definitely needed is the IEventBasic behavior. All other are optional:

<property name="behaviors">
    <element value="plone.app.event.dx.behaviors.IEventBasic"/>
    <element value="plone.app.event.dx.behaviors.IEventRecurrence"/>
    <element value="plone.app.event.dx.behaviors.IEventLocation"/>
    <element value="plone.app.event.dx.behaviors.IEventAttendees"/>
    <element value="plone.app.event.dx.behaviors.IEventContact"/>
    <element value="plone.app.contenttypes.behaviors.richtext.IRichText"/>
    <element value="plone.app.dexterity.behaviors.metadata.IDublinCore"/>
    <element value="plone.app.content.interfaces.INameFromTitle"/>
    <element value="plone.app.dexterity.behaviors.discussion.IAllowDiscussion"/>
    <element value="plone.app.dexterity.behaviors.exclfromnav.IExcludeFromNavigation"/>
    <element value="plone.app.relationfield.behavior.IRelatedItems"/>
    <element value="plone.app.versioningbehavior.behaviors.IVersionable" />
</property>

Of course, it’s also possible to create a new behavior which derives from plone.app.event’s one, like so:

from plone.app.event.dx.behaviors import IEventBasic
from plone.app.event.dx.behaviors import IEventLocation
from plone.app.event.dx.behaviors import IEventRecurrence
from plone.app.event.dx.behaviors import first_weekday_sun0
from plone.app.event.dx.interfaces import IDXEvent
from plone.app.event.dx.interfaces import IDXEventLocation
from plone.app.event.dx.interfaces import IDXEventRecurrence
from plone.autoform import directives as form
from plone.autoform.interfaces import IFormFieldProvider
from zope.interface import alsoProvides


class IEvent(IEventBasic, IEventRecurrence, IEventLocation,
             IDXEvent, IDXEventLocation, IDXEventRecurrence):
    """Custom Event behavior."""
    form.widget('start', first_day=first_weekday_sun0)
    form.widget('end', first_day=first_weekday_sun0)
    form.widget('recurrence',
                start_field='IEvent.start',
                first_day=first_weekday_sun0)
alsoProvides(IEvent, IFormFieldProvider)

Note

If you don’t register the behavior with a factory and a marker interface like it’s done in plone.app.event, the behavior is the marker interface itself (see plone.app.dexterity’s documentation on behavior marker interfaces). In this case, the behavior should also derive from the marker interfaces defined in plone.app.event.dx.interfaces in order to let it use all of plone.app.event’s functionality (indexers, adapters and the like).

Note

You have to reconfigure the start, end and recurrence fields’ widgets again. The widgets for the start and end fields have to be configured with the first_day parameter while the recurrence field widget has to be configured with the first_day and start_field parameters. Even if the start field is derived from another behavior, in this case the dotted-path includes the new behavior: IEvent.start.

Then register the behavior in ZCML:

<plone:behavior
    title="Event"
    description="A Event"
    provides=".behaviors.IEvent"
    for="plone.dexterity.interfaces.IDexterityContent"
    />

And register it in your FTI via GenericSetup as usual.

Extending the Archetypes based plone.app.event.at.content.ATEvent class

For Archetypes, derive from plone.app.event.at.content.ATEvent.

Here is an example from collective.folderishtypes:

from Products.Archetypes import atapi
from plone.app.event.at import content as event

type_schema = event.ATEventSchema.copy()  # Add your custom fields here
# Move location back to main schemata
type_schema.changeSchemataForField('location', 'default')
type_schema.moveField('location', before='attendees')

class CustomEvent(event.ATEvent):
    portal_type = 'Custom Event'
    _at_rename_after_creation = True
    schema = type_schema
atapi.registerType(CustomEvent, PROJECTNAME)

Register this type in the FTI via Generic Setup as usual.

None of the above

If you cannot use the above two methods, you can still implement the plone.event.interfaces.IEvent interface.

In any case you might need to provide an IEventAccessor adapter. For more information, see below.

Getting and setting properties

For Dexterity based types: Accessing properties behavior interface adaption

To use the functionality provided by the behaviors, get the behavior adapter first. For example, for setting or getting attributes of an event object, do:

from plone.app.event.dx.behaviors import IEventBasic
event = IEventBasic(obj)
event.start = datetime(2011,11,11,11,00)
event.end = datetime(2011,11,11,12,00)
event.timezone = 'CET'

import transaction
transaction.commit()

Alternatively, use the more convenient IEventAccessor pattern described below.

Accessing event objects via an unified accessor object

To make it easier to support Archetypes and Dexterity based objects, an adapter for content objects is provided, which allows unified interaction with event objects.

The interface definition can be found in plone.event.interfaces.IEventAccessor. Default accessors:

  • For IEvent (plone.event.interfaces.IEvent) implementing objects: plone.event.adapters.EventAccessor.
  • For IATEvent (plone.app.event.at.interfaces.IATEvent): plone.app.event.at.content.EventAccessor.
  • For IDXEvent (plone.app.event.dx.interfaces.IDXEvent): plone.app.event.dx.behaviors.EventAccessor.
  • For IOccurrence (plone.event.interfaces.IOccurrence): plone.app.event.recurrence.EventAccessor.

Event objects implement the IEvent interface from plone.event.interfaces.

The objects can be accessed like so:

from plone.event.interfaces import IEventAccessor
acc = IEventAccessor(obj)
assert(isinstance(acc.start, datetime)==True)
assert(isinstance(acc.timezone, string)==True)
assert(isinstance(acc.recurrence, string)==True)

Set properties of the object via the accessor. Don’t forget to throw ObjectModifiedEvent after setting properties to call an event subscriber which does some timezone related post calculations:

from zope.event import notify
from zope.lifecycleevent import ObjectModifiedEvent
tz = pytz.timezone('Europe/Vienna')
acc.start = datetime(2012, 12, 12, 10, 10, tzinfo=tz)
acc.timezone = 'Europe/London'
notify(ObjectModifiedEvent(obj))

You can also use the accessor edit method, which also throws the ObjectModifiedEvent event for you:

acc.edit(end=datetime(2012, 12, 12, 20, 0, tzinfo=tz))

For creating events, you can use the accessor’s create method, which again returns an accessor. E.g. if you want to create the Dexterity based event type:

from plone.app.event.dx.behaviors import EventAccessor
acc = EventAccessor.create(
    container=app.plone,
    content_id=u'new_event'
    title=u'New Event'
    start=datetime(2013, 7, 1, 10, 0, tzinfo=tz),
    end=datetime(2013, 7, 1, 12, 0, tzinfo=tz),
    timezone='Europe/Vienna'
)
acc.location = u"Graz, Austria"

Access the content object from an accessor like so:

obj = acc.context
from plone.event.interfaces import IEvent
assert(not IEvent.providedBy(acc))
assert(IEvent.providedBy(obj))

Getting occurrences from IEventRecurrence implementing objects

Events with recurrence support should implement the IEventRecurrence (plone.event.interfaces.IEventRecurrence) interface.

An IRecurrenceSupport implementing adapter allows the calculation of all occurrences:

from plone.event.interfaces import IRecurrenceSupport
rec_support = IRecurrenceSupport(obj)

# All occurrences of the object
rec_support.occurrences()

# All occurrences within a time range
start = datetime(2012,1,1)
end = datetime(2012,1,3)
rec_support.occurrences(range_start=start, range_end=end)

If you want to get all occurrences from any event within a timeframe, use the get_events function like so:

from plone.app.event.base import get_events, localized_now
occ = get_events(context, start=localized_now(), ret_mode=2, expand=True)

Reusing the @@event_summary view to list basic event information

The @@event_summary listing lists basic event information including microdata on the right hand side of the default event view. You can reuse this listing in custom views by calling the event_summary view on an IEvent providing context in page templates like so:

<tal:eventsummary replace="structure context/@@event_summary"/>

or in Python code like so:

context.restrictedTraverse('@@event_ticket_summary')()

There are cases where you might exclude some of this information. You can do that by overriding the excludes list of the view. Possible values are:

title
subjects
date
occurrences
location
contact
event_url
ical

Running tests

After running buildout with the dev.cfg or tests.cfg config files, you can run all tests (including robot tests with --all switch) like so:

./bin/test -s plone.app.event --all

The -t switch allows you to run a specific test file or method. The –list-tests lists all available tests.

To run the robot tests do:

./bin/test --all -s plone.app.event -t robot

For development, it might be more convenient to start a test server and run robot tests individually, like so:

./bin/robot-server plone.app.event.testing.PAEventDX_ROBOT_TESTING
./bin/robot plone/app/event/tests/robot/test_event_roundtrip.robot

In the robot test you can place the debug statement to access a robot shell to try things out.

For more information on this topic visit: http://developer.plone.org/reference_manuals/external/plone.app.robotframework/happy.html

Development design choices

  • Timezone support. Every event has a timezone.
  • Usage of pytz. The timezone library used it pytz. Other timezone identifiers than defined in pytz (Olson database) are not supported.
  • Dropped support for ambiguous timezones. Three letter timezones like CET, MET, PST, etc. are not supported.
  • Start/end datetime inputs are treated as localized values. If a timezone on an event is changed afterwards, the datetime values are not converted to the target timezone.
  • Whole day events last from 0:00 until 23:59:59 on the same day.
  • Open end events end on the same day at 23:59:59.
  • For recurring events, we do not support unlimited occurrences. The number of possible recurrences of an event is limited to 1000 occurrences. This way, indexing and other operations doesn’t take too long. The maximum number of occurrences is set via the MAXCOUNT constant in plone.event.recurrence.

Contributing

Contributions

Please report any bugs, issues or feature requests here: https://github.com/plone/plone.app.event/issues.

And better yet, help out with documentation and pull-requests!

Note

To accept your pull requests, we need you need to have Plone contributors agreement signed and sent-in. For more information, see: ` Contributor’s Agreement for Plone Explained <http://plone.org/foundation/contributors-agreement/contributors-agreement-explained>`_.

Contributors

Contributors, please add you name here! By doing this, you also state, that you have signed the Contributor’s Agreement for Plone Explained. Download it from plone.org. Thanks!

  • Johannes Raggam, thet (Main author and PLIP implementation)
  • Andreas Jung, zopyx
  • David Glick, davisagli
  • Érico Andrei, ericof
  • Franklin Kingma, kingel
  • Gauthier Bastien, gbastien
  • Georg Bernhard, gogo
  • Giacomo Spettoli, giacomos
  • Guido Stevens, gyst
  • JeanMichel FRANCOIS, toutpt
  • Jens Klein, jensens
  • Lennart Regebro, regebro
  • Nathan Van Gheem, vangheem
  • Philip Bauer, pbauer
  • Robert Niederreiter, rnixx
  • Rok Garbas, garbas
  • Róman Joost, romanofski
  • Sean Upton, seanupton
  • Simone Orsi, simahawk
  • Thomas Desvenain, tdesvenain
  • Timo Stollenwerk, timo
  • Tom Gross, tomgross
  • Vincent Fretin, vincentfretin
  • Vitaliy Podoba, piv, vipod
  • Wolfgang Thomas, pysailor

Find out who contributed:

$ git shortlog -s -e

API documentation

plone.app.event API

plone.app.event.base

plone.app.event.interfaces

plone.app.event.recurrence

plone.app.event.setuphandlers

plone.app.event.vocabularies

plone.app.event.at.content

plone.app.event.at.interfaces

plone.app.event.at.traverser

plone.app.event.at.upgrades.event

plone.app.event.browser.controlpanel

plone.app.event.browser.event_listing

plone.app.event.browser.event_view

plone.app.event.browser.event_view.get_location(accessor)[source]

Return the location. This method can be overwritten by external packages, for example to provide a reference to a Location object as done by collective.venue.

Parameters:accessor (IEvent, IOccurrence or IEventAccessor) – Event, Occurrence or IEventAccessor object.
Returns:A location string. Possibly a HTML structure to link to another object, representing the location.
Return type:string

plone.app.event.browser.formatted_date

plone.app.event.dx.behaviors

plone.app.event.dx.interfaces

plone.app.event.dx.traverser

plone.app.event.ical.exporter

plone.app.event.ical.importer

plone.app.event.portlets.portlet_calendar

plone.app.event.portlets.portlet_events