def occurrences(self): if not self.data.is_event: return None rec = IRecurrenceSupport(self.context, None) if rec: return rec.occurrences() else: return None
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 next_occurrences(self): """Returns occurrences for this context, except the start occurrence, limited to self.max_occurrence occurrences. :returns: List with next occurrences. :rtype: list """ occurrences = [] adapter = IRecurrenceSupport(self.event_context, None) if adapter: for cnt, occ in enumerate(adapter.occurrences()): if cnt == self.max_occurrences: break occurrences.append(occ) return occurrences
def next_occurrences(self): """Returns occurrences for this context, except the start occurrence, limited to self.max_occurrence occurrences. :returns: List with next occurrences. :rtype: list """ occurrences = [] adapter = IRecurrenceSupport(self.event_context, None) if adapter: for cnt, occ in enumerate(adapter.occurrences(range_start=self.data.start)): if cnt == self.max_occurrences: break occurrences.append(occ) return occurrences
def dictFromObject(self, item, kwargs={}): eventPhysicalPath = '/'.join(item.getPhysicalPath()) wft = getToolByName(self.context, 'portal_workflow') state = wft.getInfoFor(self.context, 'review_state') member = self.context.portal_membership.getAuthenticatedMember() if member.has_permission('Modify portal content', item): editable = True copycut = '' if self.copyDict and eventPhysicalPath == self.copyDict['url']: copycut = self.copyDict['op'] == 1 and ' event_cutted' \ or ' event_copied' typeClass = ' type-' + item.portal_type colorDict = getColorIndex(self.context, self.request, eventPhysicalPath) colorIndex = colorDict.get('class', '') color = colorDict.get('color', '') extraClass = self.getObjectExtraClass(item) events = [] if handle_recurrence(self.request) and IRecurrenceSupport(item, None): events = get_recurring_events(self.request, item) else: events = item return (dict_from_events( events, editable=editable, state=state, color=color, css=copycut + typeClass + colorIndex + extraClass ))
def dictFromBrain(self, brain, editableEvents=[]): if brain.UID in editableEvents: editable = True else: editable = False copycut = '' if self.copyDict and brain.getPath() == self.copyDict['url']: copycut = self.copyDict['op'] == 1 and ' event_cutted' \ or ' event_copied' typeClass = ' type-' + brain.portal_type colorDict = getColorIndex(self.context, self.request, brain=brain) colorIndex = colorDict.get('class', '') color = colorDict.get('color', '') extraClass = self.getBrainExtraClass(brain) events = [] if handle_recurrence(self.request): event = brain.getObject() if IRecurrenceSupport(event, None): events = get_recurring_events(self.request, event) else: events = brain else: events = brain return (dict_from_events( events, editable=editable, state=brain.review_state, color=color, css=copycut + typeClass + colorIndex + extraClass ))
def next_occurrences(self): """Returns occurrences for this context, except the start occurrence, limited to self.num_occurrences occurrences. :returns: List with next occurrences. :rtype: list """ occurrences = [] adapter = IRecurrenceSupport(self.context, None) if adapter: for cnt, occ in enumerate(adapter.occurrences()): if cnt == self.max_occurrences + 1: break elif cnt == 0: continue occurrences.append(occ) return occurrences
def _recurrence_upcoming_event(self): """Return the next upcoming event""" adapter = IRecurrenceSupport(self.context) occs = adapter.occurrences(range_start=localized_now()) try: return next(occs) except StopIteration: # No more future occurrences: passed event return IEventBasic(self.context)
def next_occurrences(self): """Returns occurrences for this context, except the start occurrence, limited to self.max_occurrence occurrences. :returns: List with next occurrences. :rtype: list """ occurrences = [] adapter = IRecurrenceSupport(self.event_context, None) if adapter: cnt_future_occ = 0 for occ in adapter.occurrences(): if cnt_future_occ == self.max_occurrences: break if occ.end >= datetime.utcnow().replace(tzinfo=pytz.utc): cnt_future_occ += 1 occurrences.append(occ) return occurrences
def create_ticket_occurrences(self): tickets = self.tickets recurrence = IRecurrenceSupport(self.context) for occurrence in recurrence.occurrences(): if not isinstance(occurrence, Occurrence): continue for ticket in tickets: if occurrence.id in ticket.objectIds(): continue ticket.invokeFactory('Ticket Occurrence', occurrence.id, title=ticket.Title()) ticket_occurrence = ticket[occurrence.id] self._copy_field_value(ticket, ticket_occurrence, 'item_available') self._copy_field_value(ticket, ticket_occurrence, 'item_overbook') ticket_occurrence.reindexObject()
def test_recurrence_occurrences_with_range_start_2(self): # Test with range rs = datetime.datetime(2011, 11, 16, 11, 0, tzinfo=self.tz) result = IRecurrenceSupport(self.data).occurrences(range_start=rs) self.assertEqual(3, len(result)) # Only IOccurrence objects in the result set self.assertTrue(IOccurrence.providedBy(result[0]))
def occurrences(self): """Returns all occurrences for this context, except the start occurrence. The maximum defaults to 7 occurrences. If there are more occurrences defined for this context, the result will contain the last item of the occurrence list. :rtype: dict - with ``events`` and ``tail`` as keys. """ eventsinfo = dict(events=[], tail=None) context = self.context adapter = IRecurrenceSupport(context, None) if adapter is not None: occurrences = adapter.occurrences()[1:] # don't include first eventsinfo['events'], eventsinfo['tail'] = ( self._get_occurrences_helper(occurrences) ) return eventsinfo
def next_occurrences(self): """Returns all occurrences for this context, except the start occurrence. The maximum defaults to 7 occurrences. If there are more occurrences defined for this context, the result will contain the last item of the occurrence list. :returns: Dictionary with ``events`` and ``tail`` as keys. :rtype: dict """ occ_dict = dict(events=[], tail=None) context = self.context adapter = IRecurrenceSupport(context, None) if adapter is not None: occurrences = adapter.occurrences()[1:] # don't include first occ_dict['events'], occ_dict['tail'] = ( self._get_occurrences_helper(occurrences)) return occ_dict
def create_ticket_occurrences(self): tickets = self.tickets recurrence = IRecurrenceSupport(self.context) for occurrence in recurrence.occurrences(): if not isinstance(occurrence, Occurrence): continue for ticket in tickets: if occurrence.id in ticket.objectIds(): continue ticket.invokeFactory( 'Ticket Occurrence', occurrence.id, title=ticket.Title()) ticket_occurrence = ticket[occurrence.id] self._copy_field_value( ticket, ticket_occurrence, 'item_available') self._copy_field_value( ticket, ticket_occurrence, 'item_overbook') ticket_occurrence.reindexObject()
def test_recurrence_occurrences(self): result = IRecurrenceSupport(self.data).occurrences() self.assertEqual(4, len(result)) # First occurrence is an IEvent object self.assertTrue(IEvent.providedBy(result[0])) # Subsequent ones are IOccurrence objects self.assertTrue(IOccurrence.providedBy(result[1]))
def expand_events(events, ret_mode, start=None, end=None, sort=None, sort_reverse=None): """Expand to the recurrence occurrences of a given set of events. :param events: IEvent based objects or IEventAccessor object wrapper. :param ret_mode: Return type of search results. These options are available: * 2 (objects): Return results as IEvent and/or IOccurrence objects. * 3 (accessors): Return results as IEventAccessor wrapper objects. Option "1" (brains) is not supported. :type ret_mode: integer [2|3] :param start: Date, from which on events should be expanded. :type start: Python datetime. :param end: Date, until which events should be expanded. :type end: Python datetime :param sort: Object or IEventAccessor Attribute to sort on. :type sort: string :param sort_reverse: Change the order of the sorting. :type sort_reverse: boolean """ assert (ret_mode is not RET_MODE_BRAINS) exp_result = [] for it in events: obj = it.getObject() if getattr(it, 'getObject', False) else it if IEventRecurrence.providedBy(obj): occurrences = [ _obj_or_acc(occ, ret_mode) for occ in IRecurrenceSupport(obj).occurrences(start, end) ] elif IEvent.providedBy(obj): occurrences = [_obj_or_acc(obj, ret_mode)] else: # No IEvent based object. Could come from a collection. continue exp_result += occurrences if sort: exp_result.sort(key=lambda x: _get_compare_attr(x, sort)) if sort_reverse: exp_result.reverse() return exp_result
def get_recurring_events(request, event): if isinstance(event.start, datetime): tz = event.start.tzinfo start = datetime.fromtimestamp(request.get('start')).replace(tzinfo=tz) end = datetime.fromtimestamp(request.get('end')).replace(tzinfo=tz) else: start = pydt(DateTime(request.get('start'))) end = pydt(DateTime(request.get('end'))) events = IRecurrenceSupport(event).occurrences(range_start=start, range_end=end) return events
def test_recurrence_occurrences_with_range_start_1(self): # Test with range rs = datetime.datetime(2011, 11, 15, 11, 0, tzinfo=self.tz) result = IRecurrenceSupport(self.data).occurrences(range_start=rs) self.assertEqual(4, len(result)) # First occurrence is an IEvent object self.assertTrue(IEvent.providedBy(result[0])) # Subsequent ones are IOccurrence objects self.assertTrue(IOccurrence.providedBy(result[1]))
def test_recurrence_occurrences_with_range_start_and_end(self): # Test with range rs = datetime.datetime(2011, 11, 11, 11, 0, tzinfo=self.tz) re = datetime.datetime(2011, 11, 12, 11, 0, tzinfo=self.tz) result = IRecurrenceSupport(self.data).occurrences(range_start=rs, range_end=re) result = list(result) # cast generator to list self.assertEqual(2, len(result)) # First occurrence is an IEvent object self.assertTrue(IEvent.providedBy(result[0])) # Subsequent ones are IOccurrence objects self.assertTrue(IOccurrence.providedBy(result[1]))
def test_recurrence(self): tz = pytz.timezone('Europe/Vienna') duration = datetime.timedelta(days=4) data = MockEvent() data.start = datetime.datetime(2011, 11, 11, 11, 00, tzinfo=tz) data.end = data.start + duration data.recurrence = 'RRULE:FREQ=DAILY;COUNT=4' zope.interface.alsoProvides(data, IEvent, IEventRecurrence) result = IRecurrenceSupport(data).occurrences() self.assertEqual(4, len(result)) # First occurrence is an IEvent object self.assertTrue(IEvent.providedBy(result[0])) # Subsequent ones are IOccurrence objects self.assertTrue(IOccurrence.providedBy(result[1]))
def dictFromObject(self, item): eventPhysicalPath = '/'.join(item.getPhysicalPath()) wft = getToolByName(self.context, 'portal_workflow') state = wft.getInfoFor(self.context, 'review_state') member = self.context.portal_membership.getAuthenticatedMember() if member.has_permission('Modify portal content', item): editable = True if item.end() - item.start() > 1.0: allday = True else: allday = False adapted = interfaces.ISFBaseEventFields(item, None) if adapted: allday = adapted.allDay copycut = '' if self.copyDict and eventPhysicalPath == self.copyDict['url']: copycut = self.copyDict['op'] == 1 and ' event_cutted' or ' event_copied' typeClass = ' type-' + item.portal_type colorIndex = getColorIndex(self.context, self.request, eventPhysicalPath) extraClass = self.getObjectExtraClass(item) if HAS_RECCURENCE_SUPPORT: occurences = IRecurrenceSupport(item).occurences() else: occurences = [(item.start().rfc822(), item.end().rfc822())] events = [] for occurence_start, occurence_end in occurences: events.append({ "status": "ok", "id": "UID_%s" % (item.UID()), "title": item.Title(), "description": item.Description(), "start": HAS_RECCURENCE_SUPPORT and occurence_start.isoformat() or occurence_start, "end": HAS_RECCURENCE_SUPPORT and occurence_end.isoformat() or occurence_end, "url": item.absolute_url(), "editable": editable, "allDay": allday, "className": "contextualContentMenuEnabled state-" + str(state) + (editable and " editable" or "")+copycut+typeClass+colorIndex+extraClass}) return events
def getEvents(self): context = self.context wft = getToolByName(context, 'portal_workflow') state = wft.getInfoFor(context, 'review_state') member = context.portal_membership.getAuthenticatedMember() editable = bool(member.has_permission('Modify portal content', context)) extraClass = self.getObjectExtraClass() typeClass = ' type-' + context.portal_type events = [] if handle_recurrence(self.request) and IRecurrenceSupport(context, None): events = get_recurring_events(self.request, context) else: events = context return (dict_from_events( events, editable=editable, state=state, css=typeClass + extraClass ))
def test_recurrence(self): tz = pytz.timezone('Europe/Vienna') duration = timedelta(days=4) mock = MockEvent() mock.start = tz.localize(datetime(2011, 11, 11, 11, 0)) mock.end = mock.start + duration mock.recurrence = 'RRULE:FREQ=DAILY;COUNT=4' zope.interface.alsoProvides(mock, IEvent, IEventBasic, IEventRecurrence, IDXEvent, IDXEventRecurrence) result = IRecurrenceSupport(mock).occurrences() result = list(result) # cast generator to list self.assertEqual(4, len(result)) # First occurrence is an IEvent object self.assertTrue(IEvent.providedBy(result[0])) # Subsequent ones are IOccurrence objects self.assertTrue(IOccurrence.providedBy(result[1]))
def dictFromBrain(self, brain, editableEvents=[]): if brain.UID in editableEvents: editable = True else: editable = False if brain.end - brain.start > 1.0: allday = True else: allday = False if getattr(brain, 'SFAllDay', None) in [False,True]: allday = brain.SFAllDay copycut = '' if self.copyDict and brain.getPath() == self.copyDict['url']: copycut = self.copyDict['op'] == 1 and ' event_cutted' or ' event_copied' typeClass = ' type-'+brain.portal_type colorIndex = getColorIndex(self.context, self.request, brain=brain) extraClass = self.getBrainExtraClass(brain) if HAS_RECCURENCE_SUPPORT: occurences = IRecurrenceSupport(brain.getObject()).occurences() else: occurences = [(brain.start.rfc822(), brain.end.rfc822())] events = [] for occurence_start, occurence_end in occurences: events.append({ "id": "UID_%s" % (brain.UID), "title": brain.Title, "description": brain.Description, "start": HAS_RECCURENCE_SUPPORT and occurence_start.isoformat() or occurence_start, "end": HAS_RECCURENCE_SUPPORT and occurence_end.isoformat() or occurence_end, "url": brain.getURL(), "editable": editable, "allDay": allday, "className": "contextualContentMenuEnabled state-" + str(brain.review_state) + (editable and " editable" or "")+copycut+typeClass+colorIndex+extraClass}) return events
def __call__(self): event = self.context context = self.context referer = self.request.get('HTTP_REFERER') if referer: portal = getToolByName(self.context, 'portal_url').getPortalObject() url = "/".join(portal.getPhysicalPath()) + referer.replace( portal.absolute_url(), '') context = portal.restrictedTraverse(url) eventPhysicalPath = '/'.join(event.getPhysicalPath()) wft = getToolByName(context, 'portal_workflow') state = wft.getInfoFor(event, 'review_state') member = context.portal_membership.getAuthenticatedMember() editable = bool(member.has_permission('Modify portal content', event)) copycut = '' if self.copyDict and eventPhysicalPath == self.copyDict['url']: copycut = self.copyDict['op'] == 1 and ' event_cutted' \ or ' event_copied' typeClass = ' type-' + event.portal_type colorDict = getColorIndex(context, self.request, eventPhysicalPath) colorIndex = colorDict.get('class', '') color = colorDict.get('color', '') extraClass = self.getExtraClass() events = [] if handle_recurrence(self.request) and IRecurrenceSupport(event, None): events = get_recurring_events(self.request, event) else: events = event return (dict_from_events(events, editable=editable, state=state, color=color, css=copycut + typeClass + colorIndex + extraClass))
def generate_source_dict_from_event(self, event): """generate_source_dict_from_event. :param event: """ view = self.request.get('view') ret = [] title = event.Title() description = event.Description() if event.text: description = event.text.output editable = api.user.has_permission('Modify portal content', obj=event) deletable = api.user.has_permission('Delete objects', obj=event) color = 'grey' if event.tag: for _view_type, group_color, categories in CATEGORIES: for cat_id, _cat_title in categories: if cat_id in event.tag: color = group_color break else: # for events imported from ICS for _view_type, group_color, categories in CATEGORIES: for cat_id, _cat_tile in categories: for tag in event.subject: if cat_id == tag.lower(): color = group_color break adapter = IRecurrenceSupport(event) # get all occurrences of the current event (if not recurrent, # the generator will only produce the event itself) and create a # results entry for each one for occurrence in adapter.occurrences( range_start=DateTime(self.request.get('start')), range_end=DateTime(self.request.get('end'))): # The default source marks an event as all day if it is longer than # one day. Marking an event as all day in contentpage will set # the times to 00:00 and 23:59. If those times are on the same # date they will not be recognised as all day because that's only a # 0.999.. day. This check will mark those events as all day. start = occurrence.start end = occurrence.end duration = occurrence.end - occurrence.start if isinstance(duration, timedelta): duration = duration.total_seconds() / 60. / 60. / 24. # compute real all day for the tooltip information real_allday = (event.whole_day or duration > 0.99 or start == end or occurrence.start.date() != occurrence.end.date()) # For the main calendar_view we set all events to allday because we # don't show start and end times anyway and we need the background # color that only appears on full day events. if view == 'calendar_view': allday = True end += timedelta(days=1) else: # on all other views we need the allday to be correct allday = real_allday iso = 'isoformat' if hasattr(start, 'isoformat') else 'ISO8601' start = getattr(start, iso)() end = getattr(end, iso)() ret.append({ "id": "UID_%s" % (event.UID()), "title": title, "start": start, "end": end, "url": event.absolute_url(), "can_edit": editable, "can_delete": deletable, "backgroundColor": color, "allDay": allday, "realAllDay": real_allday, "className": "state-" + str(get_state(event)) + (editable and " editable" or ""), "description": description, "location": event.location, "realStartTime": occurrence.start.strftime('%H:%M'), "realEndTime": occurrence.end.strftime('%H:%M'), "realStartDate": occurrence.start.strftime('%B %d'), "realEndDate": occurrence.end.strftime('%B %d'), "oneday": occurrence.start.date() == occurrence.end.date() }) return ret
def get_events(context, start=None, end=None, limit=None, ret_mode=1, expand=False, sort='start', sort_reverse=False, **kw): """Return all events as catalog brains, possibly within a given timeframe. :param context: [required] A context object. :type context: Content object :param start: Date, from which on events should be searched. :type start: Python datetime. :param end: Date, until which events should be searched. :type end: Python datetime :param limit: Number of items to be returned. :type limit: integer :param ret_mode: Return type of search results. These options are available: * 1 (brains): Return results as catalog brains. * 2 (objects): Return results as IEvent and/or IOccurrence objects. * 3 (accessors): Return results as IEventAccessor wrapper objects. :type ret_mode: integer [1|2|3] :param expand: Expand the results to all occurrences (within a timeframe, if given). With this option set to True, the resultset also includes the event's recurrence occurrences and is sorted by the start date. Only available in ret_mode 2 (objects) and 3 (accessors). :type expand: boolean :param sort: Catalog index id to sort on. Not available with expand=True. :type sort: string :param sort_reverse: Change the order of the sorting. :type sort_reverse: boolean :returns: Portal events, matching the search criteria. :rtype: catalog brains """ start, end = _prepare_range(context, start, end) query = {} query['object_provides'] = IEvent.__identifier__ if 'path' not in kw: # limit to the current navigation root, usually (not always) site portal = getSite() navroot = getNavigationRootObject(context, portal) query['path'] = '/'.join(navroot.getPhysicalPath()) else: query['path'] = kw['path'] if start: # All events from start date ongoing: # The minimum end date of events is the date from which we search. query['end'] = {'query': start, 'range': 'min'} if end: # All events until end date: # The maximum start date must be the date until we search. query['start'] = {'query': end, 'range': 'max'} # Sorting # In expand mode we sort after calculation of recurrences again. But we # need to leave this sorting here in place, since no sort definition could # lead to arbitrary results when limiting with sort_limit. query['sort_on'] = sort if sort_reverse: query['sort_order'] = 'reverse' if limit: query['sort_limit'] = limit query.update(kw) cat = getToolByName(context, 'portal_catalog') result = cat(**query) # Helper functions def _obj_or_acc(obj, ret_mode): if ret_mode == 2: return obj elif ret_mode == 3: return IEventAccessor(obj) def _get_compare_attr(obj, attr): val = getattr(obj, attr, None) if safe_callable(val): val = val() if isinstance(val, DateTime): val = pydt(val) return val if ret_mode in (2, 3) and expand == False: result = [_obj_or_acc(it.getObject(), ret_mode) for it in result] elif ret_mode in (2, 3) and expand == True: exp_result = [] for it in result: obj = it.getObject() if IEventRecurrence.providedBy(obj): occurrences = [ _obj_or_acc(occ, ret_mode) for occ in IRecurrenceSupport(obj).occurrences(start, end) ] else: occurrences = [_obj_or_acc(obj, ret_mode)] exp_result += occurrences if sort: # support AT and DX without wrapped by IEventAccessor (mainly for # sorting after "start" or "end"). exp_result.sort(key=lambda x: _get_compare_attr(x, sort)) if sort_reverse: exp_result.reverse() result = exp_result if limit: # Expanded events as well as catalog search results (which might not # exactly be limited by the query) must be limited again. result = result[:limit] return result