changeset 458:9a4bffdf37c3

Finishing up #220. Updated to GData v2.0 and using the new OAuth access token.
author Brian Neal <bgneal@gmail.com>
date Sat, 02 Jul 2011 03:52:43 +0000 (2011-07-02)
parents 7b7332037396
children 9d3bd7304050
files gpp/gcalendar/admin.py gpp/gcalendar/calendar.py gpp/gcalendar/forms.py gpp/gcalendar/models.py gpp/gcalendar/oauth.py gpp/settings.py gpp/templates/gcalendar/event.html gpp/templates/gcalendar/google_sync.html
diffstat 8 files changed, 115 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/gpp/gcalendar/admin.py	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/gcalendar/admin.py	Sat Jul 02 03:52:43 2011 +0000
@@ -12,15 +12,13 @@
 from django.core.urlresolvers import reverse
 from django.http import HttpResponse
 from django.http import HttpResponseRedirect
-from django.shortcuts import render_to_response
+from django.shortcuts import render
 from django.template import RequestContext
 
 import gdata.client
 
 from gcalendar.models import Event, AccessToken
-from gcalendar.forms import PasswordForm
-from gcalendar.calendar import Calendar
-from gcalendar.calendar import CalendarError
+from gcalendar.calendar import Calendar, CalendarError
 from gcalendar import oauth
 
 import bio.badges
@@ -83,38 +81,39 @@
     approve_events.short_description = "Approve selected events"
 
     def google_sync(self, request):
-        """View to synchronize approved event changes with Google calendar."""
+        """
+        View to synchronize approved event changes with Google calendar.
+
+        """
+        # Get pending events
         events = Event.pending_events.all()
+
+        # Attempt to get saved access token to the Google calendar
+        access_token = AccessToken.objects.get_token().access_token()
+
         messages = []
         err_msg = ''
         if request.method == 'POST':
-            form = PasswordForm(request.POST)
-            if form.is_valid():
+            if access_token:
                 try:
-                    cal = Calendar(settings.GCAL_EMAIL,
-                            form.cleaned_data['password'],
-                            settings.GCAL_CALENDAR_ID)
+                    cal = Calendar(source=oauth.USER_AGENT,
+                            calendar_id=settings.GCAL_CALENDAR_ID,
+                            access_token=access_token)
                     cal.sync_events(events)
                 except CalendarError, e:
                     err_msg = e.msg
                     events = Event.pending_events.all()
-                    form = PasswordForm()
                 else:
                     messages.append('All events processed successfully.')
                     events = Event.objects.none()
-                    form = PasswordForm()
 
-        else:
-            form = PasswordForm()
-
-        return render_to_response('gcalendar/google_sync.html', {
+        return render(request, 'gcalendar/google_sync.html', {
             'current_app': self.admin_site.name,
+            'access_token': access_token,
             'messages': messages,
             'err_msg': err_msg,
             'events': events,
-            'form': form,
-            },
-            context_instance=RequestContext(request))
+            })
 
     def fetch_auth(self, request):
         """
@@ -146,13 +145,8 @@
         except gdata.client.Error, e:
             messages.error(request, str(e))
         else:
-            try:
-                token = AccessToken.objects.get(id=1)
-            except AccessToken.DoesNotExist:
-                token = AccessToken()
-
-            token.token = gdata.gauth.TokenToBlob(access_token)
-            token.auth_date = datetime.datetime.now()
+            token = AccessToken.objects.get_token()
+            token.update(access_token)
             token.save()
 
         return HttpResponseRedirect(reverse('admin:gcalendar-google_sync'))
--- a/gpp/gcalendar/calendar.py	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/gcalendar/calendar.py	Sat Jul 02 03:52:43 2011 +0000
@@ -1,19 +1,16 @@
 """
 This file contains the calendar class wich abstracts the Google gdata API for working with
 Google Calendars.
+
 """
 import datetime
 import pytz
 
 from django.utils.tzinfo import FixedOffset
-from gdata.calendar.service import CalendarService
-from gdata.calendar import CalendarEventFeed
-from gdata.calendar import CalendarEventEntry
-from gdata.calendar import Who
-from gdata.calendar import Where
-from gdata.calendar import When
-from gdata.service import BadAuthentication
-import atom
+from gdata.calendar.client import CalendarClient
+from gdata.calendar.data import (CalendarEventEntry, CalendarEventFeed,
+        CalendarWhere, When, EventWho)
+import atom.data
 
 from gcalendar.models import Event
 
@@ -31,19 +28,12 @@
     DATE_TIME_FMT = DATE_FMT + 'T%H:%M:%S'
     DATE_TIME_TZ_FMT = DATE_TIME_FMT + '.000Z'
 
-    def __init__(self, email, password, calendar_id='default'): 
-        self.client = CalendarService()
-        self.client.email = email
-        self.client.password = password
-        self.client.source = 'Google-Calendar_Python_GCalendar'
-        self.insert_feed = '/calendar/feeds/%s/private/full' % calendar_id
+    def __init__(self, source=None, calendar_id='default', access_token=None):
+        self.client = CalendarClient(source=source, auth_token=access_token)
+
+        self.insert_feed = ('https://www.google.com/calendar/feeds/'
+            '%s/private/full' % calendar_id)
         self.batch_feed = '%s/batch' % self.insert_feed
-        try:
-            self.client.ProgrammaticLogin()
-        except BadAuthentication:
-            raise CalendarError('Incorrect password')
-        except Exception, e:
-            raise CalendarError(str(e))
 
     def sync_events(self, qs):
         request_feed = CalendarEventFeed()
@@ -71,17 +61,23 @@
             code = int(entry.batch_status.code)
 
             error = False
-            if qs[i].status == Event.NEW_APRV or qs[i].status == Event.EDIT_APRV:
-                if (code == 201 and qs[i].status == Event.NEW_APRV) or (
-                       code == 200 and qs[i].status == Event.EDIT_APRV):
-                    qs[i].google_id = entry.id.text
+            if qs[i].status == Event.NEW_APRV:
+                if code == 201:
+                    qs[i].status = Event.ON_CAL
+                    qs[i].google_id = entry.GetEditLink().href
                     qs[i].google_url = entry.GetHtmlLink().href
+                    qs[i].save()
+                    qs[i].notify_on_calendar()
+                else:
+                    error = True
+
+            elif qs[i].status == Event.EDIT_APRV:
+                if code == 200:
                     qs[i].status = Event.ON_CAL
                     qs[i].save()
-                    if code == 201:
-                        qs[i].notify_on_calendar()
                 else:
                     error = True
+
             elif qs[i].status == Event.DEL_APRV:
                 if code == 200:
                     qs[i].delete()
@@ -97,7 +93,7 @@
 
     def _retrieve_event(self, model):
         try:
-            event = self.client.GetCalendarEventEntry(model.google_id)
+            event = self.client.GetEventEntry(model.google_id)
         except Exception, e:
             raise CalendarError('Could not retrieve event from Google: %s, %s' \
                     % (model.what, e))
@@ -105,10 +101,10 @@
 
     def _populate_event(self, model, event):
         """Populates a gdata event from an Event model object."""
-        event.title = atom.Title(text=model.what)
-        event.content = atom.Content(text=model.html)
-        event.where = [Where(value_string=model.where)]
-        event.who = [Who(name=model.user.username, email=model.user.email)]
+        event.title = atom.data.Title(text=model.what)
+        event.content = atom.data.Content(text=model.html)
+        event.where = [CalendarWhere(value=model.where)]
+        event.who = [EventWho(email=model.user.email)]
 
         if model.all_day:
             start_time = self._make_time(model.start_date)
@@ -120,9 +116,9 @@
             start_time = self._make_time(model.start_date, model.start_time, model.time_zone)
             end_time = self._make_time(model.end_date, model.end_time, model.time_zone)
 
-        event.when = [When(start_time=start_time, end_time=end_time)]
+        event.when = [When(start=start_time, end=end_time)]
         return event
-    
+
     def _make_time(self, date, time=None, tz_name=None):
         """
         Returns the gdata formatted date/time string given a date, optional time,
--- a/gpp/gcalendar/forms.py	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/gcalendar/forms.py	Sat Jul 02 03:52:43 2011 +0000
@@ -155,7 +155,3 @@
             raise forms.ValidationError("Invalid timezone.")
         return tz
 
-
-class PasswordForm(forms.Form):
-    password = forms.CharField(widget=forms.PasswordInput())
-
--- a/gpp/gcalendar/models.py	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/gcalendar/models.py	Sat Jul 02 03:52:43 2011 +0000
@@ -2,12 +2,15 @@
 Models for the gcalendar application.
 
 """
+import datetime
+
 from django.db import models
 from django.db.models import Q
 from django.contrib.auth.models import User
 
 from core.markup import site_markup
 import forums.tools
+from gcalendar.oauth import serialize_token, deserialize_token
 
 
 GIG_FORUM_SLUG = "gigs"
@@ -107,6 +110,22 @@
             self.save()
 
 
+class AccessTokenManager(models.Manager):
+    """
+    A manager for the AccessToken table. Only one access token is saved in the
+    database. This manager provides a convenience method to either return that
+    access token or a brand new one.
+
+    """
+    def get_token(self):
+        try:
+            token = self.get(pk=1)
+        except AccessToken.DoesNotExist:
+            token = AccessToken()
+
+        return token
+
+
 class AccessToken(models.Model):
     """
     This model represents serialized OAuth access tokens for reading and
@@ -116,6 +135,26 @@
     auth_date = models.DateTimeField()
     token = models.TextField()
 
+    objects = AccessTokenManager()
+
     def __unicode__(self):
         return u'Access token created on ' + unicode(self.auth_date)
 
+    def update(self, access_token, auth_date=None):
+        """
+        This function updates the AccessToken object with the input parameters:
+            access_token - an access token from Google's OAuth dance
+            auth_date - a datetime or None. If None, now() is used.
+
+        """
+        self.auth_date = auth_date if auth_date else datetime.datetime.now()
+        self.token = serialize_token(access_token)
+
+    def access_token(self):
+        """
+        This function returns a Google OAuth access token by deserializing the
+        token field from the database.
+        If the token attribute is empty, None is returned.
+
+        """
+        return deserialize_token(self.token) if self.token else None
--- a/gpp/gcalendar/oauth.py	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/gcalendar/oauth.py	Sat Jul 02 03:52:43 2011 +0000
@@ -80,3 +80,20 @@
 
     logger.info("upgraded to access token...")
     return access_token
+
+
+def serialize_token(token):
+    """
+    This function turns a token into a string and returns it.
+
+    """
+    return gdata.gauth.TokenToBlob(token)
+
+
+def deserialize_token(s):
+    """
+    This function turns a string into a token returns it. The string must have
+    previously been created with serialize_token().
+
+    """
+    return gdata.gauth.TokenFromBlob(s)
--- a/gpp/settings.py	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/settings.py	Sat Jul 02 03:52:43 2011 +0000
@@ -262,12 +262,9 @@
 OEMBED_MAXHEIGHT = 295
 
 # GCalendar settings
-
-GCAL_EMAIL = 'bgneal@gmail.com'
 GCAL_CALENDAR_ID = 'i81lu3fkh57sgqqenogefd9v78@group.calendar.google.com'
 
 # Google OAuth settings
-
 GOOGLE_OAUTH_CONSUMER_KEY = local_settings.GOOGLE_OAUTH_CONSUMER_KEY
 GOOGLE_OAUTH_PRIVATE_KEY_PATH = local_settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH
 
--- a/gpp/templates/gcalendar/event.html	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/templates/gcalendar/event.html	Sat Jul 02 03:52:43 2011 +0000
@@ -12,8 +12,6 @@
 <ul>
    <li>If applicable, please fill out the <strong>Where</strong> field as completely as you can. 
    Google will generate a Google map from this information.</li>
-   <li>Currently, images and smilies won't show up correctly on the Google calendar. If you would
-   like to include an image, add a link to it instead.</li>
    <li>Once submitted, your event will be reviewed by the site staff for approval. Normally it will appear on
    the calendar within 24 hours.</li>
 </ul>
--- a/gpp/templates/gcalendar/google_sync.html	Fri Jul 01 00:49:11 2011 +0000
+++ b/gpp/templates/gcalendar/google_sync.html	Sat Jul 02 03:52:43 2011 +0000
@@ -1,4 +1,6 @@
 {% extends 'admin/base_site.html' %}
+{% load url from future %}
+{% load core_tags %}
 {% block title %}Sync Events w/Google Calendar{% endblock %}
 {% block breadcrumbs %}
 <div class="breadcrumbs">
@@ -14,9 +16,11 @@
    <li>{{ err_msg }}</li>
 </ul>
 {% endif %}
+
+<p>Access token status: {% bool_icon access_token %} &mdash; <a href="{% url 'admin:gcalendar-fetch_auth' %}">Request new access token</a></p>
+
 {% if events %}
-<p>To synchronize the following approved events with the Google calendar, please enter the password for the
-account and click submit.</p>
+<p>The following pending events have been approved and are ready to be synchronized with the Google calendar.</p>
 <ol>
 {% for event in events %}
 {% if not event.on_calendar %}
@@ -25,10 +29,12 @@
 {% endif %}
 {% endfor %}
 </ol>
+
+{% if access_token %}
 <form action="." method="POST">{% csrf_token %}
-   {{ form.as_p }}
-   <p><input type="submit" name="submit" value="Submit" /></p>
+   <p><input type="submit" name="synchronize" value="Synchronize Events" /></p>
 </form>
+{% endif %}
 {% else %}
 <p>No events to synchronize at this time.</p>
 {% endif %}