app/soc/logic/site/id_user.py
changeset 131 3db97cf7f2c7
parent 112 4d9895fb15bc
child 135 a7ccde9d9eed
--- a/app/soc/logic/site/id_user.py	Thu Sep 11 21:30:08 2008 +0000
+++ b/app/soc/logic/site/id_user.py	Fri Sep 12 02:12:38 2008 +0000
@@ -22,13 +22,28 @@
   ]
 
 
+import re
+
 from google.appengine.api import users
+from google.appengine.ext import db
 
 from soc.logic import out_of_band
 
 import soc.models.user
 
 
+def getUserKeyNameFromId(id):
+  """Return a Datastore key_name for a User derived from a Google Account.
+  
+  Args:
+    id: a Google Account (users.User) object
+  """
+  if not id:
+    return None
+
+  return 'User:%s' % id.email()
+
+
 def getIdIfMissing(id):
   """Gets Google Account of logged-in user (possibly None) if id is false.
   
@@ -38,7 +53,7 @@
   other view).
 
   Args:
-    id: a Google Account object, or None
+    id: a Google Account (users.User) object, or None
     
   Returns:
     If id is non-false, it is simply returned; otherwise, the Google Account
@@ -51,16 +66,36 @@
 
   return id
 
-  
+
 def getUserFromId(id):
   """Returns User entity for a Google Account, or None if not found.  
     
   Args:
-    id: a Google Account object
+    id: a Google Account (users.User) object
   """
-  return soc.models.user.User.gql('WHERE id = :1', id).get()
+  # first, attempt a lookup by User:id key name
+  key_name = getUserKeyNameFromId(id)
+  
+  if key_name:
+    user = soc.models.user.User.get_by_key_name(key_name)
+  else:
+    user = None
+  
+  if user:
+    return user
 
+  # email address may have changed, so query the id property
+  user = soc.models.user.User.gql('WHERE id = :1', id).get()
 
+  if user:
+    return user
+
+  # last chance: perhaps the User changed their email address at some point
+  user = soc.models.user.User.gql('WHERE former_ids = :1', id).get()
+
+  return user
+
+  
 def getUserIfMissing(user, id):
   """Conditionally returns User entity for a Google Account.
   
@@ -74,7 +109,7 @@
 
   Args:
     user: None (usually), or an existing User entity
-    id: a Google Account object
+    id: a Google Account (users.User) object
     
   Returns:
     * user (which may have already been None if passed in that way by the
@@ -100,6 +135,50 @@
     return False
 
 
+def isIdDeveloper(id=None):
+  """Returns True if Google Account is a Developer with special privileges.
+  
+  Args:
+    id: a Google Account (users.User) object; if id is not supplied,
+      the current logged-in user is checked using the App Engine Users API.
+      THIS ARGUMENT IS CURRENTLY IGNORED AND ONLY THE CURRENTLY LOGGED-IN
+      USER IS CHECKED!
+
+  See the TODO in the code below...
+  """
+  if not id:
+    return users.is_current_user_admin()
+
+  # TODO(tlarsen): this Google App Engine function only checks the currently
+  #   logged in user.  There needs to be another way to do this, such as a
+  #   field in the User Model...
+  return users.is_current_user_admin()
+
+
+LINKNAME_PATTERN = r'''(?x)
+    ^
+    [0-9a-z]   # start with ASCII digit or lowercase
+    (
+     [0-9a-z]  # additional ASCII digit or lowercase
+     |         # -OR-
+     _[0-9a-z] # underscore and ASCII digit or lowercase
+    )*         # zero or more of OR group
+    $
+'''
+
+LINKNAME_REGEX = re.compile(LINKNAME_PATTERN)
+
+def isLinkNameFormatValid(link_name):
+  """Returns True if link_name is in a valid format.
+  
+  Args:
+    link_name: link name used in URLs to identify user
+  """
+  if LINKNAME_REGEX.match(link_name):
+    return True
+  return False
+
+
 def getUserFromLinkName(link_name):
   """Returns User entity for link_name or None if not found.
     
@@ -136,3 +215,103 @@
   # else: a link name was supplied, but there is no User that has it
   raise out_of_band.ErrorResponse(
       'There is no user with a "link name" of "%s".' % link_name, status=404)
+
+
+def doesLinkNameBelongToId(link_name, id=None):
+  """Returns True if supplied link name belongs to supplied Google Account.
+  
+  Args:
+    link_name: link name used in URLs to identify user
+    id: a Google Account object; optional, current logged-in user will
+      be used (or False will be returned if no user is logged in)
+  """
+  id = getIdIfMissing(id)
+    
+  if not id:
+    # id not supplied and no Google Account logged in, so link name cannot
+    # belong to an unspecified User
+    return False
+
+  user = getUserFromId(id)
+
+  if not user:
+    # no User corresponding to id Google Account, so no link name at all 
+    return False
+
+  if user.link_name != link_name:
+    # User exists for id, but does not have this link name
+    return False
+
+  return True  # link_name does actually belong to this Google Account
+
+
+def updateOrCreateUserFromId(id, **user_properties):
+  """Update existing User entity, or create new one with supplied properties.
+
+  Args:
+    id: a Google Account object
+    **user_properties: keyword arguments that correspond to User entity
+      properties and their values
+      
+  Returns:
+    the User entity corresponding to the Google Account, with any supplied
+    properties changed, or a new User entity now associated with the Google
+    Account and with the supplied properties
+  """
+  # attempt to retrieve the existing User
+  user = getUserFromId(id)
+  
+  if not user:
+    # user did not exist, so create one in a transaction
+    key_name = getUserKeyNameFromId(id)
+    user = soc.models.user.User.get_or_insert(
+      key_name, id=id, **user_properties)
+
+  # there is no way to be sure if get_or_insert() returned a new User or
+  # got an existing one due to a race, so update with user_properties anyway,
+  # in a transaction
+  return updateUserProperties(user, **user_properties)
+
+
+def updateUserProperties(user, **user_properties):
+  """Update existing User entity using supplied User properties.
+
+  Args:
+    user: a User entity
+    **user_properties: keyword arguments that correspond to User entity
+      properties and their values
+      
+  Returns:
+    the original User entity with any supplied properties changed 
+  """
+  def update():
+    return _unsafeUpdateUserProperties(user, **user_properties)
+
+  return db.run_in_transaction(update)
+
+  
+def _unsafeUpdateUserProperties(user, **user_properties):
+  """(see updateUserProperties)
+  
+  Like updateUserProperties(), but not run within a transaction. 
+  """
+  properties = user.properties()
+  
+  for prop in properties.values():
+    if prop.name in user_properties:
+      if prop.name == 'former_ids':
+        # former_ids cannot be overwritten directly
+        continue
+
+      value = user_properties[prop.name]
+
+      if prop.name == 'id':
+        old_id = user.id
+
+        if value != old_id:
+          user.former_ids.append(old_id)
+
+      prop.__set__(user, value)
+        
+  user.put()
+  return user