/grumponado/models.py
Python | 371 lines | 344 code | 14 blank | 13 comment | 13 complexity | 3f293bbac863ba8b02494267d4763c3e MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- """The models"""
- import logging
- from sqlalchemy import Column, DateTime, Float, ForeignKey, String, LargeBinary, BigInteger
- from sqlalchemy.ext.declarative import declared_attr
- from sqlalchemy.orm import relationship
- from tornado_sqlalchemy import declarative_base
- from .utils import now_datetime, get_timestamp
- BASE = declarative_base()
- class RatingBaseModel():
- """This is the base class for all ratings. It provides the comparisons and
- the represantation. Also the methods for additional evaluations are provided
- by this class
- """
- @declared_attr
- def parent_id(cls):
- """Parent id mixin"""
- return Column(BigInteger, ForeignKey('users.id'))
- id = Column(BigInteger, primary_key=True)
- rating_value = Column(Float)
- rating_timestamp = Column(Float, nullable=False)
- @staticmethod
- def _validate_rating(rating):
- """
- Args:
- rating:
- """
- if not 1 >= rating >= 0:
- raise AssertionError("The rating must be between 0 and 1")
- def __repr__(self):
- return 'id: {}\nrating: {}\ndate:{}'.format(
- self.id,
- self.rating_value,
- self.rating_timestamp)
- def __eq__(self, other):
- try:
- return abs(self.current_value() - other.current_value()) < 1e-12
- except AttributeError:
- logging.debug("current_value() method not found")
- raise NotImplementedError("current_value() is missing")
- def __gt__(self, other):
- try:
- return self.current_value() > other.current_value()
- except AttributeError:
- logging.debug("current_value() method not found")
- raise NotImplementedError("current_value() is missing")
- def __ge__(self, other):
- try:
- return self.current_value() >= other.current_value()
- except AttributeError:
- logging.debug("current_value() method not found")
- raise NotImplementedError("current_value() is missing")
- def __le__(self, other):
- try:
- return self.current_value() <= other.current_value()
- except AttributeError:
- logging.debug("current_value() method not found")
- raise NotImplementedError("current_value() is missing")
- def __lt__(self, other):
- try:
- return self.current_value() < other.current_value()
- except AttributeError:
- logging.debug("current_value() method not found")
- raise NotImplementedError("current_value() is missing")
- def current_value(self):
- """Computes the current value under the assumption, the rating "cools
- down" to zero in a time interval of 12 hours. Should the result be
- negative, it will be corrected to zero
- Returns:
- float: the correct value of the rating
- """
- now = get_timestamp()
- then = self.rating_timestamp
- time_delta = now - then
- new_level = self.rating_value - (time_delta * time_delta) * 5.15e-10
- if not new_level <= 1:
- logging.warning("Current value must be equal or less than 1")
- return 1
- if not new_level >= 0:
- logging.warning("Current value must be equal or greater than 0")
- return 0
- return new_level
- def increase_rating(self):
- """Increases the rating without exceeding 1.
- Returns:
- float: The the increased rating value
- """
- value = self.current_value()
- if 0.0 < value <= 0.5:
- return value * 2
- if value > 0.5:
- return 1
- return 0.05
- def decrease_rating(self):
- """Decreases the rating value by 0.1 from the cooled down rating. It
- does not decrease beyond 0!
- Returns:
- float: The decreased rating value
- """
- value = self.current_value()
- if value >= 0.1:
- return value - 0.1
- return 0
- class SelfRatings(BASE, RatingBaseModel):
- """The self rating model is used to allow users to rate themselves. It
- contains the parent - this is the user - the rating values and the date when
- the user rated himself
- Args:
- parent_id (bigint): The user id of the rated user
- self_rating_value (float): the rating value - must be between 0 and 1
- rating_timestamp (float): the time stamp when this rating was evaluated
- in milliseconds of the unix epoch
- Returns:
- str: the string representation of a community rating object
- """
- __tablename__ = 'self_ratings'
- def __init__(self, parent_id, self_rating_value, rating_timestamp=get_timestamp()):
- """
- Args:
- parent_id:
- self_rating_value:
- rating_timestamp:
- """
- self._validate_rating(self_rating_value)
- self.rating_value = self_rating_value
- self.parent_id = parent_id
- self.rating_timestamp = rating_timestamp
- class CommunityRatings(BASE, RatingBaseModel):
- """This is the community rating model, which is used to store the rating for
- each user. The rating is done by the community, which could be any of the
- other users.
- Args:
- parent_id (bigint): The user id of the rated user
- community_rating_value (float): the rating value - must be between 0 and
- 1
- rating_timestamp (datetime): the time stamp when this rating was
- evaluated in milliseconds of the unix epoch
- Returns:
- str: the string representation of a community rating object
- """
- __tablename__ = 'community_ratings'
- def __init__(self, parent_id, community_rating_value, rating_timestamp=get_timestamp()):
- """
- Args:
- parent_id:
- community_rating_value:
- rating_timestamp:
- """
- self._validate_rating(community_rating_value)
- self.rating_value = community_rating_value
- self.parent_id = parent_id
- self.rating_timestamp = rating_timestamp
- class Users(BASE):
- """This is the users model. It contains the user name, email, password and
- the avatar url It also provides the methods to compare and to print a users
- object
- Args:
- user (str): The user name, maximum 20
- email (str): an email address, maximum 120
- password (byte): the (hashed) password, max 120
- avatar (str): the url to link an avatar
- Returns:
- str: the string represantation of a users object
- """
- __tablename__ = 'users'
- id = Column(BigInteger, primary_key=True, autoincrement=True)
- """Database id, used to link to ratings and advanced properties of a user"""
- user_name = Column(String(30), nullable=False, unique=True)
- """the user's name. This name will be used to have a direct access to user profile.
- It is also used as a screen name."""
- email = Column(String(120), nullable=False, unique=True)
- """A users email address. This is not published, but is used as part of the login
- credentials."""
- password = Column(LargeBinary(), nullable=False)
- """The password. This one is secret"""
- avatar = Column(String(255), nullable=True)
- """The avatar is used to show a custom picture next to a given user"""
- community_ratings = relationship('CommunityRatings', order_by='desc(CommunityRatings.rating_timestamp )')
- """Rating by the community."""
- self_ratings = relationship('SelfRatings', order_by='desc(SelfRatings.rating_timestamp)')
- """Rating of oneself."""
- tokens = relationship('Tokens', backref='users')
- def __init__(self, user, email, password, avatar):
- """
- Args:
- user:
- email:
- password:
- avatar:
- """
- self.user_name = user
- self.email = email
- self.password = password
- self.avatar = avatar
- def __str__(self):
- return 'id: {},\nuser name: {}\nemail: {}\navatar: {}'.format(
- self.id,
- self.user_name,
- self.email,
- self.avatar)
- def __repr__(self):
- output = {
- 'id': self.id,
- 'user_name': self.user_name,
- 'avatar': self.avatar,
- 'community_rating': self.current_community_rating(),
- 'self_rating': self.current_self_rating()
- }
- return output
- def current_self_rating(self):
- """Retrieve the current self rating.
- Returns:
- float: 0 or the currently last value minus the cool down
- """
- return self.self_ratings[0].current_value() if self.self_ratings else 0
- def current_community_rating(self):
- """Retrieve the current community rating.
- Returns:
- float: 0 or the currently last value minus the cool down
- """
- return self.community_ratings[0].current_value() if self.community_ratings else 0
- def get_new_self_rating(self, options=None):
- """Generates a new SelfRating object from of the current user.
- If there is now rating available, a new one with the value of 0 is created.
- Otherwise the last rating is evaluated (cool down) and the either the rating
- is increased if the options contain **increase**: *True* or the rating is decreased.
- Parameters:
- options : dict containing 'increase': True/False
- Returns:
- SelfRatings: 0 or the increased / decreased rating.
- """
- try:
- if options['increase']:
- new_value = self.self_ratings[0].increase_rating()
- else:
- new_value = self.self_ratings[0].decrease_rating()
- except (IndexError, KeyError, TypeError):
- # Basically it does not matter why the exception is thrown.
- # In any case, no new rating can be calculated, and therefore
- # the returned rating has a value of 0
- new_value = 0
- return SelfRatings(
- parent_id=self.id,
- self_rating_value=new_value,
- rating_timestamp=get_timestamp()
- )
- def get_new_community_rating(self, options=None):
- """Generates a new instance of the community rating for the current user object.
- If no rating is available, it is assumed the rating is 0 and an instance of a rating
- with a value of 0 is returned.
- If a rating is existent, it is either increased if the option **increase** is *True* or decreased
- in all other cases.
- Parameters:
- options : dict containing 'increase': True/False
- Returns:
- SelfRatings: 0 or the increased / decreased rating.
- """
- try:
- if options['increase']:
- new_value = self.community_ratings[0].increase_rating()
- else:
- new_value = self.community_ratings[0].decrease_rating()
- except (IndexError, KeyError, TypeError):
- # Basically it does not matter why the exception is thrown.
- # In any case, no new rating can be calculated, and therefore
- # the returned rating has a value of 0
- new_value = 0
- return CommunityRatings(
- parent_id=self.id,
- community_rating_value=new_value,
- rating_timestamp=get_timestamp()
- )
- class Tokens(BASE):
- """The token model class stores the refresh tokens in the database. Along
- with the tokens additional information is kept, so it is possible to detect
- if a user fails continuously to login. This might be an attack, so the
- requesting IP can be blocked.
- Args:
- user_id (bigint): user id
- token (str): a random string of characters. This is the primary sort key
- login_date (datetime): the date of the attempted login
- last_refresh (datetime): the date of the last time the user was
- refreshed. The token is invalid if this is more than 14 days ago
- ip_address (str): the IP address - for analytics. To determine the
- country of origin
- user_agent (str): the user agent - only for statistics - who uses the
- mobile version and who the desktop
- """
- __tablename__ = 'tokens'
- id = Column(BigInteger, primary_key=False)
- user_id = Column(BigInteger, ForeignKey('users.id'))
- token = Column(String(64), primary_key=True)
- login_date = Column(DateTime)
- last_refresh = Column(DateTime)
- ip = Column(String(255))
- user_agent = Column(String(255))
- def __init__(self, user_id,
- token,
- ip_address,
- user_agent,
- login_date=now_datetime(),
- last_refresh=now_datetime()):
- """
- Args:
- user_id:
- token:
- ip_address:
- user_agent:
- login_date:
- last_refresh:
- """
- self.user_id = user_id
- self.token = token
- self.login_date = login_date
- self.last_refresh = last_refresh
- self.ip = ip_address
- self.user_agent = user_agent