PageRenderTime 71ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/core/domain/user_services.py

https://bitbucket.org/pannac/oppia
Python | 1560 lines | 1516 code | 10 blank | 34 comment | 14 complexity | d33b64cd17859352eab60d9cc936f675 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. # coding: utf-8
  2. #
  3. # Copyright 2014 The Oppia Authors. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS-IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Services for user data."""
  17. import datetime
  18. import hashlib
  19. import imghdr
  20. import logging
  21. import re
  22. from constants import constants
  23. from core.domain import role_services
  24. from core.domain import user_domain
  25. from core.platform import models
  26. import feconf
  27. import utils
  28. from google.appengine.api import urlfetch
  29. current_user_services = models.Registry.import_current_user_services()
  30. (user_models,) = models.Registry.import_models([models.NAMES.user])
  31. MAX_USERNAME_LENGTH = 50
  32. # Size (in px) of the gravatar being retrieved.
  33. GRAVATAR_SIZE_PX = 150
  34. # Data url for images/avatar/user_blue_72px.png
  35. # Generated using utils.convert_png_to_data_url
  36. DEFAULT_IDENTICON_DATA_URL = (
  37. 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAABMCAYAAADHl1ErAAAAAXNSR0IArs4c6QAADhtJREFUeAHt%0AXHlwVdUZ/859jyxmIQESyCaglC0iAgkJIntrIpvKphSwY2ttxbFOp9R/cGGqdhykLaMVO2OtoyRS%0ACEKNEpYKyBIVQ1iNkBhNMCtb8shiQpJ3b7/fTW7m5uUlecu9L4nTM5Pce8895zvf93vnnPud833f%0AEdQLKXb5jsC6%2BuZERZbHKaSMYRbGKERxgpQQUkSIIigEbAmFavlfrUKiVhCVcFa%2BIJEvJOlCcNCA%0AnNKMFQ0o58vEfPgmhS5Mn0ot8n2KIs8lIZJJUfy8almIJqbxhRDSIbJKe2s%2BXvWlV/RcrGwqYGGp%0A20bI1LyaeVmjKMrodp4EycGBAy6MjgsrSxozqG7O5GgxcVREeEigNDAwwBpmsUiRKGu3y1caGlts%0AtQ3yjbOFV6sPnypXTuRXBReU2GLqGprHkUKSRlMIUcD3WyUakGbbt7JYyzf6agpgYfe9O8kui/U8%0AnB7UhJIkUTljwrBTTz449mZKUlyCEBTnjTCKQiX7T5ScfGP3Rf9j5ysny7IyTKXHPwYP690WSXnZ%0AtvcXp71pw1ldQwELm59%2BlyzbX%2BbeNL%2Btscb4EYOyNz2ZWD99wtAFnGdxxoQBefbs85f3rHsjJyiv%0AuGo60wsATe51WZJkWW/LWnXGgDZUEoYAFr58x0B7beOLPHGv5XnFIpGoS0mKOfze%2Bpmj/f2smNR9%0Alm42teQ/8vLRgv0nyuZwVwtm1Ows5BZLSMBz1RkrbnjLiNeAhaWmPWgn%2BxYeejwkRMu9idH7tm%2BY%0AE8/z0EhvmfOmPs9/RQ9tOJx3IKc8lUixkqBKC1nW2vat3u0NXY8Bi1%2B%2Bw6%2BktnETD7%2BnwEB4iP/p%0AL/5xf03U4IBZ3jBkdN2K641Hkn/7YWh17c1JoM3D9PW4kIB1eRkrmjxpyyPAeK4aLttbPuAhOIU5%0AaHpm1cTMZ1ffuRT8eMKED%2BooL6Wd%2B2Bj%2BtnFUGeYyVzJYl3Kc9sld9t2W8Dw%2BWkTWuz2fdxQ9ACr%0A9P3Jfy7%2BZuSw0HnuNtwb5Ysqaw4mPJb5k%2BYW%2BVZuv9xqsaRWZ60%2B7w4vbgEWnrJ1hp3kTO5ZYUPC%0AAnK%2B3bYiitWDWHca7O2yrI6U3r5yR8U1W2MiC2%2BzkLS4ev%2BaY67y1a749VQBYLUIZT/AGhUTduS7%0Af68Y39/AgozgGbxDBsgCmSBbT/Jr710CDMMQPYvHf2DC2Mj9p95efA8TCNKI9MNrEGSALJAJskFG%0AV%2BTocUhigrfbWz5jYtH4VdrAMksBdYVnI8vYJ/8q83hhmW0WEy23WKx39/Qh6LaHQXXA1xBgYc5i%0AsBL4/scCFoC3QCbIBhkhK2TGi65St4CpeharDvgaYoJnIv15GHaFQRBkg4w8p02BzF0VRH6XgEGD%0AV5VS1rOgOvTHCb47wfXvIBtkhE4JmSG7/r3%2B3ilg6toQyx1OUEr7i56lF8zde8gIWVEPSz1g4IyG%0AU8CwkMbaEMudNg3eWd0fXR5khcyQXcXAiYSdAMMWDY/ltVhIY23IdXr8kjqh21%2BzRKvMogUYAAtH%0AQToBhv0sbNFg16GvLaQdmTfjGTJDdmCgYuHQSIfe07pTSqewn3V9z6qrvb1F48Crzx6xNTR4QXoE%0A9tN4c2%2ByfufWqudC3VbmAYzNPwZrkf6dL%2B4LSm5Q9vkrVH79B6qs%2BoH8B1goatAtNCIqmOZOiabw%0A4G5VJMNYREdhDD7ae6J0USsmtEwj3t7DYLCwK83f8WbbzauZP7/kq53SxiY7vfmfC5R24Fv6prTr%0ADVEWgqbfEUlPLY2nlKkxGv%2BmXbFzG7H4/eE8g/tZyO92zbDSPoe1WncUgT14X4G189Nimvjobnrh%0AX6e6BQuo8DCho2crafnzB2n%2BMwe4PL5H5iVgACx4wEltli%2B1sXbA%2BGkNcmCwUN%2BY%2BI%2B3WOjZt3Lp%0Al68cpQoefu6m4%2Bcqae7TWfTfk%2BXuVnWrvA4LFRtUVockjKxKc8sJmMJsWWsiON/U9eJvNmXTtk%2B%2B%0AdYt5Z4WZX0p/bjYtmBbn7LURefaw%2BVuvwoQnBliTYCxu7WFskQb1WROjcvliKlibM/IMAQv8siD0%0A643H6etiGx7NSBbYUlXCbRipgKnme859Ysl4jwwDrnKaV2SjDe%2B0tu9qnZ7KsQWch/YxVpt6KunZ%0AexieUVPDSIJjCC86k3lwyikJ0di%2BMS09/3au2iuMbuDr4mpKN2CIO%2BMLVnpgA4yAlVRX1ziV4fOD%0ArwOv2k2bDM4UVvEkXeaMJ0PyXn3/nCF0HIkAE2ADjICVpChiLArBMcSxsJHPmdmXjCTXiVZRRS19%0AVVTdKd%2BIDA0bYCW1%2BWcRvGiMIN4Vjb1flHb1yrD8rM9LDKOlJ6RhA6ww6au%2BD3A50hcy%2Bt5sRRP8%0AFpSYo8zqsBnDPax13oJ/ltEgafSqam5SU7NdezTtWsHrTzOShg2wYtWP3SQ5wZnNjMZA80Z9s1mk%0AO9CtMakdDRtgJcGnFK3C869D6wY%2BRISp7loGUnROKtKkdtqxYawkzQGXdwNUN0nnrHiXGxxoJf40%0Ae0fEhdpRg29xoZT7RTRsgJV%2B8e0%2BJTdqJIwd4kZpz4pOGWN%2BG5Lq2s38wQHXMzZdq2XiAlllgP2%2B%0AaH6yOX4xGjbAinejlVq0CG9l10T3rNT99wwnf96KMyvNuHMoDR0UaAr5dmwYK1YrhAoYXLtNaa2N%0A6DAW5vFF6qLClGZeeHSyKXRBVMMGWLFaoUZYEPzgTWuxjfC6lROI/RgMb2bZ7JGUaOIcqWEDrDDp%0A50MCBA0YLokDQRgx0p%2BdTezH4PDG88dxI8LotaeneU7AhZo6bPK5hwkVMERYuFDX6yLT2JDx99/f%0ATVY2anibYiOCaPuGuayydDB%2BeUu2U30NG2AlCaFcRAmEo3QqaVLGynm30a6X5sHz2uMWksZH0pHX%0AF9CIYeb/zho2CAqTgoMDvoTXCmJ3EI7isQRuVpw9KYqytyykhxk8qASuJoD84mNTKGvjveSLFQQw%0AUeOaGCNE0Flqvs5o8b/9gZ8xwyMmj404NComZJyrzHtbLjTIjxZNv1X9C/S30pXqRrLVdd4lh7Ej%0AOX4oPfHAOHrzD9Np9l1RZMHnygeJ45kOZXxaPJ6byr6WueotdfAjhI73rGdu2ZXnn5oY7QM2OjZx%0Ax8hw%2BvPjCepf2bUfqJz/Llc1qHpb1OBAiosMpoFB5i%2BtOnLV%2BoTgL9ypYYZ8bZ0tOd6QmuUNbCiF%0AMoN9GPM0TCbeXYoZcgvhr48kOyLlVF6AESf1UwV7G88jBbC/ISqsjzDb62wAC9UmydhoAaz6b/tW%0AcIgQul7ntI8woMNCxQZstQOGSFYeqQriDeGI0Ud47jU2gIEae8kmtlZsWllpB6zNO2UXZwcg3rDX%0AOO0jDbdhEIDoXs1zB6y1A4YHhP3iiuBMOJXh3tfJzuZ/qBbfX65nR5UGqmto8TUL2OoqAgZoWMNE%0AY6KTMhOa%2Bt4ehCDfmxjz8c4X5y3UChp5hVk/j63Vpwuu0zdlNVTIrkuFfC1hkOobO%2B//Qw8LD/an%0A26JDaFRsKI2KCWU76kCaOi6CoHYYnZY9d/DjAzllC/lDmFWz75EFevqdFmGIkbbL9hREsiI40yg/%0A11wGhxex9PlXV%2BjEhatUU99ZQdUzpr%2BH08n1mkb1L%2BfiVf0rGs5Lo2nxkXT3HUPZ0S7WawAhsxrF%0Ay6HPwKJDY/zQqYehAPey1%2BDgDxfsSxkPwZPYaTmU7S7BPWDXkWLafayYLlWaaidW2cASK5nBWzJz%0AOD3AG5YebCgqw5dvP4PoXab1Oveu3znK5xQIOPW31DZchL/6M6vv2sn%2B68scK3b1jDlo%2B6Hv6G87%0A8ij/e1M3cbtiQc3HML4vKZbWrbyTpowe3G1Z7SVH7e7cmHZmGXePSmtI4FhnQfVOAQMBNfhdse/C%0AwvzsO/cf6ykapKlZpq0HCmlzxlc%2B6U2akK5c2XJNf3x4At3D29hdJUTrTnz0wxlwOrEIy5Kugum7%0ABAyEtaGJwKVrH63mrSDn0besEdNTmz9XJ%2B6uGOoL%2BbAr/OXJJIoM77jryx%2Bh0iGL0mSENnc1FDX%2B%0AO6gVWqZ2RfQ9I5oLQgj75fxO/q%2BvpJ9TnXTxlevr6cPjlyj5iUx2bb%2BsZ7UesqlgsayQWf/S8b7b%0AHobC3QWYrv3rZ%2BwuXuhIs88/Y4v8vfWz4BvrdoBpj4BBejWE2W4/yupTGMJ%2BD21O/emf3j1t2bTN%0ArYD8PgWkv7/FflvUwE8uFFelMAg2i8Uy05UTBlwCTAWtLUieJ8XA2MiQIxXX6xNYI%2B6XC3Wep%2Br5%0Axz/Jsszij1qDVREprp4s4DJgGmjaMQzcUA5bgaNkRTbH3GxSf5SEVMoxRBUMlrnHMIB//Arounxb%0AjgZZuWWtSzlokmyGkwWv4Bm8QwZ1GLpxZgUYcquHaRLgQ6A/SobJ4IiGpeyc7RE9ja55V/aKEOID%0A5s/3R8loQjkeVsTzwmmeF2oYuFlamT5xFeII/4qh3LMmgR/oWT4/rEgPhONxWEKifUJW4mWikfpy%0Avr5nBbNIkUQeD8BU7lm9fxyWHgDHA9fYQlzHg/0w/6qjuZzqdKwvb/J9PveiAl4Hz%2BE5q%2B8duKYX%0AHjHSjkf6sXkqWyEZK4QFLIQ51iihWrr2CJKCeE6fzm2pax8Grm8e6acHDffth0YSLdF9CCoZvFye%0A55okRU7gIetV1AkPuRJZSCfZUdefezJMYf3v0MhOwHVzLKlQxAWSRJlQlDr%2BzrPcUjjbGwbyBB2m%0ACKH62/K7KwywjWM8b5CQq%2BH9x%2B%2BCSVZiFKH8eI4ldQQOz4jJ/P/Bt86QcSFPPVqZA50Qu4NwFK7i%0A3tHK7HEEJ5reOFr5fwkK97jkk8ywAAAAAElFTkSuQmCC%0A') #pylint: disable=line-too-long
  38. SYSTEM_USERS = {
  39. feconf.SYSTEM_COMMITTER_ID: feconf.SYSTEM_COMMITTER_ID,
  40. feconf.MIGRATION_BOT_USER_ID: feconf.MIGRATION_BOT_USERNAME
  41. }
  42. class UserSettings(object):
  43. """Value object representing a user's settings.
  44. Attributes:
  45. user_id: str. The user id.
  46. email: str. The user email.
  47. role: str. Role of the user. This is used in conjunction with
  48. PARENT_ROLES to determine which actions the user can perform.
  49. username: str or None. Identifiable username to display in the UI.
  50. last_agreed_to_terms: datetime.datetime or None. When the user last
  51. agreed to the terms of the site.
  52. last_started_state_editor_tutorial: datetime.datetime or None. When
  53. the user last started the state editor tutorial.
  54. last_logged_in: datetime.datetime or None. When the user last logged in.
  55. last_created_an_exploration: datetime.datetime or None. When the user
  56. last created an exploration.
  57. last_edited_an_exploration: datetime.datetime or None. When the user
  58. last edited an exploration.
  59. profile_picture_data_url: str or None. User uploaded profile picture as
  60. a dataURI string.
  61. default_dashboard: str or None. The default dashboard of the user.
  62. user_bio: str. User-specified biography.
  63. subject_interests: list(str) or None. Subject interests specified by
  64. the user.
  65. first_contribution_msec: float or None. The time in milliseconds when
  66. the user first contributed to Oppia.
  67. preferred_language_codes: list(str) or None. Exploration language
  68. preferences specified by the user.
  69. preferred_site_language_code: str or None. System language preference.
  70. preferred_audio_language_code: str or None. Audio language preference.
  71. """
  72. def __init__(
  73. self, user_id, email, role, username=None,
  74. last_agreed_to_terms=None, last_started_state_editor_tutorial=None,
  75. last_logged_in=None, last_created_an_exploration=None,
  76. last_edited_an_exploration=None, profile_picture_data_url=None,
  77. default_dashboard=None,
  78. creator_dashboard_display_pref=(
  79. constants.ALLOWED_CREATOR_DASHBOARD_DISPLAY_PREFS['CARD']),
  80. user_bio='', subject_interests=None, first_contribution_msec=None,
  81. preferred_language_codes=None, preferred_site_language_code=None,
  82. preferred_audio_language_code=None):
  83. """Constructs a UserSettings domain object.
  84. Args:
  85. user_id: str. The user id.
  86. email: str. The user email.
  87. role: str. Role of the user. This is used in conjunction with
  88. PARENT_ROLES to determine which actions the user can perform.
  89. username: str or None. Identifiable username to display in the UI.
  90. last_agreed_to_terms: datetime.datetime or None. When the user
  91. last agreed to the terms of the site.
  92. last_started_state_editor_tutorial: datetime.datetime or None. When
  93. the user last started the state editor tutorial.
  94. last_logged_in: datetime.datetime or None. When the user last
  95. logged in.
  96. last_created_an_exploration: datetime.datetime or None. When the
  97. user last created an exploration.
  98. last_edited_an_exploration: datetime.datetime or None. When the
  99. user last edited an exploration.
  100. profile_picture_data_url: str or None. User uploaded profile
  101. picture as a dataURI string.
  102. user_bio: str. User-specified biography.
  103. subject_interests: list(str) or None. Subject interests specified by
  104. the user.
  105. first_contribution_msec: float or None. The time in milliseconds
  106. when the user first contributed to Oppia.
  107. preferred_language_codes: list(str) or None. Exploration language
  108. preferences specified by the user.
  109. preferred_site_language_code: str or None. System language
  110. preference.
  111. preferred_audio_language_code: str or None. Default language used
  112. for audio translations preference.
  113. """
  114. self.user_id = user_id
  115. self.email = email
  116. self.role = role
  117. self.username = username
  118. self.last_agreed_to_terms = last_agreed_to_terms
  119. self.last_started_state_editor_tutorial = ( # pylint: disable=invalid-name
  120. last_started_state_editor_tutorial)
  121. self.last_logged_in = last_logged_in
  122. self.last_edited_an_exploration = last_edited_an_exploration
  123. self.last_created_an_exploration = last_created_an_exploration
  124. self.profile_picture_data_url = profile_picture_data_url
  125. self.default_dashboard = default_dashboard
  126. self.creator_dashboard_display_pref = creator_dashboard_display_pref
  127. self.user_bio = user_bio
  128. self.subject_interests = (
  129. subject_interests if subject_interests else [])
  130. self.first_contribution_msec = first_contribution_msec
  131. self.preferred_language_codes = (
  132. preferred_language_codes if preferred_language_codes else [])
  133. self.preferred_site_language_code = preferred_site_language_code
  134. self.preferred_audio_language_code = preferred_audio_language_code
  135. def validate(self):
  136. """Checks that user_id and email fields of this UserSettings domain
  137. object are valid.
  138. Raises:
  139. ValidationError: user_id is not str.
  140. ValidationError: email is not str.
  141. ValidationError: email is invalid.
  142. ValidationError: role is not str.
  143. ValidationError: Given role does not exist.
  144. """
  145. if not isinstance(self.user_id, basestring):
  146. raise utils.ValidationError(
  147. 'Expected user_id to be a string, received %s' % self.user_id)
  148. if not self.user_id:
  149. raise utils.ValidationError('No user id specified.')
  150. if not isinstance(self.email, basestring):
  151. raise utils.ValidationError(
  152. 'Expected email to be a string, received %s' % self.email)
  153. if not self.email:
  154. raise utils.ValidationError('No user email specified.')
  155. if ('@' not in self.email or self.email.startswith('@')
  156. or self.email.endswith('@')):
  157. raise utils.ValidationError(
  158. 'Invalid email address: %s' % self.email)
  159. if not isinstance(self.role, basestring):
  160. raise utils.ValidationError(
  161. 'Expected role to be a string, received %s' % self.role)
  162. if self.role not in role_services.PARENT_ROLES:
  163. raise utils.ValidationError('Role %s does not exist.' % self.role)
  164. if not isinstance(self.creator_dashboard_display_pref, basestring):
  165. raise utils.ValidationError(
  166. 'Expected dashboard display preference to be a string, '
  167. 'received %s' % self.creator_dashboard_display_pref)
  168. if (self.creator_dashboard_display_pref not in
  169. constants.ALLOWED_CREATOR_DASHBOARD_DISPLAY_PREFS.values()):
  170. raise utils.ValidationError(
  171. '%s is not a valid value for the dashboard display '
  172. 'preferences.' % (self.creator_dashboard_display_pref))
  173. @property
  174. def truncated_email(self):
  175. """Returns truncated email by replacing last two characters before @
  176. with period.
  177. Returns:
  178. str. The truncated email address of this UserSettings
  179. domain object.
  180. """
  181. first_part = self.email[: self.email.find('@')]
  182. last_part = self.email[self.email.find('@'):]
  183. if len(first_part) <= 1:
  184. first_part = '..'
  185. elif len(first_part) <= 3:
  186. first_part = '%s..' % first_part[0]
  187. else:
  188. first_part = first_part[:-3] + '..'
  189. return '%s%s' % (first_part, last_part)
  190. @property
  191. def is_known_user(self):
  192. """Returns bool based on whether or not UserSettings domain
  193. object contains an email property.
  194. Returns:
  195. bool. Whether this domain object contains an 'email' property.
  196. If the return value is not True, something has gone wrong.
  197. """
  198. return bool(self.email)
  199. @property
  200. def normalized_username(self):
  201. """Returns username in lowercase or None if it does not exist.
  202. Returns:
  203. str or None. If this object has a 'username' property, returns
  204. the normalized version of the username. Otherwise, returns None.
  205. """
  206. return self.normalize_username(self.username)
  207. @classmethod
  208. def normalize_username(cls, username):
  209. """Returns the normalized version of the given username,
  210. or None if the passed-in 'username' is None.
  211. Args:
  212. username: str. Identifiable username to display in the UI.
  213. Returns:
  214. str or None. The normalized version of the given username,
  215. or None if the passed-in username is None.
  216. """
  217. return username.lower() if username else None
  218. @classmethod
  219. def require_valid_username(cls, username):
  220. """Checks if the given username is valid or not.
  221. Args:
  222. username: str. The username to validate.
  223. Raises:
  224. ValidationError: An empty username is supplied.
  225. ValidationError: The given username exceeds the maximum allowed
  226. number of characters.
  227. ValidationError: The given username contains non-alphanumeric
  228. characters.
  229. ValidationError: The given username contains reserved substrings
  230. ('admin', 'oppia').
  231. """
  232. if not username:
  233. raise utils.ValidationError('Empty username supplied.')
  234. elif len(username) > MAX_USERNAME_LENGTH:
  235. raise utils.ValidationError(
  236. 'A username can have at most %s characters.'
  237. % MAX_USERNAME_LENGTH)
  238. elif not re.match(feconf.ALPHANUMERIC_REGEX, username):
  239. raise utils.ValidationError(
  240. 'Usernames can only have alphanumeric characters.')
  241. elif ('admin' in username.lower().strip() or
  242. 'oppia' in username.lower().strip() or
  243. feconf.MIGRATION_BOT_USERNAME in username.lower().strip()):
  244. # Admin usernames are reserved for admins. Note that 'admin'
  245. # itself is already in use for the demo exploration.
  246. raise utils.ValidationError('This username is not available.')
  247. def is_username_taken(username):
  248. """"Returns whether the given username has already been taken.
  249. Args:
  250. username: str. Identifiable username to display in the UI.
  251. Returns:
  252. bool. Whether the given username is taken.
  253. """
  254. return user_models.UserSettingsModel.is_normalized_username_taken(
  255. UserSettings.normalize_username(username))
  256. def get_email_from_user_id(user_id):
  257. """Gets the email from a given user_id.
  258. Args:
  259. user_id: str. The user id.
  260. Returns:
  261. str. user_email corresponding to the given user_id.
  262. Raises:
  263. Exception: The user is not found.
  264. """
  265. user_settings = get_user_settings(user_id)
  266. return user_settings.email
  267. def get_email_from_username(username):
  268. """Gets the email for a given username.
  269. Args:
  270. username: str. Identifiable username to display in the UI.
  271. Returns:
  272. str or None. If the user with given username does not exist,
  273. return None. Otherwise return the corresponding user_email.
  274. """
  275. user_model = user_models.UserSettingsModel.get_by_normalized_username(
  276. UserSettings.normalize_username(username))
  277. if user_model is None:
  278. return None
  279. else:
  280. return user_model.email
  281. def get_user_id_from_username(username):
  282. """Gets the user_id for a given username.
  283. Args:
  284. username: str. Identifiable username to display in the UI.
  285. Returns:
  286. str or None. If the user with given username does not exist, return
  287. None. Otherwise return the user_id corresponding to given username.
  288. """
  289. user_model = user_models.UserSettingsModel.get_by_normalized_username(
  290. UserSettings.normalize_username(username))
  291. if user_model is None:
  292. return None
  293. else:
  294. return user_model.id
  295. def get_user_settings_from_username(username):
  296. """Gets the user settings for a given username.
  297. Args:
  298. username: str. Identifiable username to display in the UI.
  299. Returns:
  300. UserSettingsModel or None. The UserSettingsModel instance corresponding
  301. to the given username, or None if no such model was found.
  302. """
  303. user_model = user_models.UserSettingsModel.get_by_normalized_username(
  304. UserSettings.normalize_username(username))
  305. if user_model is None:
  306. return None
  307. else:
  308. return get_user_settings(user_model.id)
  309. def get_users_settings(user_ids):
  310. """Gets domain objects representing the settings for the given user_ids.
  311. Args:
  312. user_ids: list(str). The list of user_ids to get UserSettings
  313. domain objects for.
  314. Returns:
  315. list(UserSettings|None). The UserSettings domain objects corresponding
  316. to the given user ids. If the given user_id does not exist, the
  317. corresponding entry in the returned list is None.
  318. """
  319. user_settings_models = user_models.UserSettingsModel.get_multi(user_ids)
  320. result = []
  321. for ind, model in enumerate(user_settings_models):
  322. if user_ids[ind] == feconf.SYSTEM_COMMITTER_ID:
  323. result.append(UserSettings(
  324. feconf.SYSTEM_COMMITTER_ID,
  325. email=feconf.SYSTEM_EMAIL_ADDRESS,
  326. role=feconf.ROLE_ID_ADMIN,
  327. username='admin',
  328. last_agreed_to_terms=datetime.datetime.utcnow()
  329. ))
  330. elif model:
  331. result.append(UserSettings(
  332. model.id, email=model.email, role=model.role,
  333. username=model.username,
  334. last_agreed_to_terms=model.last_agreed_to_terms,
  335. last_started_state_editor_tutorial=(
  336. model.last_started_state_editor_tutorial),
  337. last_logged_in=model.last_logged_in,
  338. last_edited_an_exploration=model.last_edited_an_exploration,
  339. last_created_an_exploration=(
  340. model.last_created_an_exploration),
  341. profile_picture_data_url=model.profile_picture_data_url,
  342. default_dashboard=model.default_dashboard,
  343. creator_dashboard_display_pref=(
  344. model.creator_dashboard_display_pref),
  345. user_bio=model.user_bio,
  346. subject_interests=model.subject_interests,
  347. first_contribution_msec=model.first_contribution_msec,
  348. preferred_language_codes=model.preferred_language_codes,
  349. preferred_site_language_code=(
  350. model.preferred_site_language_code),
  351. preferred_audio_language_code=(
  352. model.preferred_audio_language_code)
  353. ))
  354. else:
  355. result.append(None)
  356. return result
  357. def generate_initial_profile_picture(user_id):
  358. """Generates a profile picture for a new user and
  359. updates the user's settings in the datastore.
  360. Args:
  361. user_id: str. The user id.
  362. """
  363. user_email = get_email_from_user_id(user_id)
  364. user_gravatar = fetch_gravatar(user_email)
  365. update_profile_picture_data_url(user_id, user_gravatar)
  366. def get_gravatar_url(email):
  367. """Returns the gravatar url for the specified email.
  368. Args:
  369. email: str. The user email.
  370. Returns:
  371. str. The gravatar url for the specified email.
  372. """
  373. return (
  374. 'https://www.gravatar.com/avatar/%s?d=identicon&s=%s' %
  375. (hashlib.md5(email).hexdigest(), GRAVATAR_SIZE_PX))
  376. def fetch_gravatar(email):
  377. """Returns the gravatar corresponding to the user's email, or an
  378. identicon generated from the email if the gravatar doesn't exist.
  379. Args:
  380. email: str. The user email.
  381. Returns:
  382. str. The gravatar url corresponding to the given user email. If the call
  383. to the gravatar service fails, this returns DEFAULT_IDENTICON_DATA_URL
  384. and logs an error.
  385. """
  386. gravatar_url = get_gravatar_url(email)
  387. try:
  388. result = urlfetch.fetch(
  389. gravatar_url,
  390. headers={'Content-Type': 'image/png'},
  391. follow_redirects=False)
  392. except (urlfetch.InvalidURLError, urlfetch.DownloadError):
  393. logging.error('Failed to fetch Gravatar from %s' % gravatar_url)
  394. else:
  395. if result.status_code == 200:
  396. if imghdr.what(None, result.content) == 'png':
  397. return utils.convert_png_binary_to_data_url(result.content)
  398. else:
  399. logging.error(
  400. '[Status %s] Failed to fetch Gravatar from %s' %
  401. (result.status_code, gravatar_url))
  402. return DEFAULT_IDENTICON_DATA_URL
  403. def get_profile_pictures_by_user_ids(user_ids):
  404. """Gets the profile_picture_data_url from the domain objects
  405. representing the settings for the given user_ids.
  406. Args:
  407. user_ids: list(str). The list of user_ids to get
  408. profile_picture_data_url for.
  409. Returns:
  410. dict. A dictionary whose keys are user_ids and whose corresponding
  411. values are their profile_picture_data_url entries. If a user_id does
  412. not exist, the corresponding value is None.
  413. """
  414. user_settings_models = user_models.UserSettingsModel.get_multi(user_ids)
  415. result = {}
  416. for model in user_settings_models:
  417. if model:
  418. result[model.id] = model.profile_picture_data_url
  419. else:
  420. result[model.id] = None
  421. return result
  422. def get_user_settings(user_id, strict=False):
  423. """Return the user settings for a single user.
  424. Args:
  425. user_id: str. The user id.
  426. strict: bool. Whether to fail noisily if no user with the given
  427. id exists in the datastore. Defaults to False.
  428. Returns:
  429. UserSettings or None. If the given user_id does not exist and strict
  430. is False, returns None. Otherwise, returns the corresponding
  431. UserSettings domain object.
  432. Raises:
  433. Exception: strict is True and given user_id does not exist.
  434. """
  435. user_settings = get_users_settings([user_id])[0]
  436. if strict and user_settings is None:
  437. logging.error('Could not find user with id %s' % user_id)
  438. raise Exception('User not found.')
  439. return user_settings
  440. def get_user_role_from_id(user_id):
  441. """Returns role of the user with given user_id.
  442. Args:
  443. user_id: str. The User id.
  444. Returns:
  445. str. Role of the user with given id.
  446. """
  447. user_settings = get_user_settings(user_id, strict=False)
  448. if user_settings is None:
  449. return feconf.ROLE_ID_GUEST
  450. return user_settings.role
  451. def get_usernames_by_role(role):
  452. """Get usernames of all the users with given role ID.
  453. Args:
  454. role: str. The role ID of users requested.
  455. Returns:
  456. list(str). List of usernames of users with given role ID.
  457. """
  458. user_settings = user_models.UserSettingsModel.get_by_role(role)
  459. return [user.username for user in user_settings]
  460. def get_user_ids_by_role(role):
  461. """Get user ids of all the users with given role ID.
  462. Args:
  463. role: str. The role ID of users requested.
  464. Returns:
  465. list(str). List of user ids of users with given role ID.
  466. """
  467. user_settings = user_models.UserSettingsModel.get_by_role(role)
  468. return [user.id for user in user_settings]
  469. class UserActionsInfo(object):
  470. def __init__(self, user_id=None):
  471. self._user_id = user_id
  472. self._role = get_user_role_from_id(user_id)
  473. self._actions = role_services.get_all_actions(self._role)
  474. @property
  475. def user_id(self):
  476. return self._user_id
  477. @property
  478. def role(self):
  479. return self._role
  480. @property
  481. def actions(self):
  482. return self._actions
  483. def get_system_user():
  484. """Returns user object with system committer user id."""
  485. system_user = UserActionsInfo(feconf.SYSTEM_COMMITTER_ID)
  486. return system_user
  487. def _save_user_settings(user_settings):
  488. """Commits a user settings object to the datastore.
  489. Args:
  490. user_settings: UserSettings domain object.
  491. """
  492. user_settings.validate()
  493. user_models.UserSettingsModel(
  494. id=user_settings.user_id,
  495. email=user_settings.email,
  496. role=user_settings.role,
  497. username=user_settings.username,
  498. normalized_username=user_settings.normalized_username,
  499. last_agreed_to_terms=user_settings.last_agreed_to_terms,
  500. last_started_state_editor_tutorial=(
  501. user_settings.last_started_state_editor_tutorial),
  502. last_logged_in=user_settings.last_logged_in,
  503. last_edited_an_exploration=user_settings.last_edited_an_exploration,
  504. last_created_an_exploration=(
  505. user_settings.last_created_an_exploration),
  506. profile_picture_data_url=user_settings.profile_picture_data_url,
  507. default_dashboard=user_settings.default_dashboard,
  508. creator_dashboard_display_pref=(
  509. user_settings.creator_dashboard_display_pref),
  510. user_bio=user_settings.user_bio,
  511. subject_interests=user_settings.subject_interests,
  512. first_contribution_msec=user_settings.first_contribution_msec,
  513. preferred_language_codes=user_settings.preferred_language_codes,
  514. preferred_site_language_code=(
  515. user_settings.preferred_site_language_code),
  516. preferred_audio_language_code=(
  517. user_settings.preferred_audio_language_code)
  518. ).put()
  519. def is_user_registered(user_id):
  520. """Checks if a user is registered with given user_id.
  521. Args:
  522. user_id: str. The user id.
  523. Returns:
  524. bool. Whether a user with the given user_id is registered.
  525. """
  526. if user_id is None:
  527. return False
  528. user_settings = user_models.UserSettingsModel.get(user_id, strict=False)
  529. return bool(user_settings)
  530. def has_ever_registered(user_id):
  531. """Checks if a user has ever been registered with given user_id.
  532. Args:
  533. user_id: str. The user id.
  534. Returns:
  535. bool. Whether a user with the given user_id has ever been registered.
  536. """
  537. user_settings = get_user_settings(user_id, strict=True)
  538. return bool(user_settings.username and user_settings.last_agreed_to_terms)
  539. def has_fully_registered(user_id):
  540. """Checks if a user has fully registered.
  541. Args:
  542. user_id: str. The user id.
  543. Returns:
  544. bool. Whether a user with the given user_id has fully registered.
  545. """
  546. if user_id is None:
  547. return False
  548. user_settings = get_user_settings(user_id, strict=True)
  549. return user_settings.username and user_settings.last_agreed_to_terms and (
  550. user_settings.last_agreed_to_terms >=
  551. feconf.REGISTRATION_PAGE_LAST_UPDATED_UTC)
  552. def create_new_user(user_id, email):
  553. """Creates a new user.
  554. Args:
  555. user_id: str. The user id.
  556. email: str. The user email.
  557. Returns:
  558. UserSettings. The newly-created user settings domain object.
  559. Raises:
  560. Exception: If a user with the given user_id already exists.
  561. """
  562. user_settings = get_user_settings(user_id, strict=False)
  563. if user_settings is not None:
  564. raise Exception('User %s already exists.' % user_id)
  565. user_settings = UserSettings(
  566. user_id, email, feconf.ROLE_ID_EXPLORATION_EDITOR,
  567. preferred_language_codes=[constants.DEFAULT_LANGUAGE_CODE])
  568. _save_user_settings(user_settings)
  569. create_user_contributions(user_id, [], [])
  570. return user_settings
  571. def get_username(user_id):
  572. """Gets username corresponding to the given user_id.
  573. Args:
  574. user_id: str. The user id.
  575. Returns:
  576. str. Username corresponding to the given user_id.
  577. """
  578. if user_id in SYSTEM_USERS:
  579. return SYSTEM_USERS[user_id]
  580. return get_user_settings(user_id, strict=True).username
  581. def get_usernames(user_ids):
  582. """Gets usernames corresponding to the given user_ids.
  583. Args:
  584. user_ids: list(str). The list of user_ids to get usernames for.
  585. Returns:
  586. list(str|None). Containing usernames based on given user_ids.
  587. If a user_id does not exist, the corresponding entry in the
  588. returned list is None.
  589. """
  590. usernames = [None] * len(user_ids)
  591. non_system_user_indices = []
  592. non_system_user_ids = []
  593. for index, user_id in enumerate(user_ids):
  594. if user_id in SYSTEM_USERS:
  595. usernames[index] = SYSTEM_USERS[user_id]
  596. else:
  597. non_system_user_indices.append(index)
  598. non_system_user_ids.append(user_id)
  599. non_system_users_settings = get_users_settings(non_system_user_ids)
  600. for index, user_settings in enumerate(non_system_users_settings):
  601. if user_settings:
  602. usernames[non_system_user_indices[index]] = user_settings.username
  603. return usernames
  604. # NB: If we ever allow usernames to change, update the
  605. # config_domain.BANNED_USERNAMES property.
  606. def set_username(user_id, new_username):
  607. """Updates the username of the user with the given user_id.
  608. Args:
  609. user_id: str. The user id.
  610. new_username: str. The new username to set.
  611. Raises:
  612. ValidationError: The new_username supplied is already taken.
  613. """
  614. user_settings = get_user_settings(user_id, strict=True)
  615. UserSettings.require_valid_username(new_username)
  616. if is_username_taken(new_username):
  617. raise utils.ValidationError(
  618. 'Sorry, the username \"%s\" is already taken! Please pick '
  619. 'a different one.' % new_username)
  620. user_settings.username = new_username
  621. _save_user_settings(user_settings)
  622. def record_agreement_to_terms(user_id):
  623. """Records that the user with given user_id has agreed to the license terms.
  624. Args:
  625. user_id: str. The user id.
  626. """
  627. user_settings = get_user_settings(user_id, strict=True)
  628. user_settings.last_agreed_to_terms = datetime.datetime.utcnow()
  629. _save_user_settings(user_settings)
  630. def update_profile_picture_data_url(user_id, profile_picture_data_url):
  631. """Updates profile_picture_data_url of user with given user_id.
  632. Args:
  633. user_id: str. The user id.
  634. profile_picture_data_url: str. New profile picture url to be set.
  635. """
  636. user_settings = get_user_settings(user_id, strict=True)
  637. user_settings.profile_picture_data_url = profile_picture_data_url
  638. _save_user_settings(user_settings)
  639. def update_user_bio(user_id, user_bio):
  640. """Updates user_bio of user with given user_id.
  641. Args:
  642. user_id: str. The user id.
  643. user_bio: str. New user biography to be set.
  644. """
  645. user_settings = get_user_settings(user_id, strict=True)
  646. user_settings.user_bio = user_bio
  647. _save_user_settings(user_settings)
  648. def update_user_default_dashboard(user_id, default_dashboard):
  649. """Updates the default dashboard of user with given user id.
  650. Args:
  651. user_id: str. The user id.
  652. default_dashboard: str. The dashboard the user wants.
  653. """
  654. user_settings = get_user_settings(user_id, strict=True)
  655. user_settings.default_dashboard = default_dashboard
  656. _save_user_settings(user_settings)
  657. def update_user_creator_dashboard_display(
  658. user_id, creator_dashboard_display_pref):
  659. """Updates the creator dashboard preference of user with given user id.
  660. Args:
  661. user_id: str. The user id.
  662. creator_dashboard_display_pref: str. The creator dashboard preference
  663. the user wants.
  664. """
  665. user_settings = get_user_settings(user_id, strict=True)
  666. user_settings.creator_dashboard_display_pref = (
  667. creator_dashboard_display_pref)
  668. _save_user_settings(user_settings)
  669. def update_subject_interests(user_id, subject_interests):
  670. """Updates subject_interests of user with given user_id.
  671. Args:
  672. user_id: str. The user id.
  673. subject_interests: list(str). New subject interests to be set.
  674. """
  675. if not isinstance(subject_interests, list):
  676. raise utils.ValidationError('Expected subject_interests to be a list.')
  677. else:
  678. for interest in subject_interests:
  679. if not isinstance(interest, basestring):
  680. raise utils.ValidationError(
  681. 'Expected each subject interest to be a string.')
  682. elif not interest:
  683. raise utils.ValidationError(
  684. 'Expected each subject interest to be non-empty.')
  685. elif not re.match(feconf.TAG_REGEX, interest):
  686. raise utils.ValidationError(
  687. 'Expected each subject interest to consist only of '
  688. 'lowercase alphabetic characters and spaces.')
  689. if len(set(subject_interests)) != len(subject_interests):
  690. raise utils.ValidationError(
  691. 'Expected each subject interest to be distinct.')
  692. user_settings = get_user_settings(user_id, strict=True)
  693. user_settings.subject_interests = subject_interests
  694. _save_user_settings(user_settings)
  695. def _update_first_contribution_msec(user_id, first_contribution_msec):
  696. """Updates first_contribution_msec of user with given user_id.
  697. Args:
  698. user_id: str. The user id.
  699. first_contribution_msec: float. New time to set in milliseconds
  700. representing user's first contribution to Oppia.
  701. """
  702. user_settings = get_user_settings(user_id, strict=True)
  703. user_settings.first_contribution_msec = first_contribution_msec
  704. _save_user_settings(user_settings)
  705. def update_first_contribution_msec_if_not_set(user_id, first_contribution_msec):
  706. """Updates first_contribution_msec of user with given user_id
  707. if it is set to None.
  708. Args:
  709. user_id: str. The user id.
  710. first_contribution_msec: float. New time to set in milliseconds
  711. representing user's first contribution to Oppia.
  712. """
  713. user_settings = get_user_settings(user_id, strict=True)
  714. if user_settings.first_contribution_msec is None:
  715. _update_first_contribution_msec(
  716. user_id, first_contribution_msec)
  717. def update_preferred_language_codes(user_id, preferred_language_codes):
  718. """Updates preferred_language_codes of user with given user_id.
  719. Args:
  720. user_id: str. The user id.
  721. preferred_language_codes: list(str). New exploration language
  722. preferences to set.
  723. """
  724. user_settings = get_user_settings(user_id, strict=True)
  725. user_settings.preferred_language_codes = preferred_language_codes
  726. _save_user_settings(user_settings)
  727. def update_preferred_site_language_code(user_id, preferred_site_language_code):
  728. """Updates preferred_site_language_code of user with given user_id.
  729. Args:
  730. user_id: str. The user id.
  731. preferred_site_language_code: str. New system language preference
  732. to set.
  733. """
  734. user_settings = get_user_settings(user_id, strict=True)
  735. user_settings.preferred_site_language_code = (
  736. preferred_site_language_code)
  737. _save_user_settings(user_settings)
  738. def update_preferred_audio_language_code(
  739. user_id, preferred_audio_language_code):
  740. """Updates preferred_audio_language_code of user with given user_id.
  741. Args:
  742. user_id: str. The user id.
  743. preferred_audio_language_code: str. New audio language preference
  744. to set.
  745. """
  746. user_settings = get_user_settings(user_id, strict=True)
  747. user_settings.preferred_audio_language_code = (
  748. preferred_audio_language_code)
  749. _save_user_settings(user_settings)
  750. def update_user_role(user_id, role):
  751. """Updates the role of the user with given user_id.
  752. Args:
  753. user_id: str. Id of user whose role is to be updated.
  754. role: str. The role to be assigned to user with given id.
  755. Raises:
  756. Exception: The given role does not exist.
  757. """
  758. if role not in role_services.PARENT_ROLES:
  759. raise Exception('Role %s does not exist.' % role)
  760. user_settings = get_user_settings(user_id, strict=True)
  761. user_settings.role = role
  762. _save_user_settings(user_settings)
  763. def get_human_readable_user_ids(user_ids):
  764. """Converts the given ids to usernames, or truncated email addresses.
  765. Requires all users to be known.
  766. Args:
  767. user_ids: list(str). The list of user_ids to get UserSettings domain
  768. objects for.
  769. Returns:
  770. list(str). List of usernames corresponding to given user_ids. If
  771. username does not exist, the corresponding entry in the returned
  772. list is the user's truncated email address.
  773. Raises:
  774. Exception: At least one of the user_ids does not correspond to a valid
  775. UserSettingsModel.
  776. """
  777. users_settings = get_users_settings(user_ids)
  778. usernames = []
  779. for ind, user_settings in enumerate(users_settings):
  780. if user_settings is None:
  781. logging.error('User id %s not known in list of user_ids %s' % (
  782. user_ids[ind], user_ids))
  783. raise Exception('User not found.')
  784. elif user_settings.user_id == feconf.SYSTEM_COMMITTER_ID:
  785. usernames.append('admin')
  786. elif user_settings.username:
  787. usernames.append(user_settings.username)
  788. else:
  789. usernames.append(
  790. '[Awaiting user registration: %s]' %
  791. user_settings.truncated_email)
  792. return usernames
  793. def record_user_started_state_editor_tutorial(user_id):
  794. """Updates last_started_state_editor_tutorial to the current datetime
  795. for the user with given user_id.
  796. Args:
  797. user_id: str. The user id.
  798. """
  799. user_settings = get_user_settings(user_id, strict=True)
  800. user_settings.last_started_state_editor_tutorial = (
  801. datetime.datetime.utcnow())
  802. _save_user_settings(user_settings)
  803. def record_user_logged_in(user_id):
  804. """Updates last_logged_in to the current datetime for the user with
  805. given user_id.
  806. Args:
  807. user_id: str. The user id.
  808. """
  809. user_settings = get_user_settings(user_id, strict=True)
  810. user_settings.last_logged_in = datetime.datetime.utcnow()
  811. _save_user_settings(user_settings)
  812. def record_user_edited_an_exploration(user_id):
  813. """Updates last_edited_an_exploration to the current datetime for
  814. the user with given user_id.
  815. Args:
  816. user_id: str. The user id.
  817. """
  818. user_settings = get_user_settings(user_id)
  819. if user_settings:
  820. user_settings.last_edited_an_exploration = datetime.datetime.utcnow()
  821. _save_user_settings(user_settings)
  822. def record_user_created_an_exploration(user_id):
  823. """Updates last_created_an_exploration to the current datetime for
  824. the user with given user_id.
  825. Args:
  826. user_id: str. The user id.
  827. """
  828. user_settings = get_user_settings(user_id)
  829. if user_settings:
  830. user_settings.last_created_an_exploration = datetime.datetime.utcnow()
  831. _save_user_settings(user_settings)
  832. def update_email_preferences(
  833. user_id, can_receive_email_updates, can_receive_editor_role_email,
  834. can_receive_feedback_email, can_receive_subscription_email):
  835. """Updates whether the user has chosen to receive email updates.
  836. If no UserEmailPreferencesModel exists for this user, a new one will
  837. be created.
  838. Args:
  839. user_id: str. The user id.
  840. can_receive_email_updates: bool. Whether the given user can receive
  841. email updates.
  842. can_receive_editor_role_email: bool. Whether the given user can receive
  843. emails notifying them of role changes.
  844. can_receive_feedback_email: bool. Whether the given user can receive
  845. emails when users submit feedback to their explorations.
  846. can_receive_subscription_email: bool. Whether the given user can receive
  847. emails related to his/her creator subscriptions.
  848. """
  849. email_preferences_model = user_models.UserEmailPreferencesModel.get(
  850. user_id, strict=False)
  851. if email_preferences_model is None:
  852. email_preferences_model = user_models.UserEmailPreferencesModel(
  853. id=user_id)
  854. email_preferences_model.site_updates = can_receive_email_updates
  855. email_preferences_model.editor_role_notifications = (
  856. can_receive_editor_role_email)
  857. email_preferences_model.feedback_message_notifications = (
  858. can_receive_feedback_email)
  859. email_preferences_model.subscription_notifications = (
  860. can_receive_subscription_email)
  861. email_preferences_model.put()
  862. def get_email_preferences(user_id):
  863. """Gives email preferences of user with given user_id.
  864. Args:
  865. user_id: str. The user id.
  866. Returns:
  867. UserGlobalPrefs. Representing whether the user has chosen to receive
  868. email updates.
  869. """
  870. email_preferences_model = user_models.UserEmailPreferencesModel.get(
  871. user_id, strict=False)
  872. if email_preferences_model is None:
  873. return user_domain.UserGlobalPrefs.create_default_prefs()
  874. else:
  875. return user_domain.UserGlobalPrefs(
  876. email_preferences_model.site_updates,
  877. email_preferences_model.editor_role_notifications,
  878. email_preferences_model.feedback_message_notifications,
  879. email_preferences_model.subscription_notifications)
  880. def get_users_email_preferences(user_ids):
  881. """Get email preferences for the list of users.
  882. Args:
  883. user_ids: list. A list of user IDs for whom we want to get email
  884. preferences.
  885. Returns:
  886. list(UserGlobalPrefs). Representing whether the users had chosen to
  887. receive email updates.
  888. """
  889. user_email_preferences_models = (
  890. user_models.UserEmailPreferencesModel.get_multi(user_ids))
  891. result = []
  892. for email_preferences_model in user_email_preferences_models:
  893. if email_preferences_model is None:
  894. result.append(
  895. user_domain.UserGlobalPrefs.create_default_prefs())
  896. else:
  897. result.append(user_domain.UserGlobalPrefs(
  898. email_preferences_model.site_updates,
  899. email_preferences_model.editor_role_notifications,
  900. email_preferences_model.feedback_message_notifications,
  901. email_preferences_model.subscription_notifications))
  902. return result
  903. def set_email_preferences_for_exploration(
  904. user_id, exploration_id, mute_feedback_notifications=None,
  905. mute_suggestion_notifications=None):
  906. """Sets mute preferences for exploration with given exploration_id of user
  907. with given user_id.
  908. If no ExplorationUserDataModel exists for this user and exploration,
  909. a new one will be created.
  910. Args:
  911. user_id: str. The user id.
  912. exploration_id: str. The exploration id.
  913. mute_feedback_notifications: bool. Whether the given user has muted
  914. feedback emails. Defaults to None.
  915. mute_suggestion_notifications: bool. Whether the given user has muted
  916. suggestion emails. Defaults to None.
  917. """
  918. exploration_user_model = user_models.ExplorationUserDataModel.get(
  919. user_id, exploration_id)
  920. if exploration_user_model is None:
  921. exploration_user_model = user_models.ExplorationUserDataModel.create(
  922. user_id, exploration_id)
  923. if mute_feedback_notifications is not None:
  924. exploration_user_model.mute_feedback_notifications = (
  925. mute_feedback_notifications)
  926. if mute_suggestion_notifications is not None:
  927. exploration_user_model.mute_suggestion_notifications = (
  928. mute_suggestion_notifications)
  929. exploration_user_model.put()
  930. def get_email_preferences_for_exploration(user_id, exploration_id):
  931. """Gives mute preferences for exploration with given exploration_id of user
  932. with given user_id.
  933. Args:
  934. user_id: str. The user id.
  935. exploration_id: str. The exploration id.
  936. Returns:
  937. UserExplorationPrefs. Representing whether the user has chosen to
  938. receive email updates for particular exploration.
  939. """
  940. exploration_user_model = user_models.ExplorationUserDataModel.get(
  941. user_id, exploration_id)
  942. if exploration_user_model is None:
  943. return user_domain.UserExplorationPrefs.create_default_prefs()
  944. else:
  945. return user_domain.UserExplorationPrefs(
  946. exploration_user_model.mute_feedback_notifications,
  947. exploration_user_model.mute_suggestion_notifications)
  948. def get_users_email_preferences_for_exploration(user_ids, exploration_id):
  949. """Gives mute preferences for exploration with given exploration_id of user
  950. with given user_id.
  951. Args:
  952. user_id: list. A list of user IDs for whom we want to get email
  953. preferences.
  954. exploration_id: str. The exploration id.
  955. Returns:
  956. list(UserExplorationPrefs). Representing whether the users has chosen to
  957. receive email updates for particular exploration.
  958. """
  959. exploration_user_models = (
  960. user_models.ExplorationUserDataModel.get_multi(
  961. user_ids, exploration_id))
  962. result = []
  963. for exploration_user_model in exploration_user_models:
  964. if exploration_user_model is None:
  965. result.append(
  966. user_domain.UserExplorationPrefs.create_default_prefs())
  967. else:
  968. result.append(user_domain.UserExplorationPrefs(
  969. exploration_user_model.mute_feedback_notifications,
  970. exploration_user_model.mute_suggestion_notifications))
  971. return result
  972. class UserContributions(object):
  973. """Value object representing a user's contributions.
  974. Attributes:
  975. user_id: str. The user id.
  976. created_exploration_ids: list(str). IDs of explorations that this
  977. user has created.
  978. edited_exploration_ids: list(str). IDs of explorations that this
  979. user has edited.
  980. """
  981. def __init__(
  982. self, user_id, created_exploration_ids, edited_exploration_ids):
  983. """Constructs a UserContributions domain object.
  984. Args:
  985. user_id: str. The user id.
  986. created_exploration_ids: list(str). IDs of explorations that this
  987. user has created.
  988. edited_exploration_ids: list(str). IDs of explorations that this
  989. user has edited.
  990. """
  991. self.user_id = user_id
  992. self.created_exploration_ids = created_exploration_ids
  993. self.edited_exploration_ids = edited_exploration_ids
  994. def validate(self):
  995. """Checks that user_id, created_exploration_ids and
  996. edited_exploration_ids fields of this UserContributions
  997. domain object are valid.
  998. Raises:
  999. ValidationError: user_id is not str.
  1000. ValidationError: created_exploration_ids is not a list.
  1001. ValidationError: exploration_id in created_exploration_ids
  1002. is not str.

Large files files are truncated, but you can click here to view the full file