I haven’t had a lot of time to explore implementing an event calendar with Google Calendar, but I did try a few things out. First of all, it is pretty easy to embed a Google Calendar into your website:
As I mentioned in the last post, I downloaded the Google Python gdata client library. I wrote a small Python script based on the provided example code. I was able to add events to my calendar from this Python script:
import gdata.calendar
import gdata.calendar.service
import gdata.service
import atom
import atom.service
import time
cal_client = gdata.calendar.service.CalendarService()
cal_client.email = 'xxx@gmail.com'
cal_client.password = 'xxx'
cal_client.source = 'Google-Calendar_Python_Sample-1.0'
cal_client.ProgrammaticLogin()
title = 'My Test Event from Python'
content = '''This is the content of the event in some fashion.
<a href="http://google.com">My Link</a>
http://surfguitar101.com.
<b>Bold</b> text.
<p>Here is a paragraph.</p>
<p>And another</p>
<ul><li>Item 1</li><li>Item 2</li></ul>
<img src="http://surfguitar101.com/modules/Forums/images/smiles/icon_cool.gif" alt="Smiley" />
'''
where = 'An undisclosed location.'
#cal_path = '/calendar/feeds/default/private/full'
cal_path = '/calendar/feeds/xxx@group.calendar.google.com/private/full'
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=title)
event.content = atom.Content(text=content)
event.where.append(gdata.calendar.Where(value_string=where))
# Use current time for the start_time and have the event last 1 hour
start_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
end_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(time.time() + 3600))
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
new_event = cal_client.InsertEvent(event, cal_path)
As you can see, I was testing to see if I could insert HTML into the event, and if it would display correctly on the Google Calendar. According to my tests, it does appear you can insert simple HTML. The only thing that did not work was <img> tags. Unfortunately, if you log into your Google account, and then go to your Google Calendar and try to edit this event using the Google GUI, all HTML will be stripped. This will make it difficult to edit an existing event and will probably steer me towards keeping the user added event around in the database for future editing until that event has expired.
I have to say I am not really impressed with the Python API. It seems rather “un-Pythonic”. Why do I have to construct atom objects and assign them to the event title and content fields? The API should do this for me. Why do I have to format my own date strings? The API should accept Python datetime objects and do that for me. Pretty disappointing coming from Google.
I am starting to see a way forward here. I think I need to do the following:
Initially I will support only non-repeating events. I’ll go back and try to add support for repeating events in “phase 2″ once I get the above functionality working.
I was looking through the Django docs and came across a new feature in trunk that allows you to extend the set of URLs supported by a model in the admin section. This will allow me to create a custom admin view to insert the events. I think this is just a new way of doing something that was already supported, and is described in the online Django book. I’ll have to update my working copy of Django trunk and try this out.
I wrote a pretty complex event calendar for my PHP-Nuke site. You can find it here on sourceforge. It allows users to submit events for admin approval. Once approved they appear on the calendar. The application does all the calendar logic, including the drawing of the calendar. Repeating events were added, and they are pretty hairy as well. There are some pretty massively complicated SQL queries in that code, not to mention some crazy PHP logic to figure out if repeating events belong on a particular calendar view. It is also possible to make exceptions to repeating events.
I’ve been thinking about how to port this to my new Django powered site. One day I stumbled across the Google Calendar API, and I’m thinking about leveraging this. I will let Google handle all the crazy calendar logic, as they are no doubt better at it than I am. I will still let users submit events to the admin, but this time I will use the Google API to add events to a Google Calendar. Google can do the heavy lifting for me when it comes to displaying events and handling repeating events. Am I cheating by doing this? Ha. Well there are some advantages to this idea for my end users:
So I’ve decided to explore this approach. If it doesn’t look like it will pan out, I will fall back to a more direct port of my old PHP code.
This approach isn’t without its own challenges. I can see right now I am going to have to think carefully about how to provide authentication to the Django code to allow it to add events to one of my Google calendars. I obviously don’t want to hard code my Google login and password in the Django application.
I think I will attack this problem in phases:
I’ve already downloaded the Python version of the gdata library and have started reading the API docs. It looks interesting. Once I get familiar with this library, it looks like I could use it for other cool things like interfacing with YouTube and Google Docs.
I’ve gotten the Member Map application ported to my Django powered site now. This was pretty straightforward and a lot of fun because of the Javascript aspect. Let me address the points I made in part 1 of this post, below.
Another thing I did differently for the port was when a user updates or adds their position on the map, I don’t reload the entire map. I just adjust their information to reduce bandwidth with an AJAX post. I’m not sure why I didn’t do this before, perhaps because it was to tedious to do without jQuery. It was just easier to reload the whole thing.
This was all well and cool, doing all this pre-computation, but what happens when a user changes her avatar? Her entry on the map will likely have a broken graphic. No problem. Here I took advantage of Django’s signals. I attached a signal handler to listen for changes to the UserProfile model. Whenever the UserProfile is saved, my signal handler runs and re-saves the corresponding MapEntry to regenerate the JSON. Very slick.
And finally, another thing that I learned about Django was that simple_tags can have defaulted arguments. I added an argument to my avatar tag so that I could apply CSS to the generated HTML img tag if needed. In the Member Map pop-up balloon, it looked nicer if the avatar was floated to the left.
So all and all, again, I’m very happy with how easy Django makes writing an application like this. Using jQuery was also a big productivity booster.
Here are some screenshots. You can also see the new Blueprints CSS in action as well.
I started working on the Member Map application last weekend. This will be a port of the PHP-Nuke module I wrote, which you can find here. In a nutshell, this application displays a Google Map on your site, and allows site members to place markers representing their location on the map. If you click on a marker, a balloon pops up which displays the user’s avatar, a link to their profile, and a short message that they have typed previously. The Nuke version of this application is a big hit on the current site, and last time I checked, we had over 220 members on the map.
The existing application consists of the PHP-Nuke code, written in PHP, on the server side, and a fair amount of Javascript on the client side. All interaction with Google Maps is done through the Javascript code.
For the port to Django, I want to do a couple of things different.
So, those are my going in goals. I’ve already started working on this, and I’ve learned a lot so far. In the next post or two I will detail the progress and what problems I ran into.