New version of SampleSyncAdapter sample code that allows local editing
The changes made were
pretty sweeping. The biggest addition was to allow on-device contact
creation/editing, and supporting 2-way sync to the sample server that
runs in Google App Engine.
The client-side sample code also includes examples of how to support
the user of AuthTokens (instead of always sending username/password
to the server), how to change a contact's picture, and how to set
IM-style status messages for each contact.
I also greatly simplified the server code so that instead of mimicking
both an addressbook and an IM-style status update system for multiple
users, it really just simulates an addressbook for a single user. The
server code also includes a cron job that (once a week) blows away the
contact database, so that it's relatively self-cleaning.
Change-Id: I017f1d3f9320a02fe05a20f1613846963107145e
diff --git a/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py b/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
index c986f7e..b9bac5e 100644
--- a/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
+++ b/samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
@@ -1,21 +1,23 @@
#!/usr/bin/python2.5
# Copyright (C) 2010 The Android Open Source Project
-#
+#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
-"""Defines Django forms for inserting/updating/viewing data
- to/from SampleSyncAdapter datastore."""
+"""
+Defines Django forms for inserting/updating/viewing contact data
+to/from SampleSyncAdapter datastore.
+"""
import cgi
import datetime
@@ -26,248 +28,181 @@
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms
from model import datastore
+from google.appengine.api import images
import wsgiref.handlers
+class BaseRequestHandler(webapp.RequestHandler):
+ """
+ Base class for our page-based request handlers that contains
+ some helper functions we use in most pages.
+ """
-class UserForm(djangoforms.ModelForm):
- """Represents django form for entering user info."""
+ """
+ Return a form (potentially partially filled-in) to
+ the user.
+ """
+ def send_form(self, title, action, contactId, handle, content_obj):
+ if (contactId >= 0):
+ idInfo = '<input type="hidden" name="_id" value="%s">'
+ else:
+ idInfo = ''
- class Meta:
- model = datastore.User
+ template_values = {
+ 'title': title,
+ 'header': title,
+ 'action': action,
+ 'contactId': contactId,
+ 'handle': handle,
+ 'has_contactId': (contactId >= 0),
+ 'has_handle': (handle != None),
+ 'form_data_rows': str(content_obj)
+ }
+
+ path = os.path.join(os.path.dirname(__file__), 'templates', 'simple_form.html')
+ self.response.out.write(template.render(path, template_values))
+
+class ContactForm(djangoforms.ModelForm):
+ """Represents django form for entering contact info."""
+
+ class Meta:
+ model = datastore.Contact
-class UserInsertPage(webapp.RequestHandler):
- """Inserts new users. GET presents a blank form. POST processes it."""
+class ContactInsertPage(BaseRequestHandler):
+ """
+ Processes requests to add a new contact. GET presents an empty
+ contact form for the user to fill in. POST saves the new contact
+ with the POSTed information.
+ """
- def get(self):
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/add_user">'
- '<table>')
- # This generates our shopping list form and writes it in the response
- self.response.out.write(UserForm())
- self.response.out.write('</table>'
- '<input type="submit">'
- '</form></body></html>')
+ def get(self):
+ self.send_form('Add Contact', '/add_contact', -1, None, ContactForm())
- def post(self):
- data = UserForm(data=self.request.POST)
- if data.is_valid():
- # Save the data, and redirect to the view page
- entity = data.save(commit=False)
- entity.put()
- self.redirect('/users')
- else:
- # Reprint the form
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/">'
- '<table>')
- self.response.out.write(data)
- self.response.out.write('</table>'
- '<input type="submit">'
- '</form></body></html>')
+ def post(self):
+ data = ContactForm(data=self.request.POST)
+ if data.is_valid():
+ # Save the data, and redirect to the view page
+ entity = data.save(commit=False)
+ entity.put()
+ self.redirect('/')
+ else:
+ # Reprint the form
+ self.send_form('Add Contact', '/add_contact', -1, None, data)
-class UserEditPage(webapp.RequestHandler):
- """Edits users. GET presents a form prefilled with user info
- from datastore. POST processes it."""
+class ContactEditPage(BaseRequestHandler):
+ """
+ Process requests to edit a contact's information. GET presents a form
+ with the current contact information filled in. POST saves new information
+ into the contact record.
+ """
- def get(self):
- id = int(self.request.get('user'))
- user = datastore.User.get(db.Key.from_path('User', id))
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/edit_user">'
- '<table>')
- # This generates our shopping list form and writes it in the response
- self.response.out.write(UserForm(instance=user))
- self.response.out.write('</table>'
- '<input type="hidden" name="_id" value="%s">'
- '<input type="submit">'
- '</form></body></html>' % id)
+ def get(self):
+ id = int(self.request.get('id'))
+ contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+ self.send_form('Edit Contact', '/edit_contact', id, contact.handle,
+ ContactForm(instance=contact))
- def post(self):
- id = int(self.request.get('_id'))
- user = datastore.User.get(db.Key.from_path('User', id))
- data = UserForm(data=self.request.POST, instance=user)
- if data.is_valid():
- # Save the data, and redirect to the view page
- entity = data.save(commit=False)
- entity.updated = datetime.datetime.utcnow()
- entity.put()
- self.redirect('/users')
- else:
- # Reprint the form
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/edit_user">'
- '<table>')
- self.response.out.write(data)
- self.response.out.write('</table>'
- '<input type="hidden" name="_id" value="%s">'
- '<input type="submit">'
- '</form></body></html>' % id)
+ def post(self):
+ id = int(self.request.get('id'))
+ contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+ data = ContactForm(data=self.request.POST, instance=contact)
+ if data.is_valid():
+ # Save the data, and redirect to the view page
+ entity = data.save(commit=False)
+ entity.updated = datetime.datetime.utcnow()
+ entity.put()
+ self.redirect('/')
+ else:
+ # Reprint the form
+ self.send_form('Edit Contact', '/edit_contact', id, contact.handle, data)
+class ContactDeletePage(BaseRequestHandler):
+ """Processes delete contact request."""
-class UsersListPage(webapp.RequestHandler):
- """Lists all Users. In addition displays links for editing user info,
- viewing user's friends and adding new users."""
+ def get(self):
+ id = int(self.request.get('id'))
+ contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+ contact.deleted = True
+ contact.updated = datetime.datetime.utcnow()
+ contact.put()
- def get(self):
- users = datastore.User.all()
- template_values = {
- 'users': users
- }
+ self.redirect('/')
- path = os.path.join(os.path.dirname(__file__), 'templates', 'users.html')
- self.response.out.write(template.render(path, template_values))
+class AvatarEditPage(webapp.RequestHandler):
+ """
+ Processes requests to edit contact's avatar. GET is used to fetch
+ a page that displays the contact's current avatar and allows the user
+ to specify a file containing a new avatar image. POST is used to
+ submit the form which will change the contact's avatar.
+ """
+ def get(self):
+ id = int(self.request.get('id'))
+ contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+ template_values = {
+ 'avatar': contact.avatar,
+ 'contactId': id
+ }
+
+ path = os.path.join(os.path.dirname(__file__), 'templates', 'edit_avatar.html')
+ self.response.out.write(template.render(path, template_values))
-class UserCredentialsForm(djangoforms.ModelForm):
- """Represents django form for entering user's credentials."""
+ def post(self):
+ id = int(self.request.get('id'))
+ contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+ #avatar = images.resize(self.request.get("avatar"), 128, 128)
+ avatar = self.request.get("avatar")
+ contact.avatar = db.Blob(avatar)
+ contact.updated = datetime.datetime.utcnow()
+ contact.put()
+ self.redirect('/')
- class Meta:
- model = datastore.UserCredentials
+class AvatarViewPage(BaseRequestHandler):
+ """
+ Processes request to view contact's avatar. This is different from
+ the GET AvatarEditPage request in that this doesn't return a page -
+ it just returns the raw image itself.
+ """
+ def get(self):
+ id = int(self.request.get('id'))
+ contact = datastore.Contact.get(db.Key.from_path('Contact', id))
+ if (contact.avatar):
+ self.response.headers['Content-Type'] = "image/png"
+ self.response.out.write(contact.avatar)
+ else:
+ self.redirect(self.request.host_url + '/static/img/default_avatar.gif')
-class UserCredentialsInsertPage(webapp.RequestHandler):
- """Inserts user credentials. GET shows a blank form, POST processes it."""
+class ContactsListPage(webapp.RequestHandler):
+ """
+ Display a page that lists all the contacts associated with
+ the specifies user account.
+ """
- def get(self):
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/add_credentials">'
- '<table>')
- # This generates our shopping list form and writes it in the response
- self.response.out.write(UserCredentialsForm())
- self.response.out.write('</table>'
- '<input type="submit">'
- '</form></body></html>')
+ def get(self):
+ contacts = datastore.Contact.all()
+ template_values = {
+ 'contacts': contacts,
+ 'username': 'user'
+ }
- def post(self):
- data = UserCredentialsForm(data=self.request.POST)
- if data.is_valid():
- # Save the data, and redirect to the view page
- entity = data.save(commit=False)
- entity.put()
- self.redirect('/users')
- else:
- # Reprint the form
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/add_credentials">'
- '<table>')
- self.response.out.write(data)
- self.response.out.write('</table>'
- '<input type="submit">'
- '</form></body></html>')
-
-
-class UserFriendsForm(djangoforms.ModelForm):
- """Represents django form for entering user's friends."""
-
- class Meta:
- model = datastore.UserFriends
- exclude = ['deleted', 'username']
-
-
-class UserFriendsInsertPage(webapp.RequestHandler):
- """Inserts user's new friends. GET shows a blank form, POST processes it."""
-
- def get(self):
- user = self.request.get('user')
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/add_friend">'
- '<table>')
- # This generates our shopping list form and writes it in the response
- self.response.out.write(UserFriendsForm())
- self.response.out.write('</table>'
- '<input type = hidden name = "user" value = "%s">'
- '<input type="submit">'
- '</form></body></html>' % user)
-
- def post(self):
- data = UserFriendsForm(data=self.request.POST)
- if data.is_valid():
- user = self.request.get('user')
- # Save the data, and redirect to the view page
- entity = data.save(commit=False)
- entity.username = user
- query = datastore.UserFriends.all()
- query.filter('username = ', user)
- query.filter('friend_handle = ', entity.friend_handle)
- result = query.get()
- if result:
- result.deleted = False
- result.updated = datetime.datetime.utcnow()
- result.put()
- else:
- entity.deleted = False
- entity.put()
- self.redirect('/user_friends?user=' + user)
- else:
- # Reprint the form
- self.response.out.write('<html><body>'
- '<form method="POST" '
- 'action="/add_friend">'
- '<table>')
- self.response.out.write(data)
- self.response.out.write('</table>'
- '<input type="submit">'
- '</form></body></html>')
-
-
-class UserFriendsListPage(webapp.RequestHandler):
- """Lists all friends for a user. In addition displays links for removing
- friends and adding new friends."""
-
- def get(self):
- user = self.request.get('user')
- query = datastore.UserFriends.all()
- query.filter('deleted = ', False)
- query.filter('username = ', user)
- friends = query.fetch(50)
- template_values = {
- 'friends': friends,
- 'user': user
- }
- path = os.path.join(os.path.dirname(__file__),
- 'templates', 'view_friends.html')
- self.response.out.write(template.render(path, template_values))
-
-
-class DeleteFriendPage(webapp.RequestHandler):
- """Processes delete friend request."""
-
- def get(self):
- user = self.request.get('user')
- friend = self.request.get('friend')
- query = datastore.UserFriends.all()
- query.filter('username =', user)
- query.filter('friend_handle =', friend)
- result = query.get()
- result.deleted = True
- result.updated = datetime.datetime.utcnow()
- result.put()
-
- self.redirect('/user_friends?user=' + user)
+ path = os.path.join(os.path.dirname(__file__), 'templates', 'contacts.html')
+ self.response.out.write(template.render(path, template_values))
def main():
- application = webapp.WSGIApplication(
- [('/add_user', UserInsertPage),
- ('/users', UsersListPage),
- ('/add_credentials', UserCredentialsInsertPage),
- ('/add_friend', UserFriendsInsertPage),
- ('/user_friends', UserFriendsListPage),
- ('/delete_friend', DeleteFriendPage),
- ('/edit_user', UserEditPage)
- ],
- debug=True)
- wsgiref.handlers.CGIHandler().run(application)
+ application = webapp.WSGIApplication(
+ [('/', ContactsListPage),
+ ('/add_contact', ContactInsertPage),
+ ('/edit_contact', ContactEditPage),
+ ('/delete_contact', ContactDeletePage),
+ ('/avatar', AvatarViewPage),
+ ('/edit_avatar', AvatarEditPage)
+ ],
+ debug=True)
+ wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()
\ No newline at end of file