--- 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