Hide keyboard shortcuts

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 

5 

6from src import db, bcrypt 

7from src.utils import GUID 

8from .content_model import ContentType 

9from .event import MetaAddedEvent, ChangedEvent 

10 

11 

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 ) 

18 

19 

20class MetaUserContentModel(db.Model): 

21 """ 

22 MetaUserContent Model for storing metadata between user and content 

23 """ 

24 __tablename__ = "meta_user_content" 

25 

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) 

38 

39 

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)) 

44 

45 

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 

51 

52 changes = {} 

53 for attr in db.inspect(target).attrs: 

54 hist = attr.load_history() 

55 

56 if not hist.has_changes(): 

57 continue 

58 

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 )) 

68 

69 

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 ) 

76 

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 ) 

83 

84 

85class RecommendedContentModel(db.Model): 

86 """ 

87 RecommendedContent Model for storing recommended contents for a user 

88 """ 

89 __tablename__ = "recommended_content" 

90 

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)) 

99 

100 

101class BadRecommendationContentModel(db.Model): 

102 """ 

103 BadRecommendationContent Model for storing bad recommended contents for a user 

104 """ 

105 __tablename__ = "bad_recommendation_content" 

106 

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) 

113 

114 

115class UserModel(db.Model): 

116 """ 

117 User Model for storing user related details 

118 """ 

119 __tablename__ = "user" 

120 

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) 

128 

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") 

132 

133 recommended_contents = db.relationship( 

134 "RecommendedContentModel", lazy="subquery") 

135 

136 bad_recommadation_contents = db.relationship( 

137 "BadRecommendationContentModel", lazy="subquery") 

138 

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') 

143 

144 linked_services = db.relationship( 

145 "ExternalModel", backref="user", lazy='dynamic') 

146 

147 liked_genres = db.relationship( 

148 "GenreModel", secondary=LikedGenreModel, lazy="dynamic") 

149 

150 role = db.relationship( 

151 "RoleModel", secondary=UserRoleModel, lazy="subquery") 

152 

153 @property 

154 def password(self): 

155 raise AttributeError("Password is not a readable attribute") 

156 

157 @password.setter 

158 def password(self, password): 

159 self.password_hash = bcrypt.generate_password_hash( 

160 password).decode("utf-8") 

161 

162 def verify_password(self, password): 

163 return bcrypt.check_password_hash(self.password_hash, password) 

164 

165 def __repr__(self): 

166 return f"<User {self.username}>"