def test_event_accessor__start_end(self):
        e1 = createContentInContainer(
            self.portal,
            'plone.app.event.dx.event',
            title='event1'
        )

        dt = datetime(2161, 1, 1)  # United Federation of Planets
        DT = DateTime('2161/01/01 00:00:00 UTC')

        acc = IEventAccessor(e1)

        # Setting a timezone-naive datetime should convert it to UTC
        acc.start = dt
        self.assertEqual(acc.start, utils.utc(dt))
        self.assertEqual(e1.start, utils.utc(dt))
        # Setting a DateTime should convert it to datetime
        acc.start = DT
        self.assertEqual(acc.start, utils.utc(dt))
        self.assertEqual(e1.start, utils.utc(dt))

        # Same goes for acc.end
        # Setting a timezone-naive datetime should convert it to UTC
        acc.end = dt
        self.assertEqual(acc.end, utils.utc(dt))
        self.assertEqual(e1.end, utils.utc(dt))
        # Setting a DateTime should convert it to datetime
        acc.end = DT
        self.assertEqual(acc.end, utils.utc(dt))
        self.assertEqual(e1.end, utils.utc(dt))
    def test_event_accessor__start_end(self):
        e1 = createContentInContainer(
            self.portal,
            'plone.app.event.dx.event',
            title=u'event1'
        )

        dt = datetime(2161, 1, 1)  # United Federation of Planets
        DT = DateTime('2161/01/01 00:00:00 UTC')

        acc = IEventAccessor(e1)

        # Setting a timezone-naive datetime should convert it to UTC
        acc.start = dt
        self.assertEqual(acc.start, utils.utc(dt))
        self.assertEqual(e1.start, utils.utc(dt))
        # Setting a DateTime should convert it to datetime
        acc.start = DT
        self.assertEqual(acc.start, utils.utc(dt))
        self.assertEqual(e1.start, utils.utc(dt))

        # Same goes for acc.end
        # Setting a timezone-naive datetime should convert it to UTC
        acc.end = dt
        self.assertEqual(acc.end, utils.utc(dt))
        self.assertEqual(e1.end, utils.utc(dt))
        # Setting a DateTime should convert it to datetime
        acc.end = DT
        self.assertEqual(acc.end, utils.utc(dt))
        self.assertEqual(e1.end, utils.utc(dt))
    def migrate_schema_fields(self):
        newacc = IEventAccessor(self.new)
        newacc.start = self.old.start_date
        newacc.end = self.old.end_date
        newacc.timezone = str(self.old.start_date.tzinfo) \
            if self.old.start_date.tzinfo \
            else default_timezone(fallback='UTC')

        if hasattr(self.old, 'location'):
            newacc.location = self.old.location
        if hasattr(self.old, 'attendees'):
            newacc.attendees = tuple(self.old.attendees.splitlines())
        if hasattr(self.old, 'event_url'):
            newacc.event_url = self.old.event_url
        if hasattr(self.old, 'contact_name'):
            newacc.contact_name = self.old.contact_name
        if hasattr(self.old, 'contact_email'):
            newacc.contact_email = self.old.contact_email
        if hasattr(self.old, 'contact_phone'):
            newacc.contact_phone = self.old.contact_phone
        if hasattr(self.old, 'text'):
            # Copy the entire richtext object, not just it's representation
            IEventSummary(self.new).text = self.old.text

        # Trigger ObjectModified, so timezones can be fixed up.
        notify(ObjectModifiedEvent(self.new))
示例#4
0
    def test_event_accessor(self):
        obj = MockObject()
        tz = pytz.timezone('Europe/Vienna')
        obj.start = datetime(2012, 12, 12, 10, 0, tzinfo=tz)
        obj.end = datetime(2012, 12, 12, 12, 0, tzinfo=tz)
        zope.interface.alsoProvides(obj, IEvent)

        # Create accessor
        acc = IEventAccessor(obj)

        # Accessor getters
        self.assertEqual(acc.start, obj.start)
        self.assertEqual(acc.end, obj.end)
        self.assertEqual(acc.duration, obj.end - obj.start)

        # Accessor setters
        start = datetime(2013, 4, 5, 16, 31, tzinfo=tz)
        end = datetime(2013, 4, 5, 16, 35, tzinfo=tz)
        acc.start = start
        acc.end = end
        self.assertTrue(acc.start == obj.start == start)
        self.assertTrue(acc.end == obj.end == end)

        # Accessor deletor
        acc.something = True
        self.assertTrue(acc.something == obj.something is True)
        del acc.something
        self.assertTrue(hasattr(acc, 'something') is False)
        self.assertTrue(hasattr(obj, 'something') is False)

        del acc.start
        self.assertTrue(hasattr(acc, 'start') is False)
        self.assertTrue(hasattr(obj, 'start') is False)
示例#5
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'])
示例#6
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'])
示例#7
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)
示例#8
0
def onEventsFolderCreate(context, event, sample=True):

    # restrict what this folder can contain
    behavior = ISelectableConstrainTypes(context)
    behavior.setConstrainTypesMode(constrains.ENABLED)
    behavior.setImmediatelyAddableTypes(['Event'])
    behavior.setLocallyAllowedTypes(['Event', 'Collection'])

    # Create sample event and set publishing date to 01-01-YYYY
    if sample:

        # Calculate dates
        now = DateTime()
        start_date = DateTime() + 30
        end_date = start_date + 1.0 / 24

        item = createContentInContainer(context,
                                        "Event",
                                        id="sample",
                                        title="Sample Event",
                                        description="This is a sample Event",
                                        checkConstraints=False)

        item.text = RichTextValue(raw='<p>You may delete this item</p>',
                                  mimeType=u'text/html',
                                  outputMimeType='text/x-html-safe')

        item.setEffectiveDate(now)

        acc = IEventAccessor(item)
        acc.start = localize(start_date)
        acc.end = localize(end_date)

    # create 'upcoming' collection
    if 'upcoming' not in context.objectIds():
        item = createContentInContainer(
            context,
            "Collection",
            id="upcoming",
            title='Upcoming Events',
        )

        item.setQuery([{
            u'i': u'path',
            u'o': u'plone.app.querystring.operation.string.absolutePath',
            u'v': u'%s::1' % context.UID()
        }, {
            u'i': u'portal_type',
            u'o': u'plone.app.querystring.operation.selection.any',
            u'v': [u'Event']
        }])

        item.setSort_on('start')

    # Set default page to the latest news collection
    context.setDefaultPage('upcoming')
def setup_event(site):
    """Set the default start and end event properties."""
    folder = site['institucional']['eventos']
    event = folder['1o-ano-do-site']
    acc = IEventAccessor(event)
    future = datetime.now() + relativedelta(years=1)
    year = future.year
    month = future.month
    day = future.day
    acc.start = datetime(year, month, day, 0, 0, 0)
    acc.end = datetime(year, month, day, 23, 59, 59)
    notify(ObjectModifiedEvent(event))
    event.reindexObject()
    logger.debug(u'Evento padrao configurado')
示例#10
0
    def validate_dates(self, performance, performance_data):
        startDateTime = performance_data.get('startDateTime', '')
        endDateTime = performance_data.get('endDateTime', '')

        if startDateTime and not endDateTime:
            performance_date_fields = IEventAccessor(performance)
            performance_date_fields.end = performance_date_fields.start
            return True

        if not startDateTime and not endDateTime:
            logger("[Error] There are no dates for the performance. ",
                   "Performance dates cannot be found.")
            return False

        return True
    def migrate_schema_fields(self):
        oldacc = IEventAccessor(self.old)
        newacc = IEventAccessor(self.new)
        newacc.start = oldacc.start
        newacc.end = oldacc.end
        newacc.timezone = oldacc.timezone
        newacc.location = oldacc.location
        newacc.attendees = oldacc.attendees
        newacc.event_url = oldacc.event_url
        newacc.contact_name = oldacc.contact_name
        newacc.contact_email = oldacc.contact_email
        newacc.contact_phone = oldacc.contact_phone
        # Copy the entire richtext object, not just it's representation
        IEventSummary(self.new).text = IEventSummary(self.old).text

        # Trigger ObjectModified, so timezones can be fixed up.
        notify(ObjectModifiedEvent(self.new))
示例#12
0
    def test_event_accessor(self):
        tz = pytz.timezone("Europe/Vienna")
        e1 = createContentInContainer(
            self.portal,
            'plone.app.event.dx.event',
            title='event1',
            start=tz.localize(datetime(2011, 11, 11, 11, 0)),
            end=tz.localize(datetime(2011, 11, 11, 12, 0)),
        )

        # setting attributes via the accessor
        acc = IEventAccessor(e1)
        new_end = tz.localize(datetime(2011, 11, 13, 10, 0))
        acc.end = new_end

        # context's end should be set to new_end
        self.assertEqual(e1.end, new_end)

        # accessor's and context datetime should be the same
        self.assertEqual(acc.end, e1.end)
示例#13
0
    def test_event_accessor(self):
        tz = pytz.timezone("Europe/Vienna")
        e1 = createContentInContainer(
            self.portal,
            'plone.app.event.dx.event',
            title=u'event1',
            start=tz.localize(datetime(2011, 11, 11, 11, 0)),
            end=tz.localize(datetime(2011, 11, 11, 12, 0)),
        )

        # setting attributes via the accessor
        acc = IEventAccessor(e1)
        new_end = tz.localize(datetime(2011, 11, 13, 10, 0))
        acc.end = new_end

        # context's end should be set to new_end
        self.assertEqual(e1.end, new_end)

        # accessor's and context datetime should be the same
        self.assertEqual(acc.end, e1.end)
示例#14
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))
示例#15
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))
示例#16
0
    def create_cvent_event(self, **kwargs):

        item = createContentInContainer(self.context,
                                        "Event",
                                        id=kwargs['id'],
                                        title=kwargs['title'],
                                        event_url=kwargs['url'],
                                        location=kwargs['location'],
                                        exclude_from_nav=True,
                                        checkConstraints=False)

        start_date = kwargs['start']
        end_date = kwargs['end']

        acc = IEventAccessor(item)
        acc.start = start_date
        acc.end = end_date

        item.manage_addProperty('cventid', kwargs['id'], 'string')
        item.setLayout("event_redirect_view")
        item.reindexObject()

        return item
示例#17
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}
示例#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 = 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}
示例#19
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}
示例#20
0
 def _transform_end_date(self, performance, fieldname, fieldvalue):
     performance_date_fields = IEventAccessor(performance)
     date_datetime = datetime.strptime(fieldvalue, '%Y-%m-%d %H:%M')
     performance_date_fields.end = date_datetime
     return fieldvalue
示例#21
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()
示例#22
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}
示例#23
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}
示例#24
0
    def test_event_accessor(self):
        utc = pytz.utc
        vienna = pytz.timezone('Europe/Vienna')

        self.portal.invokeFactory('Event', 'event1',
                description='a description',
                startDate=datetime(2011, 11, 11, 11, 0, tzinfo=utc),
                endDate=datetime(2011, 11, 11, 12, 0, tzinfo=utc),
                timezone='UTC',
                wholeDay=False)
        e1 = self.portal['event1']
        acc = IEventAccessor(e1)

        # TEST DATES
        self.assertEqual(acc.start, datetime(2011, 11, 11, 11, 0, tzinfo=utc))
        self.assertEqual(acc.end, datetime(2011, 11, 11, 12, 0, tzinfo=utc))

        acc.start = datetime(2011, 11, 13, 9, 0)  # tzinfo does not matter,
        acc.end = datetime(2011, 11, 13, 10, 0)  # it's set by subscription
                                                # adapter

        # If using EventAccessor's edit method, calling notify isn't needed
        acc.edit(timezone=u'Europe/Vienna')

        # accessor should return start/end datetimes in the event's timezone
        self.assertEqual(
            acc.start,
            datetime(2011, 11, 13, 9, 0, tzinfo=vienna))
        self.assertEqual(
            acc.end,
            datetime(2011, 11, 13, 10, 0, tzinfo=vienna))

        # start/end dates are stored in UTC zone on the context, but converted
        # to event's timezone via the attribute getter.
        self.assertEqual(
            e1.end(),
            DateTime('2011/11/13 10:00:00 Europe/Vienna')
        )

        # timezone should be the same on the event object and accessor
        self.assertEqual(e1.getTimezone(), acc.timezone)

        # Open End Test
        acc.edit(open_end=True)
        self.assertEqual(
            acc.start,
            datetime(2011, 11, 13, 9, 0, tzinfo=vienna))
        self.assertEqual(
            acc.end,
            datetime(2011, 11, 13, 23, 59, 59, tzinfo=vienna))

        # Whole Day Test
        acc.edit(whole_day=True, open_end=False)
        self.assertEqual(
            acc.start,
            datetime(2011, 11, 13, 0, 0, tzinfo=vienna))
        self.assertEqual(
            acc.end,
            datetime(2011, 11, 13, 23, 59, 59, tzinfo=vienna))

        # TEST DESCRIPTION
        self.assertTrue(acc.description == 'a description')
        acc.description = 'another desc'
        self.assertTrue(acc.description == 'another desc')

        # TEST OTHER PROPERTIES
        acc.title = u"An Event"
        acc.recurrence = u'RRULE:FREQ=DAILY;COUNT=5'
        acc.location = u"Home"
        acc.attendees = [u'me', u'you']
        acc.contact_name = u"Max Mustermann"
        acc.contact_email = u"*****@*****.**"
        acc.contact_phone = u"+1234567890"
        acc.event_url = u"http://plone.org/"
        acc.subjects = [u"tag1", u"tag2"]
        acc.text = u"body text with <b>html</b> formating."

        # If not using EventAccessor's edit method, call notify manually
        notify(ObjectModifiedEvent(acc.context))

        self.assertEqual(acc.recurrence, u'RRULE:FREQ=DAILY;COUNT=5')
        self.assertEqual(acc.location, u'Home')
        self.assertEqual(acc.attendees, (u'me', u'you'))
        self.assertEqual(acc.contact_name, u"Max Mustermann")
        self.assertEqual(acc.contact_email, u'*****@*****.**')
        self.assertEqual(acc.contact_phone, u"+1234567890")
        self.assertEqual(acc.event_url, u"http://plone.org/")
        self.assertEqual(acc.subjects, (u"tag1", u"tag2"))
        self.assertEqual(acc.text, u"body text with <b>html</b> formating.")

        # CLEANUP
        self.portal.manage_delObjects(['event1'])
示例#25
0
    def __call__(self, force=False):

        if not self.data.data:
            raise Exception("No data provided.")

        parent = self.parent

        if not parent:
            raise Exception("Cannot find parent object for %s" % self.path)

        _id = self.getId()

        if not self.exists:

            # People have no title or description.
            if self.portal_type in ('agsci_person', ):
                item = createContentInContainer(parent,
                                                self.portal_type,
                                                id=_id,
                                                checkConstraints=False)

            else:
                item = createContentInContainer(
                    parent,
                    self.portal_type,
                    id=_id,
                    title=self.data.title,
                    description=self.data.description,
                    checkConstraints=False)

            # Set UID
            setattr(item, ATTRIBUTE_NAME, self.UID)

        else:

            # If the item exists, and it's published, no further changes
            if self.review_state in [
                    'published',
            ] and not force:
                return

            item = self.context

        # Set subject (tags)
        if self.data.subject:
            item.setSubject(list(self.data.subject))

        # Set HTML
        html = self.html

        if html:

            item.text = RichTextValue(raw=html,
                                      mimeType=u'text/html',
                                      outputMimeType='text/x-html-safe')

        # Set File field
        file = self.file

        if file:
            item.file = self.file

        # Set Lead Image or Image field
        image = self.image

        if image:
            item.image = image

            # Unset full width field if image is too small, or is portrait.
            try:
                (w, h) = image.getImageSize()
            except:
                pass
            else:
                if w < h or w < 600:
                    item.image_full_width = False

        # Set field values
        # Map current field name 'field' to old 'data_field' from feed.

        fields = self.fields
        field_names = self.field_names

        for field_name in field_names:

            field = fields.get(field_name)

            if self.map_fields:
                data_field = self.fields_mapping.get(field_name, field_name)
            else:
                data_field = field_name

            # Skip fields if we're only importing specific fields
            if self.include_fields and field_name not in self.include_fields:
                continue

            if field_name not in self.exclude_fields:

                value = getattr(self.data, data_field, None)

                if value or isinstance(value, (bool, )):

                    value = self.transform_value(
                        field=field,
                        field_name=data_field,
                        value=value,
                    )

                    setattr(item, field_name, value)

                    if self.debug:
                        LOG(
                            self.__class__.__name__, INFO,
                            "%s: Setting %s to %r" %
                            (item.absolute_url(), field_name, value))

        # Set collection criteria
        if self.portal_type in ('Collection', 'Newsletter'):
            if self.data.collection_criteria:
                item.setQuery(self.data.collection_criteria)

            if self.data.collection_sort_field:
                item.setSort_on(self.data.collection_sort_field)

                if self.data.collection_sort_reversed:
                    item.setSort_reversed(True)

        # Set default page
        if self.data.default_page:
            default_page_id = safe_unicode(
                self.data.default_page).encode('utf-8')
            self.context.setDefaultPage(default_page_id)
        else:

            # Set layout if no default page
            layout = self.data.layout

            if layout in self.valid_layouts:
                item.setLayout(layout)

        # Set dates
        effective = self.data.effective_date
        expires = self.data.expiration_date

        if effective:
            item.setEffectiveDate(DateTime(effective))

        if expires:
            item.setExpirationDate(DateTime(expires))

        # If event, set start and end
        if self.portal_type in ('Event', ):
            start_date = localize(DateTime(self.data.start_date))
            end_date = localize(DateTime(self.data.end_date))

            acc = IEventAccessor(item)
            acc.start = start_date
            acc.end = end_date

        # Set references
        modifiedContent(item, None)

        # Reindex
        item.reindexObject()
示例#26
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}
示例#27
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}