Example #1
0
    def test_default_timezone(self):
        self.assertTrue(os_default_timezone() == default_timezone() == 'UTC')

        reg = getUtility(IRegistry)
        settings = reg.forInterface(IEventSettings, prefix="plone.app.event")

        settings.portal_timezone = "Europe/Vienna"
        self.assertTrue(default_timezone() == 'Europe/Vienna')
    def test_default_timezone(self):
        assert os_default_timezone() == default_timezone() == "UTC"

        reg = getUtility(IRegistry)
        settings = reg.forInterface(IEventSettings, prefix="plone.app.event")

        settings.portal_timezone = "Europe/Vienna"
        assert default_timezone() == "Europe/Vienna"
Example #3
0
    def __call__(self, value, filestore, item,
                 disable_constraints=False, logger=None):
        if isinstance(value, datetime):
            value = value.date()
        if isinstance(value, basestring):
            # Fix some rare use case
            if 'Universal' in value:
                value = value.replace('Universal', 'UTC')

            try:
                value = datetime.strptime(value, '%Y-%m-%d %H:%M')
            except:
                value = datetime.now()

            # Fix timezone
            tz_default = default_timezone(as_tzinfo=True)
            if value.tzinfo is None:
                value = tz_default.localize(value)
        try:
            self.field.validate(value)
        except Exception, e:
            if not disable_constraints:
                raise e
            else:
                if logger:
                    logger(
                        "%s is invalid in %s: %s" % (
                            self.field.__name__,
                            item['_path'],
                            e)
                    )
Example #4
0
def construct_calendar(context, events):
    """ Returns an icalendar.Calendar object.

    :param context: A content object, which is used for calendar details like
                    Title and Description. Usually a container, collection or
                    the event itself.

    :param events: The list of event objects, which are included in this
                   calendar.

    """
    cal = icalendar.Calendar()
    cal.add('prodid', PRODID)
    cal.add('version', VERSION)

    # TODO: use plone's new uuid adapter instead of uid
    # Non-Standard calendar extensions. also see:
    # http://en.wikipedia.org/wiki/ICalendar#cite_note-11
    cal_title = context.Title()
    if cal_title: cal.add('x-wr-calname', cal_title)
    cal_desc = context.Description()
    if cal_desc: cal.add('x-wr-caldesc', cal_desc)
    if getattr(context, 'UID', False): # portal object does not have UID
        cal_uid = context.UID()
        cal.add('x-wr-relcalid', cal_uid)
    cal_tz = default_timezone(context)
    if cal_tz: cal.add('x-wr-timezone', cal_tz)

    for event in events:
        cal.add_component(IICalendarComponent(event))

    return cal
Example #5
0
    def extract(self, default=NO_VALUE):
        value = self.request.get(self.name, default)
        if value == default:
            return default
        if isinstance(value, str):
            # Date only
            date = datetime(value, '%Y-%m-%d')
        if value[0] == u'':
            return None
        else:
            if value[1] == u'':
                value[1] = u'0:00'
            time_str = "{0} {1}".format(*value)
            date = datetime.strptime(time_str, '%Y-%m-%d %H:%M')
        timezone_name = (self.request.get('%s-timezone' % self.name, '')
                         or self.request.get('timezone', '')
                         or default_timezone(self.context))
        if isinstance(timezone_name, unicode):
            timezone_name.encode('utf8')

        # pytz "does not work" with datetime tzinfo. Use localize instead
        # Symptoms are times in "LMT" format which are off a few minutes.
        # http://stackoverflow.com/questions/24856643/unexpected-results-converting-timezones-in-python  # noqa
        tz = timezone(timezone_name)
        date = tz.localize(date)
        return date
Example #6
0
def default_now():
    """ Returns the current time using the default_timezone from
    plone.app.event. Said timezone is either set globally or per user.

    """
    utcnow = to_utc(datetime.utcnow())
    return pytz.timezone(default_timezone()).normalize(utcnow)
Example #7
0
def construct_calendar(context, events):
    """ Returns an icalendar.Calendar object.

    :param context: A content object, which is used for calendar details like
                    Title and Description. Usually a container, collection or
                    the event itself.

    :param events: The list of event objects, which are included in this
                   calendar.

    """
    cal = icalendar.Calendar()
    cal.add('prodid', PRODID)
    cal.add('version', VERSION)

    cal_title = context.Title()
    if cal_title: cal.add('x-wr-calname', cal_title)
    cal_desc = context.Description()
    if cal_desc: cal.add('x-wr-caldesc', cal_desc)

    uuid = IUUID(context, None)
    if uuid: # portal object does not have UID
        cal.add('x-wr-relcalid', uuid)

    cal_tz = default_timezone(context)
    if cal_tz: cal.add('x-wr-timezone', cal_tz)

    for event in events:
        cal.add_component(IICalendarEventComponent(event).to_ical())

    return cal
Example #8
0
    def __call__(
        self, value, filestore, item, disable_constraints=False, logger=None
    ):
        if value == "None":
            return None
        if isinstance(value, six.string_types):
            # Fix some rare use case
            if "Universal" in value:
                value = value.replace("Universal", "UTC")
            try:
                value = dateutil.parser.isoparse(value)
                # Fix timezone
                tz_default = default_timezone(as_tzinfo=True)
                if value.tzinfo is None:
                    value = tz_default.localize(value)
                value = value.astimezone(tz_default)
            except ValueError:
                value = DateTime(value)
        try:
            self.field.validate(value)
        except Exception as e:
            if not disable_constraints:
                raise e
            else:
                if logger:
                    logger(
                        "%s is invalid in %s: %s"
                        % (self.field.__name__, item["_path"], e)
                    )

        return value
Example #9
0
    def __call__(self,
                 value,
                 filestore,
                 item,
                 disable_constraints=False,
                 logger=None):
        if isinstance(value, datetime):
            value = value.date()
        if isinstance(value, basestring):
            # Fix some rare use case
            if 'Universal' in value:
                value = value.replace('Universal', 'UTC')

            try:
                value = datetime.strptime(value, '%Y-%m-%d %H:%M')
            except:
                value = datetime.now()

            # Fix timezone
            tz_default = default_timezone(as_tzinfo=True)
            if value.tzinfo is None:
                value = tz_default.localize(value)
        try:
            self.field.validate(value)
        except Exception, e:
            if not disable_constraints:
                raise e
            else:
                if logger:
                    logger("%s is invalid in %s: %s" %
                           (self.field.__name__, item['_path'], e))
Example #10
0
    def getICal(self):
        # collect iCal entries for found events
        events = self.getEvents()
        if not events: return None

        cal = icalendar.Calendar()
        cal.add('prodid', PRODID)
        cal.add('version', VERSION)

        # TODO: is there a UUID to use instead of UID?
        # Non-Standard calendar extensions. also see:
        # http://en.wikipedia.org/wiki/ICalendar#cite_note-11
        cal_context = self.context
        cal_title = cal_context.Title()
        if cal_title: cal.add('x-wr-calname', cal_title)
        cal_desc = cal_context.Description()
        if cal_desc: cal.add('x-wr-caldesc', cal_desc)
        if getattr(cal_context, 'UID', False): # portal object does not have UID
            cal_uid = cal_context.UID()
            cal.add('x-wr-relcalid', cal_uid)
        cal_tz = default_timezone(cal_context)
        if cal_tz: cal.add('x-wr-timezone', cal_tz)

        for event in events:
            cal.add_component(event.to_icalendar())

        return cal
Example #11
0
    def test_importer_values(self):
        try:
            importer = ExternalEventImporter(self.directory)

            string_values = [
                'title', 'short_description', 'long_description', 'locality',
                'street', 'housenumber', 'zipcode', 'town', 'location_url',
                'event_url', 'organizer', 'contact_name', 'contact_email',
                'contact_phone', 'prices', 'registration', 'source_id',
                'fetch_id'
            ]
            now = default_now().replace(microsecond=0)
            then = now + timedelta(days=10)

            event = {s: s for s in string_values}

            event['last_update'] = now
            event['start'] = then
            event['end'] = then + timedelta(hours=1)
            event['timezone'] = default_timezone()
            event['whole_day'] = False
            event['recurrence'] = 'RRULE:FREQ=DAILY;COUNT=2'

            event['cat1'] = set(['c1', 'c2'])
            event['cat2'] = set(['c3', 'c4'])

            event['longitude'] = 7.8673189
            event['latitude'] = 46.6859853

            # :TODO: test image and attachement download
            # event['image'] =
            # event['attachment_1'] =
            # event['attachment_2'] =

            imports, deleted = importer.fetch_one('source', lambda: [event])
            imported = self.catalog.query()
            self.assertEquals(imports, 1)
            self.assertEquals(len(imported), 1)

            imported = imported[0].getObject()
            for s in string_values:
                self.assertTrue(s in vars(imported))
                self.assertTrue(vars(imported)[s] == s)

            self.assertEquals(imported.start, now + timedelta(days=10))
            self.assertEquals(imported.end, now + timedelta(days=10, hours=1))
            self.assertEquals(imported.recurrence, 'RRULE:FREQ=DAILY;COUNT=2')
            self.assertFalse(imported.whole_day)

            self.assertTrue('c1' in imported.cat1)
            self.assertTrue('c2' in imported.cat1)
            self.assertTrue('c3' in imported.cat2)
            self.assertTrue('c4' in imported.cat2)

            self.assertEquals(
                IGeoreferenced(imported).coordinates, [7.8673189, 46.6859853])

        finally:
            # Clean up (transaction has been commited)
            self.cleanup_after_fetch_one()
Example #12
0
def migrate_to_1300(context):
    """
    Add tzinfo to scadenza_bando
    """
    tz = pytz.timezone(default_timezone())

    bandi = api.content.find(portal_type="Bando")
    tot_results = len(bandi)
    logger.info("### Fixing {tot} Bandi ###".format(tot=tot_results))
    for counter, brain in enumerate(bandi):
        bando = brain.getObject()
        if not getattr(bando, "scadenza_bando", None):
            continue
        try:
            bando.scadenza_bando = pytz.utc.localize(
                bando.scadenza_bando).astimezone(tz)
        except ValueError:
            # convert to right timezone
            if bando.scadenza_bando.tzinfo.zone == tz.zone:
                # same tz, skip
                continue
            bando.scadenza_bando = pytz.utc.localize(
                bando.scadenza_bando.replace(tzinfo=None)).astimezone(tz)
        bando.reindexObject(idxs=["scadenza_bando"])

        logger.info("[{counter}/{tot}] - {bando}".format(
            counter=counter + 1, tot=tot_results, bando=brain.getPath()))
Example #13
0
def default_now():
    """ Returns the current time using the default_timezone from
    plone.app.event. Said timezone is either set globally or per user.

    """
    utcnow = to_utc(datetime.utcnow())
    return pytz.timezone(default_timezone()).normalize(utcnow)
Example #14
0
def patched_now(context=None):
    """Patch localized_now to allow stable results in tests.
    """
    if not context:
        context = None
    tzinfo = default_timezone(context=context, as_tzinfo=True)
    now = datetime(2013, 5, 5, 10, 0, 0, tzinfo=tzinfo).replace(microsecond=0)
    return now
 def get_selected_tz(self):
     ''' Let's try to get this from the start attribute (if found).
     Otherwise default to the default timezone.
     '''
     try:
         return str(self.context.start.tzinfo)
     except:
         return default_timezone()
Example #16
0
 def get_selected_tz(self):
     ''' Let's try to get this from the start attribute (if found).
     Otherwise default to the default timezone.
     '''
     try:
         return str(self.context.start.tzinfo)
     except:
         return default_timezone()
Example #17
0
def patched_now(context=None):
    """Patch localized_now to allow stable results in tests.
    """
    if not context:
        context = None
    tzinfo = default_timezone(context=context, as_tzinfo=True)
    now = datetime(2013, 5, 5, 10, 0, 0, tzinfo=tzinfo).replace(microsecond=0)
    return now
 def make_dates(self):
     def_tz = default_timezone()
     now      = self.now      = DateTime(2012, 9,10,10,10, 0, def_tz)
     past     = self.past     = DateTime(2012, 9, 1,10,10, 0, def_tz)
     future   = self.future   = DateTime(2012, 9,20,10,10, 0, def_tz)
     far      = self.far      = DateTime(2012, 9,22,10,10, 0, def_tz)
     duration = self.duration = 0.1
     return (now, past, future, far, duration)
Example #19
0
def construct_icalendar(context, events):
    """Returns an icalendar.Calendar object.

    :param context: A content object, which is used for calendar details like
                    Title and Description. Usually a container, collection or
                    the event itself.

    :param events: The list of event objects, which are included in this
                   calendar.
    """
    cal = icalendar.Calendar()
    cal.add('prodid', PRODID)
    cal.add('version', VERSION)

    cal_tz = default_timezone(context)
    if cal_tz:
        cal.add('x-wr-timezone', cal_tz)

    tzmap = {}
    if not hasattr(events, '__getslice__'):  # LazyMap doesn't have __iter__
        events = [events]
    for event in events:
        if ICatalogBrain.providedBy(event) or\
                IContentListingObject.providedBy(event):
            event = event.getObject()
        acc = IEventAccessor(event)
        tz = acc.timezone
        # TODO: the standard wants each recurrence to have a valid timezone
        # definition. sounds decent, but not realizable.
        if not acc.whole_day:  # whole day events are exported as dates without
                               # timezone information
            tzmap = add_to_zones_map(tzmap, tz, acc.start)
            tzmap = add_to_zones_map(tzmap, tz, acc.end)
        cal.add_component(IICalendarEventComponent(event).to_ical())

    for (tzid, transitions) in tzmap.items():
        cal_tz = icalendar.Timezone()
        cal_tz.add('tzid', tzid)
        cal_tz.add('x-lic-location', tzid)

        for (transition, tzinfo) in transitions.items():

            if tzinfo['dst']:
                cal_tz_sub = icalendar.TimezoneDaylight()
            else:
                cal_tz_sub = icalendar.TimezoneStandard()

            cal_tz_sub.add('tzname', tzinfo['name'])
            cal_tz_sub.add('dtstart', transition)
            cal_tz_sub.add('tzoffsetfrom', tzinfo['tzoffsetfrom'])
            cal_tz_sub.add('tzoffsetto', tzinfo['tzoffsetto'])
            # TODO: add rrule
            # tzi.add('rrule',
            #         {'freq': 'yearly', 'bymonth': 10, 'byday': '-1su'})
            cal_tz.add_component(cal_tz_sub)
        cal.add_component(cal_tz)

    return cal
Example #20
0
def construct_icalendar(context, events):
    """Returns an icalendar.Calendar object.

    :param context: A content object, which is used for calendar details like
                    Title and Description. Usually a container, collection or
                    the event itself.

    :param events: The list of event objects, which are included in this
                   calendar.
    """
    cal = icalendar.Calendar()
    cal.add('prodid', PRODID)
    cal.add('version', VERSION)

    cal_tz = default_timezone(context)
    if cal_tz:
        cal.add('x-wr-timezone', cal_tz)

    tzmap = {}
    if not hasattr(events, '__getslice__'):  # LazyMap doesn't have __iter__
        events = [events]
    for event in events:
        if ICatalogBrain.providedBy(event) or\
                IContentListingObject.providedBy(event):
            event = event.getObject()
        acc = IEventAccessor(event)
        tz = acc.timezone
        # TODO: the standard wants each recurrence to have a valid timezone
        # definition. sounds decent, but not realizable.
        if not acc.whole_day:  # whole day events are exported as dates without
            # timezone information
            tzmap = add_to_zones_map(tzmap, tz, acc.start)
            tzmap = add_to_zones_map(tzmap, tz, acc.end)
        cal.add_component(IICalendarEventComponent(event).to_ical())

    for (tzid, transitions) in tzmap.items():
        cal_tz = icalendar.Timezone()
        cal_tz.add('tzid', tzid)
        cal_tz.add('x-lic-location', tzid)

        for (transition, tzinfo) in transitions.items():

            if tzinfo['dst']:
                cal_tz_sub = icalendar.TimezoneDaylight()
            else:
                cal_tz_sub = icalendar.TimezoneStandard()

            cal_tz_sub.add('tzname', tzinfo['name'])
            cal_tz_sub.add('dtstart', transition)
            cal_tz_sub.add('tzoffsetfrom', tzinfo['tzoffsetfrom'])
            cal_tz_sub.add('tzoffsetto', tzinfo['tzoffsetto'])
            # TODO: add rrule
            # tzi.add('rrule',
            #         {'freq': 'yearly', 'bymonth': 10, 'byday': '-1su'})
            cal_tz.add_component(cal_tz_sub)
        cal.add_component(cal_tz)

    return cal
Example #21
0
 def make_dates(self):
     def_tz = default_timezone()
     now = self.now = DateTime(2013, 5, 5, 10, 0, 0, def_tz)
     self.tomorrow = DateTime(2013, 5, 6, 10, 0, 0, def_tz)
     past = self.past = DateTime(2013, 4, 25, 10, 0, 0, def_tz)
     future = self.future = DateTime(2013, 5, 15, 10, 0, 0, def_tz)
     far = self.far = DateTime(2013, 6, 4, 10, 0, 0, def_tz)
     duration = self.duration = 0.1
     return (now, past, future, far, duration)
    def create_fetch_entry(self, **kw):
        def_start = datetime.today().replace(second=0) + timedelta(days=10)
        def_end = def_start + timedelta(hours=1)
        defaults = {
            # from IDirectoryItem
            # description not used
            'title': '',

            # from IEventsDirectoryItem
            # submitter, submitter_email not used
            'short_description': '',
            'long_description': '',
            'image': '',
            'attachment_1': '',
            'attachment_2': '',
            'locality': '',
            'street': '',
            'housenumber': '',
            'zipcode': '',
            'town': '',
            'location_url': '',
            'event_url': '',
            'organizer': '',
            'contact_name': '',
            'contact_email': '',
            'contact_phone': '',
            'prices': '',
            'registration': '',

            # from IExternalEvent
            # source not used (filled in by ExternalEventImporter)
            'source_id': 'id-1',

            # From IEventBasic
            'start': def_start,
            'end': def_end,
            'whole_day': False,
            'timezone': default_timezone(),

            # From IEventRecurrence
            'recurrence': '',

            # additional attributes used to control import
            'fetch_id': 'fetch-1',
            'last_update': default_now().replace(microsecond=0),
            'latitude': '',
            'longitude': '',
            'cat1': set(),
            'cat2': set(),
        }

        for attr in defaults:
            if not attr in kw:
                kw[attr] = defaults[attr]

        return kw
Example #23
0
    def create_fetch_entry(self, **kw):
        def_start = datetime.today().replace(second=0) + timedelta(days=10)
        def_end = def_start + timedelta(hours=1)
        defaults = {
            # from IDirectoryItem
            # description not used
            'title': '',

            # from IEventsDirectoryItem
            # submitter, submitter_email not used
            'short_description': '',
            'long_description': '',
            'image': '',
            'attachment_1': '',
            'attachment_2': '',
            'locality': '',
            'street': '',
            'housenumber': '',
            'zipcode': '',
            'town': '',
            'location_url': '',
            'event_url': '',
            'organizer': '',
            'contact_name': '',
            'contact_email': '',
            'contact_phone': '',
            'prices': '',
            'registration': '',

            # from IExternalEvent
            # source not used (filled in by ExternalEventImporter)
            'source_id': 'id-1',

            # From IEventBasic
            'start': def_start,
            'end': def_end,
            'whole_day': False,
            'timezone': default_timezone(),

            # From IEventRecurrence
            'recurrence': '',

            # additional attributes used to control import
            'fetch_id': 'fetch-1',
            'last_update': default_now().replace(microsecond=0),
            'latitude': '',
            'longitude': '',
            'cat1': set(),
            'cat2': set(),
        }

        for attr in defaults:
            if not attr in kw:
                kw[attr] = defaults[attr]

        return kw
Example #24
0
    def setUp(self):
        self.portal = self.layer['portal']
        self.app = self.layer['app']
        self.request = self.layer['request']
        set_browserlayer(self.request)

        reg = zope.component.getUtility(IRegistry)
        settings = reg.forInterface(IEventSettings, prefix="plone.app.event")
        default_tz = default_timezone()
        settings.portal_timezone = default_tz

        now, past, future, far, duration = self.make_dates()
        setRoles(self.portal, TEST_USER_ID, ['Manager'])

        factory = self.event_factory()
        self.past_event = factory(
            container=self.portal,
            content_id='past',
            title=u'Past Event',
            start=past,
            end=past + duration,
            location=u"Vienna",
            timezone=default_tz,
            recurrence='RRULE:FREQ=DAILY;COUNT=3',
            ).context

        self.now_event = factory(
            container=self.portal,
            content_id='now',
            title=u'Now Event',
            start=now,
            end=now + duration,
            location=u"Vienna",
            timezone=default_tz,
            recurrence='RRULE:FREQ=DAILY;COUNT=3;INTERVAL=2',
            ).context

        self.future_event = factory(
            container=self.portal,
            content_id='future',
            title=u'Future Event',
            start=future,
            end=future + duration,
            location=u'Graz',
            timezone=default_tz).context

        self.portal.invokeFactory('Folder', 'sub', title=u'sub')
        self.long_event = factory(
            container=self.portal.sub,
            content_id='long',
            title=u'Long Event',
            start=past,
            end=far,
            location=u'Schaftal',
            timezone=default_tz).context
Example #25
0
    def setUp(self):
        self.portal = self.layer['portal']
        self.app = self.layer['app']
        self.request = self.layer['request']
        set_browserlayer(self.request)

        reg = zope.component.getUtility(IRegistry)
        settings = reg.forInterface(IEventSettings, prefix="plone.app.event")
        default_tz = default_timezone()
        settings.portal_timezone = default_tz

        now, past, future, far, duration = self.make_dates()
        setRoles(self.portal, TEST_USER_ID, ['Manager'])

        factory = self.event_factory()
        self.past_event = factory(
            container=self.portal,
            content_id='past',
            title=u'Past Event',
            start=past,
            end=past + duration,
            location=u"Vienna",
            timezone=default_tz,
            recurrence='RRULE:FREQ=DAILY;COUNT=3',
        ).context

        self.now_event = factory(
            container=self.portal,
            content_id='now',
            title=u'Now Event',
            start=now,
            end=now + duration,
            location=u"Vienna",
            timezone=default_tz,
            recurrence='RRULE:FREQ=DAILY;COUNT=3;INTERVAL=2',
        ).context

        self.future_event = factory(container=self.portal,
                                    content_id='future',
                                    title=u'Future Event',
                                    start=future,
                                    end=future + duration,
                                    location=u'Graz',
                                    timezone=default_tz).context

        self.portal.invokeFactory('Folder', 'sub', title=u'sub')
        self.long_event = factory(container=self.portal.sub,
                                  content_id='long',
                                  title=u'Long Event',
                                  start=past,
                                  end=far,
                                  location=u'Schaftal',
                                  timezone=default_tz).context
    def create_fetch_entry(self, **kw):
        def_start = datetime.today().replace(second=0) + timedelta(days=10)
        def_end = def_start + timedelta(hours=1)
        defaults = {
            # from IDirectoryItem
            # description not used
            "title": "",
            # from IEventsDirectoryItem
            # submitter, submitter_email not used
            "short_description": "",
            "long_description": "",
            "image": "",
            "attachment_1": "",
            "attachment_2": "",
            "locality": "",
            "street": "",
            "housenumber": "",
            "zipcode": "",
            "town": "",
            "location_url": "",
            "event_url": "",
            "organizer": "",
            "contact_name": "",
            "contact_email": "",
            "contact_phone": "",
            "prices": "",
            "registration": "",
            # from IExternalEvent
            # source not used (filled in by ExternalEventImporter)
            "source_id": "id-1",
            # From IEventBasic
            "start": def_start,
            "end": def_end,
            "whole_day": False,
            "timezone": default_timezone(),
            # From IEventRecurrence
            "recurrence": "",
            # additional attributes used to control import
            "fetch_id": "fetch-1",
            "last_update": default_now().replace(microsecond=0),
            "latitude": "",
            "longitude": "",
            "cat1": set(),
            "cat2": set(),
        }

        for attr in defaults:
            if attr not in kw:
                kw[attr] = defaults[attr]

        return kw
Example #27
0
    def create(self, data):
        data['timezone'] = default_timezone()

        self.prepare_coordinates(data)
        self.prepare_submission(data)

        content = createContent('seantis.dir.events.item', **data)

        if IAcquirer.providedBy(content):
            content = content.__of__(aq_inner(self.context))

        # must be done before adding to the container
        self.apply_submission(content)

        return aq_base(content)
Example #28
0
    def create(self, data):
        data['timezone'] = default_timezone()

        self.prepare_coordinates(data)
        self.prepare_submission(data)

        content = createContent('seantis.dir.events.item', **data)

        if IAcquirer.providedBy(content):
            content = content.__of__(aq_inner(self.context))

        # must be done before adding to the container
        self.apply_submission(content)

        return aq_base(content)
Example #29
0
    def calendar_timezone(self):
        """http://tools.ietf.org/html/rfc4791#section-5.2.2
        """
        try:
            from plone.app.event.base import default_timezone
        except ImportError:
            timezone = 'UTC'
        else:
            timezone = default_timezone()

        calendar = icalendar.Calendar()
        tzc = icalendar.Timezone()
        tzc.add('tzid', timezone)
        calendar.add_component(tzc)
        return calendar.to_ical()
Example #30
0
 def extract(self, default=NO_VALUE):
     value = self.request.get(self.name, default)
     if value == default:
         return default
     if isinstance(value, str):
         # Date only
         date = datetime(value, '%Y-%m-%d')
     if value[0] == u'':
         return None
     else:
         if value[1] == u'':
             value[1] = u'0:00'
         time_str = "{0} {1}".format(*value)
         date = datetime.strptime(time_str, '%Y-%m-%d %H:%M')
     date = date.replace(
         tzinfo=default_timezone(self.context, as_tzinfo=True))
     return date
Example #31
0
    def extract(self, default=NO_VALUE):
        value = self.request.get(self.name, default)
        if (value == default or not value
                or not isinstance(value, basestring)):
            return None

        date = datetime.strptime(value, '%Y-%m-%d')

        timezone_name = default_timezone(self.context)
        if isinstance(timezone_name, unicode):
            timezone_name.encode('utf8')

        # pytz "does not work" with datetime tzinfo. Use localize instead
        # Symptoms are times in "LMT" format which are off a few minutes.
        # http://stackoverflow.com/questions/24856643/unexpected-results-converting-timezones-in-python  # noqa
        tz = timezone(timezone_name)
        date = tz.localize(date)
        return date
Example #32
0
    def __call__(self, value):
        # Datetime fields may contain timezone naive or timezone aware
        # objects. Unfortunately the zope.schema.Datetime field does not
        # contain any information if the field value should be timezone naive
        # or timezone aware. While some fields (start, end) store timezone
        # aware objects others (effective, expires) store timezone naive
        # objects.
        # We try to guess the correct deserialization from the current field
        # value.

        if value is None:
            self.field.validate(value)
            return

        # get tz from already stored value
        dm = queryMultiAdapter((self.context, self.field), IDataManager)
        current = dm.get()
        if current is not None:
            tzinfo = current.tzinfo
        else:
            # this is the patch
            tzinfo = pytz.timezone(default_timezone())

        # Parse ISO 8601 string with dateutil
        try:
            dt = dateutil.parser.parse(value)
        except ValueError:
            raise ValueError(u"Invalid date: {}".format(value))

        # Convert to TZ aware in UTC
        if dt.tzinfo is not None:
            dt = dt.astimezone(utc)
        else:
            dt = utc.localize(dt)

        # Convert to local TZ aware or naive UTC
        if tzinfo is not None:
            tz = timezone(tzinfo.zone)
            value = tz.normalize(dt.astimezone(tz))
        else:
            value = utc.normalize(dt.astimezone(utc)).replace(tzinfo=None)

        self.field.validate(value)
        return value
Example #33
0
def data_postprocessing(start, end, whole_day, open_end):
    """Adjust start and end according to whole_day and open_end setting.
    """

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

        return dt.replace(microsecond=0)

    tz_default = default_timezone()

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

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

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

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

        return dt.replace(microsecond=0)

    tz_default = default_timezone()

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

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

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

    return start, end, whole_day, open_end
Example #35
0
 def extract(self, default=NO_VALUE):
     value = self.request.get(self.name, default)
     if value == default:
         return default
     if isinstance(value, str):
         # Date only
         date = datetime(value, '%Y-%m-%d')
     if value[0] == u'':
         return None
     else:
         if value[1] == u'':
             value[1] = u'0:00'
         time_str = "{0} {1}".format(*value)
         date = datetime.strptime(time_str, '%Y-%m-%d %H:%M')
     timezone_name = (self.request.get('%s-timezone' % self.name, '')
                      or self.request.get('timezone', '')
                      or default_timezone(self.context))
     if isinstance(timezone_name, unicode):
         timezone_name.encode('utf8')
     date = date.replace(tzinfo=timezone(timezone_name))
     return date
Example #36
0
 def get_dates_status_error(self):
     open_date = getattr(self.context, "open_date", None)
     close_date = getattr(self.context, "close_date", None)
     now = datetime.now(pytz.timezone(default_timezone()))
     res = {}
     if not open_date and not close_date:
         return res
     if not open_date:
         if now > close_date:
             res["status_message"] = self.close_past_msg(close_date)
         return res
     elif not close_date:
         if now < open_date:
             res["status_message"] = self.open_future_msg(open_date)
         return res
     if not open_date < now < close_date:
         # too early or too late
         res["active"] = False
         if open_date > now:
             res["status_message"] = self.open_future_msg(open_date)
         elif close_date < now:
             res["status_message"] = self.close_past_msg(close_date)
     return res
 def extract(self, default=NO_VALUE):
     value = self.request.get(self.name, default)
     if value == default:
         return default
     if isinstance(value, str):
         # Date only
         date = datetime(value, '%Y-%m-%d')
     if value[0] == u'':
         return None
     else:
         if value[1] == u'':
             value[1] = u'0:00'
         time_str = "{0} {1}".format(*value)
         date = datetime.strptime(time_str, '%Y-%m-%d %H:%M')
     timezone_name = (
         self.request.get('%s-timezone' % self.name, '')
         or self.request.get('timezone', '')
         or default_timezone(self.context)
     )
     if isinstance(timezone_name, unicode):
         timezone_name.encode('utf8')
     date = date.replace(tzinfo=timezone(timezone_name))
     return date
Example #38
0
    def fix_start_end(self):
        ''' If the start date is lower than the end one,
        modify the request setting end = start + 1 hour
        '''
        localized_start = DateTime(
            '%s %s' % (' '.join(self.request.get('start')),
                       self.request.get('timezone', default_timezone())))
        localized_end = localized_start + 1. / 24
        # If you know a smarter way to hijack the request,
        # please modify the following lines:)
        self.request.end = [
            localized_end.strftime('%Y-%m-%d'),
            localized_end.strftime('%H:%M'),
        ]
        self.request.form['end'] = self.request.end
        self.request.other['end'] = self.request.end

        ts = api.portal.get_tool('translation_service')
        msg = _('dates_hijacked',
                default=(u'Start time should be lower than end time. '
                         u'The system set the end time to: ${date}'),
                mapping={u'date': ts.toLocalizedTime(localized_end)})
        api.portal.show_message(msg, request=self.request, type="warning")
Example #39
0
    def fix_start_end(self):
        ''' If the start date is lower than the end one,
        modify the request setting end = start + 1 hour
        '''
        localized_start = DateTime(
            '%s %s' % (
                ' '.join(self.request.get('start')),
                self.request.get('timezone', default_timezone())
            )
        )
        localized_end = localized_start + 1. / 24
        # If you know a smarter way to hijack the request,
        # please modify the following lines:)
        self.request.end = [
            localized_end.strftime('%Y-%m-%d'),
            localized_end.strftime('%H:%M'),
        ]
        self.request.form['end'] = self.request.end
        self.request.other['end'] = self.request.end

        ts = api.portal.get_tool('translation_service')
        msg = _(
            'dates_hijacked',
            default=(
                u'Start time should be lower than end time. '
                u'The system set the end time to: ${date}'
            ),
            mapping={
                u'date': ts.toLocalizedTime(localized_end)
            }
        )
        api.portal.show_message(
            msg,
            request=self.request,
            type="warning"
        )
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}
Example #41
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}
Example #42
0
def default_tz(data):
    return default_timezone()
Example #43
0
 def last_modified(self, value):
     tz = default_timezone(self.context, as_tzinfo=True)
     mod = DT(pydt(value, missing_zone=tz))
     setattr(self.context, 'modification_date', mod)
def add_timezone(dt, force=False):
    if HAS_PAE:
        TZ = default_timezone(as_tzinfo=True)
    if force or (not HAS_PLONE_5 and not dt.tzinfo):
        return TZ.localize(dt)
    return dt
Example #45
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}
Example #46
0
 def test__default_timezone(self):
     """Test, if default_timezone returns something other than None if
     called with and without a context.
     """
     self.assertTrue(default_timezone() is not None)
     self.assertTrue(default_timezone(context=self.portal) is not None)
Example #47
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}
    def setUp(self):
        self.portal = self.layer["portal"]
        default_tz = default_timezone()

        reg = zope.component.getUtility(IRegistry)
        settings = reg.forInterface(IEventSettings, prefix="plone.app.event")
        settings.portal_timezone = default_tz

        # Zope DateTime
        now = DateTime(2012, 9, 10, 10, 10, 0, default_tz)
        past = DateTime(2012, 9, 1, 10, 10, 0, default_tz)
        future = DateTime(2012, 9, 20, 10, 10, 0, default_tz)
        far = DateTime(2012, 9, 22, 10, 10, 0, default_tz)

        setRoles(self.portal, TEST_USER_ID, ["Manager"])

        self.portal.invokeFactory(
            "Event",
            "past",
            title=u"Past event",
            startDate=past,
            endDate=past + 0.1,  # Zope DT
            location=u"Vienna",
            timezone=default_tz,
            whole_day=False,
        )

        self.portal.invokeFactory(
            "Event",
            "now",
            title=u"Now event",
            startDate=now,
            endDate=now + 0.1,
            location=u"Vienna",
            recurrence="RRULE:FREQ=DAILY;COUNT=4;INTERVAL=4",
            timezone=default_tz,
            whole_day=False,
        )

        self.portal.invokeFactory(
            "Event",
            "future",
            title=u"Future event",
            startDate=future,
            endDate=future + 0.1,
            location=u"Graz",
            timezone=default_tz,
            whole_day=False,
        )

        self.portal.invokeFactory(
            "Event",
            "long",
            title=u"Long event",
            startDate=past,
            endDate=future,
            location=u"Schaftal",
            timezone=default_tz,
            whole_day=False,
        )

        self.now = now
        self.past = past
        self.future = future
        self.far = far

        self.now_event = self.portal["now"]
        self.past_event = self.portal["past"]
        self.future_event = self.portal["future"]
        self.long_event = self.portal["long"]
    def setUp(self):
        self.portal = self.layer["portal"]
        default_tz = default_timezone()
        # default_tz = 'Europe/Vienna'

        reg = zope.component.getUtility(IRegistry)
        settings = reg.forInterface(IEventSettings, prefix="plone.app.event")
        settings.portal_timezone = default_tz

        now = localized_now()
        past = now - datetime.timedelta(days=2)
        future = now + datetime.timedelta(days=2)
        far = now + datetime.timedelta(days=8)

        setRoles(self.portal, TEST_USER_ID, ["Manager"])

        self.portal.invokeFactory(
            "Event",
            "past",
            title=u"Past event",
            startDate=past,
            endDate=past + datetime.timedelta(hours=1),
            location=u"Vienna",
            timezone=default_tz,
            whole_day=False,
        )

        self.portal.invokeFactory(
            "Event",
            "now",
            title=u"Now event",
            startDate=now,
            endDate=now + datetime.timedelta(hours=1),
            location=u"Vienna",
            recurrence="RRULE:FREQ=DAILY;COUNT=4;INTERVAL=4",
            timezone=default_tz,
            whole_day=False,
        )

        self.portal.invokeFactory(
            "Event",
            "future",
            title=u"Future event",
            startDate=future,
            endDate=future + datetime.timedelta(hours=1),
            location=u"Graz",
            timezone=default_tz,
            whole_day=False,
        )

        self.portal.invokeFactory(
            "Event",
            "long",
            title=u"Long event",
            startDate=past,
            endDate=future,
            location=u"Schaftal",
            timezone=default_tz,
            whole_day=False,
        )

        self.now = now
        self.past = past
        self.future = future
        self.far = far

        self.now_event = self.portal["now"]
        self.past_event = self.portal["past"]
        self.future_event = self.portal["future"]
        self.long_event = self.portal["long"]
Example #50
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}
Example #51
0
    def test_default_timezone(self):
        self.assertTrue(os_default_timezone() == default_timezone() == 'UTC')

        registry = getUtility(IRegistry)
        registry['plone.portal_timezone'] = "Europe/Vienna"
        self.assertTrue(default_timezone() == 'Europe/Vienna')
Example #52
0
def default_tz(data):
    return default_timezone()
Example #53
0
 def last_modified(self, value):
     tz = default_timezone(self.context, as_tzinfo=True)
     mod = DT(pydt(value, missing_zone=tz))
     setattr(self.context, 'modification_date', mod)
Example #54
0
    def import_csv(self, action):

        # Extract form field values and errors from HTTP request
        data, errors = self.extractData()
        if errors:
            self.status = self.formErrorsMessage
            return

        # Delete all existing events
        self.context.manage_delObjects(self.context.objectIds())

        workflow_tool = getToolByName(self.context, 'portal_workflow')
        fieldmap = get_map(self.context)

        io = StringIO(data['csv_file'].data)
        reader = unicode_csv_reader(io, delimiter=';', quotechar='"')

        # Skip header line
        reader.next()
        counter = 0
        for row in reader:

            attributes = dict()
            for attr, ix in fieldmap.fieldmap.items():
                if not attr in fieldmap.readonly:
                    value = row[ix]
                    unwrapped = fieldmap.get_unwrapper(ix=ix)(value)
                    attributes[attr] = unwrapped

            # Unmapped fields are filled with defaults
            add_defaults(attributes, fieldmap)

            # Adjust coordinates
            coordinates = attributes['coordinates_json']
            if coordinates:
                coordinates = coordinates.replace("'", '"')

                cords = json.loads(coordinates)
                latitude = cords[1][0]
                longitude = cords[1][1]
                cords[1][0] = longitude
                cords[1][1] = latitude
                attributes['coordinates_json'] = json.dumps(cords)

            # "What" category
            cats1 = []
            if attributes['cat1']:
                for cat in attributes['cat1']:
                    if cat:
                        cats1.append(categories1[cat])
            attributes['cat1'] = cats1

            # "Where" category
            if attributes['town']:
                attributes['town'] = categories2.get(
                    attributes['town'], attributes['town']
                )
                attributes['cat2'] = [attributes['town']]

            # Manipulate some attributes
            attributes['timezone'] = default_timezone()
            attributes['long_description'] = self.readable_html(
                attributes['long_description']
            )

            # Fetch image form URL
            image_url = row[-3]
            if image_url:
                response = urllib2.urlopen(image_url)
                image = response.read()
                attributes['image'] = NamedImage(image)

            # Fetach PDF from URL
            pdf_url = row[-2]
            if pdf_url:
                response = urllib2.urlopen(pdf_url)
                pdf_file = response.read()
                attributes['attachment_1'] = NamedFile(pdf_file)

            # Create event
            event = createContentInContainer(
                self.context, fieldmap.typename, **attributes
            )

            # Log the events which span multiple days, they need to manually
            # adjusted by the client
            if attributes['start'].date() != attributes['end'].date():
                log.info(
                    '"%s" spans multiple days' % event.absolute_url()
                )

            # Publish event
            workflow_tool.doActionFor(event, 'submit')
            workflow_tool.doActionFor(event, 'publish')
            counter += 1

        self.status = u'Imported %s events' % counter