コード例 #1
0
def data_postprocessing(obj, event):
    # newly created object, without start/end/timezone (e.g. invokeFactory()
    # called without data from add form), ignore event; it will be notified 
    # again later:
    if getattr(obj, 'start', None) is None:
        return
 
    # We handle date inputs as floating dates without timezones and apply
    # timezones afterwards.
    start = tzdel(obj.start)
    end = tzdel(obj.end)

    # set the timezone
    tz = pytz.timezone(obj.timezone)
    start = tz.localize(start)
    end = tz.localize(end)

    # adapt for whole day
    if IEventBasic(obj).whole_day:
        start = start.replace(hour=0,minute=0,second=0)
        end = end.replace(hour=23,minute=59,second=59)

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

    # reindex
    obj.reindexObject()
コード例 #2
0
ファイル: browser.py プロジェクト: garbas/uu.task
 def computed_state(self):
     task = self.task
     state = task.state
     if state != 'completed' and task.due and \
             utc(datetime.now()) > utc(task.due):
         return 'overdue'
     return dict(id=state, title=TASK_STATES[state])
コード例 #3
0
def recurrence_sequence_timedelta(start, delta=None, until=None, count=None,
                                  dst=DSTAUTO):
    """ Calculates a sequence of datetime objects from a timedelta integer,
    which defines the minutes between each occurence.

    :param start: datetime or DateTime instance of the date from which the
                  recurrence sequence is calculated.
    :type start: datetime

    :param delta: Integer which defines the minutes
                  between each date occurence.
    :type delta: integer

    :param until: datetime or DateTime instance of the date, until the
                  recurrence is calculated. If not given,
                  count or MAXDATE limit the recurrence calculation.
    :type until: datetime

    :param count: Integer which defines the number of occurences. If not given,
                  until or MAXDATE limits the recurrence calculation.
    :param count: integer

    :param dst:   Daylight Saving Time crossing behavior. DSTAUTO, DSTADJUST or
                  DSTKEEP. For more information, see
                  plone.event.utils.utcoffset_normalize.
    :param dst: string

    :return: A generator which generates a sequence of datetime instances.
    :rtype: generator

    """
    start = pydt(start)
    yield start

    if delta is None or delta < 1 or until is None:
        return

    until = pydt(until)

    before = start
    delta = datetime.timedelta(minutes=delta)
    cnt = 0
    while True:
        after = before + delta
        after = utcoffset_normalize(after, delta, dst)

        # Limit number of recurrences otherwise calculations take too long
        if MAXCOUNT and cnt + 1 > MAXCOUNT:
            break
        if count and cnt + 1 > count:
            break
        if until and utc(after) > utc(until):
            break
        cnt += 1

        yield after
        before = after
コード例 #4
0
def data_postprocessing(obj, event):

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

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

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

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

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

        return dt.replace(microsecond=0)

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

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

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

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

    # Reindex
    obj.reindexObject()
コード例 #5
0
def data_postprocessing(obj, event):

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

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

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

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

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

        return dt.replace(microsecond=0)

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

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

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

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

    # Reindex
    obj.reindexObject()
コード例 #6
0
ファイル: ical.py プロジェクト: Solgema/plone.app.event
def event_component(context):
    """ Returns an icalendar object of the event.

    """
    ical_event = icalendar.Event()
    # TODO: until VTIMETZONE component is added and TZID used, everything is
    #       converted to UTC. use real TZID, when VTIMEZONE is used!
    ical_event.add('dtstamp', utc(pydt(datetime.now())))
    ical_event.add('created', utc(pydt(context.creation_date)))
    ical_event.add('uid', context.UID())
    ical_event.add('last-modified', utc(pydt(context.modification_date)))
    ical_event.add('summary', context.Title())
    ical_event.add('dtstart', utc(pydt(context.start())))
    ical_event.add('dtend', utc(pydt(context.end())))

    recurrence = context.recurrence
    if recurrence:
        if recurrence.startswith('RRULE:'): recurrence = recurrence[6:]
        ical_event.add('rrule', icalendar.prop.vRecur.from_ical(recurrence))

    description = context.Description()
    if description:
        ical_event.add('description', description)

    location = context.getLocation()
    if location:
        ical_event.add('location', location)

    subjects= context.Subject()
    for subject in subjects:
        ical_event.add('categories', subject)

    # TODO: revisit and implement attendee export according to RFC
    attendees = context.getAttendees()
    for attendee in attendees:
        ical_event.add('attendee', attendee)

    cn = []
    contact = context.contact_name()
    if contact:
        cn.append(contact) # TODO safe_unicode conversion needed?
    phone = context.contact_phone()
    if phone:
        cn.append(phone)
    email = context.contact_email()
    if email:
        cn.append(email)
    if cn:
        ical_event.add('contact', u', '.join(cn))

    url = context.event_url()
    if url:
        ical_event.add('url', url)

    return ical_event
コード例 #7
0
    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))
コード例 #8
0
    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))
コード例 #9
0
ファイル: migration.py プロジェクト: upiq/uu.eventintegration
def migrate_event(source, site):
    parent_folder = aq_parent(source)
    evid = source.getId()
    newid = '%s-new' % evid
    if newid not in parent_folder.objectIds():
        timezone = sitetz(site)
        parent_folder.invokeFactory(
            id=newid,
            type_name='plone.app.event.dx.event',
            title=source.Title(),
            start=utc(source.start().asdatetime()),
            end=utc(source.start().asdatetime()),
            timezone=timezone,
            whole_day=False,
            )
        dest = parent_folder.get(newid)
        # behaviors:
        basic = behaviors.IEventBasic(dest)
        recur = behaviors.IEventRecurrence(dest)
        loc = behaviors.IEventLocation(dest)
        contact = behaviors.IEventContact(dest)
        summary = behaviors.IEventSummary(dest)
        # default values for fields we will not copy:
        basic.timezone = timezone
        basic.wholeday = False
        recur.recurrence = None  # default, no recurrence specified
        # start, end dates:
        basic.start = utc(source.start().asdatetime())
        basic.end = utc(source.start().asdatetime())
        # contact name, phone, email, event_url
        contact.contact_name = source.contact_name()
        contact.contact_email = source.contact_email()
        contact.contact_phone = source.contact_phone()
        contact.event_url = source.event_url()
        # location
        loc.location = source.getLocation()
        # modification date and Subject/tags metadata
        dest.modification_date = source.modification_date
        dest.setSubject(source.Subject())
        # Deal with body text.
        text = source.getText()
        if text:
            summary.text = RichTextValue(raw=text.decode('utf-8'))
        dest.reindexObject()
    try:
        parent_folder.manage_delObjects([evid])
    except LinkIntegrityNotificationException:
        print 'skipped deleting %s for link integrity' % repr(source)
コード例 #10
0
ファイル: browser.py プロジェクト: garbas/uu.task
 def formatted_date(self, item):
     DT_item = DT(item)
     return dict(
         date=ulocalized_time(
             DT_item,
             long_format=False,
             time_only=None,
             context=self.context),
         time=ulocalized_time(
             DT_item,
             long_format=False,
             time_only=True,
             context=self.context),
         iso=item.isoformat(),
         extra=utc(datetime.now()) > utc(item) and ' (past-due)' or '',
     )
コード例 #11
0
ファイル: behaviors.py プロジェクト: Solgema/plone.app.event
def data_postprocessing(obj, event):
    # set the timezone
    tz = pytz.timezone(obj.timezone)
    start = tz.localize(obj.start)
    end = obj.end.replace(tzinfo=tz)

    # adapt for whole day
    if obj.whole_day:
        start = start.replace(hour=0,minute=0,second=0)
        end = end.replace(hour=23,minute=59,second=59)

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

    # reindex
    obj.reindexObject()
コード例 #12
0
def data_postprocessing(obj, event):
    # We handle date inputs as floating dates without timezones and apply
    # timezones afterwards.
    start = tzdel(obj.start)
    end = tzdel(obj.end)

    # set the timezone
    tz = pytz.timezone(obj.timezone)
    start = tz.localize(start)
    end = tz.localize(end)

    # adapt for whole Day
    if obj.whole_day:
        start = start.replace(hour=0,minute=0,second=0)
        end = end.replace(hour=23,minute=59,second=59)

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

    # reindex
    obj.reindexObject()
コード例 #13
0
def add_to_zones_map(tzmap, tzid, dt):
    if tzid.lower() == 'utc' or not is_datetime(dt):
        # no need to define UTC nor timezones for date objects.
        return tzmap
    null = datetime(1, 1, 1)
    tz = pytz.timezone(tzid)
    transitions = getattr(tz, '_utc_transition_times', None)
    if not transitions:
        return tzmap  # we need transition definitions
    dtzl = tzdel(utc(dt))

    # get transition time, which is the dtstart of timezone.
    #     the key function returns the value to compare with. as long as item
    #     is smaller or equal like the dt value in UTC, return the item. as
    #     soon as it becomes greater, compare with the smallest possible
    #     datetime, which wouldn't create a match within the max-function. this
    #     way we get the maximum transition time which is smaller than the
    #     given datetime.
    transition = max(transitions,
                     key=lambda item: item <= dtzl and item or null)

    # get previous transition to calculate tzoffsetfrom
    idx = transitions.index(transition)
    prev_idx = idx > 0 and idx - 1 or idx
    prev_transition = transitions[prev_idx]

    def localize(tz, dt):
        if dt is null:
            # dummy time, edge case
            # (dt at beginning of all transitions, see above.)
            return null
        return pytz.utc.localize(dt).astimezone(tz)  # naive to utc + localize

    transition = localize(tz, transition)
    dtstart = tzdel(transition)  # timezone dtstart must be in local time
    prev_transition = localize(tz, prev_transition)

    if tzid not in tzmap:
        tzmap[tzid] = {}  # initial
    if dtstart in tzmap[tzid]:
        return tzmap  # already there
    tzmap[tzid][dtstart] = {
        'dst': transition.dst() > timedelta(0),
        'name': transition.tzname(),
        'tzoffsetfrom': prev_transition.utcoffset(),
        'tzoffsetto': transition.utcoffset(),
        # TODO: recurrence rule
    }
    return tzmap
コード例 #14
0
ファイル: exporter.py プロジェクト: kombinat/plone.app.event
def add_to_zones_map(tzmap, tzid, dt):
    if tzid.lower() == 'utc' or not is_datetime(dt):
        # no need to define UTC nor timezones for date objects.
        return tzmap
    null = datetime(1, 1, 1)
    tz = pytz.timezone(tzid)
    transitions = getattr(tz, '_utc_transition_times', None)
    if not transitions:
        return tzmap  # we need transition definitions
    dtzl = tzdel(utc(dt))

    # get transition time, which is the dtstart of timezone.
    #     the key function returns the value to compare with. as long as item
    #     is smaller or equal like the dt value in UTC, return the item. as
    #     soon as it becomes greater, compare with the smallest possible
    #     datetime, which wouldn't create a match within the max-function. this
    #     way we get the maximum transition time which is smaller than the
    #     given datetime.
    transition = max(transitions,
                     key=lambda item: item <= dtzl and item or null)

    # get previous transition to calculate tzoffsetfrom
    idx = transitions.index(transition)
    prev_idx = idx > 0 and idx - 1 or idx
    prev_transition = transitions[prev_idx]

    def localize(tz, dt):
        if dt is null:
            # dummy time, edge case
            # (dt at beginning of all transitions, see above.)
            return null
        return pytz.utc.localize(dt).astimezone(tz)  # naive to utc + localize
    transition = localize(tz, transition)
    dtstart = tzdel(transition)  # timezone dtstart must be in local time
    prev_transition = localize(tz, prev_transition)

    if tzid not in tzmap:
        tzmap[tzid] = {}  # initial
    if dtstart in tzmap[tzid]:
        return tzmap  # already there
    tzmap[tzid][dtstart] = {
        'dst': transition.dst() > timedelta(0),
        'name': transition.tzname(),
        'tzoffsetfrom': prev_transition.utcoffset(),
        'tzoffsetto': transition.utcoffset(),
        # TODO: recurrence rule
    }
    return tzmap
コード例 #15
0
ファイル: exporter.py プロジェクト: plone/plone.app.event
 def created(self):
     # must be in uc
     return {'value': utc(self.event.created)}
コード例 #16
0
ファイル: exporter.py プロジェクト: kombinat/plone.app.event
    def to_ical(self):

        ical = icalendar.Event()

        event = self.event

        # TODO: event.text

        # must be in utc
        ical.add('dtstamp', utc(pydt(datetime.now())))
        ical.add('created', utc(pydt(event.created)))
        ical.add('last-modified', utc(pydt(event.last_modified)))

        ical.add('uid', event.uid)
        ical.add('url', event.url)

        ical.add('summary', event.title)

        if event.description:
            ical.add('description', event.description)

        if event.whole_day:
            ical.add('dtstart', event.start.date())
            # RFC5545, 3.6.1
            # For cases where a "VEVENT" calendar component
            # specifies a "DTSTART" property with a DATE value type but no
            # "DTEND" nor "DURATION" property, the event's duration is taken to
            # be one day.
            #
            # RFC5545 doesn't define clearly, if all-day events should have
            # a end date on the same date or one day after the start day at
            # 0:00. Most icalendar libraries use the latter method.
            # Internally, whole_day events end on the same day one second
            # before midnight. Using the RFC5545 preferred method for
            # plone.app.event seems not appropriate, since we would have to fix
            # the date to end a day before for displaying.
            # For exporting, we let whole_day events end on the next day at
            # midnight.
            # See:
            # http://stackoverflow.com/questions/1716237/single-day-all-day
            # -appointments-in-ics-files
            # http://icalevents.com/1778-all-day-events-adding-a-day-or-not/
            # http://www.innerjoin.org/iCalendar/all-day-events.html
            ical.add('dtend', event.end.date() + timedelta(days=1))
        elif event.open_end:
            # RFC5545, 3.6.1
            # For cases where a "VEVENT" calendar component
            # specifies a "DTSTART" property with a DATE-TIME value type but no
            # "DTEND" property, the event ends on the same calendar date and
            # time of day specified by the "DTSTART" property.
            ical.add('dtstart', event.start)
        else:
            ical.add('dtstart', event.start)
            ical.add('dtend', event.end)

        if event.recurrence:
            for recdef in event.recurrence.split():
                prop, val = recdef.split(':')
                if prop == 'RRULE':
                    ical.add(prop, icalendar.prop.vRecur.from_ical(val))
                elif prop in ('EXDATE', 'RDATE'):
                    factory = icalendar.prop.vDDDLists

                    # localize ex/rdate
                    # TODO: should better already be localized by event object
                    tzid = event.timezone
                    # get list of datetime values from ical string
                    try:
                        dtlist = factory.from_ical(val, timezone=tzid)
                    except ValueError:
                        # TODO: caused by a bug in plone.formwidget.recurrence,
                        # where the recurrencewidget or plone.event fails with
                        # COUNT=1 and a extra RDATE.
                        # TODO: REMOVE this workaround, once this failure is
                        # fixed in recurrence widget.
                        continue
                    ical.add(prop, dtlist)

        if event.location:
            ical.add('location', event.location)

        # TODO: revisit and implement attendee export according to RFC
        if event.attendees:
            for attendee in event.attendees:
                att = icalendar.prop.vCalAddress(attendee)
                att.params['cn'] = icalendar.prop.vText(attendee)
                att.params['ROLE'] = icalendar.prop.vText('REQ-PARTICIPANT')
                ical.add('attendee', att)

        cn = []
        if event.contact_name:
            cn.append(event.contact_name)
        if event.contact_phone:
            cn.append(event.contact_phone)
        if event.contact_email:
            cn.append(event.contact_email)
        if event.event_url:
            cn.append(event.event_url)
        if cn:
            ical.add('contact', u', '.join(cn))

        if event.subjects:
            for subject in event.subjects:
                ical.add('categories', subject)

        return ical
コード例 #17
0
    def to_ical(self):

        ical = icalendar.Event()

        event = self.event

        # TODO: event.text

        # must be in utc
        ical.add('dtstamp', utc(pydt(datetime.now())))
        ical.add('created', utc(pydt(event.created)))
        ical.add('last-modified', utc(pydt(event.last_modified)))

        ical.add('uid', event.uid)
        ical.add('url', event.url)

        ical.add('summary', event.title)

        if event.description: ical.add('description', event.description)

        if event.whole_day:
            ical.add('dtstart', event.start.date())
            # RFC5545, 3.6.1
            # For cases where a "VEVENT" calendar component
            # specifies a "DTSTART" property with a DATE value type but no
            # "DTEND" nor "DURATION" property, the event's duration is taken to
            # be one day.
            #
            # RFC5545 doesn't define clearly, if all-day events should have
            # a end date on the same date or one day after the start day at
            # 0:00. Most icalendar libraries use the latter method.
            # Internally, whole_day events end on the same day one second
            # before midnight. Using the RFC5545 preferred method for
            # plone.app.event seems not appropriate, since we would have to fix
            # the date to end a day before for displaying.
            # For exporting, we let whole_day events end on the next day at
            # midnight.
            # See:
            # http://stackoverflow.com/questions/1716237/single-day-all-day-appointments-in-ics-files
            # http://icalevents.com/1778-all-day-events-adding-a-day-or-not/
            # http://www.innerjoin.org/iCalendar/all-day-events.html
            ical.add('dtend', event.end.date() + timedelta(days=1))
        elif event.open_end:
            # RFC5545, 3.6.1
            # For cases where a "VEVENT" calendar component
            # specifies a "DTSTART" property with a DATE-TIME value type but no
            # "DTEND" property, the event ends on the same calendar date and
            # time of day specified by the "DTSTART" property.
            ical.add('dtstart', event.start)
        else:
            ical.add('dtstart', event.start)
            ical.add('dtend', event.end)

        if event.recurrence:
            for recdef in event.recurrence.split():
                prop, val = recdef.split(':')
                if prop == 'RRULE':
                    ical.add(prop, icalendar.prop.vRecur.from_ical(val))
                elif prop in ('EXDATE', 'RDATE'):
                    factory = icalendar.prop.vDDDLists

                    # localize ex/rdate
                    # TODO: should better already be localized by event object
                    tzid = event.timezone
                    # get list of datetime values from ical string
                    try:
                        dtlist = factory.from_ical(val, timezone=tzid)
                    except ValueError:
                        # TODO: caused by a bug in plone.formwidget.recurrence,
                        # where the recurrencewidget or plone.event fails with
                        # COUNT=1 and a extra RDATE.
                        # TODO: REMOVE this workaround, once this failure is
                        # fixed in recurrence widget.
                        continue
                    ical.add(prop, dtlist)

        if event.location: ical.add('location', event.location)

        # TODO: revisit and implement attendee export according to RFC
        if event.attendees:
            for attendee in event.attendees:
                att = icalendar.prop.vCalAddress(attendee)
                att.params['cn'] = icalendar.prop.vText(attendee)
                att.params['ROLE'] = icalendar.prop.vText('REQ-PARTICIPANT')
                ical.add('attendee', att)

        cn = []
        if event.contact_name:
            cn.append(event.contact_name)
        if event.contact_phone:
            cn.append(event.contact_phone)
        if event.contact_email:
            cn.append(event.contact_email)
        if event.event_url:
            cn.append(event.event_url)
        if cn:
            ical.add('contact', u', '.join(cn))

        if event.subjects:
            for subject in event.subjects:
                ical.add('categories', subject)

        return ical
コード例 #18
0
ファイル: exporter.py プロジェクト: plone/plone.app.event
 def last_modified(self):
     # must be in uc
     return {'value': utc(self.event.last_modified)}
コード例 #19
0
ファイル: importer.py プロジェクト: FHNW/plone.app.event
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 add_to_zones_map(tzmap, tzid, dt):
    """Build a dictionary of timezone information from a timezone identifier
    and a date/time object for which the timezone information should be
    calculated.

    :param tzmap: An existing dictionary of timezone information to be extended
                  or an empty dictionary.
    :type tzmap: dictionary
    :param tzid: A timezone identifier.
    :type tzid: string
    :param dt: A datetime object.
    :type dt: datetime

    :returns: A dictionary with timezone information needed to build VTIMEZONE
              entries.
    :rtype: dictionary
    """

    if tzid.lower() == 'utc' or not is_datetime(dt):
        # no need to define UTC nor timezones for date objects.
        return tzmap
    null = datetime(1, 1, 1)
    tz = pytz.timezone(tzid)
    transitions = getattr(tz, '_utc_transition_times', None)
    if not transitions:
        return tzmap  # we need transition definitions
    dtzl = tzdel(utc(dt))

    # get transition time, which is the dtstart of timezone.
    #     the key function returns the value to compare with. as long as item
    #     is smaller or equal like the dt value in UTC, return the item. as
    #     soon as it becomes greater, compare with the smallest possible
    #     datetime, which wouldn't create a match within the max-function. this
    #     way we get the maximum transition time which is smaller than the
    #     given datetime.
    transition = max(transitions,
                     key=lambda item: item if item <= dtzl else null)

    # get previous transition to calculate tzoffsetfrom
    idx = transitions.index(transition)
    prev_idx = idx - 1 if idx > 0 else idx
    prev_transition = transitions[prev_idx]

    def localize(tz, dt):
        if dt is null:
            # dummy time, edge case
            # (dt at beginning of all transitions, see above.)
            return null
        return pytz.utc.localize(dt).astimezone(tz)  # naive to utc + localize

    transition = localize(tz, transition)
    dtstart = tzdel(transition)  # timezone dtstart must be in local time
    prev_transition = localize(tz, prev_transition)

    if tzid not in tzmap:
        tzmap[tzid] = {}  # initial
    if dtstart in tzmap[tzid]:
        return tzmap  # already there
    tzmap[tzid][dtstart] = {
        'dst': transition.dst() > timedelta(0),
        'name': transition.tzname(),
        'tzoffsetfrom': prev_transition.utcoffset(),
        'tzoffsetto': transition.utcoffset(),
        # TODO: recurrence rule
    }
    return tzmap
コード例 #21
0
 def created(self):
     # must be in uc
     return {'value': utc(self.event.created)}
コード例 #22
0
 def dtstamp(self):
     # must be in uc
     return {'value': utc(datetime.now())}
コード例 #23
0
    userId=admin['UserId'],
    pagination={ 'MaxNumberResults': page_size }
)
if gudu_response['TotalNumberResponses'] > 0:
    # get the last page!
    gudu_response = usage.call_service(
        'GetUserDetailedUsage',
        userId=admin['UserId'],
        pagination={
            'MaxNumberResults': page_size,
            'PageNumber': int(ceil(gudu_response['TotalNumberResponses'] / float(page_size)) - 1)
        }
    )
    endRange = datetime.utcnow()
    beginRange = endRange - timedelta(days=7)
    week_views = [v for v in gudu_response['PagedResponses']['DetailedUsageResponseItem'] if v['Time'] > utils.utc(beginRange)]
    if week_views:
        # let's get the sessionId of the first view and see who else has been watching it in the past week
        sessionId = week_views[0]['SessionId']
        print('admin viewed session {} in the past week'.format(sessionId))

        ssu_response = usage.call_service(
            'GetSessionSummaryUsage',
            sessionId=sessionId,
            beginRange=beginRange,
            endRange=endRange,
            granularity='Daily' # zeep doesn't currently support parsing enumeration types, so this is a magic string
        )
        view_days = [r for r in ssu_response if r['Views'] > 0]
        for day in sorted(view_days, key=lambda d:d['Time']):
            day_offset = int(ceil((endRange - day['Time'].replace(tzinfo=None)).total_seconds() / (3600 * 24)))
コード例 #24
0
 def last_modified(self):
     # must be in uc
     return {'value': utc(self.event.last_modified)}
コード例 #25
0
 def created(self):
     return utc(self.context.creation_date)
コード例 #26
0
ファイル: content.py プロジェクト: enfold/plone.app.event
 def created(self):
     return utc(self.context.creation_date)
コード例 #27
0
ファイル: ical.py プロジェクト: seanupton/plone.app.event
    def to_ical(self):

        ical = icalendar.Event()

        event = self.event

        # TODO: event.text

        # TODO: until VTIMETZONE component is added and TZID used, everything is
        #       converted to UTC. use real TZID, when VTIMEZONE is used!

        ical.add('dtstamp', utc(pydt(datetime.now())))
        ical.add('created', utc(pydt(event.created)))

        ical.add('uid', event.uid)
        ical.add('url', event.url)

        ical.add('last-modified', utc(pydt(event.last_modified)))
        ical.add('summary', event.title)

        if event.description: ical.add('description', event.description)

        if event.whole_day:
            ical.add('dtstart', utc(pydt(event.start)).date())
            ical.add('dtend', utc(pydt(event.end + timedelta(days=1))).date())
        else:
            ical.add('dtstart', utc(pydt(event.start)))
            ical.add('dtend', utc(pydt(event.end)))

        if event.recurrence:
            if event.recurrence.startswith('RRULE:'):
                recurrence = event.recurrence[6:]
            else:
                recurrence = event.recurrence
            ical.add('rrule', icalendar.prop.vRecur.from_ical(recurrence))

        if event.location: ical.add('location', event.location)

        # TODO: revisit and implement attendee export according to RFC
        if event.attendees:
            for attendee in event.attendees:
                att = icalendar.prop.vCalAddress(attendee)
                att.params['cn'] = icalendar.prop.vText(attendee)
                att.params['ROLE'] = icalendar.prop.vText('REQ-PARTICIPANT')
                ical.add('attendee', att)

        cn = []
        if event.contact_name:
            cn.append(event.contact_name)
        if event.contact_phone:
            cn.append(event.contact_phone)
        if event.contact_email:
            cn.append(event.contact_email)
        if event.event_url:
            cn.append(event.event_url)
        if cn:
            ical.add('contact', u', '.join(cn))

        if event.subjects:
            for subject in event.subjects:
                ical.add('categories', subject)

        return ical
コード例 #28
0
ファイル: exporter.py プロジェクト: plone/plone.app.event
 def dtstamp(self):
     # must be in uc
     return {'value': utc(datetime.now())}
コード例 #29
0
ファイル: ical.py プロジェクト: senner/plone.app.event
    def to_ical(self):

        ical = icalendar.Event()

        event = self.event

        # TODO: event.text

        # must be in utc
        ical.add('dtstamp', utc(pydt(datetime.now())))
        ical.add('created', utc(pydt(event.created)))
        ical.add('last-modified', utc(pydt(event.last_modified)))

        ical.add('uid', event.uid)
        ical.add('url', event.url)

        ical.add('summary', event.title)

        if event.description: ical.add('description', event.description)

        if event.whole_day:
            ical.add('dtstart', event.start.date())
            ical.add('dtend', event.end.date())
        else:
            ical.add('dtstart', event.start)
            ical.add('dtend', event.end)

        if event.recurrence:
            for recdef in event.recurrence.split():
                prop, val = recdef.split(':')
                if prop == 'RRULE':
                    ical.add(prop, icalendar.prop.vRecur.from_ical(val))
                elif prop in ('EXDATE', 'RDATE'):
                    factory = icalendar.prop.vDDDLists

                    # localize ex/rdate
                    # TODO: should better already be localized by event object
                    tzid = event.timezone
                    # get list of datetime values from ical string
                    dtlist = factory.from_ical(val, timezone=tzid)

                    ical.add(prop, dtlist)

        if event.location: ical.add('location', event.location)

        # TODO: revisit and implement attendee export according to RFC
        if event.attendees:
            for attendee in event.attendees:
                att = icalendar.prop.vCalAddress(attendee)
                att.params['cn'] = icalendar.prop.vText(attendee)
                att.params['ROLE'] = icalendar.prop.vText('REQ-PARTICIPANT')
                ical.add('attendee', att)

        cn = []
        if event.contact_name:
            cn.append(event.contact_name)
        if event.contact_phone:
            cn.append(event.contact_phone)
        if event.contact_email:
            cn.append(event.contact_email)
        if event.event_url:
            cn.append(event.event_url)
        if cn:
            ical.add('contact', u', '.join(cn))

        if event.subjects:
            for subject in event.subjects:
                ical.add('categories', subject)

        return ical
コード例 #30
0
ファイル: recurrence.py プロジェクト: enzus/plone.event
def recurrence_sequence_ical(start, recrule=None, from_=None, until=None, count=None):
    """ Calculates a sequence of datetime objects from a recurrence rule
    following the RFC2445 specification, using python-dateutil recurrence rules.

    @param start:   datetime or DateTime instance of the date from which the
                    recurrence sequence is calculated.

    @param recrule: String with RFC2445 compatible recurrence definition,
                    dateutil.rrule or dateutil.rruleset instances.

    @param from_:   datetime or DateTime instance of the date, to limit -
                    possibly with until - the result within a timespan -
                    The Date Horizon.

    @param until:   datetime or DateTime instance of the date, until the
                    recurrence is calculated. If not given, count or MAXDATE
                    limit the recurrence calculation.

    @param count:   Integer which defines the number of occurences. If not
                    given, until or MAXDATE limits the recurrence calculation.

    @return: A generator which generates a sequence of datetime instances.

    """
    start = pydt(start) # always use python datetime objects
    from_ = pydt(from_)
    until = pydt(until)
    tz = start.tzinfo
    start = tzdel(start) # tznaive | start defines tz
    _from = tzdel(from_)
    _until = tzdel(until)

    if isinstance(recrule, str):
        # RFC2445 string
        # forceset: always return a rruleset
        # dtstart: optional used when no dtstart is in RFC2445 string
        #          dtstart is given as timezone naive time. timezones are
        #          applied afterwards, since rrulestr doesn't normalize
        #          timezones over DST boundaries
        rset = rrule.rrulestr(recrule,
                              dtstart=start,
                              forceset=True,
                              # ignoretz=True
                              # compatible=True # RFC2445 compatibility
                              )
    else:
        rset = rrule.rruleset()
    rset.rdate(start) # RCF2445: always include start date

    # limit
    if _from and _until:
        # between doesn't add a ruleset but returns a list
        rset = rset.between(_from, _until, inc=True)

    for cnt, date in enumerate(rset):
        # Localize tznaive dates from rrulestr sequence
        date = tz.localize(date)

        # Limit number of recurrences otherwise calculations take too long
        if MAXCOUNT and cnt+1 > MAXCOUNT: break
        if count and cnt+1 > count: break
        if from_ and utc(date) < utc(from_): continue
        if until and utc(date) > utc(until): break

        yield date
    return
コード例 #31
0
ファイル: exporter.py プロジェクト: plone/plone.app.event
def add_to_zones_map(tzmap, tzid, dt):
    """Build a dictionary of timezone information from a timezone identifier
    and a date/time object for which the timezone information should be
    calculated.

    :param tzmap: An existing dictionary of timezone information to be extended
                  or an empty dictionary.
    :type tzmap: dictionary
    :param tzid: A timezone identifier.
    :type tzid: string
    :param dt: A datetime object.
    :type dt: datetime

    :returns: A dictionary with timezone information needed to build VTIMEZONE
              entries.
    :rtype: dictionary
    """

    if tzid.lower() == 'utc' or not is_datetime(dt):
        # no need to define UTC nor timezones for date objects.
        return tzmap
    null = datetime(1, 1, 1)
    tz = pytz.timezone(tzid)
    transitions = getattr(tz, '_utc_transition_times', None)
    if not transitions:
        return tzmap  # we need transition definitions
    dtzl = tzdel(utc(dt))

    # get transition time, which is the dtstart of timezone.
    #     the key function returns the value to compare with. as long as item
    #     is smaller or equal like the dt value in UTC, return the item. as
    #     soon as it becomes greater, compare with the smallest possible
    #     datetime, which wouldn't create a match within the max-function. this
    #     way we get the maximum transition time which is smaller than the
    #     given datetime.
    transition = max(transitions,
                     key=lambda item: item if item <= dtzl else null)

    # get previous transition to calculate tzoffsetfrom
    idx = transitions.index(transition)
    prev_idx = idx - 1 if idx > 0 else idx
    prev_transition = transitions[prev_idx]

    def localize(tz, dt):
        if dt is null:
            # dummy time, edge case
            # (dt at beginning of all transitions, see above.)
            return null
        return pytz.utc.localize(dt).astimezone(tz)  # naive to utc + localize
    transition = localize(tz, transition)
    dtstart = tzdel(transition)  # timezone dtstart must be in local time
    prev_transition = localize(tz, prev_transition)

    if tzid not in tzmap:
        tzmap[tzid] = {}  # initial
    if dtstart in tzmap[tzid]:
        return tzmap  # already there
    tzmap[tzid][dtstart] = {
        'dst': transition.dst() > timedelta(0),
        'name': transition.tzname(),
        'tzoffsetfrom': prev_transition.utcoffset(),
        'tzoffsetto': transition.utcoffset(),
        # TODO: recurrence rule
    }
    return tzmap
コード例 #32
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}
コード例 #33
0
ファイル: importer.py プロジェクト: gyst/plone.app.event
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}
コード例 #34
0
ファイル: content.py プロジェクト: enfold/plone.app.event
 def last_modified(self):
     return utc(self.context.modification_date)
コード例 #35
0
ファイル: recurrence.py プロジェクト: vedantc98/Plone-test
def recurrence_sequence_ical(
        start,
        recrule=None,
        from_=None,
        until=None,
        count=None,
        duration=None,
):
    """Calculates a sequence of datetime objects from a recurrence rule
    following the RFC2445 specification, using python-dateutil recurrence
    rules.  The resolution of the resulting datetime objects is one second,
    since python-dateutil rrulestr doesn't support microseconds.

    :param start:   datetime or DateTime instance of the date from which the
                    recurrence sequence is calculated.
    :type start: datetime.datetime

    :param recrule: Optional string with RFC2445 compatible recurrence
                    definition, dateutil.rrule or dateutil.rruleset instances.
    :type recrule: string

    :param from_:   Optional datetime or DateTime instance of the date, to
                    limit the result within a timespan.
    :type from_: datetime.datetime

    :param until:   Optional datetime or DateTime instance of the date, until
                    the recurrence is calculated. If not given, count or
                    MAXDATE limit the recurrence calculation.
    :type until: datetime.datetime

    :param count:   Optional integer which defines the number of occurences.
                    If not given, until or MAXDATE limits the recurrence
                    calculation.
    :type count: integer

    :param duration: Optional timedelta instance, which is used to calculate
                     if a occurence datetime plus duration is within the
                     queried timerange.
    :type duration:  datetime.timedelta

    :returns: A generator which generates a sequence of datetime instances.
    :rtype: generator

    """
    # Always use python datetime objects and remove the microseconds
    start = pydt(start, exact=False)
    from_ = pydt(from_, exact=False)
    until = pydt(until, exact=False)
    tz = start.tzinfo
    start = tzdel(start)  # tznaive | start defines tz
    _from = tzdel(from_)
    _until = tzdel(until)
    if duration:
        assert (isinstance(duration, datetime.timedelta))
    else:
        duration = datetime.timedelta(0)

    if recrule:
        # TODO BUGFIX WRONG TIME DEFINITIONS
        # THIS HACK ensures, that UNTIL, RDATE and EXDATE definitions with
        # incorrect time (currently always set to 0:00 by the recurrence
        # widget) are handled correctly.
        #
        # Following fixes are made:
        # - The UNTIL date should be included in the recurrence set, as defined
        #   by RFC5545 (fix sets it to the end of the day)
        # - RDATE definitions should have the same time as the start date.
        # - EXDATE definitions should exclude occurrences on the specific date
        #   only the same time as the start date.
        # In the long term ,the recurrence widget should be able to set the
        # time for UNTIL, RDATE and EXDATE.
        t0 = start.time()  # set initial time information.
        # First, replace all times in the recurring rule with starttime
        t0str = 'T{0:02d}{1:02d}{2:02d}'.format(t0.hour, t0.minute, t0.second)
        # Replace any times set to 000000 with start time, not all
        # rrules are set by a specific broken widget.  Don't waste time
        # subbing if the start time is already 000000.
        if t0str != 'T000000':
            recrule = re.sub(r'T000000', t0str, recrule)
        # Then, replace incorrect until times with the end of the day
        recrule = re.sub(
            r'(UNTIL[^T]*[0-9]{8})T(000000)',
            r'\1T235959',
            recrule,
        )

        # RFC2445 string
        # forceset: always return a rruleset
        # dtstart: optional used when no dtstart is in RFC2445 string
        #          dtstart is given as timezone naive time. timezones are
        #          applied afterwards, since rrulestr doesn't normalize
        #          timezones over DST boundaries
        rset = rrule.rrulestr(
            recrule,
            dtstart=start,
            forceset=True,
            ignoretz=True,
            # compatible=True # RFC2445 compatibility
        )
    else:
        rset = rrule.rruleset()
    rset.rdate(start)  # RCF2445: always include start date

    # limit
    if _from and _until:
        # between doesn't add a ruleset but returns a list
        rset = rset.between(_from - duration, _until, inc=True)
    for cnt, date in enumerate(rset):
        # Localize tznaive dates from rrulestr sequence
        date = tz.localize(date)

        # Limit number of recurrences otherwise calculations take too long
        if MAXCOUNT and cnt + 1 > MAXCOUNT:
            break
        if count and cnt + 1 > count:
            break
        if from_ and utc(date) + duration < utc(from_):
            continue
        if until and utc(date) > utc(until):
            break

        yield date
    return
コード例 #36
0
 def last_modified(self):
     return utc(self.context.modification_date)
コード例 #37
0
ファイル: importer.py プロジェクト: plone/plone.app.event
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}