Source code for plone.app.event.browser.event_listing

# -*- coding: utf-8 -*-
from calendar import monthrange
from datetime import date
from datetime import timedelta
from plone.app.event import _
from plone.app.event.base import _prepare_range
from plone.app.event.base import expand_events
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 RET_MODE_ACCESSORS
from plone.app.event.base import RET_MODE_OBJECTS
from plone.app.event.base import spell_date
from plone.app.event.base import start_end_from_mode
from plone.app.event.base import start_end_query
from plone.app.event.ical.exporter import construct_icalendar
from plone.app.querystring import queryparser
from plone.memoize import view
from plone.uuid.interfaces import IUUID
from Products.CMFPlone.PloneBatch import Batch
from Products.Five.browser import BrowserView
from zope.component import getMultiAdapter
from zope.contentprovider.interfaces import IContentProvider


try:
    from Products.CMFPlone.defaultpage import get_default_page
except ImportError:
    # Plone 4
    from plone.app.layout.navigation.defaultpage import getDefaultPage as get_default_page  # noqa

try:
    from plone.app.contenttypes.behaviors.collection import ISyndicatableCollection as ICollection  # noqa
except ImportError:
    ICollection = None


[docs]class EventListing(BrowserView): def __init__(self, context, request): super(EventListing, self).__init__(context, request) self.now = now = localized_now(context) # Try to get the default page default = get_default_page(context) self.default_context = context[default] if default else context self.is_collection = False if ICollection: self.is_collection = ICollection.providedBy(self.default_context) # Request parameter req = self.request.form b_size = int(req.get('b_size', 0)) if not b_size and self.is_collection: collection_behavior = ICollection(self.default_context) b_size = getattr(collection_behavior, 'item_count', 0) self.b_size = b_size or 10 self.b_start = int(req.get('b_start', 0)) self.orphan = int(req.get('orphan', 1)) self.mode = req.get('mode', None) self._date = req.get('date', None) self.tags = req.get('tags', None) self.searchable_text = req.get('SearchableText', None) self.path = req.get('path', None) day = int(req.get('day', 0)) or None month = int(req.get('month', 0)) or None year = int(req.get('year', 0)) 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 = 'day' if self._date else 'future' self.uid = None # Used to get all occurrences from a single event. Overrides all other settings # noqa @property def show_filter(self): ret = True if self.is_collection: ctx = self.default_context query = queryparser.parseFormquery(ctx, ctx.query) if 'start' in query or 'end' in query: # Don't show the date filter, if a date is given in the # collection's query ret = False return ret @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 def _get_events(self, ret_mode=RET_MODE_ACCESSORS, expand=True): context = self.context kw = {} if self.uid: # In this case, restrict search for single event kw['UID'] = self.uid else: if self.path: kw['path'] = self.path else: # Search current and subsequent folders kw['path'] = '/'.join(context.getPhysicalPath()) if self.tags: kw['Subject'] = {'query': self.tags, 'operator': 'and'} if self.searchable_text: kw['SearchableText'] = self.searchable_text # kw['b_start'] = self.b_start # kw['b_size'] = self.b_size 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=expand, **kw) @view.memoize def events(self, ret_mode=RET_MODE_ACCESSORS, expand=True, batch=True): res = [] if self.is_collection: ctx = self.default_context # Whatever sorting is defined, we're overriding it. sort_on = 'start' sort_order = None if self.mode in ('past', 'all'): sort_order = 'reverse' query = queryparser.parseFormquery( ctx, ctx.query, sort_on=sort_on, sort_order=sort_order ) custom_query = self.request.get('contentFilter', {}) if 'start' not in query or 'end' not in query: # ... else don't show the navigation bar start, end = self._start_end start, end = _prepare_range(ctx, start, end) custom_query.update(start_end_query(start, end)) res = ctx.results( batch=False, brains=True, custom_query=custom_query ) if expand: # get start and end values from the query to ensure limited # listing for occurrences start, end = self._expand_events_start_end( query.get('start') or custom_query.get('start'), query.get('end') or custom_query.get('end') ) res = expand_events( res, ret_mode, start=start, end=end, sort=sort_on, sort_reverse=True if sort_order else False ) else: res = self._get_events(ret_mode, expand=expand) 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): # Get as objects. # Don't include occurrences to avoid having them along with their # original events and it's recurrence definition in icalendar exports. events = self.events(ret_mode=RET_MODE_OBJECTS, expand=False, batch=False) cal = construct_icalendar(self.context, events) name = '%s.ics' % self.context.getId() contents = cal.to_ical() self.request.response.setHeader('Content-Type', 'text/calendar') self.request.response.setHeader( 'Content-Disposition', 'attachment; filename="%s"' % name ) self.request.response.setHeader('Content-Length', len(contents)) self.request.response.write(contents) @property def ical_url(self): date = self.date mode = self.mode qstr = '&'.join([ it for it in ['mode=%s' % mode if mode else None, 'date=%s' % date if date else None] if it ]) qstr = '?%s' % qstr if qstr else '' return '%s/@@event_listing_ical%s' % ( self.context.absolute_url(), qstr ) # COLLECTION daterange start/end determination def _expand_events_start_end(self, start, end): # make sane start and end values for expand_events from # Collection start/end criterions. # if end/min is given, it overrides start/min settings to make sure, # ongoing events are shown in the listing! # XXX: This actually fits most needs, but not all. Maybe someone # wants to come up with some edgecases! se = dict(start=None, end=None) if start: q = start.get('query') r = start.get('range') if r == "min": se["start"] = q elif r == "max": se["end"] = q elif r in ("minmax", "min:max"): list(q).sort() se["start"] = q[0] se["end"] = q[1] if end: q = end.get('query') r = end.get('range') if r == "min": se["start"] = q return se["start"], se["end"] def formatted_date(self, occ): provider = getMultiAdapter( (self.context, self.request, self), IContentProvider, name='formatted_date' ) return provider(occ) def date_speller(self, date): return spell_date(date, self.context) @property def header_string(self): start, end = self._start_end start_dict = spell_date(start, self.context) if start else None end_dict = spell_date(end, self.context) if end else 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_name'], start.day, start_dict['month_name'], start.year ), 'until': "%s, %s. %s %s" % ( end_dict['wkday_name'], end.day, end_dict['month_name'], 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_name'], start.day, start_dict['month_name'], 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_name'], start.day, start_dict['month_name'], start.year ), 'until': "%s, %s. %s %s" % ( end_dict['wkday_name'], end.day, end_dict['month_name'], end.year ), } ) elif mode == 'month': main_msgid = _( u"events_in_month", default=u"Events in ${month} ${year}", mapping={ 'month': start_dict['month_name'], 'year': start.year, } ) trans = self.context.translate return {'main': trans(main_msgid) if main_msgid else '', 'sub': trans(sub_msgid) if sub_msgid else ''} # MODE URLs def _date_nav_url(self, mode, datestr=''): return '%s?mode=%s%s' % ( self.request.getURL(), mode, '&date=%s' % datestr if datestr else '' ) @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)
[docs]class EventListingIcal(EventListing): def __call__(self, *args, **kwargs): return self.ical
[docs]class EventEventListing(EventListing): """This is an EventListing for an individual event, to list all occurrences batched and navigatable with all the features, the EventListing offers. """ def __init__(self, context, request): super(EventEventListing, self).__init__(context, request) self.uid = IUUID(self.context)