from Products.CMFPlone.PloneBatch import Batch
from Products.Five.browser import BrowserView
from calendar import monthrange
from datetime import date
from datetime import timedelta
from plone.app.event import messageFactory as _
from plone.app.event.base import AnnotationAdapter
from plone.app.event.base import date_speller
from plone.app.event.base import get_events
from plone.app.event.base import guess_date_from
from plone.app.event.base import localized_now
from plone.app.event.base import start_end_from_mode
from plone.app.event.browser.event_view import get_location
from plone.app.event.ical.exporter import construct_icalendar
from plone.app.layout.navigation.defaultpage import getDefaultPage
from plone.event.interfaces import IEvent
from plone.event.interfaces import IEventAccessor
from plone.memoize import view
from plone.z3cform.layout import wrap_form
from z3c.form import button
from z3c.form import field
from z3c.form import form
from zope import schema
from zope.component import adapts
from zope.component import getMultiAdapter
from zope.contentprovider.interfaces import IContentProvider
from zope.interface import Interface
from zope.interface import implements
try:
from plone.app.collection.interfaces import ICollection
except ImportError:
ICollection = None
try:
from Products.ATContentTypes.interfaces import IATTopic
except ImportError:
IATTopic = None
class EventListing(BrowserView):
def __init__(self, context, request):
super(EventListing, self).__init__(context, request)
self.now = now = localized_now(context)
self.settings = IEventListingSettings(self.context)
# Batch parameter
req = self.request.form
self.b_start = 'b_start' in req and int(req['b_start']) or 0
self.b_size = 'b_size' in req and int(req['b_size']) or 10
self.orphan = 'orphan' in req and int(req['orphan']) or 1
self.mode = 'mode' in req and req['mode'] or None
self._date = 'date' in req and req['date'] or None
self.tags = 'tags' in req and req['tags'] or None
self.searchable_text = 'SearchableText' in req and\
req['SearchableText'] or None
self.path = 'path' in req and req['path'] or None
day = 'day' in req and int(req['day']) or None
month = 'month' in req and int(req['month']) or None
year = 'year' in req and int(req['year']) or None
if not self._date and day or month or year:
self._date = date(year or now.year,
month or now.month,
day or now.day).isoformat()
if self.mode is None:
self.mode = self._date and 'day' or 'future'
@property
def default_context(self):
# Try to get the default page
context = self.context
default = getDefaultPage(context)
if default:
context = context[default]
return context
@property
def is_collection(self):
ctx = self.default_context
return ICollection and ICollection.providedBy(ctx) or False
@property
def is_topic(self):
ctx = self.default_context
return IATTopic and IATTopic.providedBy(ctx) or False
@property
def date(self):
dt = None
if self._date:
try:
dt = guess_date_from(self._date)
except TypeError:
pass
return dt
@property
def _start_end(self):
start, end = start_end_from_mode(self.mode, self.date, self.context)
return start, end
@view.memoize
def _get_events(self, ret_mode=3):
context = self.context
kw = {}
if self.path:
kw['path'] = self.path
elif self.settings.current_folder_only:
kw['path'] = '/'.join(context.getPhysicalPath())
#kw['b_start'] = self.b_start
#kw['b_size'] = self.b_size
if self.tags:
kw['Subject'] = {'query': self.tags, 'operator': 'and'}
if self.searchable_text:
kw['SearchableText'] = self.searchable_text
start, end = self._start_end
sort = 'start'
sort_reverse = False
if self.mode in ('past', 'all'):
sort_reverse = True
return get_events(context, start=start, end=end,
sort=sort, sort_reverse=sort_reverse,
ret_mode=ret_mode, expand=True, **kw)
def events(self, ret_mode=3, batch=True):
res = []
is_col = self.is_collection
is_top = self.is_topic
if is_col or is_top:
ctx = self.default_context
if is_col:
res = ctx.results(batch=False, sort_on='start', brains=False)
else:
res = ctx.queryCatalog(
REQUEST=self.request, batch=False, full_objects=True
)
# TODO: uff, we have to walk through all results...
if ret_mode == 3:
res = [IEventAccessor(obj) for obj in res
if IEvent.providedBy(obj)]
else:
res = self._get_events(ret_mode)
if batch:
b_start = self.b_start
b_size = self.b_size
res = Batch(res, size=b_size, start=b_start, orphan=self.orphan)
return res
@property
def ical(self):
events = self.events(ret_mode=2, batch=False) # get as objects
cal = construct_icalendar(self.context, events)
name = '%s.ics' % self.context.getId()
self.request.RESPONSE.setHeader('Content-Type', 'text/calendar')
self.request.RESPONSE.setHeader(
'Content-Disposition',
'attachment; filename="%s"' % name
)
self.request.RESPONSE.write(cal.to_ical())
@property
def ical_url(self):
date = self.date
mode = self.mode
qstr = (date or mode) and '?%s%s%s' % (
mode and 'mode=%s' % mode,
mode and date and '&' or '',
date and 'date=%s' % date or ''
) or ''
return '%s/@@event_listing_ical%s' % (
self.context.absolute_url(),
qstr
)
def get_location(self, event_accessor):
return get_location(event_accessor.context)
def formatted_date(self, occ):
provider = getMultiAdapter(
(self.context, self.request, self),
IContentProvider, name='formatted_date'
)
return provider(occ.context)
def date_speller(self, date):
return date_speller(self.context, date)
@property
def header_string(self):
start, end = self._start_end
start_dict = start and date_speller(self.context, start) or None
end_dict = end and date_speller(self.context, end) or None
mode = self.mode
main_msgid = None
sub_msgid = None
if mode == 'all':
main_msgid = _(u"all_events", default=u"All events")
elif mode == 'past':
main_msgid = _(u"past_events", default=u"Past events")
elif mode == 'future':
main_msgid = _(u"future_events", default=u"Future events")
elif mode == 'now':
main_msgid = _(u"todays_upcoming_events",
default=u"Todays upcoming events")
elif mode == 'today':
main_msgid = _(u"todays_events", default=u"Todays events")
elif mode == '7days':
main_msgid = _(u"7days_events", default=u"Events in next 7 days.")
sub_msgid = _(
u"events_from_until",
default=u"${from} until ${until}.",
mapping={
'from': "%s, %s. %s %s" % (
start_dict['wkday'],
start.day,
start_dict['month'],
start.year
),
'until': "%s, %s. %s %s" % (
end_dict['wkday'],
end.day,
end_dict['month'],
end.year
),
}
)
elif mode == 'day':
main_msgid = _(
u"events_on_day",
default=u"Events on ${day}",
mapping={
'day': "%s, %s. %s %s" % (
start_dict['wkday'],
start.day,
start_dict['month'],
start.year
),
}
)
elif mode == 'week':
main_msgid = _(u"events_in_week",
default=u"Events in week ${weeknumber}",
mapping={'weeknumber': start.isocalendar()[1]})
sub_msgid = _(
u"events_from_until",
default=u"${from} until ${until}.",
mapping={
'from': "%s, %s. %s %s" % (
start_dict['wkday'],
start.day,
start_dict['month'],
start.year
),
'until': "%s, %s. %s %s" % (
end_dict['wkday'],
end.day,
end_dict['month'],
end.year
),
}
)
elif mode == 'month':
main_msgid = _(
u"events_in_month",
default=u"Events in ${month} ${year}",
mapping={
'month': start_dict['month'],
'year': start.year,
}
)
trans = self.context.translate
return {'main': main_msgid and trans(main_msgid) or '',
'sub': sub_msgid and trans(sub_msgid) or ''}
# MODE URLs
def _date_nav_url(self, mode, datestr=''):
return '%s?mode=%s%s' % (
self.request.getURL(),
mode,
datestr and '&date=%s' % datestr or ''
)
@property
def mode_all_url(self):
return self._date_nav_url('all')
@property
def mode_future_url(self):
return self._date_nav_url('future')
@property
def mode_past_url(self):
return self._date_nav_url('past')
@property
def mode_day_url(self):
now = self.date or self.now
return self._date_nav_url('day', now.date().isoformat())
@property
def mode_week_url(self):
now = self.date or self.now
return self._date_nav_url('week', now.date().isoformat())
@property
def mode_month_url(self):
now = self.date or self.now
return self._date_nav_url('month', now.date().isoformat())
# DAY NAV
@property
def next_day_url(self):
now = self.date or self.now
datestr = (now + timedelta(days=1)).date().isoformat()
return self._date_nav_url('day', datestr)
@property
def today_url(self):
return self._date_nav_url('day')
@property
def prev_day_url(self):
now = self.date or self.now
datestr = (now - timedelta(days=1)).date().isoformat()
return self._date_nav_url('day', datestr)
# WEEK NAV
@property
def next_week_url(self):
now = self.date or self.now
datestr = (now + timedelta(days=7)).date().isoformat()
return self._date_nav_url('week', datestr)
@property
def this_week_url(self):
return self._date_nav_url('week')
@property
def prev_week_url(self):
now = self.date or self.now
datestr = (now - timedelta(days=7)).date().isoformat()
return self._date_nav_url('week', datestr)
# MONTH NAV
@property
def next_month_url(self):
now = self.date or self.now
last_day = monthrange(now.year, now.month)[1] # (wkday, days)
datestr = (now.replace(day=last_day) +
timedelta(days=1)).date().isoformat()
return self._date_nav_url('month', datestr)
@property
def this_month_url(self):
return self._date_nav_url('month')
@property
def prev_month_url(self):
now = self.date or self.now
datestr = (now.replace(day=1) - timedelta(days=1)).date().isoformat()
return self._date_nav_url('month', datestr)
class EventListingIcal(EventListing):
def __call__(self, *args, **kwargs):
return self.ical
class IEventListingSettings(Interface):
current_folder_only = schema.Bool(
title=_('label_current_folder', default=u'Current folder'),
description=_('help_current_folder',
default=u'Search events in current folder only.'),
default=False
)
[docs]class EventListingSettings(AnnotationAdapter):
"""Annotation Adapter for IEventListingSettings
"""
implements(IEventListingSettings)
adapts(Interface)
ANNOTATION_KEY = "plone.app.event-event_listing-settings"
class EventListingSettingsForm(form.Form):
fields = field.Fields(IEventListingSettings)
ignoreContext = False
def getContent(self):
data = {}
settings = IEventListingSettings(self.context)
data['current_folder_only'] = settings.current_folder_only
return data
def form_next(self):
self.request.response.redirect(self.context.absolute_url())
@button.buttonAndHandler(u'Save')
def handleSave(self, action):
data, errors = self.extractData()
if errors:
return False
settings = IEventListingSettings(self.context)
settings.current_folder_only = data['current_folder_only']
self.form_next()
@button.buttonAndHandler(u'Cancel')
def handleCancel(self, action):
self.form_next()
EventListingSettingsFormView = wrap_form(EventListingSettingsForm)