merge
diff --git a/.hgignore b/.hgignore
index 274be82..3a3f338 100644
--- a/.hgignore
+++ b/.hgignore
@@ -7,3 +7,4 @@
 samples/cmdline/*.dat
 htmlcov/*
 .coverage
+database.sqlite3
diff --git a/apiclient/contrib/moderator/future.json b/apiclient/contrib/moderator/future.json
index 7e3d978..87d525b 100644
--- a/apiclient/contrib/moderator/future.json
+++ b/apiclient/contrib/moderator/future.json
@@ -2,7 +2,7 @@
   "data": {
     "moderator": {
       "v1": {
-        "baseUrl": "https://www.googleapis.com/", 
+        "baseUrl": "https://www.googleapis.com/",
           "auth": {
             "request": {
               "url": "https://www.google.com/accounts/OAuthGetRequestToken",
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index 328d970..945db74 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -45,13 +45,21 @@
     import json as simplejson
 
 
-class HttpError(Exception):
+class Error(Exception):
+  """Base error for this module."""
   pass
 
 
-class UnknownLinkType(Exception):
+class HttpError(Error):
+  """HTTP data was invalid or unexpected."""
   pass
 
+
+class UnknownLinkType(Error):
+  """Link type unknown or unexpected."""
+  pass
+
+
 DISCOVERY_URI = ('http://www.googleapis.com/discovery/0.1/describe'
   '{?api,apiVersion}')
 
@@ -86,12 +94,15 @@
     if body_value is None:
       return (headers, path_params, query, None)
     else:
-      model = {'data': body_value}
+      if len(body_value) == 1 and 'data' in body_value:
+        model = body_value
+      else:
+        model = {'data': body_value}
       headers['content-type'] = 'application/json'
       return (headers, path_params, query, simplejson.dumps(model))
 
   def build_query(self, params):
-    params.update({'alt': 'json', 'prettyprint': 'true'})
+    params.update({'alt': 'json'})
     astuples = []
     for key, value in params.iteritems():
       if getattr(value, 'encode', False) and callable(value.encode):
@@ -103,6 +114,9 @@
     # Error handling is TBD, for example, do we retry
     # for some operation/error combinations?
     if resp.status < 300:
+      if resp.status == 204:
+        # A 204: No Content response should be treated differently to all the other success states
+        return simplejson.loads('{}')
       return simplejson.loads(content)['data']
     else:
       logging.debug('Content from bad request was: %s' % content)
diff --git a/apiclient/oauth.py b/apiclient/oauth.py
index 8b827c6..9907c46 100644
--- a/apiclient/oauth.py
+++ b/apiclient/oauth.py
@@ -21,7 +21,17 @@
     from cgi import parse_qs, parse_qsl
 
 
-class MissingParameter(Exception):
+class Error(Exception):
+  """Base error for this module."""
+  pass
+
+
+class RequestError(Error):
+  """Error occurred during request."""
+  pass
+
+
+class MissingParameter(Error):
   pass
 
 
@@ -120,8 +130,10 @@
       if headers == None:
         headers = {}
       headers.update(req.to_header())
-      if 'user-agent' not in headers:
-        headers['user-agent'] = self.user_agent
+      if 'user-agent' in headers:
+        headers['user-agent'] =  self.user_agent + ' ' + headers['user-agent']
+      else:
+        headers['user-agent'] =  self.user_agent
       return request_orig(uri, method, body, headers,
                           redirections, connection_type)
 
@@ -185,7 +197,7 @@
                                    body=body)
     if resp['status'] != '200':
       logging.error('Failed to retrieve temporary authorization: %s' % content)
-      raise Exception('Invalid response %s.' % resp['status'])
+      raise RequestError('Invalid response %s.' % resp['status'])
 
     self.request_token = dict(parse_qsl(content))
 
@@ -222,7 +234,7 @@
     resp, content = client.request(uri, 'POST', headers=headers)
     if resp['status'] != '200':
       logging.error('Failed to retrieve access token: %s' % content)
-      raise Exception('Invalid response %s.' % resp['status'])
+      raise RequestError('Invalid response %s.' % resp['status'])
 
     oauth_params = dict(parse_qsl(content))
     token = oauth.Token(
diff --git a/buzz_gae_client.py b/buzz_gae_client.py
index ffc74c3..f790e02 100644
--- a/buzz_gae_client.py
+++ b/buzz_gae_client.py
@@ -36,6 +36,17 @@
 AUTHORIZE_URL = 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken?domain=anonymous&scope=https://www.googleapis.com/auth/buzz'
 ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken'
 
+
+class Error(Exception):
+  """Base error for this module."""
+  pass
+
+
+class RequestError(Error):
+  """Request returned failure or unexpected data."""
+  pass
+
+
 # TODO(ade) This class is really a BuzzGaeBuilder. Rename it.
 class BuzzGaeClient(object):
   def __init__(self, consumer_key='anonymous', consumer_secret='anonymous'):
@@ -49,7 +60,7 @@
 
     if resp['status'] != '200':
       logging.warn('Request: %s failed with status: %s. Content was: %s' % (url, resp['status'], content))
-      raise Exception('Invalid response %s.' % resp['status'])
+      raise RequestError('Invalid response %s.' % resp['status'])
     return resp, content
 
   def get_request_token(self, callback_url, display_name = None):
diff --git a/functional_tests/test_services.py b/functional_tests/test_services.py
index 6403946..a849577 100644
--- a/functional_tests/test_services.py
+++ b/functional_tests/test_services.py
@@ -10,6 +10,7 @@
 only work with publicly visible data in order to avoid dealing with OAuth.
 """
 import httplib2
+import pprint
 
 __author__ = 'ade@google.com (Ade Oshineye)'
 
@@ -18,6 +19,7 @@
 import logging
 import pickle
 import os
+import time
 import unittest
 
 # TODO(ade) Remove this mock once the bug in the discovery document is fixed
@@ -34,29 +36,43 @@
     return httplib2.Response(self.headers), self.data
 
 class BuzzFunctionalTest(unittest.TestCase):
+  def test_can_get_specific_activity(self):
+    buzz = build('buzz', 'v1')
+    activity = buzz.activities().get(userId='105037104815911535953',
+                                     postId='B:z12sspviqyakfvye123wehng0muwz5jzq04').execute()
+
+    self.assertTrue(activity is not None)
+
+  def test_can_get_specific_activity_with_tag_id(self):
+    buzz = build('buzz', 'v1')
+    activity = buzz.activities().get(userId='105037104815911535953',
+                                     postId='tag:google.com,2010:buzz:z13ptnw5usmnv15ey22fzlswnuqoebasu').execute()
+
+    self.assertTrue(activity is not None)
+
   def test_can_get_buzz_activities_with_many_params(self):
     buzz = build('buzz', 'v1')
     max_results = 2
-    actcol = buzz.activities()
-    activities = actcol.list(userId='googlebuzz', scope='@self',
+    activities_command = buzz.activities()
+    activities = activities_command.list(userId='googlebuzz', scope='@self',
                              max_comments=max_results*2 ,max_liked=max_results*3,
                              max_results=max_results).execute()
     activity_count = len(activities['items'])
     self.assertEquals(max_results, activity_count)
 
-    activities = actcol.list_next(activities).execute()
+    activities = activities_command.list_next(activities).execute()
     activity_count = len(activities['items'])
     self.assertEquals(max_results, activity_count)
 
   def test_can_get_multiple_pages_of_buzz_activities(self):
     buzz = build('buzz', 'v1')
     max_results = 2
-    actcol = buzz.activities()
+    activities_command = buzz.activities()
     
-    activities = actcol.list(userId='adewale', scope='@self',
+    activities = activities_command.list(userId='adewale', scope='@self',
                              max_results=max_results).execute()
     for count in range(10):
-      activities = actcol.list_next(activities).execute()
+      activities = activities_command.list_next(activities).execute()
       activity_count = len(activities['items'])
       self.assertEquals(max_results, activity_count, 'Failed after %s pages' % str(count))
 
@@ -86,6 +102,31 @@
     self.assertEquals('111062888259659218284', person['id'])
     self.assertEquals('http://www.google.com/profiles/googlebuzz', person['profileUrl'])
 
+  def test_can_get_followees_of_user(self):
+    buzz = build('buzz', 'v1')
+    expected_followees = 30
+    following = buzz.people().list(userId='googlebuzz', groupId='@following', max_results=expected_followees).execute()
+
+    self.assertEquals(expected_followees, following['totalResults'])
+    self.assertEquals(expected_followees, len(following['entry']))
+
+  def test_can_efficiently_get_follower_count_of_user(self):
+    buzz = build('buzz', 'v1')
+
+    # Restricting max_results to 1 means only a tiny amount of data comes back but the totalResults still has the total.
+    following = buzz.people().list(userId='googlebuzz', groupId='@followers', max_results=1).execute()
+
+    # @googlebuzz has a large but fluctuating number of followers
+    # It is sufficient if the result is bigger than 10, 000
+    follower_count = following['totalResults']
+    self.assertTrue(follower_count > 10000, follower_count)
+
+  def test_follower_count_is_zero_for_user_with_hidden_follower_count(self):
+    buzz = build('buzz', 'v1')
+    following = buzz.people().list(userId='adewale', groupId='@followers').execute()
+    
+    self.assertEquals(0, following['totalResults'])
+
 
 class BuzzAuthenticatedFunctionalTest(unittest.TestCase):
   def __init__(self, method_name):
@@ -97,8 +138,36 @@
 
     self.http = credentials.authorize(httplib2.Http())
 
-  def test_can_list_groups_belonging_to_user(self):
-    # TODO(ade) This should not require authentication. It does because we're adding a spurious @self to the URL
+  def test_can_create_activity(self):
+    buzz = build('buzz', 'v1', http=self.http)
+
+    activity = buzz.activities().insert(userId='@me', body={
+      'title': 'Testing insert',
+      'object': {
+        'content': u'Just a short note to show that insert is working. ?',
+        'type': 'note'}
+      }
+    ).execute()
+    self.assertTrue(activity is not None)
+
+  def test_can_create_private_activity(self):
+    buzz = build('buzz', 'v1', http=self.http)
+
+    activity = buzz.activities().insert(userId='@me', body={
+        'title': 'Testing insert',
+        'object': {
+        'content': 'This is a private post.'
+      },
+      'visibility': {
+        'entries': [
+          { 'id': 'tag:google.com,2010:buzz-group:108242092577082601423:13' }
+        ]
+      }
+    }
+    ).execute()
+    self.assertTrue(activity is not None)
+
+  def test_can_identify_number_of_groups_belonging_to_user(self):
     buzz = build('buzz', 'v1', http=self.http)
     groups = buzz.groups().list(userId='108242092577082601423').execute()
 
@@ -106,14 +175,75 @@
     expected_default_number_of_groups = 4
     self.assertEquals(expected_default_number_of_groups, len(groups['items']))
 
-  def IGNORE__test_can_get_followees_of_user(self):
-    # This currently fails with:
-    # Attempting to access self view of a different user.
-    # and URL: 
+  def IGNORE__test_can_like_activity(self):
     buzz = build('buzz', 'v1', http=self.http)
-    following = buzz.groups().get(userId='googlebuzz', groupId='@following').execute()
+    activity = buzz.activities().insert(userId='@me', body={
+      'title': 'Testing insert',
+      'object': {
+        'content': u'Just a short note to show that insert is working. ?',
+        'type': 'note'}
+      }
+    ).execute()
+    pprint.pprint(activity)
+    id = activity['id']
+    likers = buzz.people().liked(userId='105037104815911535953', postId=id, groupId='@liked', scope='@self').execute()
+    # Todo(ade) Insert the new liker once the Buzz back-end bug is fixed
 
-    self.assertEquals(17, len(following))
+  def test_can_comment_on_activity(self):
+    buzz = build('buzz', 'v1', http=self.http)
+
+    activity = buzz.activities().insert(userId='@me', body={
+      'title': 'A new activity',
+      'object': {
+        'content': u'The body of the new activity',
+        'type': 'note'}
+      }
+    ).execute()
+
+    id = activity['id']
+    comment = buzz.comments().insert(userId='@me', postId=id, body={
+      "content": "A comment on the new activity"
+    }).execute()
+
+  def IGNORE__test_can_list_groups_belonging_to_user(self):
+    # TODO(ade) Uncomment this test once the related Buzz back-end bug is fixed
+    buzz = build('buzz', 'v1', http=self.http)
+    groups = buzz.groups().list(userId='108242092577082601423').execute()
+    pprint.pprint(groups)
+
+    group = buzz.groups().get(userId='108242092577082601423', groupId='G:108242092577082601423:15').execute()
+    self.assertEquals('G:108242092577082601423:15', group['id'], group)
+
+    group = buzz.groups().get(userId='108242092577082601423', groupId='G:108242092577082601423:14').execute()
+    self.assertEquals('G:108242092577082601423:14', group['id'], group)
+
+    group = buzz.groups().get(userId='108242092577082601423', groupId='G:108242092577082601423:13').execute()
+    self.assertEquals('G:108242092577082601423:13', group['id'], group)
+
+    group = buzz.groups().get(userId='108242092577082601423', groupId='G:108242092577082601423:6').execute()
+    self.assertEquals('G:108242092577082601423:6', group['id'], group)
+
+    group = buzz.groups().get(userId='108242092577082601423', groupId='G:108242092577082601423:9999999').execute()
+    self.assertEquals(None, group, group)
+
+  def test_can_delete_activity(self):
+    buzz = build('buzz', 'v1', http=self.http)
+
+    activity = buzz.activities().insert(userId='@me', body={
+      'title': 'Activity to be deleted',
+      'object': {
+        'content': u'Created this activity so that it can be deleted.',
+        'type': 'note'}
+      }
+    ).execute()
+    id = activity['id']
+
+    buzz.activities().delete(scope='@self', userId='@me', postId=id).execute()
+    time.sleep(2)
+
+    activity_url = activity['links']['self'][0]['href']
+    resp, content = self.http.request(activity_url, 'GET')
+    self.assertEquals(404, resp.status)
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/httplib2/__init__.py b/httplib2/__init__.py
index 61e9caa..567e24e 100644
--- a/httplib2/__init__.py
+++ b/httplib2/__init__.py
@@ -55,9 +55,9 @@
 import socket
 
 try:
-  from httplib2 import socks
+    from httplib2 import socks
 except ImportError:
-  socks = None
+    socks = None
 
 # Build the appropriate socket wrapper for ssl
 try:
@@ -83,7 +83,7 @@
 __all__ = ['Http', 'Response', 'ProxyInfo', 'HttpLib2Error',
   'RedirectMissingLocation', 'RedirectLimit', 'FailedToDecompressContent',
   'UnimplementedDigestAuthOptionError', 'UnimplementedHmacDigestAuthOptionError',
-  'debuglevel']
+  'debuglevel', 'ProxiesUnavailableError']
 
 
 # The httplib debug level, set to a non-zero value to get debug output
@@ -125,6 +125,7 @@
 
 class RelativeURIError(HttpLib2Error): pass
 class ServerNotFoundError(HttpLib2Error): pass
+class ProxiesUnavailableError(HttpLib2Error): pass
 
 # Open Items:
 # -----------
@@ -721,6 +722,9 @@
     def connect(self):
         """Connect to the host and port specified in __init__."""
         # Mostly verbatim from httplib.py.
+        if self.proxy_info and socks is None:
+            raise ProxiesUnavailableError(
+                'Proxy support missing but proxy use was requested!')
         msg = "getaddrinfo returns an empty list"
         for res in socket.getaddrinfo(self.host, self.port, 0,
                 socket.SOCK_STREAM):
diff --git a/httplib2/socks.py b/httplib2/socks.py
index 6f4f020..b65fb38 100644
--- a/httplib2/socks.py
+++ b/httplib2/socks.py
@@ -41,12 +41,13 @@
 """
 
 import socket
+
+if getattr(socket, 'socket', None) is None:
+    raise ImportError('socket.socket missing, proxy support unusable')
+
 import struct
 import sys
 
-if not hasattr(socket, 'socket'):
-  raise ImportError("Running on App Engine?")
-
 PROXY_TYPE_SOCKS4 = 1
 PROXY_TYPE_SOCKS5 = 2
 PROXY_TYPE_HTTP = 3
diff --git a/samples/api-python-client-doc/README b/samples/api-python-client-doc/README
new file mode 100644
index 0000000..835a60c
--- /dev/null
+++ b/samples/api-python-client-doc/README
@@ -0,0 +1,6 @@
+This sample is the code that drives
+
+   http://api-python-client-doc.appspot.com/
+
+It is an application that serves up the Python help documentation
+for each API.
diff --git a/samples/helpdoc/apiclient b/samples/api-python-client-doc/apiclient
similarity index 100%
rename from samples/helpdoc/apiclient
rename to samples/api-python-client-doc/apiclient
diff --git a/samples/helpdoc/app.yaml b/samples/api-python-client-doc/app.yaml
similarity index 100%
rename from samples/helpdoc/app.yaml
rename to samples/api-python-client-doc/app.yaml
diff --git a/samples/helpdoc/httplib2 b/samples/api-python-client-doc/httplib2
similarity index 100%
rename from samples/helpdoc/httplib2
rename to samples/api-python-client-doc/httplib2
diff --git a/samples/helpdoc/index.yaml b/samples/api-python-client-doc/index.yaml
similarity index 100%
rename from samples/helpdoc/index.yaml
rename to samples/api-python-client-doc/index.yaml
diff --git a/samples/helpdoc/main.py b/samples/api-python-client-doc/main.py
similarity index 100%
rename from samples/helpdoc/main.py
rename to samples/api-python-client-doc/main.py
diff --git a/samples/helpdoc/oauth2 b/samples/api-python-client-doc/oauth2
similarity index 100%
rename from samples/helpdoc/oauth2
rename to samples/api-python-client-doc/oauth2
diff --git a/samples/helpdoc/simplejson b/samples/api-python-client-doc/simplejson
similarity index 100%
rename from samples/helpdoc/simplejson
rename to samples/api-python-client-doc/simplejson
diff --git a/samples/helpdoc/uritemplate b/samples/api-python-client-doc/uritemplate
similarity index 100%
rename from samples/helpdoc/uritemplate
rename to samples/api-python-client-doc/uritemplate
diff --git a/samples/buzz/buzz.py b/samples/buzz/buzz.py
new file mode 100644
index 0000000..14b5ea1
--- /dev/null
+++ b/samples/buzz/buzz.py
@@ -0,0 +1,66 @@
+#!/usr/bin/python2.4
+# -*- coding: utf-8 -*-
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+
+"""Simple command-line example for Buzz.
+
+Command-line application that retrieves the users
+latest content and then adds a new entry.
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+from apiclient.discovery import build
+
+import httplib2
+import pickle
+import pprint
+
+# Uncomment the next line to get very detailed logging
+# httplib2.debuglevel = 4
+
+def main():
+  f = open("buzz.dat", "r")
+  credentials = pickle.loads(f.read())
+  f.close()
+
+  http = httplib2.Http()
+  http = credentials.authorize(http)
+
+  p = build("buzz", "v1", http=http)
+  activities = p.activities()
+
+  # Retrieve the first two activities
+  activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute()
+  print "Retrieved the first two activities"
+
+  # Retrieve the next two activities
+  activitylist = activities.list_next(activitylist).execute()
+  print "Retrieved the next two activities"
+
+  # Add a new activity
+  new_activity_body = {
+      'title': 'Testing insert',
+      'object': {
+          'content': u'Just a short note to show that insert is working. ☄',
+          'type': 'note'}
+      }
+  activity = activities.insert(userId='@me', body=new_activity_body).execute()
+  print "Added a new activity"
+
+  activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute()
+
+  # Add a comment to that activity
+  comment_body = {
+          "content": "This is a comment"
+      }
+  item = activitylist['items'][0]
+  comment = p.comments().insert(
+      userId=item['actor']['id'], postId=item['id'], body=comment_body
+      ).execute()
+  print 'Added a comment to the new activity'
+  pprint.pprint(comment)
+
+if __name__ == '__main__':
+  main()
diff --git a/samples/cmdline/three_legged_dance.py b/samples/buzz/three_legged_dance.py
similarity index 93%
rename from samples/cmdline/three_legged_dance.py
rename to samples/buzz/three_legged_dance.py
index ff1d657..9972455 100644
--- a/samples/cmdline/three_legged_dance.py
+++ b/samples/buzz/three_legged_dance.py
@@ -22,11 +22,6 @@
 
 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
 
-# Enable this sample to be run from the top-level directory
-import os
-import sys
-sys.path.insert(0, os.getcwd())
-
 from apiclient.discovery import build
 from apiclient.oauth import FlowThreeLegged
 
diff --git a/samples/cmdline/buzz.py b/samples/cmdline/buzz.py
deleted file mode 100644
index f24c032..0000000
--- a/samples/cmdline/buzz.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/python2.4
-# -*- coding: utf-8 -*-
-#
-# Copyright 2010 Google Inc. All Rights Reserved.
-
-"""Simple command-line example for Buzz.
-
-Command-line application that retrieves the users
-latest content and then adds a new entry.
-"""
-
-__author__ = 'jcgregorio@google.com (Joe Gregorio)'
-
-# Enable this sample to be run from the top-level directory
-import os
-import sys
-sys.path.insert(0, os.getcwd())
-
-from apiclient.discovery import build
-
-import httplib2
-# httplib2.debuglevel = 4
-import pickle
-import pprint
-
-def main():
-  f = open("buzz.dat", "r")
-  credentials = pickle.loads(f.read())
-  f.close()
-
-  http = httplib2.Http()
-  http = credentials.authorize(http)
-
-  p = build("buzz", "v1", http=http)
-  activities = p.activities()
-  activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute()
-  print activitylist['items'][0]['title']
-  activitylist = activities.list_next(activitylist).execute()
-  print activitylist['items'][0]['title']
-
-  activity = activities.insert(userId='@me', body={
-    'title': 'Testing insert',
-    'object': {
-      'content': u'Just a short note to show that insert is working. ☄',
-      'type': 'note'}
-    }
-  ).execute()
-  pprint.pprint(activity)
-  print
-  print 'Just created: ', activity['links']['alternate'][0]['href']
-
-if __name__ == '__main__':
-  main()
diff --git a/samples/cmdline/moderator.py b/samples/cmdline/moderator.py
deleted file mode 100644
index 36f354a..0000000
--- a/samples/cmdline/moderator.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/python2.4
-# -*- coding: utf-8 -*-
-#
-# Copyright 2010 Google Inc. All Rights Reserved.
-
-"""Simple command-line example for Buzz.
-
-Command-line application that retrieves the users
-latest content and then adds a new entry.
-"""
-
-__author__ = 'jcgregorio@google.com (Joe Gregorio)'
-
-
-from apiclient.discovery import build
-
-import httplib2
-import pickle
-
-
-def main():
-  f = open("moderator.dat", "r")
-  credentials = pickle.loads(f.read())
-  f.close()
-
-  http = httplib2.Http()
-  http = credentials.authorize(http)
-
-  p = build("moderator", "v1", http=http)
-  print p.submissions().list(seriesId="7035", topicId="64").execute()
-
-if __name__ == '__main__':
-  main()
diff --git a/samples/django_sample/__init__.py b/samples/django_sample/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/django_sample/__init__.py
diff --git a/samples/django_sample/buzz/__init__.py b/samples/django_sample/buzz/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/django_sample/buzz/__init__.py
diff --git a/samples/django_sample/buzz/models.py b/samples/django_sample/buzz/models.py
new file mode 100644
index 0000000..91cdcce
--- /dev/null
+++ b/samples/django_sample/buzz/models.py
@@ -0,0 +1,62 @@
+import pickle
+import base64
+
+import apiclient.oauth
+from django.contrib import admin
+from django.contrib.auth.models import User
+from django.db import models
+
+# Create your models here.
+
+class OAuthCredentialsField(models.Field):
+
+  __metaclass__ = models.SubfieldBase
+
+  def db_type(self):
+    return 'VARCHAR'
+
+  def to_python(self, value):
+    if value is None:
+      return None
+    if isinstance(value, apiclient.oauth.Credentials):
+      return value
+    return pickle.loads(base64.b64decode(value))
+
+  def get_db_prep_value(self, value):
+    return base64.b64encode(pickle.dumps(value))
+
+class FlowThreeLeggedField(models.Field):
+
+  __metaclass__ = models.SubfieldBase
+
+  def db_type(self):
+    return 'VARCHAR'
+
+  def to_python(self, value):
+    print "In to_python", value
+    if value is None:
+      return None
+    if isinstance(value, apiclient.oauth.FlowThreeLegged):
+      return value
+    return pickle.loads(base64.b64decode(value))
+
+  def get_db_prep_value(self, value):
+    return base64.b64encode(pickle.dumps(value))
+
+# The Flow could also be stored in memcache since it is short lived.
+class Flow(models.Model):
+  id = models.ForeignKey(User, primary_key=True)
+  flow = FlowThreeLeggedField()
+
+class Credential(models.Model):
+  id = models.ForeignKey(User, primary_key=True)
+  credential = OAuthCredentialsField()
+
+class CredentialAdmin(admin.ModelAdmin):
+    pass
+
+class FlowAdmin(admin.ModelAdmin):
+    pass
+
+admin.site.register(Credential, CredentialAdmin)
+admin.site.register(Flow, FlowAdmin)
diff --git a/samples/django_sample/buzz/tests.py b/samples/django_sample/buzz/tests.py
new file mode 100644
index 0000000..2247054
--- /dev/null
+++ b/samples/django_sample/buzz/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/samples/django_sample/buzz/views.py b/samples/django_sample/buzz/views.py
new file mode 100644
index 0000000..aeb0ca2
--- /dev/null
+++ b/samples/django_sample/buzz/views.py
@@ -0,0 +1,62 @@
+import os
+import logging
+import httplib2
+
+from django.http import HttpResponse
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+from django_sample.buzz.models import Credential, Flow
+from apiclient.discovery import build
+from apiclient.oauth import FlowThreeLegged
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+
+print os.environ
+STEP2_URI = 'http://localhost:8000/auth_return'
+
+@login_required
+def index(request):
+  try:
+    c = Credential.objects.get(id=request.user)
+    http = httplib2.Http()
+    http = c.credential.authorize(http)
+    p = build("buzz", "v1", http=http)
+    activities = p.activities()
+    activitylist = activities.list(scope='@consumption',
+                                   userId='@me').execute()
+    logging.info(activitylist)
+
+    return render_to_response('buzz/welcome.html', {
+                'activitylist': activitylist,
+                })
+
+  except Credential.DoesNotExist:
+    p = build("buzz", "v1")
+    flow = FlowThreeLegged(p.auth_discovery(),
+                   consumer_key='anonymous',
+                   consumer_secret='anonymous',
+                   user_agent='google-api-client-python-buzz-django/1.0',
+                   domain='anonymous',
+                   scope='https://www.googleapis.com/auth/buzz',
+                   xoauth_displayname='Django Example Web App')
+
+    authorize_url = flow.step1_get_authorize_url(STEP2_URI)
+    f = Flow(id=request.user, flow=flow)
+    f.save()
+    return HttpResponseRedirect(authorize_url)
+
+@login_required
+def auth_return(request):
+    try:
+      f = Flow.objects.get(id=request.user)
+      print f
+      print f.flow
+      print dir(f.flow)
+      print type(f.flow)
+      credential = f.flow.step2_exchange(request.REQUEST)
+      c = Credential(id=request.user, credential=credential)
+      c.save()
+      f.delete()
+      return HttpResponseRedirect("/")
+    except Flow.DoesNotExist:
+      pass
diff --git a/samples/django_sample/manage.py b/samples/django_sample/manage.py
new file mode 100755
index 0000000..bcdd55e
--- /dev/null
+++ b/samples/django_sample/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)
diff --git a/samples/django_sample/settings.py b/samples/django_sample/settings.py
new file mode 100644
index 0000000..834ce1f
--- /dev/null
+++ b/samples/django_sample/settings.py
@@ -0,0 +1,83 @@
+# Django settings for django_sample project.
+import os
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3'           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = 'database.sqlite3'             # Or path to database file if using sqlite3.
+DATABASE_USER = ''             # Not used with sqlite3.
+DATABASE_PASSWORD = ''         # Not used with sqlite3.
+DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/New_York'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '_=9hq-$t_uv1ckf&s!y2$9g$1dm*6p1cl%*!^mg=7gr)!zj32d'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'django_sample.urls'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+    os.path.join(os.path.dirname(__file__), 'templates')
+)
+
+INSTALLED_APPS = (
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django_sample.buzz'
+)
diff --git a/samples/django_sample/static/go.png b/samples/django_sample/static/go.png
new file mode 100644
index 0000000..e5aacda
--- /dev/null
+++ b/samples/django_sample/static/go.png
Binary files differ
diff --git a/samples/django_sample/templates/buzz/login.html b/samples/django_sample/templates/buzz/login.html
new file mode 100644
index 0000000..567bf68
--- /dev/null
+++ b/samples/django_sample/templates/buzz/login.html
@@ -0,0 +1,23 @@
+{% block content %}
+
+{% if form.errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+<form method="post" action="{% url django.contrib.auth.views.login %}">
+<table>
+<tr>
+    <td>{{ form.username.label_tag }}</td>
+    <td>{{ form.username }}</td>
+</tr>
+<tr>
+    <td>{{ form.password.label_tag }}</td>
+    <td>{{ form.password }}</td>
+</tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+
+{% endblock %}
diff --git a/samples/django_sample/templates/buzz/welcome.html b/samples/django_sample/templates/buzz/welcome.html
new file mode 100644
index 0000000..07e8027
--- /dev/null
+++ b/samples/django_sample/templates/buzz/welcome.html
@@ -0,0 +1,33 @@
+
+<html>
+  <head>
+    <title>Buzz Stuff</title>
+    <style type=text/css>
+      td  { vertical-align: top; padding: 0.5em }
+      img { border:0 }
+    </style>
+  </head>
+  <body>
+      <table border=0>
+      {% for item in activitylist.items %}
+      <tr valign=top>
+        <td>
+          {% if item.actor.thumbnailUrl %}
+            <a href="{{ item.actor.profileUrl }}">
+              <img src="{{ item.actor.thumbnailUrl }}">
+            </a>
+          {% endif %}
+          <br>
+          <a href="{{ item.actor.profileUrl }}">{{ item.actor.name }}</a></td>
+      <td>
+        {{ item.object.content|safe }}
+        </td>
+        <td>
+          <a href="{{ item.object.links.alternate.0.href }}"><img
+            src="/static/go.png"></a>
+        </td>
+      </tr>
+      {% endfor %}
+      </table>
+  </body>
+</html>
diff --git a/samples/django_sample/urls.py b/samples/django_sample/urls.py
new file mode 100644
index 0000000..aeba620
--- /dev/null
+++ b/samples/django_sample/urls.py
@@ -0,0 +1,25 @@
+import os
+from django.conf.urls.defaults import *
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    # Example:
+    (r'^$', 'django_sample.buzz.views.index'),
+    (r'^auth_return', 'django_sample.buzz.views.auth_return'),
+
+    # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
+    # to INSTALLED_APPS to enable admin documentation:
+    # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+    # Uncomment the next line to enable the admin:
+    (r'^admin/', include(admin.site.urls)),
+    (r'^accounts/login/$', 'django.contrib.auth.views.login',
+                        {'template_name': 'buzz/login.html'}),
+
+    (r'^static/(?P<path>.*)$', 'django.views.static.serve',
+        {'document_root': os.path.join(os.path.dirname(__file__), 'static')
+}),
+)
diff --git a/samples/moderator/moderator.py b/samples/moderator/moderator.py
new file mode 100644
index 0000000..3d742e8
--- /dev/null
+++ b/samples/moderator/moderator.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python2.4
+# -*- coding: utf-8 -*-
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+
+"""Simple command-line example for Buzz.
+
+Command-line application that retrieves the users
+latest content and then adds a new entry.
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
+from apiclient.discovery import build
+
+import httplib2
+import pickle
+
+# Uncomment to get detailed logging
+# httplib2.debuglevel = 4
+
+def main():
+  f = open("moderator.dat", "r")
+  credentials = pickle.loads(f.read())
+  f.close()
+
+  http = httplib2.Http()
+  http = credentials.authorize(http)
+
+  p = build("moderator", "v1", http=http)
+
+  series_body = {
+        "description": "Share and rank tips for eating healthily on the cheaps!",
+        "name": "Eating Healthy & Cheap",
+        "videoSubmissionAllowed": False
+      }
+  series = p.series().insert(body=series_body).execute()
+  print "Created a new series"
+
+  topic_body = {
+      "data": {
+        "description": "Share your ideas on eating healthy!",
+        "name": "Ideas",
+        "presenter": "liz"
+        }
+      }
+  topic = p.topics().insert(seriesId=series['id']['seriesId'], body=topic_body).execute()
+  print "Created a new topic"
+
+  submission_body = {
+      "data": {
+        "attachmentUrl": "http://www.youtube.com/watch?v=1a1wyc5Xxpg",
+        "attribution": {
+          "displayName": "Bashan",
+          "location": "Bainbridge Island, WA"
+          },
+        "text": "Charlie Ayers @ Google"
+        }
+      }
+  submission = p.submissions().insert(seriesId=topic['id']['seriesId'],
+      topicId=topic['id']['topicId'], body=submission_body).execute()
+  print "Inserted a new submisson on the topic"
+
+  vote_body = {
+      "data": {
+        "vote": "PLUS"
+        }
+      }
+  p.votes().insert(seriesId=topic['id']['seriesId'], submissionId=submission['id']['submissionId'], body=vote_body)
+  print "Voted on the submission"
+
+
+if __name__ == '__main__':
+  main()
diff --git a/samples/cmdline/three_legged_dance_moderator.py b/samples/moderator/three_legged_dance.py
similarity index 95%
rename from samples/cmdline/three_legged_dance_moderator.py
rename to samples/moderator/three_legged_dance.py
index f09410d..fbc90ec 100644
--- a/samples/cmdline/three_legged_dance_moderator.py
+++ b/samples/moderator/three_legged_dance.py
@@ -35,6 +35,7 @@
                        user_agent='google-api-client-python-mdrtr-cmdline/1.0',
                        domain='anonymous',
                        scope='https://www.googleapis.com/auth/moderator',
+                       #scope='tag:google.com,2010:auth/moderator',
                        xoauth_displayname='Google API Client Example App')
 
 authorize_url = flow.step1_get_authorize_url()
diff --git a/tests/test_json_model.py b/tests/test_json_model.py
index 79b63ab..01015f1 100644
--- a/tests/test_json_model.py
+++ b/tests/test_json_model.py
@@ -65,6 +65,22 @@
     self.assertNotEqual(query, '')
     self.assertEqual(body, '{"data": {}}')
 
+  def test_json_body_default_data(self):
+    """Test that a 'data' wrapper doesn't get added if one is already present."""
+    model = JsonModel()
+
+    headers = {}
+    path_params = {}
+    query_params = {}
+    body = {'data': 'foo'}
+
+    headers, params, query, body = model.request(headers, path_params, query_params, body)
+
+    self.assertEqual(headers['accept'], 'application/json')
+    self.assertEqual(headers['content-type'], 'application/json')
+    self.assertNotEqual(query, '')
+    self.assertEqual(body, '{"data": "foo"}')
+
   def test_json_build_query(self):
     model = JsonModel()
 
diff --git a/upload-diffs.py b/upload-diffs.py
index 1b5daac..c3ff6b9 100644
--- a/upload-diffs.py
+++ b/upload-diffs.py
@@ -487,7 +487,7 @@
                  help="Base revision/branch/tree to diff against. Use "
                       "rev1:rev2 range to review already committed changeset.")
 group.add_option("--send_mail", action="store_true",
-                 dest="send_mail", default=False,
+                 dest="send_mail", default=True,
                  help="Send notification email to reviewers.")
 group.add_option("--vcs", action="store", dest="vcs",
                  metavar="VCS", default=None,