from Acquisition import aq_parent
from OFS.SimpleItem import SimpleItem
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
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.namedfile.scaling import ImageScale as DXImageScaling
from zope.component import adapts
from zope.interface import Interface
from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest
try:
from repoze.zope2.publishtraverse import DefaultPublishTraverse
except ImportError:
from ZPublisher.BaseRequest import DefaultPublishTraverse
try:
from plone.app.event.at.interfaces import IATEvent
except ImportError:
class IATEvent(Interface):
pass
try:
from plone.app.event.dx.interfaces import IDXEvent
except ImportError:
class IDXEvent(Interface):
pass
try:
from plone.app.imaging.scaling import ImageScaling as ATImageScaling
except ImportError:
pass
[docs]class RecurrenceSupport(object):
"""IRecurrenceSupport Adapter.
"""
implements(IRecurrenceSupport)
adapts(IEventRecurrence)
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 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.
duration = event.duration
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 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]class OccurrenceTraverser(DefaultPublishTraverse):
"""Generic Occurrence traverser.
Please note: the .at and .dx subpackages implement their own Occurrence
traversers.
"""
adapts(IEventRecurrence, IBrowserRequest)
def publishTraverse(self, request, name):
context = self.context
dateobj = guess_date_from(name, context)
if dateobj:
occs = IRecurrenceSupport(context).occurrences(range_start=dateobj)
occurrence = occs.next()
occ_acc = IEventAccessor(occurrence)
if is_same_day(dateobj, occ_acc.start):
return occurrence
return self.fallbackTraverse(request, name)
def fallbackTraverse(self, request, name):
return super(OccurrenceTraverser, self).publishTraverse(request, name)
[docs]class Occurrence(SimpleItem):
"""Transient Occurrence object, representing an individual event in a
recurrecne set.
"""
implements(IOccurrence)
def __init__(self, id, start, end):
self.id = id
self.start = start
self.end = end
self.portal_type = 'Occurrence'
[docs]class EventOccurrenceAccessor(object):
"""Generic event accessor adapter implementation for Occurrence objects.
"""
implements(IEventAccessor)
adapts(IOccurrence)
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 AT or DX specific view and rebinds to the parent context.
"""
def __new__(cls, context, request):
parent = aq_parent(context)
if IATEvent.providedBy(parent):
return ATImageScaling(parent, request)
elif IDXEvent.providedBy(parent):
return DXImageScaling(parent, request)
return None