Coverage for src/model/user_model.py : 96%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from sqlalchemy.orm.session import object_session
2from sqlalchemy import event
3from sqlalchemy import CheckConstraint
4import uuid
6from src import db, bcrypt
7from src.utils import GUID
8from .content_model import ContentType
9from .event import MetaAddedEvent, ChangedEvent
12UserRoleModel = db.Table("user_role",
13 db.Column("user_id", db.Integer, db.ForeignKey(
14 "user.user_id", ondelete="CASCADE"), primary_key=True),
15 db.Column("role_id", db.Integer, db.ForeignKey(
16 "role.role_id"), primary_key=True)
17 )
20class MetaUserContentModel(db.Model):
21 """
22 MetaUserContent Model for storing metadata between user and content
23 """
24 __tablename__ = "meta_user_content"
26 user_id = db.Column(db.Integer, db.ForeignKey(
27 "user.user_id", ondelete="CASCADE"), primary_key=True)
28 content_id = db.Column(db.Integer, db.ForeignKey(
29 "content.content_id", ondelete="CASCADE"), primary_key=True)
30 rating = db.Column(db.Integer, CheckConstraint(
31 "rating <= 5 and rating >= 0"), default=None)
32 last_rating_date = db.Column(db.DateTime, default=None)
33 review_see_count = db.Column(db.Integer, default=0)
34 last_review_see_date = db.Column(db.DateTime, default=None)
35 # can be play_count, watch_count, num_watched_episodes
36 count = db.Column(db.Integer, default=0)
37 last_count_increment = db.Column(db.DateTime, default=None)
40@event.listens_for(MetaUserContentModel, 'after_insert')
41def receive_after_insert(mapper, connection, target):
42 "listen for the 'after_insert' event"
43 connection.execute(MetaAddedEvent.insert(target))
46@event.listens_for(MetaUserContentModel, 'after_update')
47def receive_after_update(mapper, connection, target):
48 "listen for the 'after_update' event"
49 if not object_session(target).is_modified(target, include_collections=False):
50 return
52 changes = {}
53 for attr in db.inspect(target).attrs:
54 hist = attr.load_history()
56 if not hist.has_changes():
57 continue
59 # hist.deleted holds old value
60 # hist.added holds new value
61 connection.execute(ChangedEvent.__table__.insert().values(
62 occured_by=target.user_id,
63 object_id=target.content_id,
64 model_name=MetaUserContentModel.__tablename__,
65 attribute_name=attr.key,
66 new_value=str(hist.added[0])
67 ))
70GroupMembersModel = db.Table("group_members",
71 db.Column("user_id", db.Integer, db.ForeignKey(
72 "user.user_id", ondelete="CASCADE"), primary_key=True),
73 db.Column("group_id", db.Integer, db.ForeignKey(
74 "group.group_id", ondelete="CASCADE"), primary_key=True)
75 )
77LikedGenreModel = db.Table("liked_genres",
78 db.Column("user_id", db.Integer, db.ForeignKey(
79 "user.user_id", ondelete="CASCADE"), primary_key=True),
80 db.Column("genre_id", db.Integer, db.ForeignKey(
81 "genre.genre_id", ondelete="CASCADE"), primary_key=True)
82 )
85class RecommendedContentModel(db.Model):
86 """
87 RecommendedContent Model for storing recommended contents for a user
88 """
89 __tablename__ = "recommended_content"
91 user_id = db.Column(db.Integer, db.ForeignKey(
92 "user.user_id", ondelete="CASCADE"), primary_key=True)
93 content_id = db.Column(db.Integer, db.ForeignKey(
94 "content.content_id", ondelete="CASCADE"), primary_key=True)
95 score = db.Column(db.Float)
96 engine = db.Column(db.String)
97 engine_priority = db.Column(db.Integer)
98 content_type = db.Column(db.Enum(ContentType))
101class BadRecommendationContentModel(db.Model):
102 """
103 BadRecommendationContent Model for storing bad recommended contents for a user
104 """
105 __tablename__ = "bad_recommendation_content"
107 user_id = db.Column(db.Integer, db.ForeignKey(
108 "user.user_id", ondelete="CASCADE"), primary_key=True)
109 content_id = db.Column(db.Integer, db.ForeignKey(
110 "content.content_id", ondelete="CASCADE"), primary_key=True)
111 reason_categorie = db.Column(db.Text, primary_key=True)
112 reason = db.Column(db.Text, primary_key=True)
115class UserModel(db.Model):
116 """
117 User Model for storing user related details
118 """
119 __tablename__ = "user"
121 user_id = db.Column(db.Integer, primary_key=True,
122 autoincrement=True, index=True)
123 uuid = db.Column(GUID(), default=uuid.uuid4, unique=True)
124 email = db.Column(db.String(255), unique=True)
125 username = db.Column(db.String(45), nullable=False)
126 password_hash = db.Column(db.String(255), nullable=False)
127 preferences_defined = db.Column(db.Boolean, default=False)
129 # Loaded immediately after loading User, but when querying multiple users, you will not get additional queries.
130 meta_user_contents = db.relationship(
131 "MetaUserContentModel", lazy="subquery")
133 recommended_contents = db.relationship(
134 "RecommendedContentModel", lazy="subquery")
136 bad_recommadation_contents = db.relationship(
137 "BadRecommendationContentModel", lazy="subquery")
139 groups = db.relationship(
140 "GroupModel", secondary=GroupMembersModel, lazy="dynamic", backref=db.backref('members', lazy='dynamic'))
141 owned_groups = db.relationship(
142 "GroupModel", backref="owner", lazy='dynamic')
144 linked_services = db.relationship(
145 "ExternalModel", backref="user", lazy='dynamic')
147 liked_genres = db.relationship(
148 "GenreModel", secondary=LikedGenreModel, lazy="dynamic")
150 role = db.relationship(
151 "RoleModel", secondary=UserRoleModel, lazy="subquery")
153 @property
154 def password(self):
155 raise AttributeError("Password is not a readable attribute")
157 @password.setter
158 def password(self, password):
159 self.password_hash = bcrypt.generate_password_hash(
160 password).decode("utf-8")
162 def verify_password(self, password):
163 return bcrypt.check_password_hash(self.password_hash, password)
165 def __repr__(self):
166 return f"<User {self.username}>"