예제 #1
0
 def test__dt_end_of_day(self):
     from plone.app.event.base import dt_end_of_day
     self.assertEqual(
         dt_end_of_day(datetime(2013, 2, 1, 18, 35)),
         datetime(2013, 2, 1, 23, 59, 59, 0)
     )
     self.assertEqual(
         dt_end_of_day(date(2013, 2, 1)),
         datetime(2013, 2, 1, 23, 59, 59, 0)
     )
예제 #2
0
 def end(self):
     end = IEventBasic(self.context).end
     if self.open_end:
         end = IEventBasic(self.context).start
     if self.open_end or self.whole_day:
         end = dt_end_of_day(end)
     return end
예제 #3
0
    def get_nearest_today_event(self):
        context = aq_inner(self.context)
        pc = getToolByName(context, 'portal_catalog')
        now = localized_now()

        portal_state = getMultiAdapter((self.context, self.request), name='plone_portal_state')
        navigation_root_path = portal_state.navigation_root_path()

        context = aq_inner(self.context)
        path = navigation_root_path
        for obj in aq_chain(context):
            if ICommunity.providedBy(obj):
                community = aq_inner(obj)
                path = '/'.join(community.getPhysicalPath())

        query = {
            'portal_type': 'Event',
            'review_state': self.state,
            'start': {'query': [now, dt_end_of_day(now)], 'range': 'min:max'},
            'end': {'query': now, 'range': 'min'},
            'sort_on': 'start',
            'path': path,
            'sort_limit': 1
        }

        result = pc(**query)
        if result:
            return result[0]
        else:
            return
예제 #4
0
 def end(self):
     end = IEventBasic(self.context).end
     if self.open_end:
         end = IEventBasic(self.context).start
     if self.open_end or self.whole_day:
         end = dt_end_of_day(end)
     return end
예제 #5
0
def data_postprocessing(obj, event):

    # newly created object, without start/end/timezone (e.g. invokeFactory()
    # called without data from add form), ignore event; it will be notified
    # again later:
    if getattr(obj, 'start', None) is None:
        return

    # We handle date inputs as floating dates without timezones and apply
    # timezones afterwards.
    def _fix_zone(dt, zone):

        if dt.tzinfo is not None and isinstance(dt.tzinfo, FakeZone):
            # Delete the tzinfo only, if it was set by IEventBasic setter.
            # Only in this case the start value on the object itself is what
            # was entered by the user. After running this event subscriber,
            # it's in UTC then.
            # If tzinfo it's not available at all, a naive datetime was set
            # probably by invokeFactory in tests.
            dt = tzdel(dt)

        if dt.tzinfo is None:
            # In case the tzinfo was deleted above or was not present, we can
            # localize the dt value to the target timezone.
            dt = tz.localize(dt)

        else:
            # In this case, no changes to start, end or the timezone were made.
            # Just return the object's datetime (which is in UTC) localized to
            # the target timezone.
            dt = dt.astimezone(tz)

        return dt.replace(microsecond=0)

    behavior = IEventBasic(obj)
    tz = pytz.timezone(behavior.timezone)

    # Fix zones
    start = _fix_zone(obj.start, tz)
    end = _fix_zone(obj.end, tz)

    # Adapt for whole day
    if behavior.whole_day:
        start = dt_start_of_day(start)
    if behavior.open_end:
        end = start  # Open end events end on same day
    if behavior.open_end or behavior.whole_day:
        end = dt_end_of_day(end)

    # Save back
    obj.start = utc(start)
    obj.end = utc(end)

    # Reindex
    obj.reindexObject()
예제 #6
0
def data_postprocessing(obj, event):

    # newly created object, without start/end/timezone (e.g. invokeFactory()
    # called without data from add form), ignore event; it will be notified
    # again later:
    if getattr(obj, 'start', None) is None:
        return

    # We handle date inputs as floating dates without timezones and apply
    # timezones afterwards.
    def _fix_zone(dt, zone):

        if dt.tzinfo is not None and isinstance(dt.tzinfo, FakeZone):
            # Delete the tzinfo only, if it was set by IEventBasic setter.
            # Only in this case the start value on the object itself is what
            # was entered by the user. After running this event subscriber,
            # it's in UTC then.
            # If tzinfo it's not available at all, a naive datetime was set
            # probably by invokeFactory in tests.
            dt = tzdel(dt)

        if dt.tzinfo is None:
            # In case the tzinfo was deleted above or was not present, we can
            # localize the dt value to the target timezone.
            dt = tz.localize(dt)

        else:
            # In this case, no changes to start, end or the timezone were made.
            # Just return the object's datetime (which is in UTC) localized to
            # the target timezone.
            dt = dt.astimezone(tz)

        return dt.replace(microsecond=0)

    behavior = IEventBasic(obj)
    tz = pytz.timezone(behavior.timezone)

    # Fix zones
    start = _fix_zone(obj.start, tz)
    end = _fix_zone(obj.end, tz)

    # Adapt for whole day
    if behavior.whole_day:
        start = dt_start_of_day(start)
    if behavior.open_end:
        end = start  # Open end events end on same day
    if behavior.open_end or behavior.whole_day:
        end = dt_end_of_day(end)

    # Save back
    obj.start = utc(start)
    obj.end = utc(end)

    # Reindex
    obj.reindexObject()
예제 #7
0
    def end(self):
        if getattr(self.context, 'recurrence', None):
            end = self._recurrence_upcoming_event().end
        elif self.open_end:
            end = IEventBasic(self.context).start
        else:
            end = IEventBasic(self.context).end

        if self.open_end or self.whole_day:
            end = dt_end_of_day(end)
        return end
예제 #8
0
    def end(self):
        if getattr(self.context, 'recurrence', None):
            end = self._recurrence_upcoming_event().end
        elif self.open_end:
            end = IEventBasic(self.context).start
        else:
            end = IEventBasic(self.context).end

        if self.open_end or self.whole_day:
            end = dt_end_of_day(end)
        return end
예제 #9
0
    def test__start_end_from_mode(self):
        from plone.app.event.base import start_end_from_mode
        from plone.app.event.base import dt_end_of_day

        start, end = start_end_from_mode('all')
        self.assertTrue(start is None and end is None)

        start, end = start_end_from_mode('past')
        self.assertTrue(start is None and isinstance(end, datetime.datetime))

        start, end = start_end_from_mode('future')
        self.assertTrue(isinstance(start, datetime.datetime) and end is None)

        start, end = start_end_from_mode('now')
        self.assertTrue(isinstance(start, datetime.datetime) and
                        isinstance(end, datetime.datetime) and
                        end.hour==23 and end.minute==59 and end.second==59)

        start, end = start_end_from_mode('7days')
        self.assertTrue(isinstance(start, datetime.datetime) and
                        isinstance(end, datetime.datetime) and
                        end == dt_end_of_day(start+datetime.timedelta(days=7)))

        start, end = start_end_from_mode('today')
        self.assertTrue(isinstance(start, datetime.datetime) and
                        isinstance(end, datetime.datetime) and
                        start.hour==0 and start.minute==0 and start.second==0
                        and
                        end.hour==23 and end.minute==59 and end.second==59 and
                        (start, end) == start_end_from_mode('day'))

        day = datetime.datetime(2013,2,1,18,22)
        start, end = start_end_from_mode('day', day)
        self.assertTrue(start.date() == day.date() == end.date() and
                        start.hour==0 and start.minute==0 and start.second==0
                        and
                        end.hour==23 and end.minute==59 and end.second==59)

        # test with date-only
        day = datetime.datetime(2013,2,1)
        start, end = start_end_from_mode('day', day)
        self.assertTrue(start.date() == day.date() == end.date() and
                        start.hour==0 and start.minute==0 and start.second==0
                        and
                        end.hour==23 and end.minute==59 and end.second==59)
예제 #10
0
def data_postprocessing(start, end, whole_day, open_end):
    """Adjust start and end according to whole_day and open_end setting.
    """

    def _fix_dt(dt, tz):
        """Fix datetime: Apply missing timezones, remove microseconds.
        """
        if dt.tzinfo is None:
            dt = tz.localize(dt)

        return dt.replace(microsecond=0)

    tz_default = default_timezone()

    tz_start = getattr(start, 'tzinfo', tz_default)
    tz_end = getattr(end, 'tzinfo', tz_default)
    start = _fix_dt(start, tz_start)
    end = _fix_dt(end, tz_end)

    # Adapt for whole day
    if whole_day:
        start = dt_start_of_day(start)
    if open_end:
        end = start  # Open end events end on same day
    if open_end or whole_day:
        end = dt_end_of_day(end)

    # TODO:
    """
    if not obj.sync_uid:
        # sync_uid has to be set for icalendar data exchange.
        uid = IUUID(obj)
        # We don't want to fail when getRequest() returns None, e.g when
        # creating an event during test layer setup time.
        request = getRequest() or {}
        domain = request.get('HTTP_HOST')
        obj.sync_uid = '%s%s' % (
            uid,
            domain and '@%s' % domain or ''
        )
    """

    return start, end, whole_day, open_end
예제 #11
0
def data_postprocessing(start, end, whole_day, open_end):
    """Adjust start and end according to whole_day and open_end setting.
    """
    def _fix_dt(dt, tz):
        """Fix datetime: Apply missing timezones, remove microseconds.
        """
        if dt.tzinfo is None:
            dt = tz.localize(dt)

        return dt.replace(microsecond=0)

    tz_default = default_timezone()

    tz_start = getattr(start, 'tzinfo', tz_default)
    tz_end = getattr(end, 'tzinfo', tz_default)
    start = _fix_dt(start, tz_start)
    end = _fix_dt(end, tz_end)

    # Adapt for whole day
    if whole_day:
        start = dt_start_of_day(start)
    if open_end:
        end = start  # Open end events end on same day
    if open_end or whole_day:
        end = dt_end_of_day(end)

    # TODO:
    """
    if not obj.sync_uid:
        # sync_uid has to be set for icalendar data exchange.
        uid = IUUID(obj)
        # We don't want to fail when getRequest() returns None, e.g when
        # creating an event during test layer setup time.
        request = getRequest() or {}
        domain = request.get('HTTP_HOST')
        obj.sync_uid = '%s%s' % (
            uid,
            domain and '@%s' % domain or ''
        )
    """

    return start, end, whole_day, open_end
예제 #12
0
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}
예제 #13
0
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}
예제 #14
0
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}
예제 #15
0
    def test__start_end_from_mode(self):
        from plone.app.event.base import start_end_from_mode
        from plone.app.event.base import dt_end_of_day

        # ALL
        #
        start, end = start_end_from_mode('all')
        self.assertTrue(start is None and end is None)

        # PAST
        #
        start, end = start_end_from_mode('past')
        self.assertTrue(start is None and isinstance(end, datetime))

        # FUTURE
        #
        start, end = start_end_from_mode('future')
        self.assertTrue(isinstance(start, datetime) and end is None)

        # NOW
        #
        start, end = start_end_from_mode('now')
        self.assertTrue(
            isinstance(start, datetime) and
            isinstance(end, datetime) and
            end.hour == 23 and end.minute == 59 and end.second == 59
        )

        # 7DAYS
        #
        start, end = start_end_from_mode('7days')
        self.assertTrue(
            isinstance(start, datetime) and
            isinstance(end, datetime) and
            end == dt_end_of_day(start + timedelta(days=6))
        )

        # TODAY
        #
        start, end = start_end_from_mode('today')
        self.assertTrue(
            isinstance(start, datetime) and
            isinstance(end, datetime) and
            start.hour == 0 and start.minute == 0 and start.second == 0 and
            end.hour == 23 and end.minute == 59 and end.second == 59 and
            (start, end) == start_end_from_mode('day')
        )

        # DAY
        #
        day = datetime(2013, 2, 1, 18, 22)
        start, end = start_end_from_mode('day', day)
        self.assertTrue(
            start.date() == day.date() == end.date() and
            start.hour == 0 and start.minute == 0 and start.second == 0 and
            end.hour == 23 and end.minute == 59 and end.second == 59
        )

        # test with date-only
        day = datetime(2013, 2, 1)
        start, end = start_end_from_mode('day', day)
        self.assertTrue(
            start.date() == day.date() == end.date() and
            start.hour == 0 and start.minute == 0 and start.second == 0 and
            end.hour == 23 and end.minute == 59 and end.second == 59
        )

        # WEEK
        #
        def ret_0():
            return 0  # Monday

        def ret_1():
            return 1  # Tuesday

        def ret_6():
            return 6  # Sunday

        # prepare patched first_weekday
        orig_first_weekday = base.first_weekday

        base.first_weekday = ret_0
        day = datetime(2013, 2, 2)
        start, end = start_end_from_mode('week', day)
        self.assertTrue(
            start.isoformat() == '2013-01-28T00:00:00' and
            end.isoformat() == '2013-02-03T23:59:59'
        )

        base.first_weekday = ret_1
        day = datetime(2013, 2, 2)
        start, end = start_end_from_mode('week', day)
        self.assertTrue(
            start.isoformat() == '2013-01-29T00:00:00' and
            end.isoformat() == '2013-02-04T23:59:59'
        )

        base.first_weekday = ret_6
        day = datetime(2013, 2, 1)
        start, end = start_end_from_mode('week', day)
        self.assertTrue(
            start.isoformat() == '2013-01-27T00:00:00' and
            end.isoformat() == '2013-02-02T23:59:59'
        )

        base.first_weekday = orig_first_weekday  # restore orig first_weekday

        # MONTH
        #
        start, end = start_end_from_mode('month')
        self.assertTrue(start < end and start.day == 1)

        day = datetime(2013, 2, 7)
        start, end = start_end_from_mode('month', day)
        self.assertTrue(
            start.year == 2013 and start.month == 2 and start.day == 1 and
            start.hour == 0 and start.minute == 0 and start.second == 0 and
            end.year == 2013 and end.month == 2 and end.day == 28 and
            end.hour == 23 and end.minute == 59 and end.second == 59
        )
예제 #16
0
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}
예제 #17
0
    def createOffers(self, hasHeaders, fitxer, marketUID):
        registry = queryUtility(IRegistry)
        tfe_tool = registry.forInterface(ITfemarketSettings)

        catalog = api.portal.get_tool(name='portal_catalog')
        market = catalog(UID=marketUID)[0].getObject()

        msgError = []
        csv_file = csv.reader(fitxer, delimiter=',', quotechar='"')

        if hasHeaders:
            csv_file.next()  # Ignore header for csv

        for count, row in enumerate(csv_file):
            # Importa ofertas
            notValidDegrees = self.checkNotValidDegrees(
                row[5].decode("utf-8").split(","))
            if len(notValidDegrees) == 0:
                teacher = getExactUserData(row[7].decode("utf-8"))
                if teacher:
                    data = {
                        'title':
                        row[0].decode("utf-8"),
                        'description':
                        row[1].decode("utf-8"),
                        'topic':
                        row[2].decode("utf-8"),
                        'offer_type':
                        row[3].decode("utf-8"),
                        'tfgm':
                        row[4].decode("utf-8").split(","),
                        'degree':
                        row[5].decode("utf-8").split(","),
                        'keys':
                        row[6].decode("utf-8").split(","),
                        'teacher_manager':
                        teacher['id'],
                        'teacher_fullname':
                        teacher['sn1'] + ' ' + teacher['sn2'] + ', ' +
                        teacher['givenName'] if 'sn2' in teacher else
                        teacher['sn1'] + ', ' + teacher['givenName'],
                        'teacher_email':
                        teacher['mail'],
                        'dept':
                        teacher['unitCode'] + "-" + teacher['unit'],
                        'num_students':
                        int(row[10].decode("utf-8")),
                        'workload':
                        row[11].decode("utf-8"),
                        'targets':
                        row[12].decode("utf-8"),
                        'features':
                        row[13].decode("utf-8"),
                        'requirements':
                        row[14].decode("utf-8"),
                        'lang':
                        row[15].decode("utf-8").split(","),
                        'modality':
                        row[16].decode("utf-8"),
                        'company':
                        row[17].decode("utf-8"),
                        'grant':
                        bool(row[18].decode("utf-8") == "True"),
                        'confidential':
                        bool(row[19].decode("utf-8") == "True"),
                        'environmental_theme':
                        bool(row[20].decode("utf-8") == "True"),
                        'scope_cooperation':
                        bool(row[21].decode("utf-8") == "True"),
                    }

                    type_codirector = row[8].decode("utf-8")
                    data.update({'type_codirector': type_codirector})
                    if type_codirector == 'UPC':
                        codirector = getExactUserData(row[9].decode("utf-8"))
                        if codirector:
                            data.update({
                                'codirector_id':
                                codirector['id'],
                                'codirector':
                                codirector['sn1'] + ' ' + codirector['sn2'] +
                                ', ' + codirector['givenName']
                                if 'sn2' in codirector else codirector['sn1'] +
                                ', ' + codirector['givenName'],
                                'codirector_email':
                                codirector['mail'],
                                'codirector_dept':
                                codirector['unitCode'] + "-" +
                                codirector['unit']
                            })
                        else:
                            msg = row[0].decode(
                                "utf-8") + " - Codirector (" + row[9].decode(
                                    "utf-8") + ") not exist."
                            print str(count + 1) + ": Error - " + msg
                            msgError.append(str(count + 1) + ": " + msg)
                            continue
                    else:
                        data.update({'codirector': row[9].decode("utf-8")})

                    offer = createContentInContainer(market,
                                                     "genweb.tfemarket.offer",
                                                     **data)
                    offer.setEffectiveDate(
                        dt_start_of_day(datetime.datetime.today() +
                                        datetime.timedelta(1)))
                    offer.setExpirationDate(
                        dt_end_of_day(datetime.datetime.today() +
                                      datetime.timedelta(365)))
                    offer.reindexObject()

                    # Importa topics y tags
                    strTopics = row[2].decode("utf-8") + ","
                    topics = list(dict.fromkeys(strTopics.split(",")[:-1]))
                    actualTopics = tfe_tool.topics.split('\r\n')
                    newTopics = "\r\n".join([
                        topic for topic in topics if topic not in actualTopics
                    ])
                    if newTopics:
                        tfe_tool.topics += "\r\n" + newTopics

                    strTags = row[6].decode("utf-8") + ","
                    tags = list(dict.fromkeys(strTags.split(",")[:-1]))
                    actualTags = tfe_tool.tags.split('\r\n')
                    newTags = "\r\n".join(
                        [tag for tag in tags if tag not in actualTags])
                    if newTags:
                        tfe_tool.tags += "\r\n" + newTags

                    transaction.commit()

                    print str(count + 1) + ": Done - " + row[0].decode("utf-8")
                else:
                    msg = row[0].decode("utf-8") + " - Teacher (" + row[
                        7].decode("utf-8") + ") not exist."
                    print str(count + 1) + ": Error - " + msg
                    msgError.append(str(count + 1) + ": " + msg)
            else:
                msg = row[0].decode("utf-8") + " - Degree (" + " - ".join(
                    notValidDegrees) + ") not valid."
                print str(count + 1) + ": Error - " + msg
                msgError.append(str(count + 1) + ": " + msg)

        return msgError
예제 #18
0
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}
예제 #19
0
def expiresDefaultValue():
    return dt_end_of_day(datetime.datetime.today() + datetime.timedelta(365))
예제 #20
0
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}
예제 #21
0
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}