Пример #1
0
    def test_data_postprocessing(self):
        # TODO: since we use an IEventAccessor here, this is a possible
        # canditate for merging with
        # the test_dxevent.TestDXIntegration.test_data_postprocessing test.

        # Addressing bug #62
        self.portal.invokeFactory('Event', 'ate1',
                startDate=DateTime(2012,10,19,0,30),
                endDate=DateTime(2012,10,19,1,30),
                timezone="Europe/Vienna",
                wholeDay=False)
        e1 = self.portal['ate1']
        e1.reindexObject()

        acc = IEventAccessor(e1)

        # Prepare reference objects
        tzname_1 = "Europe/Vienna"
        tz_1 = pytz.timezone(tzname_1)
        dt_1 = tz_1.localize(datetime(2012,10,19,0,30))
        dt_1_1 = tz_1.localize(datetime(2012,10,19,0,0))
        dt_1_2 = tz_1.localize(datetime(2012,10,19,23,59,59))

        tzname_2 = "Hongkong"
        tz_2 = pytz.timezone(tzname_2)
        dt_2 = tz_2.localize(datetime(2012,10,19,0,30))
        dt_2_1 = tz_2.localize(datetime(2012,10,19,0,0))
        dt_2_2 = tz_2.localize(datetime(2012,10,19,23,59,59))

        # See, if start isn't moved by timezone offset.
        self.assertTrue(acc.start == dt_1)
        notify(ObjectModifiedEvent(e1))
        self.assertTrue(acc.start == dt_1)

        # After timezone changes, only the timezone should be applied, but the
        # date and time values not converted.
        acc.timezone = tzname_2
        notify(ObjectModifiedEvent(e1))
        self.assertTrue(acc.start == dt_2)

        # Likewise with wholeDay events. If values were converted, the days
        # would drift apart.
        acc.whole_day = True
        acc.timezone = tzname_1
        notify(ObjectModifiedEvent(e1))
        self.assertTrue(acc.start == dt_1_1)
        self.assertTrue(acc.end == dt_1_2)

        acc.timezone = tzname_2
        notify(ObjectModifiedEvent(e1))
        self.assertTrue(acc.start == dt_2_1)
        self.assertTrue(acc.end == dt_2_2)

        self.portal.manage_delObjects(['ate1'])
Пример #2
0
    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'])
Пример #3
0
    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)
Пример #4
0
    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'])
Пример #5
0
    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))
Пример #6
0
    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))
Пример #7
0
    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))
Пример #8
0
    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))
Пример #9
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}
Пример #10
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}
Пример #11
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}
Пример #12
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}
Пример #13
0
def _last_migrate_time_zone(self):
    acc = IEventAccessor(self.new)
    acc.timezone = 'Europe/Berlin'
    notify(ObjectModifiedEvent(self.new))
Пример #14
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}
Пример #15
0
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()