def test_event_accessor__start_end(self): e1 = createContentInContainer( self.portal, 'plone.app.event.dx.event', title='event1' ) dt = datetime(2161, 1, 1) # United Federation of Planets DT = DateTime('2161/01/01 00:00:00 UTC') acc = IEventAccessor(e1) # Setting a timezone-naive datetime should convert it to UTC acc.start = dt self.assertEqual(acc.start, utils.utc(dt)) self.assertEqual(e1.start, utils.utc(dt)) # Setting a DateTime should convert it to datetime acc.start = DT self.assertEqual(acc.start, utils.utc(dt)) self.assertEqual(e1.start, utils.utc(dt)) # Same goes for acc.end # Setting a timezone-naive datetime should convert it to UTC acc.end = dt self.assertEqual(acc.end, utils.utc(dt)) self.assertEqual(e1.end, utils.utc(dt)) # Setting a DateTime should convert it to datetime acc.end = DT self.assertEqual(acc.end, utils.utc(dt)) self.assertEqual(e1.end, utils.utc(dt))
def test_event_accessor__start_end(self): e1 = createContentInContainer( self.portal, 'plone.app.event.dx.event', title=u'event1' ) dt = datetime(2161, 1, 1) # United Federation of Planets DT = DateTime('2161/01/01 00:00:00 UTC') acc = IEventAccessor(e1) # Setting a timezone-naive datetime should convert it to UTC acc.start = dt self.assertEqual(acc.start, utils.utc(dt)) self.assertEqual(e1.start, utils.utc(dt)) # Setting a DateTime should convert it to datetime acc.start = DT self.assertEqual(acc.start, utils.utc(dt)) self.assertEqual(e1.start, utils.utc(dt)) # Same goes for acc.end # Setting a timezone-naive datetime should convert it to UTC acc.end = dt self.assertEqual(acc.end, utils.utc(dt)) self.assertEqual(e1.end, utils.utc(dt)) # Setting a DateTime should convert it to datetime acc.end = DT self.assertEqual(acc.end, utils.utc(dt)) self.assertEqual(e1.end, utils.utc(dt))
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 test_event_accessor(self): obj = MockObject() tz = pytz.timezone('Europe/Vienna') obj.start = datetime(2012, 12, 12, 10, 0, tzinfo=tz) obj.end = datetime(2012, 12, 12, 12, 0, tzinfo=tz) zope.interface.alsoProvides(obj, IEvent) # Create accessor acc = IEventAccessor(obj) # Accessor getters self.assertEqual(acc.start, obj.start) self.assertEqual(acc.end, obj.end) self.assertEqual(acc.duration, obj.end - obj.start) # Accessor setters start = datetime(2013, 4, 5, 16, 31, tzinfo=tz) end = datetime(2013, 4, 5, 16, 35, tzinfo=tz) acc.start = start acc.end = end self.assertTrue(acc.start == obj.start == start) self.assertTrue(acc.end == obj.end == end) # Accessor deletor acc.something = True self.assertTrue(acc.something == obj.something is True) del acc.something self.assertTrue(hasattr(acc, 'something') is False) self.assertTrue(hasattr(obj, 'something') is False) del acc.start self.assertTrue(hasattr(acc, 'start') is False) self.assertTrue(hasattr(obj, 'start') is False)
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('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 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 onEventsFolderCreate(context, event, sample=True): # restrict what this folder can contain behavior = ISelectableConstrainTypes(context) behavior.setConstrainTypesMode(constrains.ENABLED) behavior.setImmediatelyAddableTypes(['Event']) behavior.setLocallyAllowedTypes(['Event', 'Collection']) # Create sample event and set publishing date to 01-01-YYYY if sample: # Calculate dates now = DateTime() start_date = DateTime() + 30 end_date = start_date + 1.0 / 24 item = createContentInContainer(context, "Event", id="sample", title="Sample Event", description="This is a sample Event", checkConstraints=False) item.text = RichTextValue(raw='<p>You may delete this item</p>', mimeType=u'text/html', outputMimeType='text/x-html-safe') item.setEffectiveDate(now) acc = IEventAccessor(item) acc.start = localize(start_date) acc.end = localize(end_date) # create 'upcoming' collection if 'upcoming' not in context.objectIds(): item = createContentInContainer( context, "Collection", id="upcoming", title='Upcoming Events', ) item.setQuery([{ u'i': u'path', u'o': u'plone.app.querystring.operation.string.absolutePath', u'v': u'%s::1' % context.UID() }, { u'i': u'portal_type', u'o': u'plone.app.querystring.operation.selection.any', u'v': [u'Event'] }]) item.setSort_on('start') # Set default page to the latest news collection context.setDefaultPage('upcoming')
def setup_event(site): """Set the default start and end event properties.""" folder = site['institucional']['eventos'] event = folder['1o-ano-do-site'] acc = IEventAccessor(event) future = datetime.now() + relativedelta(years=1) year = future.year month = future.month day = future.day acc.start = datetime(year, month, day, 0, 0, 0) acc.end = datetime(year, month, day, 23, 59, 59) notify(ObjectModifiedEvent(event)) event.reindexObject() logger.debug(u'Evento padrao configurado')
def validate_dates(self, performance, performance_data): startDateTime = performance_data.get('startDateTime', '') endDateTime = performance_data.get('endDateTime', '') if startDateTime and not endDateTime: performance_date_fields = IEventAccessor(performance) performance_date_fields.end = performance_date_fields.start return True if not startDateTime and not endDateTime: logger("[Error] There are no dates for the performance. ", "Performance dates cannot be found.") return False return True
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 test_event_accessor(self): tz = pytz.timezone("Europe/Vienna") e1 = createContentInContainer( self.portal, 'plone.app.event.dx.event', title='event1', start=tz.localize(datetime(2011, 11, 11, 11, 0)), end=tz.localize(datetime(2011, 11, 11, 12, 0)), ) # setting attributes via the accessor acc = IEventAccessor(e1) new_end = tz.localize(datetime(2011, 11, 13, 10, 0)) acc.end = new_end # context's end should be set to new_end self.assertEqual(e1.end, new_end) # accessor's and context datetime should be the same self.assertEqual(acc.end, e1.end)
def test_event_accessor(self): tz = pytz.timezone("Europe/Vienna") e1 = createContentInContainer( self.portal, 'plone.app.event.dx.event', title=u'event1', start=tz.localize(datetime(2011, 11, 11, 11, 0)), end=tz.localize(datetime(2011, 11, 11, 12, 0)), ) # setting attributes via the accessor acc = IEventAccessor(e1) new_end = tz.localize(datetime(2011, 11, 13, 10, 0)) acc.end = new_end # context's end should be set to new_end self.assertEqual(e1.end, new_end) # accessor's and context datetime should be the same self.assertEqual(acc.end, e1.end)
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): 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 create_cvent_event(self, **kwargs): item = createContentInContainer(self.context, "Event", id=kwargs['id'], title=kwargs['title'], event_url=kwargs['url'], location=kwargs['location'], exclude_from_nav=True, checkConstraints=False) start_date = kwargs['start'] end_date = kwargs['end'] acc = IEventAccessor(item) acc.start = start_date acc.end = end_date item.manage_addProperty('cventid', kwargs['id'], 'string') item.setLayout("event_redirect_view") item.reindexObject() return item
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 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 _transform_end_date(self, performance, fieldname, fieldvalue): performance_date_fields = IEventAccessor(performance) date_datetime = datetime.strptime(fieldvalue, '%Y-%m-%d %H:%M') performance_date_fields.end = date_datetime return fieldvalue
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()
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): 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 test_event_accessor(self): utc = pytz.utc vienna = pytz.timezone('Europe/Vienna') self.portal.invokeFactory('Event', 'event1', description='a description', startDate=datetime(2011, 11, 11, 11, 0, tzinfo=utc), endDate=datetime(2011, 11, 11, 12, 0, tzinfo=utc), timezone='UTC', wholeDay=False) e1 = self.portal['event1'] acc = IEventAccessor(e1) # TEST DATES self.assertEqual(acc.start, datetime(2011, 11, 11, 11, 0, tzinfo=utc)) self.assertEqual(acc.end, datetime(2011, 11, 11, 12, 0, tzinfo=utc)) acc.start = datetime(2011, 11, 13, 9, 0) # tzinfo does not matter, acc.end = datetime(2011, 11, 13, 10, 0) # it's set by subscription # adapter # If using EventAccessor's edit method, calling notify isn't needed acc.edit(timezone=u'Europe/Vienna') # accessor should return start/end datetimes in the event's timezone self.assertEqual( acc.start, datetime(2011, 11, 13, 9, 0, tzinfo=vienna)) self.assertEqual( acc.end, datetime(2011, 11, 13, 10, 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.assertEqual( e1.end(), DateTime('2011/11/13 10:00:00 Europe/Vienna') ) # timezone should be the same on the event object and accessor self.assertEqual(e1.getTimezone(), acc.timezone) # Open End Test acc.edit(open_end=True) self.assertEqual( acc.start, datetime(2011, 11, 13, 9, 0, tzinfo=vienna)) self.assertEqual( acc.end, datetime(2011, 11, 13, 23, 59, 59, tzinfo=vienna)) # Whole Day Test acc.edit(whole_day=True, open_end=False) self.assertEqual( acc.start, datetime(2011, 11, 13, 0, 0, tzinfo=vienna)) self.assertEqual( acc.end, datetime(2011, 11, 13, 23, 59, 59, tzinfo=vienna)) # TEST DESCRIPTION self.assertTrue(acc.description == 'a description') acc.description = 'another desc' self.assertTrue(acc.description == 'another desc') # TEST OTHER PROPERTIES acc.title = u"An Event" acc.recurrence = u'RRULE:FREQ=DAILY;COUNT=5' acc.location = u"Home" acc.attendees = [u'me', u'you'] acc.contact_name = u"Max Mustermann" acc.contact_email = u"*****@*****.**" acc.contact_phone = u"+1234567890" acc.event_url = u"http://plone.org/" acc.subjects = [u"tag1", u"tag2"] acc.text = u"body text with <b>html</b> formating." # If not using EventAccessor's edit method, call notify manually notify(ObjectModifiedEvent(acc.context)) self.assertEqual(acc.recurrence, u'RRULE:FREQ=DAILY;COUNT=5') self.assertEqual(acc.location, u'Home') self.assertEqual(acc.attendees, (u'me', u'you')) self.assertEqual(acc.contact_name, u"Max Mustermann") self.assertEqual(acc.contact_email, u'*****@*****.**') self.assertEqual(acc.contact_phone, u"+1234567890") self.assertEqual(acc.event_url, u"http://plone.org/") self.assertEqual(acc.subjects, (u"tag1", u"tag2")) self.assertEqual(acc.text, u"body text with <b>html</b> formating.") # CLEANUP self.portal.manage_delObjects(['event1'])
def __call__(self, force=False): if not self.data.data: raise Exception("No data provided.") parent = self.parent if not parent: raise Exception("Cannot find parent object for %s" % self.path) _id = self.getId() if not self.exists: # People have no title or description. if self.portal_type in ('agsci_person', ): item = createContentInContainer(parent, self.portal_type, id=_id, checkConstraints=False) else: item = createContentInContainer( parent, self.portal_type, id=_id, title=self.data.title, description=self.data.description, checkConstraints=False) # Set UID setattr(item, ATTRIBUTE_NAME, self.UID) else: # If the item exists, and it's published, no further changes if self.review_state in [ 'published', ] and not force: return item = self.context # Set subject (tags) if self.data.subject: item.setSubject(list(self.data.subject)) # Set HTML html = self.html if html: item.text = RichTextValue(raw=html, mimeType=u'text/html', outputMimeType='text/x-html-safe') # Set File field file = self.file if file: item.file = self.file # Set Lead Image or Image field image = self.image if image: item.image = image # Unset full width field if image is too small, or is portrait. try: (w, h) = image.getImageSize() except: pass else: if w < h or w < 600: item.image_full_width = False # Set field values # Map current field name 'field' to old 'data_field' from feed. fields = self.fields field_names = self.field_names for field_name in field_names: field = fields.get(field_name) if self.map_fields: data_field = self.fields_mapping.get(field_name, field_name) else: data_field = field_name # Skip fields if we're only importing specific fields if self.include_fields and field_name not in self.include_fields: continue if field_name not in self.exclude_fields: value = getattr(self.data, data_field, None) if value or isinstance(value, (bool, )): value = self.transform_value( field=field, field_name=data_field, value=value, ) setattr(item, field_name, value) if self.debug: LOG( self.__class__.__name__, INFO, "%s: Setting %s to %r" % (item.absolute_url(), field_name, value)) # Set collection criteria if self.portal_type in ('Collection', 'Newsletter'): if self.data.collection_criteria: item.setQuery(self.data.collection_criteria) if self.data.collection_sort_field: item.setSort_on(self.data.collection_sort_field) if self.data.collection_sort_reversed: item.setSort_reversed(True) # Set default page if self.data.default_page: default_page_id = safe_unicode( self.data.default_page).encode('utf-8') self.context.setDefaultPage(default_page_id) else: # Set layout if no default page layout = self.data.layout if layout in self.valid_layouts: item.setLayout(layout) # Set dates effective = self.data.effective_date expires = self.data.expiration_date if effective: item.setEffectiveDate(DateTime(effective)) if expires: item.setExpirationDate(DateTime(expires)) # If event, set start and end if self.portal_type in ('Event', ): start_date = localize(DateTime(self.data.start_date)) end_date = localize(DateTime(self.data.end_date)) acc = IEventAccessor(item) acc.start = start_date acc.end = end_date # Set references modifiedContent(item, None) # Reindex item.reindexObject()
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 = 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}