annotate gcalendar/calendar.py @ 887:9a15f7c27526

Actually save model object upon change. This commit was tested on the comments model. Additional logging added. Added check for Markdown image references. Added TODOs after observing behavior on comments.
author Brian Neal <bgneal@gmail.com>
date Tue, 03 Feb 2015 21:09:44 -0600
parents 9165edfb1709
children 4dc6a452afd3
rev   line source
gremmie@1 1 """
bgneal@857 2 This file contains the calendar class wich abstracts the Google API for working
bgneal@857 3 with Google Calendars.
bgneal@458 4
gremmie@1 5 """
gremmie@1 6 import datetime
gremmie@1 7 import pytz
gremmie@1 8
bgneal@857 9 import httplib2
bgneal@857 10 from apiclient.discovery import build
bgneal@857 11 from apiclient.http import BatchHttpRequest
gremmie@1 12 from django.utils.tzinfo import FixedOffset
gremmie@1 13
gremmie@1 14 from gcalendar.models import Event
gremmie@1 15
gremmie@1 16
gremmie@1 17 class CalendarError(Exception):
bgneal@857 18 """Calendar exception base class."""
gremmie@1 19
bgneal@857 20
bgneal@857 21 def _make_err_msg(event, exception):
bgneal@857 22 """Returns an error message string from the given Event and exception."""
bgneal@857 23 return '%s - %s' % (event.what, exception)
gremmie@1 24
gremmie@1 25
gremmie@1 26 class Calendar(object):
bgneal@857 27 DATE_TIME_FMT = '%Y-%m-%dT%H:%M:%S'
bgneal@857 28 DATE_TIME_TZ_FMT = DATE_TIME_FMT + 'Z'
gremmie@1 29
bgneal@857 30 def __init__(self, calendar_id='primary', credentials=None):
bgneal@857 31 self.calendar_id = calendar_id
bgneal@857 32 http = httplib2.Http()
bgneal@857 33 if credentials:
bgneal@857 34 http = credentials.authorize(http)
bgneal@857 35 self.service = build('calendar', 'v3', http=http)
gremmie@1 36
gremmie@1 37 def sync_events(self, qs):
bgneal@857 38 """Process the pending events in a batch request to the Google calendar
bgneal@857 39 API.
bgneal@857 40 """
bgneal@857 41 batch = BatchHttpRequest()
bgneal@857 42
bgneal@857 43 err_msgs = []
bgneal@857 44 def insert_callback(request_id, event, exception):
bgneal@857 45 n = int(request_id)
bgneal@857 46 if not exception:
bgneal@857 47 qs[n].status = Event.ON_CAL
bgneal@857 48 qs[n].google_id = event['id']
bgneal@857 49 qs[n].google_url = event['htmlLink']
bgneal@857 50 qs[n].save()
bgneal@857 51 qs[n].notify_on_calendar()
gremmie@1 52 else:
bgneal@857 53 err_msgs.append(_make_err_msg(qs[n], exception))
bgneal@857 54
bgneal@857 55 def update_callback(request_id, event, exception):
bgneal@857 56 n = int(request_id)
bgneal@857 57 if not exception:
bgneal@857 58 qs[n].status = Event.ON_CAL
bgneal@857 59 qs[n].save()
bgneal@857 60 else:
bgneal@857 61 err_msgs.append(_make_err_msg(qs[n], exception))
bgneal@857 62
bgneal@857 63 def delete_callback(request_id, response, exception):
bgneal@857 64 n = int(request_id)
bgneal@857 65 if not exception:
bgneal@857 66 qs[n].delete()
bgneal@857 67 else:
bgneal@857 68 err_msgs.append(_make_err_msg(qs[n], exception))
bgneal@857 69
bgneal@857 70 for n, event in enumerate(qs):
bgneal@857 71 if event.status == Event.NEW_APRV:
bgneal@857 72 batch.add(self.service.events().insert(calendarId=self.calendar_id,
bgneal@857 73 body=self._make_event(event)),
bgneal@857 74 callback=insert_callback,
bgneal@857 75 request_id=str(n))
bgneal@857 76 elif event.status == Event.EDIT_APRV:
bgneal@857 77 batch.add(self.service.events().update(calendarId=self.calendar_id,
bgneal@857 78 eventId=event.google_id,
bgneal@857 79 body=self._make_event(event)),
bgneal@857 80 callback=update_callback,
bgneal@857 81 request_id=str(n))
bgneal@857 82 elif event.status == Event.DEL_APRV:
bgneal@857 83 batch.add(self.service.events().delete(calendarId=self.calendar_id,
bgneal@857 84 eventId=event.google_id),
bgneal@857 85 callback=delete_callback,
bgneal@857 86 request_id=str(n))
bgneal@857 87 else:
bgneal@857 88 raise CalendarError("Invalid event status: %s" % event.status)
gremmie@1 89
bgneal@68 90 try:
bgneal@857 91 batch.execute()
bgneal@857 92 except Exception as ex:
bgneal@857 93 raise CalendarError('Batch exception: %s' % ex)
bgneal@68 94
bgneal@857 95 if err_msgs:
bgneal@68 96 raise CalendarError(', '.join(err_msgs))
gremmie@1 97
bgneal@857 98 def _make_event(self, model):
bgneal@857 99 """Creates an event body from a model instance."""
bgneal@857 100 event = {
bgneal@857 101 'summary': model.what,
bgneal@857 102 'description': model.html,
bgneal@857 103 'location': model.where,
bgneal@857 104 'anyoneCanAddSelf': True,
bgneal@857 105 }
gremmie@1 106
gremmie@1 107 if model.all_day:
bgneal@857 108 start = {'date': model.start_date.isoformat()}
bgneal@857 109 end = {'date': model.end_date.isoformat()}
gremmie@1 110 else:
bgneal@857 111 start = {'dateTime': self._make_time(model.start_date, model.start_time,
bgneal@857 112 model.time_zone)}
bgneal@857 113 end = {'dateTime': self._make_time(model.end_date, model.end_time,
bgneal@857 114 model.time_zone)}
gremmie@1 115
bgneal@857 116 event['start'] = start
bgneal@857 117 event['end'] = end
gremmie@1 118 return event
bgneal@458 119
gremmie@1 120 def _make_time(self, date, time=None, tz_name=None):
gremmie@1 121 """
gremmie@1 122 Returns the gdata formatted date/time string given a date, optional time,
gremmie@1 123 and optional time zone name (e.g. 'US/Pacific'). If the time zone name is None,
gremmie@1 124 no time zone info will be added to the string.
gremmie@1 125 """
gremmie@1 126
bgneal@857 127 if time:
gremmie@1 128 d = datetime.datetime.combine(date, time)
gremmie@1 129 else:
gremmie@1 130 d = datetime.datetime(date.year, date.month, date.day)
gremmie@1 131
bgneal@857 132 if not tz_name:
gremmie@1 133 s = d.strftime(self.DATE_TIME_FMT)
gremmie@1 134 else:
gremmie@1 135 try:
gremmie@1 136 tz = pytz.timezone(tz_name)
gremmie@1 137 except pytz.UnknownTimeZoneError:
bgneal@857 138 raise CalendarError('Invalid time zone: %s' % tz_name)
gremmie@1 139 local = tz.localize(d)
gremmie@1 140 zulu = local.astimezone(FixedOffset(0))
gremmie@1 141 s = zulu.strftime(self.DATE_TIME_TZ_FMT)
gremmie@1 142
gremmie@1 143 return s