This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
tabellio.icalimport/tabellio/icalimport/icalimport.py

141 lines
5.7 KiB
Python

# -*- coding: utf-8 -*-
import datetime
import urllib2
import vobject
import rfc822
from zope import component
from zope.event import notify
from zope.lifecycleevent import ObjectAddedEvent, ObjectModifiedEvent
from Products.CMFCore.WorkflowCore import WorkflowException
from Products.CMFCore.utils import getToolByName
from Products.Five.browser import BrowserView
from plone.registry.interfaces import IRegistry
from plone.app.textfield.value import RichTextValue
from tabellio.config.interfaces import ITabellioSettings
class IcalImport(BrowserView):
def __call__(self):
self.settings = component.getUtility(IRegistry).forInterface(ITabellioSettings, False)
self.plone_utils = getToolByName(self.context, 'plone_utils')
self.portal_workflow = getToolByName(self.context, 'portal_workflow')
url = self.request.form.get('url')
if self.settings.ical_username:
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, url,
self.settings.ical_username, self.settings.ical_password)
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
opener = urllib2.build_opener(handler)
urlopen = opener.open
else:
urlopen = urllib2.urlopen
content = urlopen(url).read()
parsed_cal = vobject.readOne(content)
seen_ids = []
created = 0
updated = 0
for vevent in parsed_cal.vevent_list:
title = vevent.summary.value
date_start = vevent.dtstart.value
if isinstance(date_start, datetime.datetime):
date_start = datetime.datetime(*date_start.timetuple()[:6])
elif isinstance(date_start, datetime.date):
date_start = datetime.datetime.fromordinal(date_start.toordinal())
try:
date_end = vevent.dtend.value
if isinstance(date_end, datetime.datetime):
date_end = datetime.datetime(*date_end.timetuple()[:6])
elif isinstance(date_end, datetime.date):
date_end = datetime.datetime.fromordinal(date_end.toordinal())
except:
date_end = None
location = None
try:
location = vevent.location.value
except:
pass
if location and '@' in location:
try:
location, email = rfc822.parseaddr(location)
except:
pass
if location:
# some special substitutions for PCF common locations
location = location.replace(u'HL ', u'Hôtel de Ligne - ')
location = location.replace(u'HG ', u'Hôtel du Greffe - ')
base_id = '%(year)04d%(month)02d%(day)02d'
base_parts = {'year': date_start.year,
'month': date_start.month,
'day': date_start.day}
if date_start.hour != 0 and date_start.minute != 0:
base_id = base_id + '-%(hour)02d%(minute)02d'
base_parts.update({'hour': date_start.hour,
'minute': date_start.minute})
if location:
base_id = base_id + '-%(location)s'
base_parts.update({'location': location})
if title:
base_id = base_id + '-%(title)s'
base_parts.update({'title': title})
event_id = self.plone_utils.normalizeString(base_id % base_parts)
if not hasattr(self.context, event_id):
self.context.invokeFactory('tabellio.agenda.event', event_id, title=title)
created += 1
else:
updated += 1
event = getattr(self.context, event_id)
event.title = title
event.start = date_start
event.end = date_end
description = None
try:
description = vevent.description.value
except:
pass
if description:
event.text = RichTextValue(raw=description,
mimeType='text/plain', outputMimeType='text/x-html-safe')
description = None
try:
description = vevent.x_alt_desc.value
except:
pass
if description and type(description) is unicode:
# there's some bug in the transformation chain that will at
# the wrong time convert the &nbsp and turn it into an invalid
# utf-8 character, therefore we replace all of them right now,
# as it's safe to do.
description = description.replace(' ', u'\xa0')
if description:
if '*~*~*~*~*~*~*~*~*~*' in description: # zimbra lovely marker
description = '<html><body>' + \
description[description.index('*~*~*~*~*~*~*~*~*~*')+29:]
event.text = RichTextValue(raw=description,
mimeType='text/html', outputMimeType='text/x-html-safe')
event.place = location
notify(ObjectModifiedEvent(event))
try:
self.portal_workflow.doActionFor(getattr(self.context, event_id), 'publish')
except WorkflowException:
pass
seen_ids.append(event.id)
deleted = 0
for event_id in self.context.objectIds():
if event_id in seen_ids:
continue
self.context.manage_delObjects(event_id)
deleted += 1
return 'OK (created: %s, updated: %s, deleted: %s)' % (created, updated, deleted)