PageRenderTime 42ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/grumponado/models.py

https://bitbucket.org/angryshortone/grump-o-nado
Python | 371 lines | 344 code | 14 blank | 13 comment | 13 complexity | 3f293bbac863ba8b02494267d4763c3e MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. """The models"""
  5. import logging
  6. from sqlalchemy import Column, DateTime, Float, ForeignKey, String, LargeBinary, BigInteger
  7. from sqlalchemy.ext.declarative import declared_attr
  8. from sqlalchemy.orm import relationship
  9. from tornado_sqlalchemy import declarative_base
  10. from .utils import now_datetime, get_timestamp
  11. BASE = declarative_base()
  12. class RatingBaseModel():
  13. """This is the base class for all ratings. It provides the comparisons and
  14. the represantation. Also the methods for additional evaluations are provided
  15. by this class
  16. """
  17. @declared_attr
  18. def parent_id(cls):
  19. """Parent id mixin"""
  20. return Column(BigInteger, ForeignKey('users.id'))
  21. id = Column(BigInteger, primary_key=True)
  22. rating_value = Column(Float)
  23. rating_timestamp = Column(Float, nullable=False)
  24. @staticmethod
  25. def _validate_rating(rating):
  26. """
  27. Args:
  28. rating:
  29. """
  30. if not 1 >= rating >= 0:
  31. raise AssertionError("The rating must be between 0 and 1")
  32. def __repr__(self):
  33. return 'id: {}\nrating: {}\ndate:{}'.format(
  34. self.id,
  35. self.rating_value,
  36. self.rating_timestamp)
  37. def __eq__(self, other):
  38. try:
  39. return abs(self.current_value() - other.current_value()) < 1e-12
  40. except AttributeError:
  41. logging.debug("current_value() method not found")
  42. raise NotImplementedError("current_value() is missing")
  43. def __gt__(self, other):
  44. try:
  45. return self.current_value() > other.current_value()
  46. except AttributeError:
  47. logging.debug("current_value() method not found")
  48. raise NotImplementedError("current_value() is missing")
  49. def __ge__(self, other):
  50. try:
  51. return self.current_value() >= other.current_value()
  52. except AttributeError:
  53. logging.debug("current_value() method not found")
  54. raise NotImplementedError("current_value() is missing")
  55. def __le__(self, other):
  56. try:
  57. return self.current_value() <= other.current_value()
  58. except AttributeError:
  59. logging.debug("current_value() method not found")
  60. raise NotImplementedError("current_value() is missing")
  61. def __lt__(self, other):
  62. try:
  63. return self.current_value() < other.current_value()
  64. except AttributeError:
  65. logging.debug("current_value() method not found")
  66. raise NotImplementedError("current_value() is missing")
  67. def current_value(self):
  68. """Computes the current value under the assumption, the rating "cools
  69. down" to zero in a time interval of 12 hours. Should the result be
  70. negative, it will be corrected to zero
  71. Returns:
  72. float: the correct value of the rating
  73. """
  74. now = get_timestamp()
  75. then = self.rating_timestamp
  76. time_delta = now - then
  77. new_level = self.rating_value - (time_delta * time_delta) * 5.15e-10
  78. if not new_level <= 1:
  79. logging.warning("Current value must be equal or less than 1")
  80. return 1
  81. if not new_level >= 0:
  82. logging.warning("Current value must be equal or greater than 0")
  83. return 0
  84. return new_level
  85. def increase_rating(self):
  86. """Increases the rating without exceeding 1.
  87. Returns:
  88. float: The the increased rating value
  89. """
  90. value = self.current_value()
  91. if 0.0 < value <= 0.5:
  92. return value * 2
  93. if value > 0.5:
  94. return 1
  95. return 0.05
  96. def decrease_rating(self):
  97. """Decreases the rating value by 0.1 from the cooled down rating. It
  98. does not decrease beyond 0!
  99. Returns:
  100. float: The decreased rating value
  101. """
  102. value = self.current_value()
  103. if value >= 0.1:
  104. return value - 0.1
  105. return 0
  106. class SelfRatings(BASE, RatingBaseModel):
  107. """The self rating model is used to allow users to rate themselves. It
  108. contains the parent - this is the user - the rating values and the date when
  109. the user rated himself
  110. Args:
  111. parent_id (bigint): The user id of the rated user
  112. self_rating_value (float): the rating value - must be between 0 and 1
  113. rating_timestamp (float): the time stamp when this rating was evaluated
  114. in milliseconds of the unix epoch
  115. Returns:
  116. str: the string representation of a community rating object
  117. """
  118. __tablename__ = 'self_ratings'
  119. def __init__(self, parent_id, self_rating_value, rating_timestamp=get_timestamp()):
  120. """
  121. Args:
  122. parent_id:
  123. self_rating_value:
  124. rating_timestamp:
  125. """
  126. self._validate_rating(self_rating_value)
  127. self.rating_value = self_rating_value
  128. self.parent_id = parent_id
  129. self.rating_timestamp = rating_timestamp
  130. class CommunityRatings(BASE, RatingBaseModel):
  131. """This is the community rating model, which is used to store the rating for
  132. each user. The rating is done by the community, which could be any of the
  133. other users.
  134. Args:
  135. parent_id (bigint): The user id of the rated user
  136. community_rating_value (float): the rating value - must be between 0 and
  137. 1
  138. rating_timestamp (datetime): the time stamp when this rating was
  139. evaluated in milliseconds of the unix epoch
  140. Returns:
  141. str: the string representation of a community rating object
  142. """
  143. __tablename__ = 'community_ratings'
  144. def __init__(self, parent_id, community_rating_value, rating_timestamp=get_timestamp()):
  145. """
  146. Args:
  147. parent_id:
  148. community_rating_value:
  149. rating_timestamp:
  150. """
  151. self._validate_rating(community_rating_value)
  152. self.rating_value = community_rating_value
  153. self.parent_id = parent_id
  154. self.rating_timestamp = rating_timestamp
  155. class Users(BASE):
  156. """This is the users model. It contains the user name, email, password and
  157. the avatar url It also provides the methods to compare and to print a users
  158. object
  159. Args:
  160. user (str): The user name, maximum 20
  161. email (str): an email address, maximum 120
  162. password (byte): the (hashed) password, max 120
  163. avatar (str): the url to link an avatar
  164. Returns:
  165. str: the string represantation of a users object
  166. """
  167. __tablename__ = 'users'
  168. id = Column(BigInteger, primary_key=True, autoincrement=True)
  169. """Database id, used to link to ratings and advanced properties of a user"""
  170. user_name = Column(String(30), nullable=False, unique=True)
  171. """the user's name. This name will be used to have a direct access to user profile.
  172. It is also used as a screen name."""
  173. email = Column(String(120), nullable=False, unique=True)
  174. """A users email address. This is not published, but is used as part of the login
  175. credentials."""
  176. password = Column(LargeBinary(), nullable=False)
  177. """The password. This one is secret"""
  178. avatar = Column(String(255), nullable=True)
  179. """The avatar is used to show a custom picture next to a given user"""
  180. community_ratings = relationship('CommunityRatings', order_by='desc(CommunityRatings.rating_timestamp )')
  181. """Rating by the community."""
  182. self_ratings = relationship('SelfRatings', order_by='desc(SelfRatings.rating_timestamp)')
  183. """Rating of oneself."""
  184. tokens = relationship('Tokens', backref='users')
  185. def __init__(self, user, email, password, avatar):
  186. """
  187. Args:
  188. user:
  189. email:
  190. password:
  191. avatar:
  192. """
  193. self.user_name = user
  194. self.email = email
  195. self.password = password
  196. self.avatar = avatar
  197. def __str__(self):
  198. return 'id: {},\nuser name: {}\nemail: {}\navatar: {}'.format(
  199. self.id,
  200. self.user_name,
  201. self.email,
  202. self.avatar)
  203. def __repr__(self):
  204. output = {
  205. 'id': self.id,
  206. 'user_name': self.user_name,
  207. 'avatar': self.avatar,
  208. 'community_rating': self.current_community_rating(),
  209. 'self_rating': self.current_self_rating()
  210. }
  211. return output
  212. def current_self_rating(self):
  213. """Retrieve the current self rating.
  214. Returns:
  215. float: 0 or the currently last value minus the cool down
  216. """
  217. return self.self_ratings[0].current_value() if self.self_ratings else 0
  218. def current_community_rating(self):
  219. """Retrieve the current community rating.
  220. Returns:
  221. float: 0 or the currently last value minus the cool down
  222. """
  223. return self.community_ratings[0].current_value() if self.community_ratings else 0
  224. def get_new_self_rating(self, options=None):
  225. """Generates a new SelfRating object from of the current user.
  226. If there is now rating available, a new one with the value of 0 is created.
  227. Otherwise the last rating is evaluated (cool down) and the either the rating
  228. is increased if the options contain **increase**: *True* or the rating is decreased.
  229. Parameters:
  230. options : dict containing 'increase': True/False
  231. Returns:
  232. SelfRatings: 0 or the increased / decreased rating.
  233. """
  234. try:
  235. if options['increase']:
  236. new_value = self.self_ratings[0].increase_rating()
  237. else:
  238. new_value = self.self_ratings[0].decrease_rating()
  239. except (IndexError, KeyError, TypeError):
  240. # Basically it does not matter why the exception is thrown.
  241. # In any case, no new rating can be calculated, and therefore
  242. # the returned rating has a value of 0
  243. new_value = 0
  244. return SelfRatings(
  245. parent_id=self.id,
  246. self_rating_value=new_value,
  247. rating_timestamp=get_timestamp()
  248. )
  249. def get_new_community_rating(self, options=None):
  250. """Generates a new instance of the community rating for the current user object.
  251. If no rating is available, it is assumed the rating is 0 and an instance of a rating
  252. with a value of 0 is returned.
  253. If a rating is existent, it is either increased if the option **increase** is *True* or decreased
  254. in all other cases.
  255. Parameters:
  256. options : dict containing 'increase': True/False
  257. Returns:
  258. SelfRatings: 0 or the increased / decreased rating.
  259. """
  260. try:
  261. if options['increase']:
  262. new_value = self.community_ratings[0].increase_rating()
  263. else:
  264. new_value = self.community_ratings[0].decrease_rating()
  265. except (IndexError, KeyError, TypeError):
  266. # Basically it does not matter why the exception is thrown.
  267. # In any case, no new rating can be calculated, and therefore
  268. # the returned rating has a value of 0
  269. new_value = 0
  270. return CommunityRatings(
  271. parent_id=self.id,
  272. community_rating_value=new_value,
  273. rating_timestamp=get_timestamp()
  274. )
  275. class Tokens(BASE):
  276. """The token model class stores the refresh tokens in the database. Along
  277. with the tokens additional information is kept, so it is possible to detect
  278. if a user fails continuously to login. This might be an attack, so the
  279. requesting IP can be blocked.
  280. Args:
  281. user_id (bigint): user id
  282. token (str): a random string of characters. This is the primary sort key
  283. login_date (datetime): the date of the attempted login
  284. last_refresh (datetime): the date of the last time the user was
  285. refreshed. The token is invalid if this is more than 14 days ago
  286. ip_address (str): the IP address - for analytics. To determine the
  287. country of origin
  288. user_agent (str): the user agent - only for statistics - who uses the
  289. mobile version and who the desktop
  290. """
  291. __tablename__ = 'tokens'
  292. id = Column(BigInteger, primary_key=False)
  293. user_id = Column(BigInteger, ForeignKey('users.id'))
  294. token = Column(String(64), primary_key=True)
  295. login_date = Column(DateTime)
  296. last_refresh = Column(DateTime)
  297. ip = Column(String(255))
  298. user_agent = Column(String(255))
  299. def __init__(self, user_id,
  300. token,
  301. ip_address,
  302. user_agent,
  303. login_date=now_datetime(),
  304. last_refresh=now_datetime()):
  305. """
  306. Args:
  307. user_id:
  308. token:
  309. ip_address:
  310. user_agent:
  311. login_date:
  312. last_refresh:
  313. """
  314. self.user_id = user_id
  315. self.token = token
  316. self.login_date = login_date
  317. self.last_refresh = last_refresh
  318. self.ip = ip_address
  319. self.user_agent = user_agent