# -*- coding: utf-8 -*-
from Acquisition import aq_parent
from OFS.SimpleItem import SimpleItem
from plone.app.event.base import dt_start_of_day
from plone.app.event.base import guess_date_from
from plone.event.interfaces import IEventAccessor
from plone.event.interfaces import IEventRecurrence
from plone.event.interfaces import IOccurrence
from plone.event.interfaces import IRecurrenceSupport
from plone.event.recurrence import recurrence_sequence_ical
from plone.event.utils import is_same_day
from plone.event.utils import pydt
from plone.namedfile.interfaces import IImageScaleTraversable
from plone.namedfile.scaling import ImageScaling
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
from zope.component import adapter
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserRequest
import datetime
try:
from repoze.zope2.publishtraverse import DefaultPublishTraverse
except ImportError:
from ZPublisher.BaseRequest import DefaultPublishTraverse
[docs]@adapter(IEventRecurrence)
@implementer(IRecurrenceSupport)
class RecurrenceSupport(object):
"""IRecurrenceSupport Adapter.
"""
def __init__(self, context):
self.context = context
[docs] def occurrences(self, range_start=None, range_end=None):
"""Return all occurrences of an event, possibly within a start and end
limit.
:param range_start: Optional start datetime, from which you want
occurrences be returned.
:type range_start: Python datetime
:param range_end: Optional start datetime, from which you want
occurrences be returned.
:type range_end: Python datetime
:returns: List of occurrences, including the start event.
:rtype: IEvent or IOccurrence based objects
Please note: Events beginning before range_start but ending afterwards
won't be found.
TODO: really?
TODO: test with event start = 21st feb, event end = start+36h,
recurring for 10 days, range_start = 1st mar, range_end = last Mark
"""
event = IEventAccessor(self.context)
# We try to get IEventBasic start without including recurrence
event_start = getattr(self.context, 'start', None)
if not event_start:
event_start = event.start
elif getattr(event, 'whole_day', None):
event_start = dt_start_of_day(event_start)
# We get event ends by adding a duration to the start. This way, we
# prevent that the start and end lists are of different size if an
# event starts before range_start but ends afterwards.
if (getattr(event, 'whole_day', None) or
getattr(event, 'open_end', None)):
duration = datetime.timedelta(hours=23, minutes=59, seconds=59)
else:
event_end = getattr(self.context, 'end', None)
duration = event_end - event_start
starts = recurrence_sequence_ical(event_start,
recrule=event.recurrence,
from_=range_start, until=range_end,
duration=duration)
# XXX potentially occurrence won't need to be wrapped anymore
# but doing it for backwards compatibility as views/templates
# still rely on acquisition-wrapped objects.
def get_obj(start):
if pydt(event_start.replace(microsecond=0)) == start:
# If the occurrence date is the same as the event object, the
# occurrence is the event itself. return it as such.
# Dates from recurrence_sequence_ical are explicitly without
# microseconds, while event.start may contain it. So we have to
# remove it for a valid comparison.
return self.context
return Occurrence(
id=str(start.date()),
start=start,
end=start + duration).__of__(self.context)
for start in starts:
yield get_obj(start)
[docs]@adapter(IEventRecurrence, IBrowserRequest)
class OccurrenceTraverser(DefaultPublishTraverse):
"""Generic Occurrence traverser.
Please note: the .at and .dx subpackages implement their own Occurrence
traversers.
"""
def publishTraverse(self, request, name):
context = self.context
dateobj = guess_date_from(name, context)
if dateobj:
occs = IRecurrenceSupport(context).occurrences(range_start=dateobj)
try:
occurrence = next(occs)
occ_acc = IEventAccessor(occurrence)
if is_same_day(dateobj, occ_acc.start):
return occurrence
except StopIteration:
pass
return self.fallbackTraverse(request, name)
def fallbackTraverse(self, request, name):
return super(OccurrenceTraverser, self).publishTraverse(request, name)
[docs]@implementer(IOccurrence)
class Occurrence(SimpleItem):
"""Transient Occurrence object, representing an individual event in a
recurrecne set.
"""
def __init__(self, id, start, end):
self.id = id
self.start = start
self.end = end
self.portal_type = 'Occurrence'
[docs]@adapter(IOccurrence)
@implementer(IEventAccessor)
class EventOccurrenceAccessor(object):
"""Generic event accessor adapter implementation for Occurrence objects.
"""
def __init__(self, context):
object.__setattr__(self, 'context', context)
own_attr = ['start', 'end', 'url']
object.__setattr__(self, '_own_attr', own_attr)
def _get_context(self, name):
# TODO: save parent context on self, so it must not be called every
# time
oa = self._own_attr
if name in oa:
return self.context
else:
return IEventAccessor(aq_parent(self.context))
def __getattr__(self, name):
return getattr(self._get_context(name), name, None)
def __setattr__(self, name, value):
return setattr(self._get_context(name), name, value)
def __delattr__(self, name):
delattr(self._get_context(name), name)
# R/O properties
# TODO: Having uid here makes probably no sense, since Occurrences are
# created on the fly and not persistent.
@property
def url(self):
return safe_unicode(self.context.absolute_url())
[docs]class ImageScalingViewFactory(BrowserView):
"""Factory for ImageScaling view for Occurrences.
Delegates to parent @@images view.
"""
def __new__(cls, context, request):
parent = aq_parent(context)
if IImageScaleTraversable.providedBy(parent):
return ImageScaling(parent, request)
return None