I put together a python script that I run from mutt which takes ICS
files (we use outlook at work but I avoid it as much as possible) and
syncs them to my google calendar, sort of like the windows tool that
google provides. Anyway, to get this working, I had to add UID and
SyncEvent objects to the gdata API. I saw this issue as #215, it's
marked 'accepted', pardon my ignorance, I don't know if that means
someone has already supplied a patch or just that you've accepted it
as a valid issue.
I followed the examples and hope that I did it correctly. It works in
my program. I wasn't sure how to handle boolean values in the context
of the data model.
Additionally - is calendar awaiting the conversion to the new 2.0
style GDEntry/GDFeed data model? It appears so - but I wanted to
confirm that, because I couldn't find a related issue, so I thought
maybe it was not being done on purpose. I found issue 14, but the only
module I could see importing gdata.calendar is contacts, but I didn't
find a place in there where it used it.
Anyway, here's a patch.
--- /home/mballbach/src/gdata-2.0.4/src/gdata/calendar/__init__.py
2009-07-23 19:05:13.000000000 -0400
+++ ./gdata/calendar/__init__.py 2009-10-28 18:36:57.000000000 -0400
@@ -682,6 +682,33 @@
self.extension_elements = extension_elements or []
self.extension_attributes = extension_attributes or {}
+class SyncEvent(atom.AtomBase):
+ _tag = 'syncEvent'
+ _namespace = GCAL_NAMESPACE
+ _children = atom.AtomBase._children.copy()
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['value'] = 'value'
+
+ def __init__(self, value='false', extension_elements=None,
+ extension_attributes=None, text=None):
+ self.value = value
+ self.text = text
+ self.extension_elements = extension_elements or []
+ self.extension_attributes = extension_attributes or {}
+
+class UID(atom.AtomBase):
+ _tag = 'uid'
+ _namespace = GCAL_NAMESPACE
+ _children = atom.AtomBase._children.copy()
+ _attributes = atom.AtomBase._attributes.copy()
+ _attributes['value'] = 'value'
+
+ def __init__(self, value=None, extension_elements=None,
+ extension_attributes=None, text=None):
+ self.value = value
+ self.text = text
+ self.extension_elements = extension_elements or []
+ self.extension_attributes = extension_attributes or {}
class WebContentGadgetPref(atom.AtomBase):
@@ -773,6 +800,8 @@
OriginalEvent)
_children['{%s}sequence' % GCAL_NAMESPACE] = ('sequence', Sequence)
_children['{%s}reminder' % gdata.GDATA_NAMESPACE] = ('reminder',
[Reminder])
+ _children['{%s}syncEvent' % GCAL_NAMESPACE] = ('sync_event',
SyncEvent)
+ _children['{%s}uid' % GCAL_NAMESPACE] = ('uid', UID)
def __init__(self, author=None, category=None, content=None,
atom_id=None, link=None, published=None,
@@ -783,7 +812,7 @@
where=None, when=None, who=None, quick_add=None,
extended_property=None, original_event=None,
batch_operation=None, batch_id=None, batch_status=None,
- sequence=None, reminder=None,
+ sequence=None, reminder=None, sync_event=None, uid=None,
extension_elements=None, extension_attributes=None, text=None):
gdata.BatchEntry.__init__(self, author=author,
category=category,
@@ -808,6 +837,8 @@
self.original_event = original_event
self.sequence = sequence
self.reminder = reminder or []
+ self.sync_event = sync_event
+ self.uid = uid
self.text = text
self.extension_elements = extension_elements or []
self.extension_attributes = extension_attributes or {}
I also added a unit test. The first chunk in the patch fixed the
testPostEntryWithCommentAndDelete test for me (otherwise the comment
can't be deleted because it thinks someone else added it). Two other
tests still fail consistently for me with or without my patch. One is
testPostUpdateAndDeleteSubscription which seems to have a hard-coded
feed ID in it, and I always get a 403. The other is
testCreateAndDeleteEventUsingBatch which complains about GetBatchLink
() returning None, but I haven't done any work with batch queries and
didn't look into it. Other than the occasional 302 problems noted
elsewhere on the list, the new test seems to successfully check the
new fields.
--- orig-service_test.py 2009-11-11 09:48:40.000000000 -0500
+++ service_test.py 2009-11-11 10:08:52.000000000 -0500
@@ -188,7 +188,7 @@
comments_entry.content = atom.Content(text='Comments content')
comments_entry.author.append(
atom.Author(name=atom.Name(text='GData Test user'),
- email=atom.Email(text='
gdata.o...@gmail.com')))
+ email=atom.Email(text=username)))
new_comments_entry = self.cal_client.InsertEventComment
(comments_entry,
comments_feed.GetPostLink().href)
@@ -280,6 +280,78 @@
self.assertEquals(after_delete_query_result.entry
[0].event_status.value,
'CANCELED')
+ def testEventWithSyncEventAndUID(self):
+ """Test posting a new entry (with syncEvent and a UID) and
deleting it."""
+
+ # Get random data for creating event
+ r = random.Random()
+ r.seed()
+ random_event_number = str(r.randint(100000,1000000))
+ random_event_title = 'My Random Test Event %s' %
random_event_number
+
+ random_start_hour = (r.randint(1,1000000) % 23)
+ random_end_hour = random_start_hour + 1
+ non_random_start_minute = 0
+ non_random_end_minute = 0
+ random_month = (r.randint(1,1000000) % 12 + 1)
+ random_day_of_month = (r.randint(1,1000000) % 28 + 1)
+ non_random_year = 2008
+ start_time = '%04d-%02d-%02dT%02d:%02d:00.000-05:00' % (
+ non_random_year, random_month, random_day_of_month,
+ random_start_hour, non_random_start_minute,)
+ end_time = '%04d-%02d-%02dT%02d:%02d:00.000-05:00' % (
+ non_random_year, random_month, random_day_of_month,
+ random_end_hour, non_random_end_minute,)
+
+ # create a random event ID. I'm mimicing an example from outlook
here,
+ # the format doesn't seem to be important per the RFC except for
being
+ # globally unique.
+ uid_string = ''
+ for i in xrange(121):
+ uid_string += "%X" % r.randint(0, 0xf)
+
+ # Set event data
+ event = gdata.calendar.CalendarEventEntry()
+ event.author.append(atom.Author(name=atom.Name(text='GData Test
user')))
+ event.title = atom.Title(text=random_event_title)
+ event.content = atom.Content(text='Picnic with some lunch')
+ event.where.append(gdata.calendar.Where(value_string='Down by the
river'))
+ event.when.append(gdata.calendar.When
(start_time=start_time,end_time=end_time))
+ event.sync_event = gdata.calendar.SyncEvent('true')
+ event.uid = gdata.calendar.UID(value=uid_string)
+
+ # Insert event
+ self.cal_client.ProgrammaticLogin()
+ new_event = self.cal_client.InsertEvent(event,
+ '/calendar/feeds/default/private/full')
+
+ # Inserting it a second time should fail, as it'll have the same
UID
+ try:
+ bad_event = self.cal_client.InsertEvent(event,
+ '/calendar/feeds/default/private/full')
+ self.fail('Was able to insert an event with a duplicate UID')
+ except gdata.service.RequestError, error:
+ # for the current problem with redirects, just re-raise so the
+ # failure doesn't seem to be because of the duplicate UIDs.
+ status = error[0]['status']
+ if status == 302:
+ raise
+
+ # otherwise, make sure it was the right error
+ self.assertEquals(error[0]['status'], 409)
+ self.assertEquals(error[0]['reason'], 'Conflict')
+
+ # Ensure that atom data returned from calendar server equals atom
data sent
+ self.assertEquals(event.title.text, new_event.title.text)
+ self.assertEquals(event.content.text, new_event.content.text)
+
+ # Ensure that gd:where data returned from calendar equals value
sent
+ self.assertEquals(event.where[0].value_string,
+ new_event.where[0].value_string)
+
+ # Delete the event
+ self.cal_client.DeleteEvent(new_event.GetEditLink().href)
+
def testCreateAndDeleteEventUsingBatch(self):
# Get random data for creating event
r = random.Random()
Thanks!