Catch exception related to number field handling in older SDK.
[gae-samples.git] / calendarer / calendarer.py
blobe8dacf5a4aec7e63caacf05725a46c9a83c9fb67
1 import os
2 import datetime
3 import logging
4 from google.appengine.ext.webapp import template
5 from google.appengine.ext import webapp
6 from google.appengine.ext.webapp.util import run_wsgi_app
7 from google.appengine.ext import db
8 from google.appengine.api import users
9 import atom
10 import gdata.service
11 import gdata.auth
12 import gdata.alt.appengine
13 import gdata.calendar
14 import gdata.calendar.service
17 __author__ = 'j.s@google.com (Jeff Scudder)'
20 port = os.environ['SERVER_PORT']
21 if port and port != '80':
22 HOST_NAME = '%s:%s' % (os.environ['SERVER_NAME'], port)
23 else:
24 HOST_NAME = os.environ['SERVER_NAME']
27 class Event(db.Model):
28 title = db.StringProperty(required=True)
29 description = db.TextProperty()
30 time = db.DateTimeProperty()
31 location = db.TextProperty()
32 creator = db.UserProperty()
33 edit_link = db.TextProperty()
34 gcal_event_link = db.TextProperty()
35 gcal_event_xml = db.TextProperty()
38 class Attendee(db.Model):
39 email = db.StringProperty()
40 event = db.ReferenceProperty(Event)
43 class BasePage(webapp.RequestHandler):
44 title = ''
46 def write_page_header(self):
47 self.response.headers['Content-Type'] = 'text/html'
48 self.response.out.write('<html><head><title>%s</title>'
49 '<link href="static/invitations.css" rel="stylesheet" type="text/css"/>'
50 '</head><body><div id="main">' % (
51 self.title,))
52 self.write_signin_links()
54 def write_signin_links(self):
55 if users.get_current_user():
56 template_values = {
57 'signed_in': True,
58 'user_link': users.create_logout_url('/')}
59 else:
60 template_values = {
61 'signed_in': False,
62 'user_link': users.create_login_url('/events')}
63 path = os.path.join(os.path.dirname(__file__), 'templates')
64 path = os.path.join(path, 'signin.html')
65 self.response.out.write(template.render(path, template_values))
67 def write_page_footer(self):
68 self.response.out.write('</div></body></html>')
71 class StartPage(BasePage):
72 title = 'Welcome!'
74 def get(self):
75 self.write_page_header()
76 template_values = {'sign_in': users.create_login_url('/events')}
77 path = os.path.join(os.path.dirname(__file__), 'templates')
78 path = os.path.join(path, 'start.html')
79 self.response.out.write(template.render(path, template_values))
80 self.write_page_footer()
83 class EventsPage(BasePage):
84 title = 'Your events.'
86 def __init__(self):
87 # Create a Google Calendar client to talk to the Google Calendar service.
88 self.calendar_client = gdata.calendar.service.CalendarService()
89 # Modify the client to search for auth tokens in the datastore and use
90 # urlfetch instead of httplib to make HTTP requests to Google Calendar.
91 gdata.alt.appengine.run_on_appengine(self.calendar_client)
93 def get(self):
94 """Displays the events the user has created or is invited to."""
95 self.write_page_header()
97 # Find all events which this user has created, and find events which this
98 # user has been invited to.
99 invited_events = []
100 owned_events = []
101 token_request_url = None
103 # Find an AuthSub token in the current URL if we arrived at this page from
104 # an AuthSub redirect.
105 auth_token = gdata.auth.extract_auth_sub_token_from_url(self.request.uri)
106 if auth_token:
107 self.calendar_client.SetAuthSubToken(
108 self.calendar_client.upgrade_to_session_token(auth_token))
110 # Check to see if the app has permission to write to the user's
111 # Google Calendar.
112 if not isinstance(self.calendar_client.token_store.find_token(
113 'http://www.google.com/calendar/feeds/'),
114 gdata.auth.AuthSubToken):
115 token_request_url = gdata.auth.generate_auth_sub_url(self.request.uri,
116 ('http://www.google.com/calendar/feeds/',))
119 query_time = self.request.get('start_time')
120 # TODO handle times provided in the URL.
121 if not query_time:
122 query_time = datetime.datetime.now()
124 # Find the events which were created by this user, and those which the user
125 # is invited to.
126 if users.get_current_user():
127 owned_query = Event.gql('WHERE creator = :1 ORDER BY time',
128 users.get_current_user())
129 owned_events = owned_query.fetch(5)
131 invited_query = Attendee.gql('WHERE email = :1',
132 users.get_current_user().email())
133 for invitation in invited_query.fetch(5):
134 try:
135 invited_events.append(invitation.event)
136 except db.Error, message:
137 if message[0] == 'ReferenceProperty failed to be resolved':
138 # The invitee has an invitation to an event which no longer exists.
139 pass
140 else:
141 raise
143 template_values = {
144 'token_request_url': token_request_url,
145 'owned_events': owned_events,
146 'invited_events': invited_events,}
148 # Display the events.
149 path = os.path.join(os.path.dirname(__file__), 'templates')
150 path = os.path.join(path, 'events.html')
151 self.response.out.write(template.render(path, template_values))
152 self.write_page_footer()
154 def post(self):
155 """Adds an event to Google Calendar."""
156 event_id = self.request.get('event_id')
158 # Fetch the event from the datastore and make sure that the current user
159 # is an owner since only event owners are allowed to create a calendar
160 # event.
161 event = Event.get_by_id(long(event_id))
163 if users.get_current_user() == event.creator:
164 # Create a new Google Calendar event.
165 event_entry = gdata.calendar.CalendarEventEntry()
166 event_entry.title = atom.Title(text=event.title)
167 event_entry.content = atom.Content(text=event.description)
168 start_time = '%s.000Z' % event.time.isoformat()
169 event_entry.when.append(gdata.calendar.When(start_time=start_time))
170 event_entry.where.append(
171 gdata.calendar.Where(value_string=event.location))
172 # Add a who element for each attendee.
173 attendee_list = event.attendee_set
174 if attendee_list:
175 for attendee in attendee_list:
176 new_attendee = gdata.calendar.Who()
177 new_attendee.email = attendee.email
178 event_entry.who.append(new_attendee)
180 # Send the event information to Google Calendar and receive a
181 # Google Calendar event.
182 try:
183 cal_event = self.calendar_client.InsertEvent(event_entry,
184 'http://www.google.com/calendar/feeds/default/private/full')
185 edit_link = cal_event.GetEditLink()
186 if edit_link and edit_link.href:
187 # Add the edit link to the Calendar event to use for making changes.
188 event.edit_link = edit_link.href
189 alternate_link = cal_event.GetHtmlLink()
190 if alternate_link and alternate_link.href:
191 # Add a link to the event in the Google Calendar HTML web UI.
192 event.gcal_event_link = alternate_link.href
193 event.gcal_event_xml = str(cal_event)
194 event.put()
195 # If adding the event to Google Calendar failed due to a bad auth token,
196 # remove the user's auth tokens from the datastore so that they can
197 # request a new one.
198 except gdata.service.RequestError, request_exception:
199 request_error = request_exception[0]
200 if request_error['status'] == 401 or request_error['status'] == 403:
201 gdata.alt.appengine.save_auth_tokens({})
202 # If the request failure was not due to a bad auth token, reraise the
203 # exception for handling elsewhere.
204 else:
205 raise
206 else:
207 self.response.out.write('I\'m sorry, you don\'t have permission to add'
208 ' this event to Google Calendar.')
210 # Display the list of events also as if this were a get.
211 self.get()
214 class EditEvent(EventsPage):
216 def get(self):
217 self.write_page_header()
218 event_id = self.request.get('event_id')
219 template_values = {'event_id': event_id,
220 'event': Event.get_by_id(int(event_id))}
221 path = os.path.join(os.path.dirname(__file__), 'templates')
222 path = os.path.join(path, 'edit.html')
223 self.response.out.write(template.render(path, template_values))
224 self.write_page_footer()
226 def post(self):
227 """Changes the details of an event and updates Google Calendar."""
228 self.write_page_header()
229 event_id = self.request.get('event_id')
230 if event_id:
231 event = Event.get_by_id(int(event_id))
232 if event and users.get_current_user() == event.creator:
233 # If this Event is in Google Calendar, send an update to Google Calendar
234 if event.edit_link and event.gcal_event_xml:
235 # Reconstruct the Calendar entry, and update the information.
236 cal_event = gdata.calendar.CalendarEventEntryFromString(
237 str(event.gcal_event_xml))
238 # Modify the event's Google Calendar entry
239 cal_event.title = atom.Title(text=self.request.get('name'))
240 cal_event.content = atom.Content(text=self.request.get('description'))
241 start_time = '%s.000Z' % datetime.datetime.strptime(
242 self.request.get('datetimestamp'), '%d/%m/%Y %H:%M').isoformat()
243 cal_event.when = [gdata.calendar.When(start_time=start_time)]
244 cal_event.where = [gdata.calendar.Where(
245 value_string=self.request.get('location'))]
246 # Add a who element for each attendee.
247 if self.request.get('attendees'):
248 attendee_list = self.request.get('attendees').split(',')
249 if attendee_list:
250 cal_event.who = []
251 for attendee in attendee_list:
252 cal_event.who.append(gdata.calendar.Who(email=attendee))
253 # Send the updated Google Calendar entry to the Google server.
254 try:
255 updated_entry = self.calendar_client.UpdateEvent(str(event.edit_link),
256 cal_event)
257 # Change the properties of the Event object.
258 event.edit_link = updated_entry.GetEditLink().href
259 event.gcal_event_xml = str(updated_entry)
260 event.title = self.request.get('name')
261 event.time = datetime.datetime.strptime(
262 self.request.get('datetimestamp'), '%d/%m/%Y %H:%M')
263 event.description = self.request.get('description')
264 event.location = self.request.get('location')
265 # TODO: update the attendees list.
266 event.put()
267 self.response.out.write('Done')
268 except gdata.service.RequestError, request_exception:
269 request_error = request_exception[0]
270 # If the update failed because someone changed the Google Calendar
271 # event since creation, update the event and ask the user to
272 # repeat the edit.
273 if request_error['status'] == 409:
274 # Get the updated event information from Google Calendar.
275 updated_entry = gdata.calendar.CalendarEventEntryFromString(
276 request_error['body'])
277 # Change the properties of the Event object so that the next edit
278 # will begin with the new values.
279 event.edit_link = updated_entry.GetEditLink().href
280 event.gcal_event_xml = request_error['body']
281 event.title = updated_entry.title.text
282 # TODO: adjust the time
283 logging.debug('New calendar time is: %s' % updated_entry.when[0].start_time)
284 event.description = updated_entry.content.text
285 event.location = updated_entry.where[0].value_string
286 # TODO: update the attendees
287 event.put()
288 self.response.out.write('Could not update because the event '
289 'has been edited in Google Calendar. '
290 'Event details have now been updated '
291 'with the latest values from Google '
292 'Calendar. Try again.')
293 # If the request failure was not due to an optimistic concurrency
294 # conflict reraise exception for handling elsewhere.
295 else:
296 raise
297 else:
298 # This event is not in Google Calendar, so just update the datastore.
299 event.title = self.request.get('name')
300 # Take the time string passing in by JavaScript in the
301 # form and convert to a datetime object.
302 event.time = datetime.datetime.strptime(
303 self.request.get('datetimestamp'), '%d/%m/%Y %H:%M')
304 event.description = self.request.get('description')
305 event.location = self.request.get('location')
306 event.put()
307 # TODO: edit the attendees.
308 self.write_page_footer()
311 class DeleteEvent(EventsPage):
313 def get(self):
314 self.write_page_header()
315 event_id = self.request.get('event_id')
316 self.response.out.write('Are you sure?')
317 self.response.out.write('<form action="/delete_event" method="post">'
318 '<input type="hidden" name="event_id" value="%s"/>'
319 '<input type="submit" value="Yes, Delete this event."/></form>' % (
320 event_id))
321 self.write_page_footer()
323 def post(self):
324 logging.debug('Deleting the event!: %s' % self.request.get('event_id'))
325 self.write_page_header()
326 event_id = self.request.get('event_id')
327 if event_id:
328 event = Event.get_by_id(int(event_id))
329 if event and users.get_current_user() == event.creator:
330 # If we have an edit link, delete the event from Google Calendar.
331 if event.edit_link:
332 self.calendar_client.DeleteEvent(str(event.edit_link))
333 # Delete the event object from the datastore.
334 event.delete()
335 self.response.out.write('Deleted event number %s' % event_id)
336 self.write_page_footer()
339 class CreateEvent(BasePage):
340 title = 'Create!'
342 def get(self):
343 """Show the event creation form"""
344 self.write_page_header()
345 template_values = {}
346 path = os.path.join(os.path.dirname(__file__), 'templates')
347 path = os.path.join(path, 'create.html')
348 self.response.out.write(template.render(path, template_values))
349 self.write_page_footer()
351 def post(self):
352 """Create an event and store it in the datastore.
354 This event does not exist in Google Calendar. The event creator can add it
355 to Google Calendar on the 'events' page.
357 self.write_page_header()
359 # Create an event in the datastore.
360 new_event = Event(title=self.request.get('name'),
361 creator=users.get_current_user(),
362 # Take the time string passing in by JavaScript in the
363 # form and convert to a datetime object.
364 time=datetime.datetime.strptime(
365 self.request.get('datetimestamp'), '%d/%m/%Y %H:%M'),
366 description=self.request.get('description'),
367 location=self.request.get('location'))
368 new_event.put()
370 # Associate each of the attendees with the event in the datastore.
371 attendee_list = []
372 if self.request.get('attendees'):
373 attendee_list = self.request.get('attendees').split(',')
374 if attendee_list:
375 # TODO: perform one put with a list of Attendee objects.
376 for attendee in attendee_list:
377 new_attendee = Attendee(email=attendee.strip(), event=new_event)
378 new_attendee.put()
380 template_values = {
381 'name': new_event.title,
382 'description': new_event.description,
383 'time': new_event.time.strftime('%x %X %Z'),
384 'location': new_event.location
386 path = os.path.join(os.path.dirname(__file__), 'templates')
387 path = os.path.join(path, 'created.html')
388 self.response.out.write(template.render(path, template_values))
389 self.write_page_footer()
392 application = webapp.WSGIApplication([('/', StartPage),
393 ('/events', EventsPage),
394 ('/edit_event', EditEvent),
395 ('/delete_event', DeleteEvent),
396 ('/create', CreateEvent)],
397 debug=True)
400 def main():
401 run_wsgi_app(application)
404 if __name__ == "__main__":
405 main()