app/soc/logic/site/id_user.py
changeset 299 a1cc853a56e5
parent 263 9b39d93b677f
child 301 5d6d106afb23
equal deleted inserted replaced
298:c76a366c7ab4 299:a1cc853a56e5
    26 import re
    26 import re
    27 
    27 
    28 from google.appengine.api import users
    28 from google.appengine.api import users
    29 from google.appengine.ext import db
    29 from google.appengine.ext import db
    30 
    30 
       
    31 import soc.logic
    31 from soc.logic import key_name
    32 from soc.logic import key_name
    32 from soc.logic import model
    33 from soc.logic import model
    33 from soc.logic import out_of_band
    34 from soc.logic import out_of_band
    34 
    35 
    35 import soc.models.user
    36 import soc.models.user
    36 
       
    37 
       
    38 def getUserKeyNameFromId(id):
       
    39   """Return a Datastore key_name for a User derived from a Google Account.
       
    40   
       
    41   Args:
       
    42     id: a Google Account (users.User) object
       
    43   """
       
    44   if not id:
       
    45     return None
       
    46 
       
    47   return key_name.nameUser(id.email())
       
    48 
       
    49 
       
    50 def getIdIfMissing(id=None):
       
    51   """Gets Google Account of logged-in user (possibly None) if id is false.
       
    52   
       
    53   This is a convenience function that simplifies a lot of view code that
       
    54   accepts an optional id argument from the caller (such as one looked up
       
    55   already by another view that decides to "forward" the request to this
       
    56   other view).
       
    57 
       
    58   Args:
       
    59     id: a Google Account (users.User) object, or None
       
    60     
       
    61   Returns:
       
    62     If id is non-false, it is simply returned; otherwise, the Google Account
       
    63     of currently logged-in user is returned (which could be None if no user
       
    64     is logged in).
       
    65   """
       
    66   if not id:
       
    67     # id not initialized, so check if a Google Account is currently logged in
       
    68     id = users.get_current_user()
       
    69 
       
    70   return id
       
    71 
       
    72 
       
    73 def getUsersForLimitAndOffset(limit, offset=0):
       
    74   """Returns Users entities for given offset and limit or None if not found.
       
    75     
       
    76   Args:
       
    77     limit: max amount of entities to return
       
    78     offset: optional offset in entities list which defines first entity to
       
    79       return; default is zero (first entity)
       
    80   """
       
    81   return model.getEntitiesForLimitAndOffset(
       
    82       soc.models.user.User, limit, offset=offset, order_by='id')
       
    83 
       
    84 
       
    85 def getUserFromId(id):
       
    86   """Returns User entity for a Google Account, or None if not found.  
       
    87     
       
    88   Args:
       
    89     id: a Google Account (users.User) object
       
    90   """
       
    91   return soc.models.user.User.gql('WHERE id = :1', id).get()
       
    92 
       
    93 
       
    94 def getUserIfMissing(user, id):
       
    95   """Conditionally returns User entity for a Google Account.
       
    96   
       
    97   This function is used to look up the User entity corresponding to the
       
    98   supplied Google Account *if* the user parameter is false (usually None).
       
    99   This function is basically a no-op if user already refers to a User
       
   100   entity.  This is a convenience function that simplifies a lot of view
       
   101   code that accepts an optional user argument from the caller (such as
       
   102   one looked up already by another view that decides to "forward" the
       
   103   HTTP request to this other view).
       
   104 
       
   105   Args:
       
   106     user: None (usually), or an existing User entity
       
   107     id: a Google Account (users.User) object
       
   108     
       
   109   Returns:
       
   110     * user (which may have already been None if passed in that way by the
       
   111       caller) if id is false or user is non-false
       
   112     * results of getUserFromId() if user is false and id is non-false
       
   113   """
       
   114   if id and (not user):
       
   115     # Google Account supplied and User uninitialized, so look up User entity
       
   116     user = getUserFromId(id)
       
   117     
       
   118   return user
       
   119 
       
   120 
       
   121 def getNearestUsers(id=None, link_name=None):
       
   122   """Get User entities just before and just after the specified User.
       
   123     
       
   124   Args:
       
   125     id: a Google Account (users.User) object; default is None (not supplied)
       
   126     link_name: link name string; default is None (not supplied)
       
   127 
       
   128   Returns:
       
   129     User entities being those just before and just after the (possibly
       
   130     non-existent) User for given id or link_name,
       
   131       OR
       
   132     possibly None if query had no results or neither id or link_name were
       
   133     supplied.
       
   134   """
       
   135   return model.getNearestEntities(
       
   136       soc.models.user.User, [('id', id), ('link_name', link_name)])
       
   137 
    37 
   138 
    38 
   139 def findNearestUsersOffset(width, id=None, link_name=None):
    39 def findNearestUsersOffset(width, id=None, link_name=None):
   140   """Finds offset of beginning of a range of Users around the nearest User.
    40   """Finds offset of beginning of a range of Users around the nearest User.
   141   
    41   
   154   """
    54   """
   155   return model.findNearestEntitiesOffset(
    55   return model.findNearestEntitiesOffset(
   156     width, soc.models.user.User, [('id', id), ('link_name', link_name)])
    56     width, soc.models.user.User, [('id', id), ('link_name', link_name)])
   157 
    57 
   158 
    58 
   159 def doesUserExist(id):
       
   160   """Returns True if User exists in the Datastore for a Google Account.
       
   161     
       
   162   Args:
       
   163     id: a Google Account object
       
   164   """
       
   165   if getUserFromId(id):
       
   166     return True
       
   167   else:
       
   168     return False
       
   169 
       
   170 
       
   171 def isIdUser(id=None):
       
   172   """Returns True if a Google Account has it's User entity in datastore.
       
   173 
       
   174   Args:
       
   175     id: a Google Account (users.User) object; if id is not supplied,
       
   176       the current logged-in user is checked
       
   177   """
       
   178   id = getIdIfMissing(id)
       
   179 
       
   180   if not id:
       
   181     # no Google Account was supplied or is logged in
       
   182     return False
       
   183 
       
   184   user = getUserFromId(id)
       
   185 
       
   186   if not user:
       
   187     # no User entity for this Google Account
       
   188     return False
       
   189   
       
   190   return True
       
   191 
       
   192 
       
   193 def isIdDeveloper(id=None):
    59 def isIdDeveloper(id=None):
   194   """Returns True if a Google Account is a Developer with special privileges.
    60   """Returns True if a Google Account is a Developer with special privileges.
   195   
    61   
   196   Since it only works on the current logged-in user, if id matches the
    62   Since it only works on the current logged-in user, if id matches the
   197   current logged-in Google Account, the App Engine Users API function
    63   current logged-in Google Account, the App Engine Users API function
   204   
    70   
   205   Args:
    71   Args:
   206     id: a Google Account (users.User) object; if id is not supplied,
    72     id: a Google Account (users.User) object; if id is not supplied,
   207       the current logged-in user is checked
    73       the current logged-in user is checked
   208   """
    74   """
   209   id = getIdIfMissing(id)
    75 
   210  
    76   # Get the currently logged in user
   211   if not id:
    77   current_id = users.get_current_user()
       
    78 
       
    79   if not (id or current_id):
   212     # no Google Account was supplied or is logged in, so an unspecified
    80     # no Google Account was supplied or is logged in, so an unspecified
   213     # User is definitely *not* a Developer
    81     # User is definitely *not* a Developer
   214     return False
    82     return False
   215 
    83 
   216   if id == users.get_current_user():
    84   if (not id or id == current_id) and users.is_current_user_admin():
   217     if users.is_current_user_admin():
    85     # no id supplied, or current logged-in user, and that user is in the
   218       # supplied id is current logged-in user, and that user is in the
    86     # Administration->Developers list in the App Engine console
   219       # Administration->Developers list in the App Engine console
    87     return True
   220       return True
    88 
   221   
    89   # If no id is specified, default to logged in user
   222   user = getUserFromId(id)
    90   if not id:
       
    91     id = current_id
       
    92 
       
    93   user = soc.logic.user_logic.getFromFields(id=id)
   223 
    94 
   224   if not user:
    95   if not user:
   225     # no User entity for this Google Account, and id is not the currently
    96     # no User entity for this Google Account, and id is not the currently
   226     # logged-in user, so there is no conclusive way to check the
    97     # logged-in user, so there is no conclusive way to check the
   227     # Administration->Developers list in the App Engine console
    98     # Administration->Developers list in the App Engine console
   239       existing_key_name is used to look up the User entity
   110       existing_key_name is used to look up the User entity
   240     existing_key_name: the key_name of an existing User entity, used
   111     existing_key_name: the key_name of an existing User entity, used
   241       when existing_user is not supplied; default is None
   112       when existing_user is not supplied; default is None
   242   """
   113   """
   243   if not existing_user:
   114   if not existing_user:
   244     existing_user = getUserFromKeyName(existing_key_name)
   115     existing_user = soc.logic.user_logic.getFromKeyName(existing_key_name)
   245 
   116 
   246   if existing_user:
   117   if existing_user:
   247     old_email = existing_user.id.email()
   118     old_email = existing_user.id.email()
   248   else:
   119   else:
   249     old_email = None
   120     old_email = None
   251   if new_id.email() == old_email:
   122   if new_id.email() == old_email:
   252     # "new" email is same as existing User wanting it, so it is "available"
   123     # "new" email is same as existing User wanting it, so it is "available"
   253     return True
   124     return True
   254   # else: "new" email truly is new to the existing User, so keep checking
   125   # else: "new" email truly is new to the existing User, so keep checking
   255 
   126 
   256   if not isIdUser(new_id):
   127   if not soc.logic.user_logic.getFromFields(id=new_id):
   257     # new email address also does not belong to any other User,
   128     # new email address also does not belong to any other User,
   258     # so it is available
   129     # so it is available
   259     return True
   130     return True
   260 
   131 
   261   # email does not already belong to this User, but to some other User
   132   # email does not already belong to this User, but to some other User
   269     link_name: link name used in URLs to identify user
   140     link_name: link name used in URLs to identify user
   270   """
   141   """
   271   return soc.models.user.User.gql('WHERE link_name = :1', link_name).get()
   142   return soc.models.user.User.gql('WHERE link_name = :1', link_name).get()
   272 
   143 
   273 
   144 
   274 def getUserFromKeyName(key_name):
   145 def getUserFromLinkNameOrDie(link_name):
   275   """Returns User entity for key_name or None if not found.
   146   """Like getUserFromLinkName but expects to find a user
   276     
       
   277   Args:
       
   278     key_name: key name of User entity
       
   279   """
       
   280   return soc.models.user.User.get_by_key_name(key_name)
       
   281 
       
   282 
       
   283 def getUserIfLinkName(link_name):
       
   284   """Returns User entity for supplied link_name if one exists.
       
   285   
       
   286   Args:
       
   287     link_name: link name used in URLs to identify user
       
   288 
       
   289   Returns:
       
   290     * None if link_name is false.
       
   291     * User entity that has supplied link_name
       
   292 
   147 
   293   Raises:
   148   Raises:
   294     out_of_band.ErrorResponse if link_name is not false, but no User entity
   149     out_of_band.ErrorResponse if no User entity is found
   295     with the supplied link_name exists in the Datastore
       
   296   """
   150   """
   297   if not link_name:
       
   298     # exit without error, to let view know that link_name was not supplied
       
   299     return None
       
   300 
   151 
   301   link_name_user = getUserFromLinkName(link_name)
   152   user = getUserFromLinkName(link_name)
   302     
       
   303   if link_name_user:
       
   304     # a User has this link name, so return that corresponding User entity
       
   305     return link_name_user
       
   306 
   153 
   307   # else: a link name was supplied, but there is no User that has it
   154   if user:
       
   155     return user
       
   156 
   308   raise out_of_band.ErrorResponse(
   157   raise out_of_band.ErrorResponse(
   309       'There is no user with a "link name" of "%s".' % link_name, status=404)
   158       'There is no user with a "link name" of "%s".' % link_name, status=404)
   310 
   159 
   311 
   160 
   312 def isLinkNameAvailableForId(link_name, id=None):
   161 def doesLinkNameBelongToId(link_name, id):
   313   """Indicates if link name is available for the given Google Account.
       
   314   
       
   315   Args:
       
   316     link_name: link name used in URLs to identify user
       
   317     id: a Google Account object; optional, current logged-in user will
       
   318       be used (or False will be returned if no user is logged in)
       
   319       
       
   320   Returns:
       
   321     True: the link name does not exist in the Datastore,
       
   322       so it is currently "available" to any User
       
   323     True: the link name exists and already belongs to the User entity
       
   324       associated with the specified Google Account
       
   325     False: the link name exists and belongs to a User entity other than
       
   326       that associated with the supplied Google Account
       
   327   """
       
   328   link_name_exists = doesLinkNameExist(link_name)
       
   329  
       
   330   if not link_name_exists:
       
   331     # if the link name does not exist, it is clearly available for any User
       
   332     return True
       
   333 
       
   334   return doesLinkNameBelongToId(link_name, id=id)
       
   335 
       
   336 
       
   337 def doesLinkNameExist(link_name=None):
       
   338   """Returns True if link name exists in the Datastore.
       
   339 
       
   340   Args:
       
   341     link_name: link name used in URLs to identify user
       
   342   """
       
   343   if getUserFromLinkName(link_name):
       
   344     return True
       
   345   else:
       
   346     return False
       
   347 
       
   348 
       
   349 def doesLinkNameBelongToId(link_name, id=None):
       
   350   """Returns True if supplied link name belongs to supplied Google Account.
   162   """Returns True if supplied link name belongs to supplied Google Account.
   351   
   163   
   352   Args:
   164   Args:
   353     link_name: link name used in URLs to identify user
   165     link_name: link name used in URLs to identify user
   354     id: a Google Account object; optional, current logged-in user will
   166     id: a Google Account object
   355       be used (or False will be returned if no user is logged in)
       
   356   """
   167   """
   357   id = getIdIfMissing(id)
   168 
   358     
       
   359   if not id:
   169   if not id:
   360     # id not supplied and no Google Account logged in, so link name cannot
   170     # link name cannot belong to an unspecified User
   361     # belong to an unspecified User
       
   362     return False
   171     return False
   363 
   172 
   364   user = getUserFromId(id)
   173   user = soc.logic.user_logic.getFromFields(email=id.email())
   365 
   174 
   366   if not user:
   175   if not user:
   367     # no User corresponding to id Google Account, so no link name at all 
   176     # no User corresponding to id Google Account, so no link name at all 
   368     return False
   177     return False
   369 
   178 
   370   if user.link_name != link_name:
   179   return user.link_name == link_name
   371     # User exists for id, but does not have this link name
       
   372     return False
       
   373 
       
   374   return True  # link_name does actually belong to this Google Account
       
   375 
       
   376 
       
   377 def updateOrCreateUserFromId(id, **user_properties):
       
   378   """Update existing User entity, or create new one with supplied properties.
       
   379 
       
   380   Args:
       
   381     id: a Google Account object
       
   382     **user_properties: keyword arguments that correspond to User entity
       
   383       properties and their values
       
   384       
       
   385   Returns:
       
   386     the User entity corresponding to the Google Account, with any supplied
       
   387     properties changed, or a new User entity now associated with the Google
       
   388     Account and with the supplied properties
       
   389   """
       
   390   # attempt to retrieve the existing User
       
   391   user = getUserFromId(id)
       
   392   
       
   393   if not user:
       
   394     # user did not exist, so create one in a transaction
       
   395     key_name = getUserKeyNameFromId(id)
       
   396     user = soc.models.user.User.get_or_insert(
       
   397       key_name, id=id, **user_properties)
       
   398 
       
   399   # there is no way to be sure if get_or_insert() returned a new User or
       
   400   # got an existing one due to a race, so update with user_properties anyway,
       
   401   # in a transaction
       
   402   return updateUserProperties(user, **user_properties)
       
   403 
       
   404 
       
   405 def updateUserForKeyName(key_name, **user_properties):
       
   406   """Update existing User entity for keyname with supplied properties.
       
   407 
       
   408   Args:
       
   409     key_name: key name of User entity
       
   410     **user_properties: keyword arguments that correspond to User entity
       
   411       properties and their values
       
   412 
       
   413   Returns:
       
   414     the User entity corresponding to the Google Account, with any supplied
       
   415     properties changed, or a new User entity now associated with the Google
       
   416     Account and with the supplied properties
       
   417   """
       
   418   # attempt to retrieve the existing User
       
   419   user = getUserFromKeyName(key_name)
       
   420 
       
   421   if not user:
       
   422     return None
       
   423   
       
   424   # there is no way to be sure if get_or_insert() returned a new User or
       
   425   # got an existing one due to a race, so update with user_properties anyway,
       
   426   # in a transaction
       
   427   return updateUserProperties(user, **user_properties)
       
   428 
       
   429 
       
   430 def updateUserProperties(user, **user_properties):
       
   431   """Update existing User entity using supplied User properties.
       
   432 
       
   433   Args:
       
   434     user: a User entity
       
   435     **user_properties: keyword arguments that correspond to User entity
       
   436       properties and their values
       
   437       
       
   438   Returns:
       
   439     the original User entity with any supplied properties changed 
       
   440   """
       
   441   def update():
       
   442     return _unsafeUpdateUserProperties(user, **user_properties)
       
   443 
       
   444   return db.run_in_transaction(update)
       
   445 
       
   446 
       
   447 def _unsafeUpdateUserProperties(user, **user_properties):
       
   448   """(see updateUserProperties)
       
   449   
       
   450   Like updateUserProperties(), but not run within a transaction. 
       
   451   """
       
   452   properties = user.properties()
       
   453   
       
   454   for prop in properties.values():
       
   455     if prop.name in user_properties:
       
   456       if prop.name == 'former_ids':
       
   457         # former_ids cannot be overwritten directly
       
   458         continue
       
   459 
       
   460       value = user_properties[prop.name]
       
   461 
       
   462       if prop.name == 'id':
       
   463         old_id = user.id
       
   464 
       
   465         if value != old_id:
       
   466           user.former_ids.append(old_id)
       
   467 
       
   468       prop.__set__(user, value)
       
   469         
       
   470   user.put()
       
   471   return user