def test_data_postprocessing(self): # TODO: since we use an IEventAccessor here, this is a possible # canditate for merging with # the test_dxevent.TestDXIntegration.test_data_postprocessing test. # Addressing bug #62 self.portal.invokeFactory('Event', 'ate1', startDate=DateTime(2012,10,19,0,30), endDate=DateTime(2012,10,19,1,30), timezone="Europe/Vienna", wholeDay=False) e1 = self.portal['ate1'] e1.reindexObject() acc = IEventAccessor(e1) # Prepare reference objects tzname_1 = "Europe/Vienna" tz_1 = pytz.timezone(tzname_1) dt_1 = tz_1.localize(datetime(2012,10,19,0,30)) dt_1_1 = tz_1.localize(datetime(2012,10,19,0,0)) dt_1_2 = tz_1.localize(datetime(2012,10,19,23,59,59)) tzname_2 = "Hongkong" tz_2 = pytz.timezone(tzname_2) dt_2 = tz_2.localize(datetime(2012,10,19,0,30)) dt_2_1 = tz_2.localize(datetime(2012,10,19,0,0)) dt_2_2 = tz_2.localize(datetime(2012,10,19,23,59,59)) # See, if start isn't moved by timezone offset. self.assertTrue(acc.start == dt_1) notify(ObjectModifiedEvent(e1)) self.assertTrue(acc.start == dt_1) # After timezone changes, only the timezone should be applied, but the # date and time values not converted. acc.timezone = tzname_2 notify(ObjectModifiedEvent(e1)) self.assertTrue(acc.start == dt_2) # Likewise with wholeDay events. If values were converted, the days # would drift apart. acc.whole_day = True acc.timezone = tzname_1 notify(ObjectModifiedEvent(e1)) self.assertTrue(acc.start == dt_1_1) self.assertTrue(acc.end == dt_1_2) acc.timezone = tzname_2 notify(ObjectModifiedEvent(e1)) self.assertTrue(acc.start == dt_2_1) self.assertTrue(acc.end == dt_2_2) self.portal.manage_delObjects(['ate1'])
def test_event_accessor(self): utc = pytz.utc self.portal.invokeFactory('plone.app.event.dx.event', 'event1', start=datetime(2011, 11, 11, 11, 0, tzinfo=utc), end=datetime(2011, 11, 11, 12, 0, tzinfo=utc), timezone='UTC', whole_day=False) e1 = self.portal['event1'] # setting attributes via the accessor acc = IEventAccessor(e1) acc.end = datetime(2011, 11, 13, 10, 0) acc.timezone = TZNAME tz = pytz.timezone(TZNAME) # accessor should return end datetime in the event's timezone self.assertTrue(acc.end == datetime(2011, 11, 13, 11, 0, tzinfo=tz)) # the behavior's end datetime is stored in utc on the content object self.assertTrue(e1.end == datetime(2011, 11, 13, 10, 0, tzinfo=utc)) # accessing the end property via the behavior adapter, returns the # value converted to the event's timezone self.assertTrue(IEventBasic(e1).end == datetime(2011, 11, 13, 11, 0, tzinfo=tz)) # timezone should be the same on the event object and accessor self.assertTrue(e1.timezone == acc.timezone) self.portal.manage_delObjects(['event1'])
def test_event_accessor(self): utc = pytz.utc self.portal.invokeFactory( 'plone.app.event.dx.event', 'event1', start=datetime(2011, 11, 11, 11, 0, tzinfo=utc), end=datetime(2011, 11, 11, 12, 0, tzinfo=utc), timezone='UTC', whole_day=False ) e1 = self.portal['event1'] # setting attributes via the accessor acc = IEventAccessor(e1) acc.end = datetime(2011, 11, 13, 10, 0) acc.timezone = TZNAME tz = pytz.timezone(TZNAME) # accessor should return end datetime in the event's timezone self.assertTrue(acc.end == datetime(2011, 11, 13, 11, 0, tzinfo=tz)) # the behavior's end datetime is stored in utc on the content object self.assertTrue(e1.end == datetime(2011, 11, 13, 10, 0, tzinfo=utc)) # accessing the end property via the behavior adapter, returns the # value converted to the event's timezone self.assertTrue( IEventBasic(e1).end == datetime(2011, 11, 13, 11, 0, tzinfo=tz) ) # timezone should be the same on the event object and accessor self.assertTrue(e1.timezone == acc.timezone)
def test_event_accessor(self): utc = pytz.utc self.portal.invokeFactory('Event', 'event1', description='a description', start=datetime(2011,11,11,11,0, tzinfo=utc), end=datetime(2011,11,11,12,0, tzinfo=utc), timezone='UTC', wholeDay=False) e1 = self.portal['event1'] # setting attributes via the accessor acc = IEventAccessor(e1) acc.end = datetime(2011,11,13,10,0, tzinfo=utc) acc.timezone = 'Europe/Vienna' vienna = pytz.timezone('Europe/Vienna') # test description self.assertTrue(acc.description == 'a description') acc.description = 'another desc' self.assertTrue(acc.description == 'another desc') # accessor should return end datetime in the event's timezone self.assertTrue(acc.end == datetime(2011,11,13,11,0, tzinfo=vienna)) # start/end dates are stored in UTC zone on the context, but converted # to event's timezone via the attribute getter. self.assertTrue(e1.end() == DateTime('2011/11/13 11:00:00 Europe/Vienna')) # timezone should be the same on the event object and accessor self.assertTrue(e1.getTimezone() == acc.timezone) self.portal.manage_delObjects(['event1'])
def migrate_schema_fields(self): newacc = IEventAccessor(self.new) newacc.start = self.old.start_date newacc.end = self.old.end_date newacc.timezone = str(self.old.start_date.tzinfo) \ if self.old.start_date.tzinfo \ else default_timezone(fallback='UTC') if hasattr(self.old, 'location'): newacc.location = self.old.location if hasattr(self.old, 'attendees'): newacc.attendees = tuple(self.old.attendees.splitlines()) if hasattr(self.old, 'event_url'): newacc.event_url = self.old.event_url if hasattr(self.old, 'contact_name'): newacc.contact_name = self.old.contact_name if hasattr(self.old, 'contact_email'): newacc.contact_email = self.old.contact_email if hasattr(self.old, 'contact_phone'): newacc.contact_phone = self.old.contact_phone if hasattr(self.old, 'text'): # Copy the entire richtext object, not just it's representation IEventSummary(self.new).text = self.old.text # Trigger ObjectModified, so timezones can be fixed up. notify(ObjectModifiedEvent(self.new))
def migrate_schema_fields(self): from plone.app.event.dx.behaviors import IEventSummary newacc = IEventAccessor(self.new) newacc.location = self.old.location newacc.start = self.old.start_date newacc.end = self.old.end_date if self.old.start_date.tzinfo: newacc.timezone = str(self.old.start_date.tzinfo) else: newacc.timezone = default_timezone(fallback='UTC') newacc.attendees = self.old.attendees newacc.event_url = self.old.event_url newacc.contact_name = self.old.contact_name newacc.contact_email = self.old.contact_email newacc.contact_phone = self.old.contact_phone # Copy the entire richtext object, not just it's representation IEventSummary(self.new).text = self.old.text # Trigger ObjectModified, so timezones can be fixed up. notify(ObjectModifiedEvent(self.new))
def migrate_schema_fields(self): oldacc = IEventAccessor(self.old) newacc = IEventAccessor(self.new) newacc.start = oldacc.start newacc.end = oldacc.end newacc.timezone = oldacc.timezone newacc.location = oldacc.location newacc.attendees = oldacc.attendees newacc.event_url = oldacc.event_url newacc.contact_name = oldacc.contact_name newacc.contact_email = oldacc.contact_email newacc.contact_phone = oldacc.contact_phone # Copy the entire richtext object, not just it's representation IEventSummary(self.new).text = IEventSummary(self.old).text # Trigger ObjectModified, so timezones can be fixed up. notify(ObjectModifiedEvent(self.new))
def migrate_schema_fields(self): from plone.app.event.dx.behaviors import IEventSummary old_start = self.old.getField('startDate').get(self.old) old_end = self.old.getField('endDate').get(self.old) old_location = self.old.getField('location').get(self.old) old_attendees = self.old.getField('attendees').get(self.old) old_eventurl = self.old.getField('eventUrl').get(self.old) old_contactname = self.old.getField('contactName').get(self.old) old_contactemail = self.old.getField('contactEmail').get(self.old) old_contactphone = self.old.getField('contactPhone').get(self.old) old_text_field = self.old.getField('text') raw_text = safe_unicode(old_text_field.getRaw(self.old)) mime_type = old_text_field.getContentType(self.old) if raw_text.strip() == '': raw_text = '' old_richtext = RichTextValue(raw=raw_text, mimeType=mime_type, outputMimeType='text/x-html-safe') if self.old.getField('timezone'): old_timezone = self.old.getField('timezone').get(self.old) else: old_timezone = default_timezone(fallback='UTC') acc = IEventAccessor(self.new) acc.start = old_start.asdatetime() # IEventBasic acc.end = old_end.asdatetime() # IEventBasic acc.timezone = old_timezone # IEventBasic acc.location = old_location # IEventLocation acc.attendees = old_attendees # IEventAttendees acc.event_url = old_eventurl # IEventContact acc.contact_name = old_contactname # IEventContact acc.contact_email = old_contactemail # IEventContact acc.contact_phone = old_contactphone # IEventContact # Copy the entire richtext object, not just it's representation IEventSummary(self.new).text = old_richtext # Trigger ObjectModified, so timezones can be fixed up. notify(ObjectModifiedEvent(self.new))
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): 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}
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 _last_migrate_time_zone(self): acc = IEventAccessor(self.new) acc.timezone = 'Europe/Berlin' notify(ObjectModifiedEvent(self.new))
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 create_organ_content(og_unit, og_type, og_string, og_title, og_id): """ Creates all structure based on organ type """ open_og = api.content.create(type='genweb.organs.organgovern', title=og_title, id=og_id, container=og_unit, safe_id=True) open_og.acronim = og_string open_og.descripcioOrgan = getLoremIpsum(1, 'medium', 'plaintext') open_og.fromMail = '*****@*****.**' open_og.organType = og_type open_og.logoOrgan = getRandomImage(200, 200) open_og.visiblefields = True open_og.eventsColor = 'green' open_og.membresOrgan = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output open_og.convidatsPermanentsOrgan = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output open_og.adrecaLlista = '*****@*****.**' session_open = api.content.create(type='genweb.organs.sessio', id='planificada', title='Sessió Planificada', container=open_og, safe_id=True) session_open.membresConvocats = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output session_open.membresConvidats = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output session_open.llistaExcusats = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output session_open.assistents = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output session_open.noAssistents = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output session_open.adrecaLlista = '*****@*****.**' session_open.llocConvocatoria = 'Barcelona' session_open.numSessio = '01' acc = IEventAccessor(session_open) tz = pytz.timezone("Europe/Vienna") acc.start = tz.localize(datetime(2018, 11, 18, 10, 0)) acc.end = tz.localize(datetime(2018, 11, 20, 10, 0)) acc.timezone = "Europe/Vienna" punt = api.content.create(type='genweb.organs.punt', id='punt', title='Punt Exemple', container=session_open) punt.proposalPoint = 1 punt.defaultContent = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output punt.estatsLlista = u'Esborrany' # For working test code. If not added, Plone works, but test dont. constraints = ISelectableConstrainTypes(punt) constraints.setConstrainTypesMode(1) constraints.setLocallyAllowedTypes( ('genweb.organs.subpunt', 'genweb.organs.acord', 'genweb.organs.file', 'genweb.organs.document')) document_public = api.content.create(type='genweb.organs.document', id='docpublic', title='Document contingut public', container=punt) document_public.description = u"Lorem Ipsum description" document_public.defaultContent = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output document_restringit = api.content.create( type='genweb.organs.document', id='docrestringit', title='Document contingut restringit', container=punt) document_restringit.description = u"Lorem Ipsum description" document_restringit.alternateContent = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output document_both = api.content.create( type='genweb.organs.document', id='docboth', title='Document contingut public i restringit', container=punt) document_both.description = u"Lorem Ipsum description" document_both.defaultContent = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output document_both.alternateContent = RichTextValue( getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output # For working tests code # constraints = ISelectableConstrainTypes(punt) # constraints.setConstrainTypesMode(1) # constraints.setLocallyAllowedTypes(('genweb.organs.subpunt', 'genweb.organs.acord', 'genweb.organs.file', 'genweb.organs.document')) subpunt = api.content.create(type='genweb.organs.subpunt', id='subpunt', title='SubPunt Exemple', container=punt) subpunt.proposalPoint = 1.1 subpunt.defaultContent = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output subpunt.estatsLlista = u'Esborrany' subacord = api.content.create(type='genweb.organs.acord', id='acord', title='Acord Exemple', container=punt) subacord.proposalPoint = '2' subacord.agreement = og_string + '/2018/01/02' subacord.defaultContent = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output subacord.estatsLlista = u'Esborrany' acord = api.content.create(type='genweb.organs.acord', id='acord', title='Acord Exemple', container=session_open) acord.proposalPoint = '2' acord.agreement = og_string + '/2018/01/01' acord.defaultContent = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output acord.estatsLlista = u'Esborrany' api.content.copy(source=document_public, target=acord, safe_id=True) api.content.copy(source=document_restringit, target=acord, safe_id=True) api.content.copy(source=document_both, target=acord, safe_id=True) api.content.copy(source=document_public, target=subpunt, safe_id=True) api.content.copy(source=document_restringit, target=subpunt, safe_id=True) api.content.copy(source=document_both, target=subpunt, safe_id=True) api.content.copy(source=document_public, target=subacord, safe_id=True) api.content.copy(source=document_restringit, target=subacord, safe_id=True) api.content.copy(source=document_both, target=subacord, safe_id=True) pdf_file = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'tests')) + '/testfile.pdf' public_file = NamedBlobFile(data=open(pdf_file, 'r').read(), contentType='application/pdf', filename=u'pdf-public.pdf') restricted_file = NamedBlobFile(data=open(pdf_file, 'r').read(), contentType='application/pdf', filename=u'pdf-restringit.pdf') acta = api.content.create(type='genweb.organs.acta', id='acta', title='Acta Exemple', container=session_open) acta.llocConvocatoria = u'Barcelona' acta.enllacVideo = u'http://www.upc.edu' acta.ordenDelDia = RichTextValue(getLoremIpsum(2, 'long', 'html'), 'text/html', 'text/html').output acta.membresConvocats = acta.ordenDelDia acta.membresConvidats = acta.ordenDelDia acta.llistaExcusats = acta.ordenDelDia acta.llistaNoAssistens = acta.ordenDelDia acta.file = public_file acta.horaInici = session_open.start acta.horaFi = session_open.end acc.horaFi = tz.localize(datetime(2018, 11, 20, 10, 0)) audio = api.content.create(type='genweb.organs.audio', id='audio', title='Audio Exemple', container=acta) audio.description = u'audio mp3 description' mp3_file = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'tests')) + '/testaudio.mp3' audio_file = NamedBlobFile(data=open(mp3_file, 'r').read(), contentType='audio/mpeg', filename=u'acta-audio.mp3') audio.file = audio_file filepunt_1 = api.content.create(type='genweb.organs.file', id='public', title='Fitxer NOMÉS Públic', container=punt) filepunt_1.visiblefile = public_file filepunt_2 = api.content.create(type='genweb.organs.file', id='restringit', title='Fitxer NOMÉS Restringit', container=punt) filepunt_2.hiddenfile = restricted_file filepunt_3 = api.content.create(type='genweb.organs.file', id='public-restringit', title='Fitxer Públic i Restringit', container=punt) filepunt_3.visiblefile = public_file filepunt_3.hiddenfile = restricted_file api.content.copy(source=filepunt_1, target=subpunt, safe_id=True) api.content.copy(source=filepunt_2, target=subpunt, safe_id=True) api.content.copy(source=filepunt_3, target=subpunt, safe_id=True) api.content.copy(source=filepunt_1, target=acord, safe_id=True) api.content.copy(source=filepunt_2, target=acord, safe_id=True) api.content.copy(source=filepunt_3, target=acord, safe_id=True) api.content.copy(source=filepunt_1, target=subacord, safe_id=True) api.content.copy(source=filepunt_2, target=subacord, safe_id=True) api.content.copy(source=filepunt_3, target=subacord, safe_id=True) sessio_convocada = api.content.copy(source=session_open, target=open_og, id='convocada') sessio_convocada.title = 'Sessió Convocada' api.content.transition(obj=sessio_convocada, transition='convocar') transaction.commit() sessio_realitzada = api.content.copy(source=sessio_convocada, target=open_og, id='realitzada') sessio_realitzada.title = 'Sessió Realitzada' api.content.transition(obj=sessio_realitzada, transition='convocar') api.content.transition(obj=sessio_realitzada, transition='realitzar') transaction.commit() sessio_tancada = api.content.copy(source=sessio_realitzada, target=open_og, id='tancada') sessio_tancada.title = 'Sessió Tancada' api.content.transition(obj=sessio_tancada, transition='convocar') api.content.transition(obj=sessio_tancada, transition='realitzar') api.content.transition(obj=sessio_tancada, transition='tancar') transaction.commit() sessio_modificada = api.content.copy(source=sessio_realitzada, target=open_og, id='correccio') sessio_modificada.title = 'Sessió en Correcció' api.content.transition(obj=sessio_modificada, transition='convocar') api.content.transition(obj=sessio_modificada, transition='realitzar') api.content.transition(obj=sessio_modificada, transition='tancar') api.content.transition(obj=sessio_modificada, transition='corregir') transaction.commit()