class MyBaseModel(db.Model):
    """
    Base Model
    """

    __abstract__ = True

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
class Image(MyBaseModel):
    __tablename__ = "images"
    image = db.Column(MEDIUMBLOB)
    ext = db.Column(db.String(50), default="png")

    @staticmethod
    def create_one(raw_file, ext="png"):
        img = base64.b64encode(raw_file.read())
        image = Image(ext=ext, image=img)
        return image
class Role(MyBaseModel):
    """
    Table: role permissions
    """

    __tablename__ = "roles"
    role_name = db.Column(db.String(16))
    permission = db.Column(db.String(32))

    def __repr__(self):
        return "<Role %r>" % self.role_name

    @staticmethod
    def init_role():
        for _role_name, _permissions in ROLES_PERMISSIONS_MAP.items():
            for _permission in _permissions:
                if (not Role.query.filter(Role.role_name == _role_name).filter(
                        Role.permission == _permission).first()):
                    role = Role(role_name=_role_name, permission=_permission)
                    db.session.add(role)
        db.session.commit()
class Genre(MyBaseModel):
    """
    movie genre
    """

    __tablename__ = "genres"
    genre_name = db.Column(db.String(8), nullable=False)
    ext = db.Column(db.String(32))

    @staticmethod
    def create_one(genre_name):
        """
        create a Genre object, not commit session
        :param genre_name: genre name
        :return: Genre object
        """
        genre = Genre.query.filter_by(genre_name=genre_name).first()
        if genre:
            return genre
        else:
            genre = Genre(genre_name=genre_name)
            return genre
예제 #5
0
class MoviesData(sql_db.Model):
    #unique together on name + director
    id = sql_db.Column(sql_db.Integer, primary_key=True)
    name = sql_db.Column(sql_db.Text, default="")
    nn_popularity = sql_db.Column(sql_db.Float, default=0.0)
    director = sql_db.Column(sql_db.Text, default="")
    genre = sql_db.Column(sql_db.Text, default="")
    imdb_score = sql_db.Column(sql_db.Float, default=0.0)

    enabled = sql_db.Column(sql_db.Boolean, default=True)
    deleted = sql_db.Column(sql_db.Boolean, default=False)
    created_at = sql_db.Column(sql_db.DateTime, default=datetime.datetime.now)
    updated_at = sql_db.Column(sql_db.DateTime, default=datetime.datetime.now)

    def to_json(self):
        return_dict = {}
        return_dict['id'] = self.id
        return_dict['name'] = self.name
        return_dict['99popularity'] = self.nn_popularity
        return_dict['director'] = self.director
        return_dict['genre'] = self.genre
        return_dict['imdb_score'] = self.imdb_score

        return return_dict
class Tag(MyBaseModel):
    __tablename__ = "tags"
    tag_name = db.Column(db.String(8), unique=True, nullable=False, index=True)

    @staticmethod
    def create_one(tag_name):
        """
        create a tag by tag name and return
        :param tag_name: tag name
        :return: Tag object
        """
        tag = Tag.query.filter_by(tag_name=tag_name).first()
        if tag:
            return tag
        tag = Tag(tag_name=tag_name)
        return tag
예제 #7
0
class UserData(sql_db.Model):
    id = sql_db.Column(sql_db.Integer, primary_key=True)
    user_name = sql_db.Column(sql_db.Text, unique=True)
    password = sql_db.Column(sql_db.Text)
    user_role = sql_db.Column(sql_db.Text, default="user")

    enabled = sql_db.Column(sql_db.Boolean, default=True)
    deleted = sql_db.Column(sql_db.Boolean, default=False)
    created_at = sql_db.Column(sql_db.DateTime, default=datetime.datetime.now)
    updated_at = sql_db.Column(sql_db.DateTime, default=datetime.datetime.now)

    def to_json(self):
        return_dict = {}
        return_dict['id'] = self.id
        return_dict['user_name'] = self.user_name
        return_dict['user_role'] = self.user_role

        return return_dict
class Country(MyBaseModel):
    __tablename__ = "countries"
    country_name = db.Column(db.String(16), unique=True, nullable=False)

    @staticmethod
    def create_one(country_name):
        """
        create a Country object, not commit session
        :param country_name: country name
        :return: Country object
        """
        c = Country.query.filter_by(country_name=country_name).first()
        if c:
            return c
        else:
            c = Country(country_name=country_name)
            return c
class Notification(MyBaseModel):
    receiver_user_id = db.Column(db.Integer,
                                 db.ForeignKey("users.id"),
                                 nullable=False)
    sender_user_id = db.Column(db.Integer,
                               db.ForeignKey("users.id"),
                               nullable=False)
    is_read = db.Column(db.Boolean(), default=False)
    category = db.Column(TINYINT(1))  # must be in `NotificationType`
    information_text = db.Column(db.Text)  # not used
    rating_id = db.Column(db.Integer,
                          db.ForeignKey("ratings.id", ondelete="CASCADE"),
                          nullable=True)
    rating = db.relationship("Rating", backref="notification", lazy=True)

    @staticmethod
    def create_one(receiver_user_id, sender_user_id, category, rating_id=None):
        """
        add action to Notification
        :param receiver_user_id: User.id
        :param sender_user_id: user send this notification
        :param category: NotificationType.FOLLOW or .RATING_ACTION
        :param follower_id: Followers.id
        :param rating_id: rating_id
        :return: Notification or None
        """
        if category not in [
                NotificationType.FOLLOW, NotificationType.RATING_ACTION
        ]:
            return None
        notification = Notification.query.filter_by(
            receiver_user_id=receiver_user_id,
            sender_user_id=sender_user_id,
            category=category,
            rating_id=rating_id,
        ).first()
        if not notification:
            notification = Notification(
                receiver_user_id=receiver_user_id,
                sender_user_id=sender_user_id,
                category=category,
                rating_id=rating_id,
            )
            return notification
        return None
class Movie(SearchableMixin, MyBaseModel):
    __tablename__ = "movies"
    __searchable__ = [
        {
            "key": "title",
            "weight": 4
        },
        {
            "key": "original_title",
            "weight": 3
        },
        {
            "key": "summary",
            "weight": 1
        },
    ]

    douban_id = db.Column(db.Integer, unique=True, nullable=True)
    imdb_id = db.Column(db.String(16), unique=True, nullable=True)
    title = db.Column(db.String(64), nullable=False)
    original_title = db.Column(db.String(64))
    subtype = db.Column(db.String(10), nullable=False)
    year = db.Column(db.Integer, nullable=False)
    image_id = db.Column(db.Integer, db.ForeignKey("images.id"), nullable=True)
    image = db.relationship("Image", backref="movie", lazy=True)
    seasons_count = db.Column(db.Integer)  # 季数
    episodes_count = db.Column(db.Integer)  # 集数
    current_season = db.Column(db.Integer)  # 当前第几季
    summary = db.Column(db.Text)
    ext = db.Column(db.String(32))
    # must be in MovieCinemaStatus
    cinema_status = db.Column(db.Integer,
                              default=MovieCinemaStatus.FINISHED,
                              nullable=False)
    aka_list = db.Column(db.Text)  # split by '|'
    ratings = db.relationship("Rating",
                              backref="movie",
                              lazy="dynamic",
                              cascade="all, delete-orphan")
    genres = db.relationship(
        "Genre",
        secondary="movie_genres",
        backref=db.backref("movies", lazy="dynamic"),
        lazy=True,
    )
    countries = db.relationship(
        "Country",
        secondary="movie_countries",
        backref=db.backref("movies", lazy="dynamic"),
        lazy=True,
    )
    directors = db.relationship(
        "Celebrity",
        secondary="movie_directors",
        backref=db.backref("director_movies", lazy="dynamic"),
        lazy=True,
    )
    celebrities = db.relationship(
        "Celebrity",
        secondary="movie_celebrities",
        backref=db.backref("celebrity_movies", lazy="dynamic"),
        lazy=True,
    )

    @staticmethod
    def create_one(
        title,
        subtype,
        year,
        image=None,
        douban_id=None,
        imdb_id=None,
        original_title=None,
        seasons_count=None,
        episodes_count=None,
        current_season=None,
        summary=None,
        cinema_status=MovieCinemaStatus.FINISHED,
        aka_list=[],
        genres_name=[],
        countries_name=[],
        directors_obj=[],
        celebrities_obj=[],
    ):
        if douban_id:
            if Movie.query.filter_by(douban_id=douban_id).first():
                return None
        if imdb_id:
            if Movie.query.filter_by(imdb_id=imdb_id).first():
                return None
        movie = Movie(
            title=title,
            subtype=subtype,
            image=image,
            summary=summary,
            douban_id=douban_id,
            imdb_id=imdb_id,
            original_title=original_title,
            year=year,
            seasons_count=seasons_count,
            episodes_count=episodes_count,
            current_season=current_season,
            cinema_status=cinema_status,
            aka_list="/".join(aka_list),
        )
        for genre_name in genres_name:
            genre_obj = Genre.create_one(genre_name)
            movie.genres.append(genre_obj)
        for country_name in countries_name:
            country_obj = Country.create_one(country_name)
            movie.countries.append(country_obj)
        movie.directors += directors_obj
        movie.celebrities += celebrities_obj
        return movie

    @property
    def score(self):
        score = (self.ratings.filter_by(
            category=RatingType.COLLECT).with_entities(func.avg(
                Rating.score)).all()[0][0])
        try:
            return float(score)
        except TypeError:
            return 0

    @property
    def user_do_rating_query(self):
        return self.ratings.filter_by(category=RatingType.DO)

    @property
    def user_wish_rating_query(self):
        return self.ratings.filter_by(category=RatingType.WISH)

    @property
    def user_collect_query(self):
        return self.ratings.filter_by(category=RatingType.COLLECT)

    @property
    def image_url(self):
        return url_for("api.Photo",
                       image_hash_id=encode_id_to_str(self.image_id),
                       _external=True)

    def __repr__(self):
        return "<Movie %r>" % self.title
class Celebrity(SearchableMixin, MyBaseModel):
    __tablename__ = "celebrities"
    __searchable__ = [
        {
            "key": "name",
            "weight": 3
        },
        {
            "key": "name_en",
            "weight": 2
        },
        {
            "key": "born_place",
            "weight": 1
        },
    ]

    douban_id = db.Column(db.Integer, nullable=True, unique=True)
    imdb_id = db.Column(db.String(16), nullable=True, unique=True)
    name = db.Column(db.String(128), nullable=False)
    gender = db.Column(TINYINT(1), default=GenderType.MALE, nullable=True)
    image_id = db.Column(db.INTEGER, db.ForeignKey("images.id"))
    image = db.relationship("Image", backref="celebrity", lazy=True)
    born_place = db.Column(db.String(32))
    name_en = db.Column(db.String(32))
    aka_list = db.Column(db.Text)
    aka_en_list = db.Column(db.Text)
    ext = db.Column(db.String(32))

    @staticmethod
    def create_one(
        name,
        gender,
        image=None,
        douban_id=None,
        imdb_id=None,
        born_place=None,
        name_en=None,
        aka_list=[],
        aka_en_list=[],
    ):
        """"""
        if douban_id:
            if Celebrity.query.filter_by(douban_id=douban_id).first():
                return None
        if imdb_id:
            if Celebrity.query.filter_by(imdb_id=imdb_id).first():
                return None
        celebrity = Celebrity(
            name=name,
            gender=gender,
            image=image,
            douban_id=douban_id,
            imdb_id=imdb_id,
            born_place=born_place,
            name_en=name_en,
            aka_list="/".join(aka_list),
            aka_en_list="/".join(aka_en_list),
        )
        return celebrity

    @property
    def avatar_url(self):
        """
        :return: avatar image url
        """
        return url_for("api.Photo",
                       image_hash_id=encode_id_to_str(self.image_id),
                       _external=True)
class User(SearchableMixin, MyBaseModel):
    __tablename__ = "users"
    __searchable__ = [
        {
            "key": "username",
            "weight": 3
        },
        {
            "key": "signature",
            "weight": 1
        },
    ]

    username = db.Column(db.String(80),
                         nullable=False,
                         index=True,
                         unique=True)
    email = db.Column(db.String(128), nullable=False, index=True, unique=True)
    password_hash = db.Column(db.String(128), nullable=False)
    token_salt = db.Column(db.Integer, default=0, nullable=False)
    last_login_time = db.Column(db.DateTime, default=datetime.utcnow)
    avatar_image_id = db.Column(db.Integer, db.ForeignKey("images.id"))
    avatar = db.relationship("Image", backref="user", lazy=True)
    email_confirmed = db.Column(db.Boolean(), default=False, nullable=False)
    signature = db.Column(db.Text)
    city_id = db.Column(db.Integer, db.ForeignKey("china_area_code.id"))
    city = db.relationship("ChinaArea", backref="users", lazy=True)
    roles = db.relationship(
        "Role",
        secondary="user_roles",
        backref=db.backref("users", lazy="dynamic"),
        lazy=True,
    )
    followed = db.relationship(
        "User",
        secondary="followers",
        primaryjoin="followers.c.follower_id == User.id",
        secondaryjoin="followers.c.followed_id == User.id",
        backref=db.backref("followers", lazy="dynamic"),
        lazy="dynamic",
    )

    # Danger: with soft deleted
    ratings = db.relationship("Rating",
                              backref="user",
                              lazy="dynamic",
                              cascade="all, delete-orphan")
    notifications_received = db.relationship(
        "Notification",
        foreign_keys=[Notification.receiver_user_id],
        backref="receiver_user",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )
    notifications_sent = db.relationship(
        "Notification",
        foreign_keys=[Notification.sender_user_id],
        backref="send_user",
        lazy="dynamic",
        cascade="all, delete-orphan",
    )

    def __repr__(self):
        return "<User %r>" % self.username

    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self._set_role()

    def generate_token(self, expiration=3600):
        """
        generate a jwt token and update the field of last_login_time
        :param expiration: a number of seconds
        :return: token
        """
        s = Serializer(current_app.config["SECRET_KEY"], expires_in=expiration)
        token = s.dumps({
            "uid": str(self.id),
            "token_salt": self.token_salt
        }).decode("ascii")
        self.last_login_time = datetime.utcnow()
        db.session.commit()
        return token

    @staticmethod
    def verity_auth_token(token):
        """
        verity the jwt when user login
        :param token: jwt token
        :return: current_user: User
        """
        s = Serializer(current_app.config["SECRET_KEY"])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None
        except BadSignature:
            return None
        current_user = User.query.filter_by(id=data["uid"]).first()
        if current_user is None:
            return None
        else:
            token_salt = data.get("token_salt")
            if token_salt == current_user.token_salt:
                g.current_user = current_user
                return current_user
            else:
                return None

    def revoke_auth_token(self):
        self.token_salt += 1
        g.current_user = None
        db.session.commit()

    @staticmethod
    def create_one(username, email, password):
        """
        create one user from params but not commit session
        :param username: username unique
        :param email: email unique
        :param password: password
        :return: user: User if ok else None
        """
        if not username or not email or not password:
            return None
        if User.query.filter(
                or_(User.username == username, User.email == email)).first():
            return None
        current_user = User(username=username, email=email)
        current_user.password_hash = generate_password_hash(password)
        return current_user

    def change_email(self, new_email):
        """
        change email to `new_email` but not commit session
        :parm new_email: new_email
        :return: True or False
        """
        if User.query.filter_by(email=new_email).first():
            return False
        self.email = new_email
        self.email_confirmed = False
        return True

    def change_username(self, new_username):
        if User.query.filter_by(username=new_username).first():
            return False
        self.username = new_username
        return True

    def change_password(self, new_password):
        """
        change password but not commit session
        :parm new_password: new password
        :return: True or False
        """
        if not new_password:
            return False
        self.password_hash = generate_password_hash(new_password)
        self.token_salt += 1
        return True

    def validate_password(self, password):
        """
        validate the password
        :param password: he plaintext password to compare against the hash.
        :return: True or False
        """
        return check_password_hash(self.password_hash, password)

    def _set_role(self):
        """
        set role for user when create a user.
        :return: None
        """
        if Role.query.count() == 0:
            Role.init_role()
        if len(self.roles) == 0:
            if self.email == current_app.config["ADMIN_EMAIL"]:
                current_roles = Role.query.filter_by(role_name="Administrator")
            else:
                current_roles = Role.query.filter_by(role_name="User")
            self.roles += current_roles

    def change_role(self, role_name):
        """
        change user`s role
        :param role_name: ROLES_PERMISSIONS_MAP.keys()
        """
        self.roles.clear()
        if Role.query.count() == 0:
            Role.init_role()
        current_roles = Role.query.filter_by(role_name=role_name.title())
        self.roles += current_roles

    def follow(self, user):
        """
        follow one user but not commit session
        :param user: User
        :return: True or False
        """
        if self.id != user.id:
            if not self.is_following(user):
                self.followed.append(user)
                notification = Notification.create_one(user.id, self.id,
                                                       NotificationType.FOLLOW)
                if notification:
                    user.notifications_received.append(notification)
                    self.notifications_sent.append(notification)
                return True
        return False

    def unfollow(self, user):
        """
        unfollow one user
        :param user: User
        :return: True or False
        """
        if self.id != user.id:
            if self.is_following(user):
                self.followed.remove(user)
                notification = Notification.query.filter_by(
                    receiver_user_id=user.id,
                    sender_user_id=self.id,
                    category=NotificationType.FOLLOW,
                ).first()
                if notification:
                    user.notifications_received.remove(notification)
                    self.notifications_sent.remove(notification)
                return True
        return False

    def is_following(self, user):
        """
        current user is follow `user`
        :param user: User
        :return: True or False
        """
        if self == user:
            return True
        return self.followed.filter(
            followers.c.followed_id == user.id).count() > 0

    def is_followed_by(self, user):
        """
        current user is follow by `user`
        :param user: User
        :return: True or False
        """
        if self == user:
            return True
        return user.is_following(self)

    def wish_movie(self, movie, comment=None, tags_name=[]):
        """
        wish movie by rating
        :param movie: Movie
        :param comment: comment for rating
        :param tags_name: list of tags
        :return: Rating or None
        """
        if Rating.query.filter_by(movie_id=movie.id, user_id=self.id).first():
            return False
        r = Rating.create_rating_with_tags(
            self,
            movie,
            score=0,
            comment=comment,
            category=RatingType.WISH,
            tags_name=tags_name,
        )
        add_rating_to_rank_redis(movie)
        return True

    def do_movie(self, movie, score=0, comment=None, tags_name=[]):
        """
        do movie by rating
        :param movie: Movie
        :param score: score for rating
        :param comment: comment for rating
        :param tags_name: list of tags
        :return: Rating or None
        """
        if Rating.query.filter_by(movie_id=movie.id, user_id=self.id).first():
            return False
        if movie.subtype != MovieType.TV:
            return False
        r = Rating.create_rating_with_tags(
            self,
            movie,
            score=score,
            comment=comment,
            category=RatingType.DO,
            tags_name=tags_name,
        )
        add_rating_to_rank_redis(movie)
        return True

    def collect_movie(self, movie, score=0, comment=None, tags_name=[]):
        """
        collect movie by rating
        :param movie: Movie
        :param score: score for rating
        :param comment: comment for rating
        :param tags_name: list of tags
        :return: Rating or None
        """
        if Rating.query.filter_by(movie_id=movie.id, user_id=self.id).first():
            return False
        r = Rating.create_rating_with_tags(
            self,
            movie,
            score=score,
            comment=comment,
            category=RatingType.COLLECT,
            tags_name=tags_name,
        )
        add_rating_to_rank_redis(movie)
        return True

    def delete_rating_on(self, movie):
        """
        delete one rating on this movie
        :param movie:
        :return:
        """
        rating = Rating.query.filter_by(movie_id=movie.id,
                                        user_id=self.id).first()
        if not rating:
            return False
        self.ratings.remove(rating)
        add_rating_to_rank_redis(movie, True)
        return True

    @property
    def role_name(self):
        if not self.roles:
            return
        return self.roles[0].role_name

    @property
    def is_locked(self):
        """
        test this user is locked or not
        :return: True or False
        """
        return self.role_name == "LOCKED"

    @property
    def notifications_count(self):
        return self.notifications_received.count()

    def lock_this_user(self):
        """
        lock this user
        """
        self.change_role("Locked")

    def check_permission(self, permission):
        """Check Permission"""
        return permission.upper() in [role.permission for role in self.roles]

    def _gen_email_hashgravatar(self, size=500):
        """
        generate avatar image url for user
        :param size: size
        :return: avatar url
        """
        email_hash = hashlib.md5(
            self.email.lower().encode("utf-8")).hexdigest()
        url = "https://secure.gravatar.com/avatar"
        return "{url}/{hash}?s={size}&d=identicon&r=g".format(url=url,
                                                              hash=email_hash,
                                                              size=size)

    @property
    def avatar_thumb(self):
        """
        thumb avatar
        """
        if not self.avatar:
            return self._gen_email_hashgravatar(100)
        else:
            return url_for(
                "api.Photo",
                image_hash_id=encode_id_to_str(self.avatar_image_id),
                _external=True,
            )

    @property
    def avatar_image(self):
        """
        avatar image
        """
        if not self.avatar:
            return self._gen_email_hashgravatar(1000)
        else:
            return url_for(
                "api.Photo",
                image_hash_id=encode_id_to_str(self.avatar_image_id),
                _external=True,
            )

    @property
    def followers_count(self):
        return self.followers.count()

    @property
    def followings_count(self):
        return self.followed.count()
class ChinaArea(db.Model):
    __tablename__ = "china_area_code"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    code = db.Column(db.BIGINT, nullable=False)
    name = db.Column(db.String(128), default="", nullable=False)
    level = db.Column(TINYINT(1), nullable=False)
    pcode = db.Column(db.BIGINT)
    """
    select `a`.`code` AS `CODE`,`c`.`name` AS `province`,`b`.`name` AS `city`,`a`.`name` AS `country` from ((`china_area_code` `a` join `china_area_code` `b` on(((`a`.`level` = 3) and (`b`.`level` = 2) and (`a`.`pcode` = `b`.`code`)))) join `china_area_code` `c` on((`b`.`pcode` = `c`.`code`))) order by `a`.`code`
    """
    @staticmethod
    def load_data_from_json():
        with open(
                os.path.join(current_app.config["AREA_DATA_PATH"],
                             "area_code_2019.json"),
                "r",
        ) as f:
            area_data_json = json.load(f)
        for record in tqdm(area_data_json["RECORDS"]):
            china_area = ChinaArea(
                code=record["code"],
                name=record["name"],
                level=record["level"],
                pcode=record["pcode"],
            )
            db.session.add(china_area)
        db.session.commit()

    @staticmethod
    @cache.cached(timeout=60 * 60 * 24 * 7, key_prefix="get_all_data_area")
    def get_all_area_date():
        res = list(
            db.session.execute(
                "select a.id, code, name from china_area_code as a where a.level=1"
            ))
        parent = []
        for p in res:
            t = {
                "id": p[0],
                "code": p[1],
                "name": p[2],
                "label": p[2],
                "value": p[0]
            }
            two_level_children = []
            level_two_res = list(
                db.session.execute(
                    "select a.id, code, name from china_area_code as a where a.pcode="
                    + str(p[1])))
            for level_two in level_two_res:
                tt = {
                    "id": level_two[0],
                    "code": level_two[1],
                    "name": level_two[2],
                    "label": level_two[2],
                    "value": level_two[0],
                }
                three_leve_children = []
                level_three_res = list(
                    db.session.execute(
                        "select a.id, code, name from china_area_code as a where a.pcode="
                        + str(level_two[1])))
                for level_three in level_three_res:
                    ttt = {
                        "id": level_three[0],
                        "code": level_three[1],
                        "name": level_three[2],
                        "label": level_three[2],
                        "value": level_three[0],
                    }
                    three_leve_children.append(ttt)
                tt["children"] = three_leve_children
                two_level_children.append(tt)
            t["children"] = two_level_children
            parent.append(t)
        return parent
class Rating(MyBaseModel):
    __tablename__ = "ratings"
    user_id = db.Column(db.Integer,
                        db.ForeignKey("users.id", ondelete="CASCADE"),
                        nullable=False)
    movie_id = db.Column(db.Integer,
                         db.ForeignKey("movies.id", ondelete="CASCADE"),
                         nullable=False)
    score = db.Column(db.Integer, default=0)
    comment = db.Column(db.Text, default="")
    category = db.Column(TINYINT(1), default=2)  # 0=wish, 1=do, 2=collect
    tags = db.relationship(
        "Tag",
        secondary="rating_tags",
        backref=db.backref("ratings", lazy="dynamic"),
        lazy=True,
    )
    like_by_users = db.relationship(
        "User",
        secondary="rating_likes",
        backref=db.backref("like_ratings", lazy="dynamic"),
        lazy="dynamic",
    )
    report_by_users = db.relationship(
        "User",
        secondary="rating_reports",
        backref=db.backref("report_ratings", lazy="dynamic"),
        lazy="dynamic",
    )

    __table_args__ = (UniqueConstraint("user_id", "movie_id", "category"), )

    def __repr__(self):
        return "<Rating %r>" % self.comment

    @staticmethod
    def create_rating_with_tags(user,
                                movie,
                                score=0,
                                comment="",
                                category=RatingType.COLLECT,
                                tags_name=[]):
        """
        create one rating with tags but not commit
        :param user:
        :param movie:
        :param score: score
        :param comment: comment
        :param category: category must be in RatingType
        :return: Rating object
        """
        r = Rating(score=score, comment=comment, category=category)
        r.user = user
        r.movie = movie
        for tag_name in tags_name:
            if tag_name != "":
                rating_tag = Tag.create_one(tag_name=tag_name)
                r.tags.append(rating_tag)
        return r

    def like_by(self, user):
        """
        :param user:  User
        :return: False or True
        """
        if self.like_by_users.filter_by(id=user.id).first():
            return False
        self.like_by_users.append(user)
        notification = Notification.create_one(self.user_id,
                                               user.id,
                                               NotificationType.RATING_ACTION,
                                               rating_id=self.id)
        if notification:
            self.user.notifications_received.append(notification)
            user.notifications_sent.append(notification)
        return True

    def unlike_by(self, user):
        """
        :param user: User
        :return: True or False
        """
        if not self.like_by_users.filter_by(id=user.id).first():
            return False
        self.like_by_users.remove(user)
        notification = Notification.query.filter_by(
            receiver_user_id=self.user.id,
            sender_user_id=user.id,
            category=NotificationType.RATING_ACTION,
        ).first()
        if notification:
            self.user.notifications_received.remove(notification)
            user.notifications_sent.remove(notification)
        return True

    def report_by(self, user):
        """
        :param user:
        :return: True or False
        """
        if self.user_id == user.id:
            return False
        if self.report_by_users.filter_by(id=user.id).first():
            return False
        self.report_by_users.append(user)
        return True

    @property
    def like_count(self):
        return self.like_by_users.count()

    @property
    def report_count(self):
        return self.report_by_users.count()
class MyBaseModel(db.Model):
    """
    Base Model
    """

    __abstract__ = True

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)


user_roles = db.Table(
    "user_roles",
    db.Column("id", db.Integer, primary_key=True, autoincrement=True),
    db.Column("user_id", db.Integer,
              db.ForeignKey("users.id", ondelete="CASCADE")),
    db.Column("role_id", db.Integer,
              db.ForeignKey("roles.id", ondelete="CASCADE")),
    UniqueConstraint("user_id", "role_id", name="unique_user_id_and_role_id"),
)


class Role(MyBaseModel):
    """
    Table: role permissions
    """

    __tablename__ = "roles"
    role_name = db.Column(db.String(16))