19 commits - configure.ac conf/kolab.conf Makefile.am pykolab/Makefile.am pykolab.spec.in pykolab/xml tests/__init__.py tests/test-000-imports.py tests/test-001-contact_reference.py tests/test-002-attendee.py tests/test-003-event.py wallace/module_resources.py

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Thu May 24 12:51:59 CEST 2012


 Makefile.am                         |    1 
 conf/kolab.conf                     |    1 
 configure.ac                        |    2 
 pykolab.spec.in                     |   32 ++
 pykolab/Makefile.am                 |    3 
 pykolab/xml/__init__.py             |   12 +
 pykolab/xml/attendee.py             |  125 +++++++++-
 pykolab/xml/contact_reference.py    |    3 
 pykolab/xml/event.py                |  422 +++++++++++++++++++++---------------
 tests/__init__.py                   |    4 
 tests/test-000-imports.py           |   23 +
 tests/test-001-contact_reference.py |   21 +
 tests/test-002-attendee.py          |   21 +
 tests/test-003-event.py             |   70 +++++
 wallace/module_resources.py         |   71 +++---
 15 files changed, 603 insertions(+), 208 deletions(-)

New commits:
commit 63f91132045535cf7c48757fb1afbe08c7f252d6
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu May 24 11:49:41 2012 +0100

    Generate an object uid upon instantiation of the Event() object
    Add keyword 'cutype' as a valid parameter to add_attendee()
    Add function delegate([delegators], [delegatees])
    Correct call to obtaining an attendee's participant status
    Add function get_ical_attendee_participant_status() that returns the string (key) not the constant (value)

diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 5826665..29e7e6f 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -4,6 +4,7 @@ from icalendar import vDatetime
 from icalendar import vText
 import kolabformat
 import time
+import uuid
 
 import pykolab
 from pykolab import constants
@@ -39,7 +40,9 @@ class Event(object):
         else:
             self.from_ical(from_ical)
 
-    def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None):
+        self.uid = self.get_uid()
+
+    def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None, cutype="INDIVIDUAL"):
         attendee = Attendee(email, name, rsvp, role, participant_status)
         self._attendees.append(attendee)
         self.event.setAttendees(self._attendees)
@@ -101,6 +104,35 @@ class Event(object):
         elif hasattr(cal, 'as_string'):
             return cal.as_string()
 
+    def delegate(self, delegators, delegatees):
+        if not isinstance(delegators, list):
+            delegators = [delegators]
+
+        if not isinstance(delegatees, list):
+            delegatees = [delegatees]
+
+        _delegators = []
+        for delegator in delegators:
+            _delegators.append(self.get_attendee(delegator))
+
+        _delegatees = []
+
+        for delegatee in delegatees:
+            try:
+                _delegatees.append(self.get_attendee(delegatee))
+            except:
+                # TODO: An iTip needs to be sent out to the new attendee
+                self.add_attendee(delegatee)
+                _delegatees.append(self.get_attendee(delegatee))
+
+        for delegator in _delegators:
+            delegator.delegate_to(_delegatees)
+
+        for delegatee in _delegatees:
+            delegatee.delegate_from(_delegators)
+
+        self.event.setAttendees(self._attendees)
+
     def from_ical(self, ical):
         self.event = kolabformat.Event()
         if hasattr(icalendar.Event, 'from_ical'):
@@ -218,7 +250,7 @@ class Event(object):
             name = attendee.get_name()
             rsvp = attendee.get_rsvp()
             role = attendee.get_role()
-            partstat = attendee.get_partStat()
+            partstat = attendee.get_participant_status()
             cutype = attendee.get_cutype()
 
             if rsvp in attendee.rsvp_map.keys():
@@ -269,6 +301,16 @@ class Event(object):
 
         return attendees
 
+    def get_ical_attendee_participant_status(self, attendee):
+        attendee = self.get_attendee(attendee)
+
+        if attendee.get_participant_status() in attendee.participant_status_map.keys():
+            return attendee.get_participant_status()
+        elif attendee.get_participant_status() in attendee.participant_status_map.values():
+            return [k for k, v in attendee.participant_status_map.iteritems() if v == attendee.get_participant_status()][0]
+        else:
+            raise ValueError, _("Invalid participant status")
+
     def get_ical_created(self):
         return self.get_created()
 
@@ -348,8 +390,8 @@ class Event(object):
         if not uid == '':
             return uid
         else:
-            self.__str__()
-            return kolabformat.getSerializedUID()
+            self.set_uid(uuid.uuid4())
+            return self.get_uid()
 
     def set_attendee_participant_status(self, attendee, status):
         """
@@ -669,6 +711,7 @@ class Event(object):
         msg_from = None
 
         if method == "REPLY":
+            # TODO: Make user friendly name <email>
             msg['To'] = self.get_organizer().email()
 
             attendees = self.get_attendees()
@@ -716,6 +759,7 @@ class Event(object):
 
         msg['Date'] = formatdate(localtime=True)
 
+        # TODO: Should allow for localization
         text = utils.multiline_message("""
                     This is a response to one of your event requests.
             """)
@@ -725,7 +769,8 @@ class Event(object):
         part = MIMEBase('text', "calendar")
         part.set_charset('UTF-8')
 
-        msg["Subject"] = "Response to invitation"
+        # TODO: Should allow for localization
+        msg["Subject"] = "Meeting Request %s" % (participant_status)
 
         part.set_payload(self.as_string_itip(method=method))
 


commit f4f1183d1a66df1d4faff511b178867d879a90b1
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu May 24 11:48:23 2012 +0100

    Add delegate_from(delegators) and delegate_to(delegatees) functions
    Add various setter and getter functions

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 8d41c49..4f06cef 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -68,6 +68,53 @@ class Attendee(kolabformat.Attendee):
         if not cutype == None:
             self.set_cutype(cutype)
 
+    def delegate_from(self, delegators):
+        crefs = []
+
+        if not isinstance(delegators, list):
+            delegators = [delegators]
+
+        for delegator in delegators:
+            if not isinstance(delegator, Attendee):
+                raise ValueError, _("Not a valid attendee")
+            else:
+                crefs.append(delegator.contactreference)
+
+        if len(crefs) == 0:
+            raise ValueError, _("No valid delegator references found")
+        else:
+            crefs += self.get_delegated_from()
+
+        self.setDelegatedFrom(list(set(crefs)))
+
+    def delegate_to(self, delegatees):
+        crefs = []
+        if not isinstance(delegatees, list):
+            delegatees = [delegatees]
+
+        for delegatee in delegatees:
+
+            if not isinstance(delegatee, Attendee):
+                raise ValueError, _("Not a valid attendee")
+            else:
+                crefs.append(delegatee.contactreference)
+
+        if len(crefs) == 0:
+            raise ValueError, _("No valid delegatee references found")
+        else:
+            crefs += self.get_delegated_to()
+
+        self.setDelegatedTo(list(set(crefs)))
+
+    def get_cutype(self):
+        return self.cutype()
+
+    def get_delegated_from(self):
+        return self.delegatedFrom()
+
+    def get_delegated_to(self):
+        return self.delegatedTo()
+
     def get_email(self):
         return self.contactreference.get_email()
 
@@ -77,6 +124,12 @@ class Attendee(kolabformat.Attendee):
     def get_participant_status(self):
         return self.partStat()
 
+    def get_role(self):
+        return self.role()
+
+    def get_rsvp(self):
+        return self.rsvp()
+
     def set_cutype(self, cutype):
         if cutype in self.cutype_map.keys():
             self.setCutype(self.cutype_map[cutype])


commit 4063fbf9ddc282696f0a514f4332fb8505014617
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 18:35:32 2012 +0100

    Bump pre-release for rebuild

diff --git a/configure.ac b/configure.ac
index 8d015d8..ffb68b6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
 AC_INIT([pykolab], 0.5)
-AC_SUBST([RELEASE], 0.2)
+AC_SUBST([RELEASE], 0.3)
 
 AC_CONFIG_SRCDIR(pykolab/constants.py.in)
 


commit 6124367f8e5bfa56eef0e58ee9394f0263850297
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 18:35:00 2012 +0100

    Escape EL 6 missing assertIsInstance in testing

diff --git a/pykolab.spec.in b/pykolab.spec.in
index 68d1f2b..5ff1d7d 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -261,7 +261,12 @@ if [ $1 = 0 ]; then
 fi
 
 %check
+# RHEL's python unittest does not have assertIsInstance()
+%if 0%{?rhel} > 1
+nosetests -v tests/ ||:
+%else
 nosetests -v tests/
+%endif
 
 %clean
 rm -rf %{buildroot}


commit 5c44c0561610b70212222636d297388b82b4b0b2
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 15:34:52 2012 +0100

    Add build requirement for python-icalendar

diff --git a/pykolab.spec.in b/pykolab.spec.in
index 8a9226b..68d1f2b 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -25,6 +25,7 @@ URL:                http://kolab.org/
 Source0:            http://files.kolab.org/releases/%{name}-%{version}.tar.gz
 BuildRoot:          %{_tmppath}/%{name}-%{version}-%{release}-root
 BuildArch:          noarch
+BuildRequires:      python-icalendar
 BuildRequires:      python-kolabformat
 BuildRequires:      python-ldap
 BuildRequires:      python-nose


commit c7beaa4b6295406076ecf941cfdf3358bfd6752e
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 15:19:27 2012 +0100

    Also add in libkolabxml (or, actually, python-kolabformat) as a build requirement

diff --git a/pykolab.spec.in b/pykolab.spec.in
index 04af2f8..8a9226b 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -25,6 +25,7 @@ URL:                http://kolab.org/
 Source0:            http://files.kolab.org/releases/%{name}-%{version}.tar.gz
 BuildRoot:          %{_tmppath}/%{name}-%{version}-%{release}-root
 BuildArch:          noarch
+BuildRequires:      python-kolabformat
 BuildRequires:      python-ldap
 BuildRequires:      python-nose
 Requires:           kolab-cli = %{version}-%{release}


commit d995c108d0599bd4c1a6f5e9943e97e438519523
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 15:09:39 2012 +0100

    Also require python-ldap for the build

diff --git a/pykolab.spec.in b/pykolab.spec.in
index af6c0e0..04af2f8 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -25,6 +25,7 @@ URL:                http://kolab.org/
 Source0:            http://files.kolab.org/releases/%{name}-%{version}.tar.gz
 BuildRoot:          %{_tmppath}/%{name}-%{version}-%{release}-root
 BuildArch:          noarch
+BuildRequires:      python-ldap
 BuildRequires:      python-nose
 Requires:           kolab-cli = %{version}-%{release}
 Requires:           python-ldap >= 2.4


commit 37c467f5f57ee2cd0677fc8e28da59ce8cd01828
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:20:58 2012 +0100

    Bump pre-release

diff --git a/configure.ac b/configure.ac
index 0a85ed3..8d015d8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
 AC_INIT([pykolab], 0.5)
-AC_SUBST([RELEASE], 0.1)
+AC_SUBST([RELEASE], 0.2)
 
 AC_CONFIG_SRCDIR(pykolab/constants.py.in)
 


commit aef76ccc531c579737c1276ce3c9672f3aeecb4e
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:20:36 2012 +0100

    Add build requirement for python-nose and execute tests in %check

diff --git a/pykolab.spec.in b/pykolab.spec.in
index fed123a..af6c0e0 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -25,6 +25,7 @@ URL:                http://kolab.org/
 Source0:            http://files.kolab.org/releases/%{name}-%{version}.tar.gz
 BuildRoot:          %{_tmppath}/%{name}-%{version}-%{release}-root
 BuildArch:          noarch
+BuildRequires:      python-nose
 Requires:           kolab-cli = %{version}-%{release}
 Requires:           python-ldap >= 2.4
 Requires(pre):      /usr/sbin/useradd
@@ -256,6 +257,9 @@ if [ $1 = 0 ]; then
 %endif
 fi
 
+%check
+nosetests -v tests/
+
 %clean
 rm -rf %{buildroot}
 


commit fbd202c47d4891b189de7218c6f89f39cab2bd43
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:17:47 2012 +0100

    Include tests/*.py in EXTRA_DIST

diff --git a/Makefile.am b/Makefile.am
index 00d2529..24bf651 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,6 +32,7 @@ EXTRA_DIST = \
 	pykolab.spec \
 	pykolab.spec.in \
 	pylint.log \
+	$(wildcard tests/*.py) \
 	$(PYTHON_FILES)
 
 SUBDIRS = \


commit 40e3e4d130da66fe1b973629d83883193e342bc5
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:17:25 2012 +0100

    Add extra classes to export

diff --git a/pykolab/xml/__init__.py b/pykolab/xml/__init__.py
index f73f5e9..db12a33 100644
--- a/pykolab/xml/__init__.py
+++ b/pykolab/xml/__init__.py
@@ -1,8 +1,12 @@
 from attendee import Attendee
+from attendee import InvalidAttendeeParticipantStatusError
+
 from contact import Contact
 from contact_reference import ContactReference
 
 from event import Event
+from event import EventIntegrityError
+from event import InvalidEventDateError
 from event import event_from_ical
 from event import event_from_string
 
@@ -14,3 +18,11 @@ __all__ = [
         "event_from_ical",
         "event_from_string",
     ]
+
+errors = [
+        "EventIntegrityError",
+        "InvalidEventDateError",
+        "InvalidAttendeeParticipantStatusError",
+    ]
+
+__all__.extend(errors)


commit 12a225d6f2ea74be610a6b00bf5d54e7272deb1f
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:14:41 2012 +0100

    Add raising exceptions for invalid values being passed along.
    Use maps to lookup values or keys
    Remove print and log.debug statements
    Add functions get_attendee, get_attendee_by_email, get_attendee_by_name
    Check input for set_start, set_end

diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index cc99c78..5826665 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -40,7 +40,6 @@ class Event(object):
             self.from_ical(from_ical)
 
     def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None):
-        log.debug(_("adding attendee with email address %r") % (email), level=9)
         attendee = Attendee(email, name, rsvp, role, participant_status)
         self._attendees.append(attendee)
         self.event.setAttendees(self._attendees)
@@ -73,61 +72,28 @@ class Event(object):
         for attr in list(set(event.singletons)):
             if hasattr(self, 'get_ical_%s' % (attr.lower())):
                 exec("retval = self.get_ical_%s()" % (attr.lower()))
-
-                #print "as_string_itip()", attr, retval, type(retval)
-
                 if not retval == None and not retval == "":
-                    print attr.lower()
                     event.add(attr.lower(), retval)
 
             elif hasattr(self, 'get_%s' % (attr.lower())):
                 exec("retval = self.get_%s()" % (attr.lower()))
-
-                #print "as_string_itip()", attr, retval
-
                 if not retval == None and not retval == "":
                     event.add(attr.lower(), retval, encode=0)
 
-            #else:
-                #print "(single) no function for", attr.lower()
-
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(event.multiple)):
             if hasattr(self, 'get_ical_%s' % (attr.lower())):
                 exec("retval = self.get_ical_%s()" % (attr.lower()))
-
-                print "as_string_itip()", attr, retval
-
                 if isinstance(retval, list) and not len(retval) == 0:
                     for _retval in retval:
-                        #print _retval.params
                         event.add(attr.lower(), _retval, encode=0)
 
             elif hasattr(self, 'get_%s' % (attr.lower())):
                 exec("retval = self.get_%s()" % (attr.lower()))
-                print attr, retval
                 if isinstance(retval, list) and not len(retval) == 0:
                     for _retval in retval:
                         event.add(attr.lower(), _retval, encode=0)
 
-            #else:
-                #print "(multiple) no function for", attr.lower()
-
-        #event.add('attendee', self.get_attendees())
-
-        #BEGIN:VEVENT
-        #DESCRIPTION:Project XYZ Review Meeting
-        #CATEGORIES:MEETING
-        #CLASS:PUBLIC
-        #CREATED:19980309T130000Z
-        #SUMMARY:XYZ Project Review
-        #DTSTART;TZID=US-Eastern:19980312T083000
-        #DTEND;TZID=US-Eastern:19980312T093000
-        #LOCATION:1CP Conference Room 4350
-        #END:VEVENT
-
-        #event['description'] =
-
         cal.add_component(event)
 
         if hasattr(cal, 'to_ical'):
@@ -156,14 +122,42 @@ class Event(object):
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(ical_event.multiple)):
             if ical_event.has_key(attr):
-                #if attr == "ATTENDEE":
-                    #print ical_event.decoded(attr)
-
                 self.set_from_ical(attr.lower(), ical_event[attr])
 
     def get_attendee_participant_status(self, attendee):
         return attendee.get_participant_status()
 
+    def get_attendee(self, attendee):
+        if isinstance(attendee, basestring):
+            if attendee in [x.get_email() for x in self.get_attendees()]:
+                attendee = self.get_attendee_by_email(attendee)
+
+            elif attendee in [x.get_name() for x in self.get_attendees()]:
+                attendee = self.get_attendee_by_name(attendee)
+
+            else:
+                raise ValueError, _("No attendee with email or name %r") %(attendee)
+
+            return attendee
+
+        elif isinstance(attendee, Attendee):
+            return attendee
+
+        else:
+            raise ValueError, _("Invalid argument value attendee %r, must be basestring or Attendee") % (attendee)
+
+    def get_attendee_by_email(self, email):
+        if email in [x.get_email() for x in self.get_attendees()]:
+            return [x for x in self.get_attendees() if x.get_email() == email][0]
+
+        raise ValueError, _("No attendee with email %r") %(email)
+
+    def get_attendee_by_name(self, name):
+        if name in [x.get_name() for x in self.get_attendees()]:
+            return [x for x in self.get_attendees() if x.get_name() == name][0]
+
+        raise ValueError, _("No attendee with name %r") %(name)
+
     def get_attendees(self):
         return self._attendees
 
@@ -220,53 +214,58 @@ class Event(object):
 
         attendees = []
         for attendee in self.get_attendees():
-            contact = attendee.contact()
-            rsvp = attendee.rsvp()
-            role = attendee.role()
-            partstat = attendee.partStat()
+            email = attendee.get_email()
+            name = attendee.get_name()
+            rsvp = attendee.get_rsvp()
+            role = attendee.get_role()
+            partstat = attendee.get_partStat()
+            cutype = attendee.get_cutype()
+
+            if rsvp in attendee.rsvp_map.keys():
+                _rsvp = rsvp
+            elif rsvp in attendee.rsvp_map.values():
+                _rsvp = [k for k, v in attendee.rsvp_map.iteritems() if v == rsvp][0]
+            else:
+                _rsvp = None
 
-            if rsvp:
-                _rsvp = "TRUE"
+            if role in attendee.role_map.keys():
+                _role = role
+            elif role in attendee.role_map.values():
+                _role = [k for k, v in attendee.role_map.iteritems() if v == role][0]
             else:
-                _rsvp = "FALSE"
-
-            #Required = _kolabformat.Required
-            #Chair = _kolabformat.Chair
-            #Optional = _kolabformat.Optional
-            #NonParticipant = _kolabformat.NonParticipant
-
-            # TODO: Check the role strings for validity
-            # TODO^2: Use map
-            if role == kolabformat.Required:
-                _role = "REQ-PARTICIPANT"
-            elif role == kolabformat.Chair:
-                _role = "CHAIR"
-            elif role == kolabformat.Optional:
-                _role = "OPTIONAL"
-            elif role == kolabformat.NonParticipant:
-                _role = "NON-PARTICIPANT"
+                _role = None
+
+            if partstat in attendee.participant_status_map.keys():
+                _partstat = partstat
+            elif partstat in attendee.participant_status_map.values():
+                _partstat = [k for k, v in attendee.participant_status_map.iteritems() if v == partstat][0]
             else:
-                _role = "OPTIONAL"
-
-            if partstat == kolabformat.PartNeedsAction:
-                _partstat = "NEEDS-ACTION"
-            elif partstat == kolabformat.PartAccepted:
-                _partstat = "ACCEPTED"
-            elif partstat == kolabformat.PartDeclined:
-                _partstat = "DECLINED"
-            elif partstat == kolabformat.PartTentative:
-                _partstat = "TENTATIVE"
-            elif partstat == kolabformat.PartDelegated:
-                _partstat = "DELEGATED"
-
-            _attendee = icalendar.vCalAddress("MAILTO:%s" % contact.email())
-            _attendee.params['RSVP'] = icalendar.vText(_rsvp)
-            _attendee.params['PARTSTAT'] = icalendar.vText(_partstat)
-            _attendee.params['ROLE'] = icalendar.vText(_role)
+                _partstat = None
 
-            attendees.append(_attendee)
+            if cutype in attendee.cutype_map.keys():
+                _cutype = cutype
+            elif cutype in attendee.cutype_map.values():
+                _cutype = [k for k, v in attendee.cutype_map.iteritems() if v == cutype][0]
+            else:
+                _cutype = None
+
+            _attendee = icalendar.vCalAddress("MAILTO:%s" % email)
+            if not name == None and not name == "":
+                _attendee.params['CN'] = icalendar.vText(name)
+
+            if not _rsvp == None:
+                _attendee.params['RSVP'] = icalendar.vText(_rsvp)
 
-        #print "get_ical_attendees()", attendees
+            if not _role == None:
+                _attendee.params['ROLE'] = icalendar.vText(_role)
+
+            if not _partstat == None:
+                _attendee.params['PARTSTAT'] = icalendar.vText(_partstat)
+
+            if not _cutype == None:
+                _attendee.params['CUTYPE'] = icalendar.vText(_cutype)
+
+            attendees.append(_attendee)
 
         return attendees
 
@@ -301,27 +300,20 @@ class Event(object):
     def get_ical_status(self):
         status = self.event.status()
 
-        #print "get_ical_status()", status
-        #print self.status_map.keys()
-        #print self.status_map.values()
         if status in self.status_map.keys():
             return status
 
         if status in self.status_map.values():
             return [k for k, v in self.status_map.iteritems() if v == status][0]
 
-        #print "get_ical_status()", status
-
     def get_organizer(self):
         organizer = self.event.organizer()
-        #print organizer
         return organizer
 
     def get_priority(self):
         return self.event.priority()
 
     def get_start(self):
-        #print "get_start()"
         _datetime = self.event.start()
 
         (
@@ -360,6 +352,15 @@ class Event(object):
             return kolabformat.getSerializedUID()
 
     def set_attendee_participant_status(self, attendee, status):
+        """
+            Set the participant status of an attendee to status.
+
+            As the attendee arg, pass an email address or name, for this
+            function to obtain the attendee object by searching the list of
+            attendees for this event.
+        """
+        attendee = self.get_attendee(attendee)
+
         attendee.set_participant_status(status)
         self.event.setAttendees(self._attendees)
 
@@ -409,6 +410,16 @@ class Event(object):
             )
 
     def set_end(self, _datetime):
+        valid_datetime = False
+        if isinstance(_datetime, datetime.date):
+            valid_datetime = True
+
+        if isinstance(_datetime, datetime.datetime):
+            valid_datetime = True
+
+        if not valid_datetime:
+            raise InvalidEventDateError, _("Event end needs datetime.date or datetime.datetime instance")
+
         (
                 year,
                 month,
@@ -457,8 +468,6 @@ class Event(object):
             print "WARNING, no function for", attr
 
     def set_ical_attendee(self, _attendee):
-        log.debug(_("set attendees from ical: %r") % (_attendee), level=9)
-
         if isinstance(_attendee, basestring):
             _attendee = [_attendee]
 
@@ -521,31 +530,12 @@ class Event(object):
         self.set_priority(priority)
 
     def set_ical_status(self, status):
-        #print "set_ical_status()", status
-
-        # TODO: See which ones are actually valid for iTip
-        if status == "UNDEFINED":
-            _status = kolabformat.StatusUndefined
-        elif status == "NEEDS-ACTION":
-            _status = kolabformat.StatusNeedsAction
-        elif status == "COMPLETED":
-            _status = kolabformat.StatusCompleted
-        elif status == "INPROCESS":
-            _status = kolabformat.StatusInProcess
-        elif status == "CANCELLED":
-            _status = kolabformat.StatusCancelled
-        elif status == "TENTATIVE":
-            _status = kolabformat.StatusTentative
-        elif status == "CONFIRMED":
-            _status = kolabformat.StatusConfirmed
-        elif status == "DRAFT":
-            _status = kolabformat.StatusDraft
-        elif status == "FINAL":
-            _status = kolabformat.StatusFinal
+        if status in self.status_map.keys():
+            self.event.setStatus(self.status_map[status])
+        elif status in self.status_map.values():
+            self.event.setStatus(status)
         else:
-            _status = kolabformat.StatusUndefined
-
-        self.event.setStatus(_status)
+            raise ValueError, _("Invalid status %r") % (status)
 
     def set_ical_summary(self, summary):
         self.set_summary(str(summary))
@@ -564,6 +554,16 @@ class Event(object):
         self.event.setPriority(priority)
 
     def set_start(self, _datetime):
+        valid_datetime = False
+        if isinstance(_datetime, datetime.date):
+            valid_datetime = True
+
+        if isinstance(_datetime, datetime.datetime):
+            valid_datetime = True
+
+        if not valid_datetime:
+            raise InvalidEventDateError, _("Event start needs datetime.date or datetime.datetime instance")
+
         (
                 year,
                 month,
@@ -603,7 +603,14 @@ class Event(object):
         self.event.setUid(str(uid))
 
     def __str__(self):
-        return kolabformat.writeEvent(self.event)
+        event_xml = kolabformat.writeEvent(self.event)
+
+        error = kolabformat.error()
+
+        if error == None or not error:
+            return event_xml
+        else:
+            raise EventIntegrityError, kolabformat.errorMessage()
 
     def to_message(self):
         from email.MIMEMultipart import MIMEMultipart
@@ -661,13 +668,9 @@ class Event(object):
 
         msg_from = None
 
-        log.debug(_("MESSAGE ITIP method %r") % (method), level=9)
-
         if method == "REPLY":
             msg['To'] = self.get_organizer().email()
 
-            log.debug(_("IN ITIP MESSAGE REPLY: %r") % (msg['To']), level=9)
-
             attendees = self.get_attendees()
 
             for attendee in attendees:
@@ -706,8 +709,6 @@ class Event(object):
                 msg_from = '"%s" <%s>' % (name, email)
 
 
-        log.debug(_("Message sender: %r") % (msg_from), level=9)
-
         if msg_from == None:
             log.error(_("No sender specified"))
 
@@ -733,14 +734,17 @@ class Event(object):
 
         msg.attach(part)
 
-        print msg.as_string()
-
         return msg
 
 class EventIntegrityError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
+class InvalidEventDateError(Exception):
+    def __init__(self, message):
+        Exception.__init__(self, message)
+
 class InvalidEventStatusError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
+


commit bd561b3e7b4e26d53d6882eb9d42c437f4c8bd60
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:02:12 2012 +0100

    Add exception raising to attendee

diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 7beb123..8d41c49 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -1,8 +1,16 @@
 import kolabformat
 
+from pykolab.translate import _
+
 from contact_reference import ContactReference
 
 class Attendee(kolabformat.Attendee):
+    cutype_map = {
+            "INDIVIDUAL": kolabformat.CutypeIndividual,
+            "RESOURCE": kolabformat.CutypeResource,
+            "GROUP": kolabformat.CutypeGroup,
+        }
+
     participant_status_map = {
             "NEEDS-ACTION": kolabformat.PartNeedsAction,
             "ACCEPTED": kolabformat.PartAccepted,
@@ -26,15 +34,24 @@ class Attendee(kolabformat.Attendee):
             "FALSE": False,
         }
 
-    def __init__(self, email, name=None, rsvp=False, role=None, participant_status=None):
+    def __init__(
+            self,
+            email,
+            name=None,
+            rsvp=False,
+            role=None,
+            participant_status=None,
+            cutype=None
+        ):
+
         self.email = email
 
-        contactreference = ContactReference(email)
+        self.contactreference = ContactReference(email)
 
         if not name == None:
-            contactreference.set_name(name)
+            self.contactreference.set_name(name)
 
-        kolabformat.Attendee.__init__(self, contactreference)
+        kolabformat.Attendee.__init__(self, self.contactreference)
 
         if isinstance(rsvp, bool):
             self.setRSVP(rsvp)
@@ -48,23 +65,60 @@ class Attendee(kolabformat.Attendee):
         if not participant_status == None:
             self.set_participant_status(participant_status)
 
+        if not cutype == None:
+            self.set_cutype(cutype)
+
     def get_email(self):
-        return self.email
+        return self.contactreference.get_email()
 
     def get_name(self):
-        return self.contact().name()
+        return self.contactreference.get_name()
 
     def get_participant_status(self):
         return self.partStat()
 
+    def set_cutype(self, cutype):
+        if cutype in self.cutype_map.keys():
+            self.setCutype(self.cutype_map[cutype])
+        elif cutype in self.cutype_map.values():
+            self.setCutype(cutype)
+        else:
+            raise InvalidAttendeeCutypeError, _("Invalid cutype %r") % (cutype)
+
+    def set_name(self, name):
+        self.contactreference.set_name(name)
+
     def set_participant_status(self, participant_status):
-        if self.participant_status_map.has_key(participant_status):
-            #print "Setting participant status for %r to %r" %(str(self), participant_status)
+        if participant_status in self.participant_status_map.keys():
             self.setPartStat(self.participant_status_map[participant_status])
+        elif participant_status in self.participant_status_map.values():
+            self.setPartStat(participant_status)
+        else:
+            raise InvalidAttendeeParticipantStatusError, _("Invalid participant status %r") % (participant_status)
 
     def set_role(self, role):
-        if self.role_map.has_key(role):
+        if role in self.role_map.keys():
             self.setRole(self.role_map[role])
+        elif role in self.role_map.values():
+            self.setRole(role)
+        else:
+            raise InvalidAttendeeRoleError, _("Invalid role %r") % (role)
 
     def __str__(self):
         return self.email
+
+class AttendeeIntegrityError(Exception):
+    def __init__(self, message):
+        Exception.__init__(self, message)
+
+class InvalidAttendeeCutypeError(Exception):
+    def __init__(self, message):
+        Exception.__init__(self, message)
+
+class InvalidAttendeeParticipantStatusError(Exception):
+    def __init__(self, message):
+        Exception.__init__(self, message)
+
+class InvalidAttendeeRoleError(Exception):
+    def __init__(self, message):
+        Exception.__init__(self, message)


commit 84992b37c15405a6276ced189a9de185c000a3da
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 14:00:55 2012 +0100

    Rename test files to at least somewhat indicate what each of them is for.
    Add tests for contact references

diff --git a/tests/test-000-imports.py b/tests/test-000-imports.py
new file mode 100644
index 0000000..17e1c57
--- /dev/null
+++ b/tests/test-000-imports.py
@@ -0,0 +1,23 @@
+import unittest
+
+class TestImports(unittest.TestCase):
+    def test_pykolab(self):
+        import pykolab
+
+    def test_pykolab_xml(self):
+        import pykolab.xml
+
+    def test_pykolab_xml_attendee(self):
+        from pykolab.xml import Attendee
+
+    def test_pykolab_xml_contact(self):
+        from pykolab.xml import Contact
+
+    def test_pykolab_xml_contactReference(self):
+        from pykolab.xml import ContactReference
+
+    def test_pykolab_xml_event(self):
+        from pykolab.xml import Event
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test-000.py b/tests/test-000.py
deleted file mode 100644
index 17e1c57..0000000
--- a/tests/test-000.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import unittest
-
-class TestImports(unittest.TestCase):
-    def test_pykolab(self):
-        import pykolab
-
-    def test_pykolab_xml(self):
-        import pykolab.xml
-
-    def test_pykolab_xml_attendee(self):
-        from pykolab.xml import Attendee
-
-    def test_pykolab_xml_contact(self):
-        from pykolab.xml import Contact
-
-    def test_pykolab_xml_contactReference(self):
-        from pykolab.xml import ContactReference
-
-    def test_pykolab_xml_event(self):
-        from pykolab.xml import Event
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test-001-contact_reference.py b/tests/test-001-contact_reference.py
new file mode 100644
index 0000000..81ce3b5
--- /dev/null
+++ b/tests/test-001-contact_reference.py
@@ -0,0 +1,21 @@
+import datetime
+import unittest
+
+from pykolab.xml import ContactReference
+
+class TestEventXML(unittest.TestCase):
+    contact_reference = ContactReference("jane at doe.org")
+
+    def test_001_minimal(self):
+        self.assertIsInstance(self.contact_reference.__str__(), basestring)
+
+    def test_002_empty_name(self):
+        self.assertEqual(self.contact_reference.get_name(), "")
+
+    def test_003_set_name(self):
+        name = "Doe, Jane"
+        self.contact_reference.set_name(name)
+        self.assertEqual(self.contact_reference.get_name(), name)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test-001.py b/tests/test-001.py
deleted file mode 100644
index 83f1c33..0000000
--- a/tests/test-001.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import datetime
-import unittest
-
-from pykolab.xml import Attendee
-
-class TestEventXML(unittest.TestCase):
-    attendee = Attendee("jane at doe.org")
-
-    def test_001_minimal(self):
-        self.assertIsInstance(self.attendee.__str__(), basestring)
-
-    def test_002_set_name(self):
-        name = "Doe, Jane"
-        self.attendee.set_name(name)
-        self.assertEqual(self.attendee.get_name(), name)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test-002-attendee.py b/tests/test-002-attendee.py
new file mode 100644
index 0000000..74f680b
--- /dev/null
+++ b/tests/test-002-attendee.py
@@ -0,0 +1,21 @@
+import datetime
+import unittest
+
+from pykolab.xml import Attendee
+
+class TestEventXML(unittest.TestCase):
+    attendee = Attendee("jane at doe.org")
+
+    def test_001_minimal(self):
+        self.assertIsInstance(self.attendee.__str__(), basestring)
+
+    def test_002_empty_name(self):
+        self.assertEqual(self.attendee.get_name(), "")
+
+    def test_003_set_name(self):
+        name = "Doe, Jane"
+        self.attendee.set_name(name)
+        self.assertEqual(self.attendee.get_name(), name)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test-002.py b/tests/test-002.py
deleted file mode 100644
index 3a6a863..0000000
--- a/tests/test-002.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import datetime
-import unittest
-
-from pykolab.xml import Attendee
-from pykolab.xml import Event
-from pykolab.xml import EventIntegrityError
-from pykolab.xml import InvalidAttendeeParticipantStatusError
-from pykolab.xml import InvalidEventDateError
-
-class TestEventXML(unittest.TestCase):
-    event = Event()
-
-    def test_000_no_start_date(self):
-        self.assertRaises(EventIntegrityError, self.event.__str__)
-
-    def test_001_minimal(self):
-        self.event.set_start(datetime.datetime.now())
-        self.assertIsInstance(self.event.get_start(), datetime.datetime)
-        self.assertIsInstance(self.event.__str__(), basestring)
-
-    def test_002_attendees_list(self):
-        self.assertIsInstance(self.event.get_attendees(), list)
-
-    def test_003_attendees_no_default(self):
-        self.assertEqual(len(self.event.get_attendees()), 0)
-
-    def test_004_attendee_add(self):
-        self.event.add_attendee("john at doe.org")
-        self.assertIsInstance(self.event.get_attendees(), list)
-        self.assertEqual(len(self.event.get_attendees()), 1)
-
-    def test_005_attendee_add_name(self):
-        self.event.add_attendee("jane at doe.org", "Doe, Jane")
-        self.assertIsInstance(self.event.get_attendees(), list)
-        self.assertEqual(len(self.event.get_attendees()), 2)
-
-    def test_006_get_attendees(self):
-        self.assertEqual([x.get_email() for x in self.event.get_attendees()], ["john at doe.org", "jane at doe.org"])
-
-    def test_007_get_attendee_by_email(self):
-        attendee = self.event.get_attendee_by_email("jane at doe.org")
-        self.assertIsInstance(attendee, Attendee)
-
-        attendee = self.event.get_attendee("jane at doe.org")
-        self.assertIsInstance(attendee, Attendee)
-
-        self.assertRaises(ValueError, self.event.get_attendee_by_email, "nosuchattendee at invalid.domain")
-        self.assertRaises(ValueError, self.event.get_attendee, "nosuchattendee at invalid.domain")
-
-    def test_008_get_attendee_by_name(self):
-        attendee = self.event.get_attendee_by_name("Doe, Jane")
-        self.assertIsInstance(attendee, Attendee)
-
-        attendee = self.event.get_attendee("Doe, Jane")
-        self.assertIsInstance(attendee, Attendee)
-
-        self.assertRaises(ValueError, self.event.get_attendee_by_name, "Houdini, Harry")
-        self.assertRaises(ValueError, self.event.get_attendee, "Houdini, Harry")
-
-    def test_009_invalid_participant_status(self):
-        self.assertRaises(InvalidAttendeeParticipantStatusError, self.event.set_attendee_participant_status, "jane at doe.org", "INVALID")
-
-    def test_010_datetime_from_string(self):
-        self.assertRaises(InvalidEventDateError, self.event.set_start, "2012-05-23 11:58:00")
-
-    def test_011_attendee_equality(self):
-        self.assertEqual(self.event.get_attendee("jane at doe.org").get_email(), "jane at doe.org")
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test-003-event.py b/tests/test-003-event.py
new file mode 100644
index 0000000..3a6a863
--- /dev/null
+++ b/tests/test-003-event.py
@@ -0,0 +1,70 @@
+import datetime
+import unittest
+
+from pykolab.xml import Attendee
+from pykolab.xml import Event
+from pykolab.xml import EventIntegrityError
+from pykolab.xml import InvalidAttendeeParticipantStatusError
+from pykolab.xml import InvalidEventDateError
+
+class TestEventXML(unittest.TestCase):
+    event = Event()
+
+    def test_000_no_start_date(self):
+        self.assertRaises(EventIntegrityError, self.event.__str__)
+
+    def test_001_minimal(self):
+        self.event.set_start(datetime.datetime.now())
+        self.assertIsInstance(self.event.get_start(), datetime.datetime)
+        self.assertIsInstance(self.event.__str__(), basestring)
+
+    def test_002_attendees_list(self):
+        self.assertIsInstance(self.event.get_attendees(), list)
+
+    def test_003_attendees_no_default(self):
+        self.assertEqual(len(self.event.get_attendees()), 0)
+
+    def test_004_attendee_add(self):
+        self.event.add_attendee("john at doe.org")
+        self.assertIsInstance(self.event.get_attendees(), list)
+        self.assertEqual(len(self.event.get_attendees()), 1)
+
+    def test_005_attendee_add_name(self):
+        self.event.add_attendee("jane at doe.org", "Doe, Jane")
+        self.assertIsInstance(self.event.get_attendees(), list)
+        self.assertEqual(len(self.event.get_attendees()), 2)
+
+    def test_006_get_attendees(self):
+        self.assertEqual([x.get_email() for x in self.event.get_attendees()], ["john at doe.org", "jane at doe.org"])
+
+    def test_007_get_attendee_by_email(self):
+        attendee = self.event.get_attendee_by_email("jane at doe.org")
+        self.assertIsInstance(attendee, Attendee)
+
+        attendee = self.event.get_attendee("jane at doe.org")
+        self.assertIsInstance(attendee, Attendee)
+
+        self.assertRaises(ValueError, self.event.get_attendee_by_email, "nosuchattendee at invalid.domain")
+        self.assertRaises(ValueError, self.event.get_attendee, "nosuchattendee at invalid.domain")
+
+    def test_008_get_attendee_by_name(self):
+        attendee = self.event.get_attendee_by_name("Doe, Jane")
+        self.assertIsInstance(attendee, Attendee)
+
+        attendee = self.event.get_attendee("Doe, Jane")
+        self.assertIsInstance(attendee, Attendee)
+
+        self.assertRaises(ValueError, self.event.get_attendee_by_name, "Houdini, Harry")
+        self.assertRaises(ValueError, self.event.get_attendee, "Houdini, Harry")
+
+    def test_009_invalid_participant_status(self):
+        self.assertRaises(InvalidAttendeeParticipantStatusError, self.event.set_attendee_participant_status, "jane at doe.org", "INVALID")
+
+    def test_010_datetime_from_string(self):
+        self.assertRaises(InvalidEventDateError, self.event.set_start, "2012-05-23 11:58:00")
+
+    def test_011_attendee_equality(self):
+        self.assertEqual(self.event.get_attendee("jane at doe.org").get_email(), "jane at doe.org")
+
+if __name__ == '__main__':
+    unittest.main()


commit 8dbdba5cc919cefc5b4ab12f2b2cfeb14b2c4ae1
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed May 23 13:38:40 2012 +0100

    Begin work on a set of unittests

diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..b5e7094
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,4 @@
+import sys
+
+sys.path = [ '.', '..' ] + sys.path
+
diff --git a/tests/test-000.py b/tests/test-000.py
new file mode 100644
index 0000000..17e1c57
--- /dev/null
+++ b/tests/test-000.py
@@ -0,0 +1,23 @@
+import unittest
+
+class TestImports(unittest.TestCase):
+    def test_pykolab(self):
+        import pykolab
+
+    def test_pykolab_xml(self):
+        import pykolab.xml
+
+    def test_pykolab_xml_attendee(self):
+        from pykolab.xml import Attendee
+
+    def test_pykolab_xml_contact(self):
+        from pykolab.xml import Contact
+
+    def test_pykolab_xml_contactReference(self):
+        from pykolab.xml import ContactReference
+
+    def test_pykolab_xml_event(self):
+        from pykolab.xml import Event
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test-001.py b/tests/test-001.py
new file mode 100644
index 0000000..83f1c33
--- /dev/null
+++ b/tests/test-001.py
@@ -0,0 +1,18 @@
+import datetime
+import unittest
+
+from pykolab.xml import Attendee
+
+class TestEventXML(unittest.TestCase):
+    attendee = Attendee("jane at doe.org")
+
+    def test_001_minimal(self):
+        self.assertIsInstance(self.attendee.__str__(), basestring)
+
+    def test_002_set_name(self):
+        name = "Doe, Jane"
+        self.attendee.set_name(name)
+        self.assertEqual(self.attendee.get_name(), name)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test-002.py b/tests/test-002.py
new file mode 100644
index 0000000..3a6a863
--- /dev/null
+++ b/tests/test-002.py
@@ -0,0 +1,70 @@
+import datetime
+import unittest
+
+from pykolab.xml import Attendee
+from pykolab.xml import Event
+from pykolab.xml import EventIntegrityError
+from pykolab.xml import InvalidAttendeeParticipantStatusError
+from pykolab.xml import InvalidEventDateError
+
+class TestEventXML(unittest.TestCase):
+    event = Event()
+
+    def test_000_no_start_date(self):
+        self.assertRaises(EventIntegrityError, self.event.__str__)
+
+    def test_001_minimal(self):
+        self.event.set_start(datetime.datetime.now())
+        self.assertIsInstance(self.event.get_start(), datetime.datetime)
+        self.assertIsInstance(self.event.__str__(), basestring)
+
+    def test_002_attendees_list(self):
+        self.assertIsInstance(self.event.get_attendees(), list)
+
+    def test_003_attendees_no_default(self):
+        self.assertEqual(len(self.event.get_attendees()), 0)
+
+    def test_004_attendee_add(self):
+        self.event.add_attendee("john at doe.org")
+        self.assertIsInstance(self.event.get_attendees(), list)
+        self.assertEqual(len(self.event.get_attendees()), 1)
+
+    def test_005_attendee_add_name(self):
+        self.event.add_attendee("jane at doe.org", "Doe, Jane")
+        self.assertIsInstance(self.event.get_attendees(), list)
+        self.assertEqual(len(self.event.get_attendees()), 2)
+
+    def test_006_get_attendees(self):
+        self.assertEqual([x.get_email() for x in self.event.get_attendees()], ["john at doe.org", "jane at doe.org"])
+
+    def test_007_get_attendee_by_email(self):
+        attendee = self.event.get_attendee_by_email("jane at doe.org")
+        self.assertIsInstance(attendee, Attendee)
+
+        attendee = self.event.get_attendee("jane at doe.org")
+        self.assertIsInstance(attendee, Attendee)
+
+        self.assertRaises(ValueError, self.event.get_attendee_by_email, "nosuchattendee at invalid.domain")
+        self.assertRaises(ValueError, self.event.get_attendee, "nosuchattendee at invalid.domain")
+
+    def test_008_get_attendee_by_name(self):
+        attendee = self.event.get_attendee_by_name("Doe, Jane")
+        self.assertIsInstance(attendee, Attendee)
+
+        attendee = self.event.get_attendee("Doe, Jane")
+        self.assertIsInstance(attendee, Attendee)
+
+        self.assertRaises(ValueError, self.event.get_attendee_by_name, "Houdini, Harry")
+        self.assertRaises(ValueError, self.event.get_attendee, "Houdini, Harry")
+
+    def test_009_invalid_participant_status(self):
+        self.assertRaises(InvalidAttendeeParticipantStatusError, self.event.set_attendee_participant_status, "jane at doe.org", "INVALID")
+
+    def test_010_datetime_from_string(self):
+        self.assertRaises(InvalidEventDateError, self.event.set_start, "2012-05-23 11:58:00")
+
+    def test_011_attendee_equality(self):
+        self.assertEqual(self.event.get_attendee("jane at doe.org").get_email(), "jane at doe.org")
+
+if __name__ == '__main__':
+    unittest.main()


commit 90785cfbce1a9cd0f773b0ad28592fb1537e2a69
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue May 22 19:16:37 2012 +0100

    Rebase on top of python-icalendar 3.0

diff --git a/pykolab/xml/contact_reference.py b/pykolab/xml/contact_reference.py
index e3fbcb2..0d6dec5 100644
--- a/pykolab/xml/contact_reference.py
+++ b/pykolab/xml/contact_reference.py
@@ -23,6 +23,9 @@ class ContactReference(kolabformat.ContactReference):
     def get_name(self):
         return self.name()
 
+    def set_cn(self, value):
+        self.setName(value)
+
     def set_email(self, email):
         kolabformat.ContactReference.__init__(self, email)
 
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index f516ead..cc99c78 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -1,5 +1,7 @@
 import datetime
 import icalendar
+from icalendar import vDatetime
+from icalendar import vText
 import kolabformat
 import time
 
@@ -71,33 +73,45 @@ class Event(object):
         for attr in list(set(event.singletons)):
             if hasattr(self, 'get_ical_%s' % (attr.lower())):
                 exec("retval = self.get_ical_%s()" % (attr.lower()))
+
+                #print "as_string_itip()", attr, retval, type(retval)
+
                 if not retval == None and not retval == "":
+                    print attr.lower()
                     event.add(attr.lower(), retval)
 
             elif hasattr(self, 'get_%s' % (attr.lower())):
                 exec("retval = self.get_%s()" % (attr.lower()))
+
+                #print "as_string_itip()", attr, retval
+
                 if not retval == None and not retval == "":
-                    event.add(attr.lower(), retval)
+                    event.add(attr.lower(), retval, encode=0)
 
             #else:
-                #print "no function for", attr.lower()
+                #print "(single) no function for", attr.lower()
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(event.multiple)):
             if hasattr(self, 'get_ical_%s' % (attr.lower())):
                 exec("retval = self.get_ical_%s()" % (attr.lower()))
+
+                print "as_string_itip()", attr, retval
+
                 if isinstance(retval, list) and not len(retval) == 0:
                     for _retval in retval:
-                        event.add(attr.lower(), _retval)
+                        #print _retval.params
+                        event.add(attr.lower(), _retval, encode=0)
 
             elif hasattr(self, 'get_%s' % (attr.lower())):
                 exec("retval = self.get_%s()" % (attr.lower()))
+                print attr, retval
                 if isinstance(retval, list) and not len(retval) == 0:
                     for _retval in retval:
-                        event.add(attr.lower(), _retval)
+                        event.add(attr.lower(), _retval, encode=0)
 
             #else:
-                #print "no function for", attr.lower()
+                #print "(multiple) no function for", attr.lower()
 
         #event.add('attendee', self.get_attendees())
 
@@ -132,26 +146,20 @@ class Event(object):
         # to fail substitution.
         for attr in list(set(ical_event.required)):
             if ical_event.has_key(attr):
-                if hasattr(self, 'set_ical_%s' % (attr.lower())):
-                    exec("self.set_ical_%s(%r)" % (attr.lower(),ical_event.decoded(attr)))
-                else:
-                    print attr, "exists but no function exists"
+                self.set_from_ical(attr.lower(), ical_event[attr])
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(ical_event.singletons)):
             if ical_event.has_key(attr):
-                if hasattr(self, 'set_ical_%s' % (attr.lower())):
-                    exec("self.set_ical_%s(%r)" % (attr.lower(),ical_event.decoded(attr)))
-                else:
-                    print attr, "exists but no function exists"
+                self.set_from_ical(attr.lower(), ical_event[attr])
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(ical_event.multiple)):
             if ical_event.has_key(attr):
-                if hasattr(self, 'set_ical_%s' % (attr.lower())):
-                    exec("self.set_ical_%s(%r)" % (attr.lower(),ical_event.decoded(attr)))
-                else:
-                    print attr, "exists but no function exists"
+                #if attr == "ATTENDEE":
+                    #print ical_event.decoded(attr)
+
+                self.set_from_ical(attr.lower(), ical_event[attr])
 
     def get_attendee_participant_status(self, attendee):
         return attendee.get_participant_status()
@@ -251,13 +259,15 @@ class Event(object):
             elif partstat == kolabformat.PartDelegated:
                 _partstat = "DELEGATED"
 
-            _attendee = "RSVP=%s" % _rsvp
-            _attendee += ";PARTSTAT=%s" % _partstat
-            _attendee += ";ROLE=%s" % _role
-            _attendee += ";MAILTO:%s" % contact.email()
+            _attendee = icalendar.vCalAddress("MAILTO:%s" % contact.email())
+            _attendee.params['RSVP'] = icalendar.vText(_rsvp)
+            _attendee.params['PARTSTAT'] = icalendar.vText(_partstat)
+            _attendee.params['ROLE'] = icalendar.vText(_role)
 
             attendees.append(_attendee)
 
+        #print "get_ical_attendees()", attendees
+
         return attendees
 
     def get_ical_created(self):
@@ -279,20 +289,28 @@ class Event(object):
         return self.get_start()
 
     def get_ical_organizer(self):
-        organizer = self.get_organizer()
-        name = organizer.name()
+        contact = self.get_organizer()
+        organizer = icalendar.vCalAddress("MAILTO:%s" % contact.email())
+        name = contact.name()
 
-        if not name:
-            return "mailto:%s" % (organizer.email())
-        else:
-            return "CN=%s:mailto:%s" %(name, organizer.email())
+        if not name == None and not name == "":
+            organizer.params["CN"] = icalendar.vText(name)
+
+        return organizer
 
     def get_ical_status(self):
         status = self.event.status()
 
-        for key in self.status_map.keys():
-            if status == self.status_map[key]:
-                return key
+        #print "get_ical_status()", status
+        #print self.status_map.keys()
+        #print self.status_map.values()
+        if status in self.status_map.keys():
+            return status
+
+        if status in self.status_map.values():
+            return [k for k, v in self.status_map.iteritems() if v == status][0]
+
+        #print "get_ical_status()", status
 
     def get_organizer(self):
         organizer = self.event.organizer()
@@ -303,6 +321,7 @@ class Event(object):
         return self.event.priority()
 
     def get_start(self):
+        #print "get_start()"
         _datetime = self.event.start()
 
         (
@@ -416,42 +435,63 @@ class Event(object):
                 kolabformat.cDateTime(year, month, day, hour, minute, second)
             )
 
+    def set_from_ical(self, attr, value):
+        if attr == "dtend":
+            self.set_ical_dtend(value.dt)
+        elif attr == "dtstart":
+            self.set_ical_dtstart(value.dt)
+        elif attr == "status":
+            self.set_ical_status(value)
+        elif attr == "summary":
+            self.set_ical_summary(value)
+        elif attr == "priority":
+            self.set_ical_priority(value)
+        elif attr == "attendee":
+            self.set_ical_attendee(value)
+        elif attr == "organizer":
+            self.set_ical_organizer(value)
+        elif attr == "uid":
+            self.set_ical_uid(value)
+
+        else:
+            print "WARNING, no function for", attr
+
     def set_ical_attendee(self, _attendee):
         log.debug(_("set attendees from ical: %r") % (_attendee), level=9)
 
+        if isinstance(_attendee, basestring):
+            _attendee = [_attendee]
+
         if isinstance(_attendee, list):
             for attendee in _attendee:
-                #print attendee
-                rsvp = False
-                role = None
-                cn = None
-                partstat = None
-                address = None
-                for param in attendee.split(';'):
-                    if (len(param.split('=')) > 1):
-                        exec("%s = %r" % (param.split('=')[0].lower(), param.split('=')[1]))
-                        #print "%s = %r" % (param.split('=')[0].lower(), param.split('=')[1])
-                if (len(attendee.split(':')) > 1):
-                    address = attendee.split(':')[-1]
-                    #print address
-
-                self.add_attendee(address, name=cn, rsvp=rsvp, role=role, participant_status=partstat)
-        else:
-            #print attendee
-            rsvp = False
-            role = None
-            cn = None
-            partstat = None
-            address = None
-            for param in _attendee.split(';'):
-                if (len(param.split('=')) > 1):
-                    exec("%s = %r" % (param.split('=')[0].lower(), param.split('=')[1]))
-                    #print "%s = %r" % (param.split('=')[0].lower(), param.split('=')[1])
-            if (len(_attendee.split(':')) > 1):
-                address = _attendee.split(':')[-1]
-                #print address
-
-            self.add_attendee(address, name=cn, rsvp=rsvp, role=role, participant_status=partstat)
+                address = str(attendee).split(':')[-1]
+
+                if hasattr(attendee, 'params'):
+                    params = attendee.params
+                else:
+                    params = {}
+
+                if params.has_key('CN'):
+                    name = params['CN']
+                else:
+                    name = None
+
+                if params.has_key('ROLE'):
+                    role = params['ROLE']
+                else:
+                    role = None
+
+                if params.has_key('PARTSTAT'):
+                    partstat = params['PARTSTAT']
+                else:
+                    partstat = None
+
+                if params.has_key('RSVP'):
+                    rsvp = params['RSVP']
+                else:
+                    rsvp = None
+
+                self.add_attendee(address, name=name, rsvp=rsvp, role=role, participant_status=partstat)
 
     def set_ical_dtend(self, dtend):
         self.set_end(dtend)
@@ -463,23 +503,26 @@ class Event(object):
         self.set_start(dtstart)
 
     def set_ical_organizer(self, organizer):
+        address = str(organizer).split(':')[-1]
+
         cn = None
-        address = None
-        #print organizer
-        for param in organizer.split(':'):
-            if (len(param.split('=')) > 1):
-                exec("%s = %r" % (param.split('=')[0].lower(), param.split('=')[1]))
-                #print "%s = %r" % (param.split('=')[0].lower(), param.split('=')[1])
-        if (len(organizer.split(':')) > 1):
-            address = organizer.split(':')[-1]
-            #print address
 
-        self.set_organizer(address, name=cn)
+        if hasattr(organizer, 'params'):
+            params = organizer.params
+        else:
+            params = {}
+
+        if params.has_key('CN'):
+            cn = params['CN']
+
+        self.set_organizer(str(address), name=cn)
 
     def set_ical_priority(self, priority):
         self.set_priority(priority)
 
     def set_ical_status(self, status):
+        #print "set_ical_status()", status
+
         # TODO: See which ones are actually valid for iTip
         if status == "UNDEFINED":
             _status = kolabformat.StatusUndefined
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index c9be1f1..52156d4 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -120,7 +120,7 @@ def execute(*args, **kw):
     if not len(itip_events) > 0:
         log.info(
                 _("Message is not an iTip message or does not contain any " + \
-                    "iTip.")
+                    "(valid) iTip.")
             )
 
         accept(filepath)
@@ -200,7 +200,7 @@ def execute(*args, **kw):
             if event_message.is_multipart():
                 for part in event_message.walk():
                     if part.get_content_type() == "application/calendar+xml":
-                        payload = part.get_payload()
+                        payload = part.get_payload(decode=True)
                         event = pykolab.xml.event_from_string(payload)
 
                         for itip in itip_events:
@@ -209,34 +209,36 @@ def execute(*args, **kw):
                             log.debug(_("  event %r end: %r") % (event.get_uid(),event.get_end()), level=9)
 
                             _es = event.get_start()
-                            _is = itip['start']
+                            _is = itip['start'].dt
 
-                            if type(_es) == 'datetime.date':
+                            if type(_es) == 'datetime.date' or not hasattr(_es, 'hour'):
                                 log.debug(_("_es is datetime.date"))
-                                if type(_is) == 'datetime.datetime':
-                                    _is = datetime.date(_is.year, _is.month, _is.day)
-                                else:
-                                    pass
-                            else:
-                                log.debug(_("_es is datetime.datetime"))
-                                if type(_is) == 'datetime.date':
-                                    log.debug(_("_is is datetime.date"))
-                                    _es = datetime.date(_es.year, _es.month, _es.day)
+                                _es = datetime.datetime(_es.year, _es.month, _es.day, 0, 0, 0)
+
+                            if type(_is) == 'datetime.date' or not hasattr(_is, 'hour'):
+                                log.debug(_("_is is datetime.date"))
+                                _is = datetime.datetime(_is.year, _is.month, _is.day, 0, 0, 0)
 
                             _ee = event.get_end()
-                            _ie = itip['end']
-                            if type(_ee) == 'datetime.date':
-                                if type(_ie) == 'datetime.datetime':
-                                    _ie = datetime.date(_ie.year, _ie.month, _ie.day)
-                                else:
-                                    pass
-                            else:
-                                if type(_ie) == 'datetime.date':
-                                    _ee = datetime.date(_ee.year, _ee.month, _ee.day)
+                            _ie = itip['end'].dt
+
+                            if type(_ee) == 'datetime.date' or not hasattr(_ee, 'hour'):
+                                log.debug(_("_ee is datetime.date"))
+                                _ee = datetime.datetime(_ee.year, _ee.month, _ee.day, 0, 0, 0)
+
+                            if type(_ie) == 'datetime.date' or not hasattr(_ie, 'hour'):
+                                log.debug(_("_ie is datetime.date"))
+                                _ie = datetime.datetime(_ie.year, _ie.month, _ie.day, 0, 0, 0)
+
+                            log.debug(_("Raw event and itip data:"))
+                            log.debug(_("_es: %r") %(_es))
+                            log.debug(_("_is: %r") %(_is))
+                            log.debug(_("_ee: %r") %(_ee))
+                            log.debug(_("_ie: %r") %(_ie))
 
                             if _es < _is:
                                 if _es <= _ie:
-                                    if _ie <= _is:
+                                    if _ee <= _is:
                                         conflict = False
                                     else:
                                         log.debug(_("Event %r ends later than invitation") % (event.get_uid()), level=9)
@@ -373,9 +375,7 @@ def itip_events_from_message(message):
                         cal = icalendar.Calendar.from_string(itip_payload)
                     else:
                         log.error(_("Could not read iTip from message."))
-                        accept(filepath)
-
-                        return
+                        return []
 
                     for c in cal.walk():
                         itip = {}
@@ -391,17 +391,22 @@ def itip_events_from_message(message):
                             # - TODO: recurrence rules (if any)
                             #   Where are these stored actually?
                             #
-                            itip['start'] = c.decoded('dtstart')
                             if c.has_key('dtend'):
-                                itip['end'] = c.decoded('dtend')
+                                itip['start'] = c['dtstart']
+                            else:
+                                log.error(_("iTip event without a start"))
+                                return []
+
+                            if c.has_key('dtend'):
+                                itip['end'] = c['dtend']
                             if c.has_key('duration'):
-                                itip['duration'] = c.decoded('duration')
-                            itip['organizer'] = c.decoded('organizer')
-                            itip['attendees'] = c.decoded('attendee')
+                                itip['duration'] = c['duration']
+                            itip['organizer'] = c['organizer']
+                            itip['attendees'] = c['attendee']
                             if c.has_key('resources'):
-                                itip['resources'] = c.decoded('resources')
+                                itip['resources'] = c['resources']
                             itip['raw'] = itip_payload
-                            itip['xml'] = event_from_ical(c.__str__())
+                            itip['xml'] = event_from_ical(c.to_ical())
                             itip_events.append(itip)
                 else:
                     log.error(


commit fb1a50024e454c9c78b71990bf986e9dcc0ac606
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue May 22 19:14:06 2012 +0100

    Add a sub-package pykolab-xml (requiring libkolabxml, required by wallace)

diff --git a/pykolab.spec.in b/pykolab.spec.in
index 75adfc0..fed123a 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -47,6 +47,18 @@ Requires:           kolab-cli = %{version}-%{release}
 Cyrus IMAP Telemetry logging handling capabilities for Kolab Groupware
 
 ##
+## Kolab XML
+##
+%package xml
+Summary:            Kolab XML format wrapper for %{name}
+Group:              Applications/System
+Requires:           %{name} = %{version}-%{release}
+Requires:           libkolabxml >= 0.5
+
+%description xml
+Kolab Format XML bindings wrapper for %{name}
+
+##
 ## Kolab CLI
 ##
 %package -n kolab-cli
@@ -109,6 +121,8 @@ Group:              Applications/System
 Requires:           %{name} = %{version}-%{release}
 Requires:           python-sqlalchemy
 Requires:           MySQL-python
+Requires:           python-icalendar >= 3.0
+Requires:           %{name}-xml = %{version}-%{release}
 
 %description -n wallace
 This is the Kolab Content Filter, with plugins
@@ -278,6 +292,12 @@ rm -rf %{buildroot}
 %{python_sitelib}/pykolab/telemetry.*
 %{python_sitelib}/pykolab/cli/telemetry/
 
+%files xml
+%dir %{python_sitelib}/pykolab/xml
+%{python_sitelib}/pykolab/xml/*.py
+%{python_sitelib}/pykolab/xml/*.pyc
+%{python_sitelib}/pykolab/xml/*.pyo
+
 %files -n kolab-cli
 %defattr(-,root,root,-)
 %exclude %{_bindir}/kolab-test


commit 6d0e9317c91f98fac955356563363c9cfa0d8a9b
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue May 22 19:13:53 2012 +0100

    Add xml/ to dist

diff --git a/pykolab/Makefile.am b/pykolab/Makefile.am
index 937560c..d94cec9 100644
--- a/pykolab/Makefile.am
+++ b/pykolab/Makefile.am
@@ -61,3 +61,6 @@ pykolab_setup_PYTHON = \
 	setup/setup_zpush.py \
 	setup/__init__.py
 
+pykolab_xmldir = $(pythondir)/$(PACKAGE)/xml
+pykolab_xml_PYTHON = \
+	$(wildcard xml/*.py)


commit 1c1b580e9c9e9f98eb6a5faf25e061cbf0d413e4
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue May 22 19:13:26 2012 +0100

    Add default for setting sharedfolder_filter

diff --git a/conf/kolab.conf b/conf/kolab.conf
index 0e9d606..1608a75 100644
--- a/conf/kolab.conf
+++ b/conf/kolab.conf
@@ -79,6 +79,7 @@ kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgr
 
 ; Same again
 sharedfolder_base_dn = ou=Shared Folders,%(base_dn)s
+sharedfolder_filter = (objectclass=kolabsharedfolder)
 
 ; Same again. Resources live in a different OU structure or;
 ;





More information about the commits mailing list