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
12 import gdata
.alt
.appengine
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
)
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
):
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">' % (
52 self
.write_signin_links()
54 def write_signin_links(self
):
55 if users
.get_current_user():
58 'user_link': users
.create_logout_url('/')}
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
):
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.'
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
)
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.
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
)
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
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.
122 query_time
= datetime
.datetime
.now()
124 # Find the events which were created by this user, and those which the user
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):
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.
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()
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
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
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.
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
)
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
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.
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.
214 class EditEvent(EventsPage
):
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()
227 """Changes the details of an event and updates Google Calendar."""
228 self
.write_page_header()
229 event_id
= self
.request
.get('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(',')
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.
255 updated_entry
= self
.calendar_client
.UpdateEvent(str(event
.edit_link
),
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.
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
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
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.
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')
307 # TODO: edit the attendees.
308 self
.write_page_footer()
311 class DeleteEvent(EventsPage
):
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>' % (
321 self
.write_page_footer()
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')
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.
332 self
.calendar_client
.DeleteEvent(str(event
.edit_link
))
333 # Delete the event object from the datastore.
335 self
.response
.out
.write('Deleted event number %s' % event_id
)
336 self
.write_page_footer()
339 class CreateEvent(BasePage
):
343 """Show the event creation form"""
344 self
.write_page_header()
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()
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'))
370 # Associate each of the attendees with the event in the datastore.
372 if self
.request
.get('attendees'):
373 attendee_list
= self
.request
.get('attendees').split(',')
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
)
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
)],
401 run_wsgi_app(application
)
404 if __name__
== "__main__":