def test__dt_end_of_day(self): from plone.app.event.base import dt_end_of_day self.assertEqual( dt_end_of_day(datetime(2013, 2, 1, 18, 35)), datetime(2013, 2, 1, 23, 59, 59, 0) ) self.assertEqual( dt_end_of_day(date(2013, 2, 1)), datetime(2013, 2, 1, 23, 59, 59, 0) )
def end(self): end = IEventBasic(self.context).end if self.open_end: end = IEventBasic(self.context).start if self.open_end or self.whole_day: end = dt_end_of_day(end) return end
def get_nearest_today_event(self): context = aq_inner(self.context) pc = getToolByName(context, 'portal_catalog') now = localized_now() portal_state = getMultiAdapter((self.context, self.request), name='plone_portal_state') navigation_root_path = portal_state.navigation_root_path() context = aq_inner(self.context) path = navigation_root_path for obj in aq_chain(context): if ICommunity.providedBy(obj): community = aq_inner(obj) path = '/'.join(community.getPhysicalPath()) query = { 'portal_type': 'Event', 'review_state': self.state, 'start': {'query': [now, dt_end_of_day(now)], 'range': 'min:max'}, 'end': {'query': now, 'range': 'min'}, 'sort_on': 'start', 'path': path, 'sort_limit': 1 } result = pc(**query) if result: return result[0] else: return
def data_postprocessing(obj, event): # newly created object, without start/end/timezone (e.g. invokeFactory() # called without data from add form), ignore event; it will be notified # again later: if getattr(obj, 'start', None) is None: return # We handle date inputs as floating dates without timezones and apply # timezones afterwards. def _fix_zone(dt, zone): if dt.tzinfo is not None and isinstance(dt.tzinfo, FakeZone): # Delete the tzinfo only, if it was set by IEventBasic setter. # Only in this case the start value on the object itself is what # was entered by the user. After running this event subscriber, # it's in UTC then. # If tzinfo it's not available at all, a naive datetime was set # probably by invokeFactory in tests. dt = tzdel(dt) if dt.tzinfo is None: # In case the tzinfo was deleted above or was not present, we can # localize the dt value to the target timezone. dt = tz.localize(dt) else: # In this case, no changes to start, end or the timezone were made. # Just return the object's datetime (which is in UTC) localized to # the target timezone. dt = dt.astimezone(tz) return dt.replace(microsecond=0) behavior = IEventBasic(obj) tz = pytz.timezone(behavior.timezone) # Fix zones start = _fix_zone(obj.start, tz) end = _fix_zone(obj.end, tz) # Adapt for whole day if behavior.whole_day: start = dt_start_of_day(start) if behavior.open_end: end = start # Open end events end on same day if behavior.open_end or behavior.whole_day: end = dt_end_of_day(end) # Save back obj.start = utc(start) obj.end = utc(end) # Reindex obj.reindexObject()
def end(self): if getattr(self.context, 'recurrence', None): end = self._recurrence_upcoming_event().end elif self.open_end: end = IEventBasic(self.context).start else: end = IEventBasic(self.context).end if self.open_end or self.whole_day: end = dt_end_of_day(end) return end
def test__start_end_from_mode(self): from plone.app.event.base import start_end_from_mode from plone.app.event.base import dt_end_of_day start, end = start_end_from_mode('all') self.assertTrue(start is None and end is None) start, end = start_end_from_mode('past') self.assertTrue(start is None and isinstance(end, datetime.datetime)) start, end = start_end_from_mode('future') self.assertTrue(isinstance(start, datetime.datetime) and end is None) start, end = start_end_from_mode('now') self.assertTrue(isinstance(start, datetime.datetime) and isinstance(end, datetime.datetime) and end.hour==23 and end.minute==59 and end.second==59) start, end = start_end_from_mode('7days') self.assertTrue(isinstance(start, datetime.datetime) and isinstance(end, datetime.datetime) and end == dt_end_of_day(start+datetime.timedelta(days=7))) start, end = start_end_from_mode('today') self.assertTrue(isinstance(start, datetime.datetime) and isinstance(end, datetime.datetime) and start.hour==0 and start.minute==0 and start.second==0 and end.hour==23 and end.minute==59 and end.second==59 and (start, end) == start_end_from_mode('day')) day = datetime.datetime(2013,2,1,18,22) start, end = start_end_from_mode('day', day) self.assertTrue(start.date() == day.date() == end.date() and start.hour==0 and start.minute==0 and start.second==0 and end.hour==23 and end.minute==59 and end.second==59) # test with date-only day = datetime.datetime(2013,2,1) start, end = start_end_from_mode('day', day) self.assertTrue(start.date() == day.date() == end.date() and start.hour==0 and start.minute==0 and start.second==0 and end.hour==23 and end.minute==59 and end.second==59)
def data_postprocessing(start, end, whole_day, open_end): """Adjust start and end according to whole_day and open_end setting. """ def _fix_dt(dt, tz): """Fix datetime: Apply missing timezones, remove microseconds. """ if dt.tzinfo is None: dt = tz.localize(dt) return dt.replace(microsecond=0) tz_default = default_timezone() tz_start = getattr(start, 'tzinfo', tz_default) tz_end = getattr(end, 'tzinfo', tz_default) start = _fix_dt(start, tz_start) end = _fix_dt(end, tz_end) # Adapt for whole day if whole_day: start = dt_start_of_day(start) if open_end: end = start # Open end events end on same day if open_end or whole_day: end = dt_end_of_day(end) # TODO: """ if not obj.sync_uid: # sync_uid has to be set for icalendar data exchange. uid = IUUID(obj) # We don't want to fail when getRequest() returns None, e.g when # creating an event during test layer setup time. request = getRequest() or {} domain = request.get('HTTP_HOST') obj.sync_uid = '%s%s' % ( uid, domain and '@%s' % domain or '' ) """ return start, end, whole_day, open_end
def ical_import(container, ics_resource, event_type): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') def _get_prop(prop, item): ret = None if prop in item: ret = safe_unicode(item.decoded(prop)) return ret def _from_list(ical, prop): """For EXDATE and RDATE recurrence component properties, the dates can be defined within one EXDATE/RDATE line or for each date an individual line. In the latter case, icalendar creates a list. This method handles this case. TODO: component property parameters like TZID are not used here. """ val = prop in ical and ical[prop] or [] if not isinstance(val, list): val = [val] #ret = '' #for item in val: # ret = ret and '%s\n' % ret or ret # insert linebreak # ret = '%s%s:%s' % (ret, prop, item.to_ical()) #return ret # Zip multiple lines into one, since jquery.recurrenceinput.js does # not support multiple lines here # https://github.com/collective/jquery.recurrenceinput.js/issues/15 ret = '' for item in val: ret = ret and '%s,' % ret or ret # insert linebreak ret = '%s%s' % (ret, item.to_ical()) return ret and '%s:%s' % (prop, ret) or None count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end timezone = getattr(getattr(start, 'tzinfo', None), 'zone', None) or\ base.default_timezone(container) whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert(is_datetime(start)) assert(is_datetime(end)) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = rrule and 'RRULE:%s' % rrule.to_ical() or '' rdates = _from_list(item, 'RDATE') exdates = _from_list(item, 'EXDATE') rrule = '\n'.join([it for it in [rrule, rdates, exdates] if it]) # TODO: attendee-lists are not decoded properly and contain only # vCalAddress values attendees = _get_prop('ATTENDEE', item) contact = _get_prop('CONTACT', item) categories = _get_prop('CATEGORIES', item) if hasattr(categories, '__iter__'): categories = [safe_unicode(it) for it in categories] ## for sync #created = _get_prop('CREATED', item) #modified = _get_prop('LAST-MODIFIED', item) # TODO: better use plone.api, from which some of the code here is # copied content_id = str(random.randint(0, 99999999)) # TODO: if AT had the same attrs like IDXEventBase, we could set # everything within this invokeFactory call. container.invokeFactory(event_type, id=content_id, title=title, description=description) content = container[content_id] event = IEventAccessor(content) event.start = start event.end = end event.timezone = timezone event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories notify(ObjectModifiedEvent(content)) # Archetypes specific code if getattr(content, 'processForm', False): # Will finish Archetypes content item creation process, # rename-after-creation and such content.processForm() if content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) transaction.savepoint(optimistic=True) # Commit before renaming content.aq_parent.manage_renameObject(content_id, new_id) else: transaction.savepoint(optimistic=True) count += 1 return {'count': count}
def ical_import(container, ics_resource, event_type, sync_strategy=base.SYNC_KEEP_NEWER): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') cat = getToolByName(container, 'portal_catalog') container_path = '/'.join(container.getPhysicalPath()) def _get_by_sync_uid(uid): return cat( sync_uid=uid, path={'query': container_path, 'depth': 1} ) def _get_prop(prop, item, default=None): ret = default if prop in item: ret = safe_unicode(item.decoded(prop)) return ret def _from_list(ical, prop): """For EXDATE and RDATE recurrence component properties, the dates can be defined within one EXDATE/RDATE line or for each date an individual line. In the latter case, icalendar creates a list. This method handles this case. TODO: component property parameters like TZID are not used here. """ val = prop in ical and ical[prop] or [] if not isinstance(val, list): val = [val] #ret = '' #for item in val: # ret = ret and '%s\n' % ret or ret # insert linebreak # ret = '%s%s:%s' % (ret, prop, item.to_ical()) #return ret # Zip multiple lines into one, since jquery.recurrenceinput.js does # not support multiple lines here # https://github.com/collective/jquery.recurrenceinput.js/issues/15 ret = '' for item in val: ret = ret and '%s,' % ret or ret # insert linebreak ret = '%s%s' % (ret, item.to_ical()) return ret and '%s:%s' % (prop, ret) or None count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end timezone = getattr(getattr(start, 'tzinfo', None), 'zone', None) or\ base.default_timezone(container) whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert(is_datetime(start)) assert(is_datetime(end)) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = rrule and 'RRULE:%s' % rrule.to_ical() or '' rdates = _from_list(item, 'RDATE') exdates = _from_list(item, 'EXDATE') rrule = '\n'.join([it for it in [rrule, rdates, exdates] if it]) # TODO: attendee-lists are not decoded properly and contain only # vCalAddress values attendees = item.get('ATTENDEE', ()) contact = _get_prop('CONTACT', item) categories = item.get('CATEGORIES', ()) if hasattr(categories, '__iter__'): categories = [safe_unicode(it) for it in categories] ext_modified = utc(_get_prop('LAST-MODIFIED', item)) # TODO: better use plone.api for content creation, from which some of # the code here is copied content = None new_content_id = None existing_event = None sync_uid = _get_prop('UID', item) if sync_strategy != base.SYNC_NONE and sync_uid: existing_event = _get_by_sync_uid(sync_uid) if existing_event: if sync_strategy == base.SYNC_KEEP_MINE: # On conflict, keep mine continue exist_event = existing_event[0].getObject() acc = IEventAccessor(exist_event) if sync_strategy == base.SYNC_KEEP_NEWER and\ (not ext_modified or acc.last_modified >= ext_modified): # Update only, if newer, if ext_modified exists continue # Else: update content = exist_event else: # TODO: if AT had the same attrs like IDXEventBase, we could set # everything within this invokeFactory call. new_content_id = str(random.randint(0, 99999999)) container.invokeFactory(event_type, id=new_content_id, title=title, description=description) content = container[new_content_id] assert(content) # At this point, a content must be available. event = IEventAccessor(content) event.title = title event.description = description event.start = start event.end = end event.timezone = timezone event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories if sync_strategy != base.SYNC_NONE: # Don't import the sync_uid, if no sync strategy is chosen. Let the # sync_uid be autogenerated then. event.sync_uid = sync_uid notify(ObjectModifiedEvent(content)) # Archetypes specific code if getattr(content, 'processForm', False): # Will finish Archetypes content item creation process, # rename-after-creation and such content.processForm() # Use commits instead of savepoints to avoid "FileStorageError: # description too long" on large imports. transaction.get().commit() # Commit before rename if new_content_id and new_content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) content.aq_parent.manage_renameObject(new_content_id, new_id) # Do this at the end, otherwise it's overwritten if ext_modified: event.last_modified = ext_modified count += 1 return {'count': count}
def ical_import(container, ics_resource, event_type, sync_strategy=base.SYNC_KEEP_NEWER): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') cat = getToolByName(container, 'portal_catalog') container_path = '/'.join(container.getPhysicalPath()) def _get_by_sync_uid(uid): return cat(sync_uid=uid, path={'query': container_path, 'depth': 1}) def _get_prop(prop, item, default=None): ret = default if prop in item: ret = safe_unicode(item.decoded(prop)) return ret def _from_list(ical, prop): """For EXDATE and RDATE recurrence component properties, the dates can be defined within one EXDATE/RDATE line or for each date an individual line. In the latter case, icalendar creates a list. This method handles this case. TODO: component property parameters like TZID are not used here. """ val = ical[prop] if prop in ical else [] if not isinstance(val, list): val = [val] # Zip multiple lines into one, since jquery.recurrenceinput.js does # not support multiple lines here # https://github.com/collective/jquery.recurrenceinput.js/issues/15 ret = '' for item in val: ret = '%s,' % ret if ret else ret # insert linebreak ret = '%s%s' % (ret, item.to_ical()) return '%s:%s' % (prop, ret) if ret else None count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert (is_datetime(start)) assert (is_datetime(end)) # Set timezone, if not already set tz = base.default_timezone(container, as_tzinfo=True) if not getattr(start, 'tzinfo', False): start = tz.localize(start) if not getattr(end, 'tzinfo', False): end = tz.localize(end) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = 'RRULE:%s' % rrule.to_ical() if rrule else '' rdates = _from_list(item, 'RDATE') exdates = _from_list(item, 'EXDATE') rrule = '\n'.join([it for it in [rrule, rdates, exdates] if it]) # TODO: attendee-lists are not decoded properly and contain only # vCalAddress values attendees = item.get('ATTENDEE', ()) contact = _get_prop('CONTACT', item) categories = item.get('CATEGORIES', ()) if getattr(categories, '__iter__', False): categories = tuple([safe_unicode(it) for it in categories]) ext_modified = utc(_get_prop('LAST-MODIFIED', item)) content = None new_content_id = None existing_event = None sync_uid = _get_prop('UID', item) if sync_uid and sync_strategy is not base.SYNC_NONE: existing_event = _get_by_sync_uid(sync_uid) if existing_event: if sync_strategy == base.SYNC_KEEP_MINE: # On conflict, keep mine continue exist_event = existing_event[0].getObject() acc = IEventAccessor(exist_event) if sync_strategy == base.SYNC_KEEP_NEWER and\ (not ext_modified or acc.last_modified > ext_modified): # Update only if modified date was passed in and it is not # older than the current modified date. The client is not # expected to update the "last-modified" property, it is the # job of the server (calendar store) to keep it up to date. # This makes sure the client did the change on an up-to-date # version of the object. See # http://tools.ietf.org/search/rfc5545#section-3.8.7.3 continue # Else: update content = exist_event else: new_content_id = str(random.randint(0, 99999999)) container.invokeFactory(event_type, id=new_content_id, title=title, description=description) content = container[new_content_id] assert (content) # At this point, a content must be available. event = IEventAccessor(content) event.title = title event.description = description event.start = start event.end = end event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories if sync_uid and sync_strategy is not base.SYNC_NONE: # Set the external sync_uid for sync strategies other than # SYNC_NONE. event.sync_uid = sync_uid notify(ObjectModifiedEvent(content)) # Use commits instead of savepoints to avoid "FileStorageError: # description too long" on large imports. transaction.get().commit() # Commit before rename if new_content_id and new_content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) content.aq_parent.manage_renameObject(new_content_id, new_id) # Do this at the end, otherwise it's overwritten if ext_modified: event.last_modified = ext_modified count += 1 return {'count': count}
def test__start_end_from_mode(self): from plone.app.event.base import start_end_from_mode from plone.app.event.base import dt_end_of_day # ALL # start, end = start_end_from_mode('all') self.assertTrue(start is None and end is None) # PAST # start, end = start_end_from_mode('past') self.assertTrue(start is None and isinstance(end, datetime)) # FUTURE # start, end = start_end_from_mode('future') self.assertTrue(isinstance(start, datetime) and end is None) # NOW # start, end = start_end_from_mode('now') self.assertTrue( isinstance(start, datetime) and isinstance(end, datetime) and end.hour == 23 and end.minute == 59 and end.second == 59 ) # 7DAYS # start, end = start_end_from_mode('7days') self.assertTrue( isinstance(start, datetime) and isinstance(end, datetime) and end == dt_end_of_day(start + timedelta(days=6)) ) # TODAY # start, end = start_end_from_mode('today') self.assertTrue( isinstance(start, datetime) and isinstance(end, datetime) and start.hour == 0 and start.minute == 0 and start.second == 0 and end.hour == 23 and end.minute == 59 and end.second == 59 and (start, end) == start_end_from_mode('day') ) # DAY # day = datetime(2013, 2, 1, 18, 22) start, end = start_end_from_mode('day', day) self.assertTrue( start.date() == day.date() == end.date() and start.hour == 0 and start.minute == 0 and start.second == 0 and end.hour == 23 and end.minute == 59 and end.second == 59 ) # test with date-only day = datetime(2013, 2, 1) start, end = start_end_from_mode('day', day) self.assertTrue( start.date() == day.date() == end.date() and start.hour == 0 and start.minute == 0 and start.second == 0 and end.hour == 23 and end.minute == 59 and end.second == 59 ) # WEEK # def ret_0(): return 0 # Monday def ret_1(): return 1 # Tuesday def ret_6(): return 6 # Sunday # prepare patched first_weekday orig_first_weekday = base.first_weekday base.first_weekday = ret_0 day = datetime(2013, 2, 2) start, end = start_end_from_mode('week', day) self.assertTrue( start.isoformat() == '2013-01-28T00:00:00' and end.isoformat() == '2013-02-03T23:59:59' ) base.first_weekday = ret_1 day = datetime(2013, 2, 2) start, end = start_end_from_mode('week', day) self.assertTrue( start.isoformat() == '2013-01-29T00:00:00' and end.isoformat() == '2013-02-04T23:59:59' ) base.first_weekday = ret_6 day = datetime(2013, 2, 1) start, end = start_end_from_mode('week', day) self.assertTrue( start.isoformat() == '2013-01-27T00:00:00' and end.isoformat() == '2013-02-02T23:59:59' ) base.first_weekday = orig_first_weekday # restore orig first_weekday # MONTH # start, end = start_end_from_mode('month') self.assertTrue(start < end and start.day == 1) day = datetime(2013, 2, 7) start, end = start_end_from_mode('month', day) self.assertTrue( start.year == 2013 and start.month == 2 and start.day == 1 and start.hour == 0 and start.minute == 0 and start.second == 0 and end.year == 2013 and end.month == 2 and end.day == 28 and end.hour == 23 and end.minute == 59 and end.second == 59 )
def ical_import(container, ics_resource, event_type): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') def _get_prop(prop, item): ret = None if prop in item: ret = safe_unicode(item.decoded(prop)) return ret def _from_list(ical, prop): """For EXDATE and RDATE recurrence component properties, the dates can be defined within one EXDATE/RDATE line or for each date an individual line. In the latter case, icalendar creates a list. This method handles this case. TODO: component property parameters like TZID are not used here. """ val = prop in ical and ical[prop] or [] if not isinstance(val, list): val = [val] #ret = '' #for item in val: # ret = ret and '%s\n' % ret or ret # insert linebreak # ret = '%s%s:%s' % (ret, prop, item.to_ical()) #return ret # Zip multiple lines into one, since jquery.recurrenceinput.js does # not support multiple lines here # https://github.com/collective/jquery.recurrenceinput.js/issues/15 ret = '' for item in val: ret = ret and '%s,' % ret or ret # insert linebreak ret = '%s%s' % (ret, item.to_ical()) return ret and '%s:%s' % (prop, ret) or None count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end timezone = getattr(getattr(start, 'tzinfo', None), 'zone', None) or\ base.default_timezone(container) whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert(is_datetime(start)) assert(is_datetime(end)) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = rrule and 'RRULE:%s' % rrule.to_ical() or '' rdates = _from_list(item, 'RDATE') exdates = _from_list(item, 'EXDATE') rrule = '\n'.join([it for it in [rrule, rdates, exdates] if it]) # TODO: attendee-lists are not decoded properly and contain only # vCalAddress values attendees = _get_prop('ATTENDEE', item) contact = _get_prop('CONTACT', item) categories = _get_prop('CATEGORIES', item) if hasattr(categories, '__iter__'): categories = [safe_unicode(it) for it in categories] # for sync created = _get_prop('CREATED', item) modified = _get_prop('LAST-MODIFIED', item) # TODO: better use plone.api, from which some of the code here is # copied content_id = str(random.randint(0, 99999999)) # TODO: if AT had the same attrs like IDXEventBase, we could set # everything within this invokeFactory call. container.invokeFactory(event_type, id=content_id, title=title, description=description) content = container[content_id] event = IEventAccessor(content) event.start = start event.end = end event.timezone = timezone event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories notify(ObjectModifiedEvent(content)) # Archetypes specific code if getattr(content, 'processForm', False): # Will finish Archetypes content item creation process, # rename-after-creation and such content.processForm() if content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) transaction.savepoint(optimistic=True) # Commit before renaming content.aq_parent.manage_renameObject(content_id, new_id) else: transaction.savepoint(optimistic=True) count += 1 return {'count': count}
def createOffers(self, hasHeaders, fitxer, marketUID): registry = queryUtility(IRegistry) tfe_tool = registry.forInterface(ITfemarketSettings) catalog = api.portal.get_tool(name='portal_catalog') market = catalog(UID=marketUID)[0].getObject() msgError = [] csv_file = csv.reader(fitxer, delimiter=',', quotechar='"') if hasHeaders: csv_file.next() # Ignore header for csv for count, row in enumerate(csv_file): # Importa ofertas notValidDegrees = self.checkNotValidDegrees( row[5].decode("utf-8").split(",")) if len(notValidDegrees) == 0: teacher = getExactUserData(row[7].decode("utf-8")) if teacher: data = { 'title': row[0].decode("utf-8"), 'description': row[1].decode("utf-8"), 'topic': row[2].decode("utf-8"), 'offer_type': row[3].decode("utf-8"), 'tfgm': row[4].decode("utf-8").split(","), 'degree': row[5].decode("utf-8").split(","), 'keys': row[6].decode("utf-8").split(","), 'teacher_manager': teacher['id'], 'teacher_fullname': teacher['sn1'] + ' ' + teacher['sn2'] + ', ' + teacher['givenName'] if 'sn2' in teacher else teacher['sn1'] + ', ' + teacher['givenName'], 'teacher_email': teacher['mail'], 'dept': teacher['unitCode'] + "-" + teacher['unit'], 'num_students': int(row[10].decode("utf-8")), 'workload': row[11].decode("utf-8"), 'targets': row[12].decode("utf-8"), 'features': row[13].decode("utf-8"), 'requirements': row[14].decode("utf-8"), 'lang': row[15].decode("utf-8").split(","), 'modality': row[16].decode("utf-8"), 'company': row[17].decode("utf-8"), 'grant': bool(row[18].decode("utf-8") == "True"), 'confidential': bool(row[19].decode("utf-8") == "True"), 'environmental_theme': bool(row[20].decode("utf-8") == "True"), 'scope_cooperation': bool(row[21].decode("utf-8") == "True"), } type_codirector = row[8].decode("utf-8") data.update({'type_codirector': type_codirector}) if type_codirector == 'UPC': codirector = getExactUserData(row[9].decode("utf-8")) if codirector: data.update({ 'codirector_id': codirector['id'], 'codirector': codirector['sn1'] + ' ' + codirector['sn2'] + ', ' + codirector['givenName'] if 'sn2' in codirector else codirector['sn1'] + ', ' + codirector['givenName'], 'codirector_email': codirector['mail'], 'codirector_dept': codirector['unitCode'] + "-" + codirector['unit'] }) else: msg = row[0].decode( "utf-8") + " - Codirector (" + row[9].decode( "utf-8") + ") not exist." print str(count + 1) + ": Error - " + msg msgError.append(str(count + 1) + ": " + msg) continue else: data.update({'codirector': row[9].decode("utf-8")}) offer = createContentInContainer(market, "genweb.tfemarket.offer", **data) offer.setEffectiveDate( dt_start_of_day(datetime.datetime.today() + datetime.timedelta(1))) offer.setExpirationDate( dt_end_of_day(datetime.datetime.today() + datetime.timedelta(365))) offer.reindexObject() # Importa topics y tags strTopics = row[2].decode("utf-8") + "," topics = list(dict.fromkeys(strTopics.split(",")[:-1])) actualTopics = tfe_tool.topics.split('\r\n') newTopics = "\r\n".join([ topic for topic in topics if topic not in actualTopics ]) if newTopics: tfe_tool.topics += "\r\n" + newTopics strTags = row[6].decode("utf-8") + "," tags = list(dict.fromkeys(strTags.split(",")[:-1])) actualTags = tfe_tool.tags.split('\r\n') newTags = "\r\n".join( [tag for tag in tags if tag not in actualTags]) if newTags: tfe_tool.tags += "\r\n" + newTags transaction.commit() print str(count + 1) + ": Done - " + row[0].decode("utf-8") else: msg = row[0].decode("utf-8") + " - Teacher (" + row[ 7].decode("utf-8") + ") not exist." print str(count + 1) + ": Error - " + msg msgError.append(str(count + 1) + ": " + msg) else: msg = row[0].decode("utf-8") + " - Degree (" + " - ".join( notValidDegrees) + ") not valid." print str(count + 1) + ": Error - " + msg msgError.append(str(count + 1) + ": " + msg) return msgError
def ical_import(container, ics_resource, event_type, sync_strategy=base.SYNC_KEEP_NEWER): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') cat = getToolByName(container, 'portal_catalog') container_path = '/'.join(container.getPhysicalPath()) def _get_by_sync_uid(uid): return cat(sync_uid=uid, path={'query': container_path, 'depth': 1}) def _get_prop(prop, item, default=None): ret = default if prop in item: ret = safe_unicode(item.decoded(prop)) return ret def _from_list(ical, prop): """For EXDATE and RDATE recurrence component properties, the dates can be defined within one EXDATE/RDATE line or for each date an individual line. In the latter case, icalendar creates a list. This method handles this case. TODO: component property parameters like TZID are not used here. """ val = prop in ical and ical[prop] or [] if not isinstance(val, list): val = [val] #ret = '' #for item in val: # ret = ret and '%s\n' % ret or ret # insert linebreak # ret = '%s%s:%s' % (ret, prop, item.to_ical()) #return ret # Zip multiple lines into one, since jquery.recurrenceinput.js does # not support multiple lines here # https://github.com/collective/jquery.recurrenceinput.js/issues/15 ret = '' for item in val: ret = ret and '%s,' % ret or ret # insert linebreak ret = '%s%s' % (ret, item.to_ical()) return ret and '%s:%s' % (prop, ret) or None count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end timezone = getattr(getattr(start, 'tzinfo', None), 'zone', None) or\ base.default_timezone(container) whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert (is_datetime(start)) assert (is_datetime(end)) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = rrule and 'RRULE:%s' % rrule.to_ical() or '' rdates = _from_list(item, 'RDATE') exdates = _from_list(item, 'EXDATE') rrule = '\n'.join([it for it in [rrule, rdates, exdates] if it]) # TODO: attendee-lists are not decoded properly and contain only # vCalAddress values attendees = item.get('ATTENDEE', ()) contact = _get_prop('CONTACT', item) categories = item.get('CATEGORIES', ()) if hasattr(categories, '__iter__'): categories = [safe_unicode(it) for it in categories] ext_modified = utc(_get_prop('LAST-MODIFIED', item)) # TODO: better use plone.api for content creation, from which some of # the code here is copied content = None new_content_id = None existing_event = None sync_uid = _get_prop('UID', item) if sync_strategy != base.SYNC_NONE and sync_uid: existing_event = _get_by_sync_uid(sync_uid) if existing_event: if sync_strategy == base.SYNC_KEEP_MINE: # On conflict, keep mine continue exist_event = existing_event[0].getObject() acc = IEventAccessor(exist_event) if sync_strategy == base.SYNC_KEEP_NEWER and\ (not ext_modified or acc.last_modified >= ext_modified): # Update only, if newer, if ext_modified exists continue # Else: update content = exist_event else: # TODO: if AT had the same attrs like IDXEventBase, we could set # everything within this invokeFactory call. new_content_id = str(random.randint(0, 99999999)) container.invokeFactory(event_type, id=new_content_id, title=title, description=description) content = container[new_content_id] assert (content) # At this point, a content must be available. event = IEventAccessor(content) event.title = title event.description = description event.start = start event.end = end event.timezone = timezone event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories if sync_strategy != base.SYNC_NONE: # Don't import the sync_uid, if no sync strategy is chosen. Let the # sync_uid be autogenerated then. event.sync_uid = sync_uid notify(ObjectModifiedEvent(content)) # Archetypes specific code if getattr(content, 'processForm', False): # Will finish Archetypes content item creation process, # rename-after-creation and such content.processForm() # Use commits instead of savepoints to avoid "FileStorageError: # description too long" on large imports. transaction.get().commit() # Commit before rename if new_content_id and new_content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) content.aq_parent.manage_renameObject(new_content_id, new_id) # Do this at the end, otherwise it's overwritten if ext_modified: event.last_modified = ext_modified count += 1 return {'count': count}
def expiresDefaultValue(): return dt_end_of_day(datetime.datetime.today() + datetime.timedelta(365))
def ical_import(container, ics_resource, event_type, sync_strategy=base.SYNC_KEEP_NEWER): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') cat = getToolByName(container, 'portal_catalog') container_path = '/'.join(container.getPhysicalPath()) def _get_by_sync_uid(uid): return cat( sync_uid=uid, path={'query': container_path, 'depth': 1} ) def _get_prop(prop, item, default=None): ret = default if prop in item: ret = safe_unicode(item.decoded(prop)) return ret def _from_list(ical, prop): """For EXDATE and RDATE recurrence component properties, the dates can be defined within one EXDATE/RDATE line or for each date an individual line. In the latter case, icalendar creates a list. This method handles this case. TODO: component property parameters like TZID are not used here. """ val = ical[prop] if prop in ical else [] if not isinstance(val, list): val = [val] # Zip multiple lines into one, since jquery.recurrenceinput.js does # not support multiple lines here # https://github.com/collective/jquery.recurrenceinput.js/issues/15 ret = '' for item in val: ret = '%s,' % ret if ret else ret # insert linebreak ical_val = item.to_ical() if six.PY3 and isinstance(ical_val, six.binary_type): ical_val = ical_val.decode('utf8') ret = '%s%s' % (ret, ical_val) return '%s:%s' % (prop, ret) if ret else None count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert(is_datetime(start)) assert(is_datetime(end)) # Set timezone, if not already set tz = base.default_timezone(container, as_tzinfo=True) if not getattr(start, 'tzinfo', False): start = tz.localize(start) if not getattr(end, 'tzinfo', False): end = tz.localize(end) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = rrule.to_ical() if rrule else '' if rrule: if six.PY3 and isinstance(rrule, six.binary_type): rrule = rrule.decode('utf8') rrule = 'RRULE:%s' % rrule rdates = _from_list(item, 'RDATE') exdates = _from_list(item, 'EXDATE') rrule = '\n'.join([it for it in [rrule, rdates, exdates] if it]) # TODO: attendee-lists are not decoded properly and contain only # vCalAddress values attendees = item.get('ATTENDEE', ()) contact = _get_prop('CONTACT', item) categories = item.get('CATEGORIES', ()) if getattr(categories, '__iter__', False): categories = tuple([safe_unicode(it) for it in categories]) ext_modified = utc(_get_prop('LAST-MODIFIED', item)) content = None new_content_id = None existing_event = None sync_uid = _get_prop('UID', item) if sync_uid and sync_strategy is not base.SYNC_NONE: existing_event = _get_by_sync_uid(sync_uid) if existing_event: if sync_strategy == base.SYNC_KEEP_MINE: # On conflict, keep mine continue exist_event = existing_event[0].getObject() acc = IEventAccessor(exist_event) if sync_strategy == base.SYNC_KEEP_NEWER and\ (not ext_modified or acc.last_modified > ext_modified): # Update only if modified date was passed in and it is not # older than the current modified date. The client is not # expected to update the "last-modified" property, it is the # job of the server (calendar store) to keep it up to date. # This makes sure the client did the change on an up-to-date # version of the object. See # http://tools.ietf.org/search/rfc5545#section-3.8.7.3 continue # Else: update content = exist_event else: new_content_id = str(random.randint(0, 99999999)) container.invokeFactory(event_type, id=new_content_id, title=title, description=description) content = container[new_content_id] assert(content) # At this point, a content must be available. event = IEventAccessor(content) event.title = title event.description = description event.start = start event.end = end event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories if sync_uid and sync_strategy is not base.SYNC_NONE: # Set the external sync_uid for sync strategies other than # SYNC_NONE. event.sync_uid = sync_uid notify(ObjectModifiedEvent(content)) # Use commits instead of savepoints to avoid "FileStorageError: # description too long" on large imports. transaction.get().commit() # Commit before rename if new_content_id and new_content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) content.aq_parent.manage_renameObject(new_content_id, new_id) # Do this at the end, otherwise it's overwritten if ext_modified: event.last_modified = ext_modified count += 1 return {'count': count}
def ical_import(container, ics_resource, event_type): cal = icalendar.Calendar.from_ical(ics_resource) events = cal.walk('VEVENT') def _get_prop(prop, item): ret = None if prop in item: ret = safe_unicode(item.decoded(prop)) return ret count = 0 for item in events: start = _get_prop('DTSTART', item) end = _get_prop('DTEND', item) if not end: duration = _get_prop('DURATION', item) if duration: end = start + duration # else: whole day or open end timezone = getattr(getattr(start, 'tzinfo', None), 'zone', None) or\ base.default_timezone(container) whole_day = False open_end = False if is_date(start) and (is_date(end) or end is None): # All day / whole day events # End must be same type as start (RFC5545, 3.8.2.2) whole_day = True if end is None: end = start if start < end: # RFC5545 doesn't define clearly, if all day events should have # a end date one day after the start day at 0:00. # Internally, we handle all day events with start=0:00, # end=:23:59:59, so we substract one day here. end = end - datetime.timedelta(days=1) start = base.dt_start_of_day(date_to_datetime(start)) end = base.dt_end_of_day(date_to_datetime(end)) elif is_datetime(start) and end is None: # Open end event, see RFC 5545, 3.6.1 open_end = True end = base.dt_end_of_day(date_to_datetime(start)) assert(is_datetime(start)) assert(is_datetime(end)) title = _get_prop('SUMMARY', item) description = _get_prop('DESCRIPTION', item) location = _get_prop('LOCATION', item) url = _get_prop('URL', item) rrule = _get_prop('RRULE', item) rrule = rrule and 'RRULE:%s' % rrule.to_ical() or '' rdate = _get_prop('RDATE', item) rrule = rdate and '%s\nRDATE:%s' % (rrule, rdate.to_ical()) or '' exdate = _get_prop('EXDATE', item) rrule = exdate and '%s\nEXDATE:%s' % (rrule, exdate.to_ical()) or '' attendees = _get_prop('ATTENDEE', item) contact = _get_prop('CONTACT', item) categories = _get_prop('CATEGORIES', item) if hasattr(categories, '__iter__'): categories = [safe_unicode(it) for it in categories] # for sync created = _get_prop('CREATED', item) modified = _get_prop('LAST-MODIFIED', item) # TODO: better use plone.api, from which some of the code here is # copied content_id = str(random.randint(0, 99999999)) # TODO: if AT had the same attrs like IDXEventBase, we could set # everything within this invokeFactory call. container.invokeFactory(event_type, id=content_id, title=title, description=description) content = container[content_id] event = IEventAccessor(content) event.start = start event.end = end event.timezone = timezone event.whole_day = whole_day event.open_end = open_end event.location = location event.event_url = url event.recurrence = rrule event.attendees = attendees event.contact_name = contact event.subjects = categories notify(ObjectModifiedEvent(content)) # Archetypes specific code if getattr(content, 'processForm', False): # Will finish Archetypes content item creation process, # rename-after-creation and such content.processForm() if content_id in container: # Rename with new id from title, if processForm didn't do it. chooser = INameChooser(container) new_id = chooser.chooseName(title, content) transaction.savepoint(optimistic=True) # Commit before renaming content.aq_parent.manage_renameObject(content_id, new_id) else: transaction.savepoint(optimistic=True) count += 1 return {'count': count}