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