You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
11 KiB

преди 4 години
  1. # -*- coding: utf-8 -*-
  2. """
  3. :author: Grey Li ()
  4. :url: http://greyli.com
  5. :copyright: © 2018 Grey Li <withlihui@gmail.com>
  6. :license: MIT, see LICENSE for more details.
  7. """
  8. import os
  9. from datetime import datetime
  10. from flask import current_app
  11. from flask_avatars import Identicon
  12. from flask_login import UserMixin
  13. from werkzeug.security import generate_password_hash, check_password_hash
  14. from albumy.extensions import db, whooshee
  15. # relationship table
  16. roles_permissions = db.Table('roles_permissions',
  17. db.Column('role_id', db.Integer, db.ForeignKey('role.id')),
  18. db.Column('permission_id', db.Integer, db.ForeignKey('permission.id'))
  19. )
  20. class Permission(db.Model):
  21. id = db.Column(db.Integer, primary_key=True)
  22. name = db.Column(db.String(30), unique=True)
  23. roles = db.relationship('Role', secondary=roles_permissions, back_populates='permissions')
  24. class Role(db.Model):
  25. id = db.Column(db.Integer, primary_key=True)
  26. name = db.Column(db.String(30), unique=True)
  27. users = db.relationship('User', back_populates='role')
  28. permissions = db.relationship('Permission', secondary=roles_permissions, back_populates='roles')
  29. @staticmethod
  30. def init_role():
  31. roles_permissions_map = {
  32. 'Locked': ['FOLLOW', 'COLLECT'],
  33. 'User': ['FOLLOW', 'COLLECT', 'COMMENT', 'UPLOAD'],
  34. 'Moderator': ['FOLLOW', 'COLLECT', 'COMMENT', 'UPLOAD', 'MODERATE'],
  35. 'Administrator': ['FOLLOW', 'COLLECT', 'COMMENT', 'UPLOAD', 'MODERATE', 'ADMINISTER']
  36. }
  37. for role_name in roles_permissions_map:
  38. role = Role.query.filter_by(name=role_name).first()
  39. if role is None:
  40. role = Role(name=role_name)
  41. db.session.add(role)
  42. role.permissions = []
  43. for permission_name in roles_permissions_map[role_name]:
  44. permission = Permission.query.filter_by(name=permission_name).first()
  45. if permission is None:
  46. permission = Permission(name=permission_name)
  47. db.session.add(permission)
  48. role.permissions.append(permission)
  49. db.session.commit()
  50. # relationship object
  51. class Follow(db.Model):
  52. follower_id = db.Column(db.Integer, db.ForeignKey('user.id'),
  53. primary_key=True)
  54. followed_id = db.Column(db.Integer, db.ForeignKey('user.id'),
  55. primary_key=True)
  56. timestamp = db.Column(db.DateTime, default=datetime.utcnow)
  57. follower = db.relationship('User', foreign_keys=[follower_id], back_populates='following', lazy='joined')
  58. followed = db.relationship('User', foreign_keys=[followed_id], back_populates='followers', lazy='joined')
  59. # relationship object
  60. class Collect(db.Model):
  61. collector_id = db.Column(db.Integer, db.ForeignKey('user.id'),
  62. primary_key=True)
  63. collected_id = db.Column(db.Integer, db.ForeignKey('photo.id'),
  64. primary_key=True)
  65. timestamp = db.Column(db.DateTime, default=datetime.utcnow)
  66. collector = db.relationship('User', back_populates='collections', lazy='joined')
  67. collected = db.relationship('Photo', back_populates='collectors', lazy='joined')
  68. @whooshee.register_model('name', 'username')
  69. class User(db.Model, UserMixin):
  70. id = db.Column(db.Integer, primary_key=True)
  71. username = db.Column(db.String(20), unique=True, index=True)
  72. email = db.Column(db.String(254), unique=True, index=True)
  73. password_hash = db.Column(db.String(128))
  74. name = db.Column(db.String(30))
  75. website = db.Column(db.String(255))
  76. bio = db.Column(db.String(120))
  77. location = db.Column(db.String(50))
  78. member_since = db.Column(db.DateTime, default=datetime.utcnow)
  79. avatar_s = db.Column(db.String(64))
  80. avatar_m = db.Column(db.String(64))
  81. avatar_l = db.Column(db.String(64))
  82. avatar_raw = db.Column(db.String(64))
  83. confirmed = db.Column(db.Boolean, default=False)
  84. locked = db.Column(db.Boolean, default=False)
  85. active = db.Column(db.Boolean, default=True)
  86. public_collections = db.Column(db.Boolean, default=True)
  87. receive_comment_notification = db.Column(db.Boolean, default=True)
  88. receive_follow_notification = db.Column(db.Boolean, default=True)
  89. receive_collect_notification = db.Column(db.Boolean, default=True)
  90. role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
  91. role = db.relationship('Role', back_populates='users')
  92. photos = db.relationship('Photo', back_populates='author', cascade='all')
  93. comments = db.relationship('Comment', back_populates='author', cascade='all')
  94. notifications = db.relationship('Notification', back_populates='receiver', cascade='all')
  95. collections = db.relationship('Collect', back_populates='collector', cascade='all')
  96. following = db.relationship('Follow', foreign_keys=[Follow.follower_id], back_populates='follower',
  97. lazy='dynamic', cascade='all')
  98. followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], back_populates='followed',
  99. lazy='dynamic', cascade='all')
  100. def __init__(self, **kwargs):
  101. super(User, self).__init__(**kwargs)
  102. self.generate_avatar()
  103. self.follow(self) # follow self
  104. self.set_role()
  105. def set_password(self, password):
  106. self.password_hash = generate_password_hash(password)
  107. def set_role(self):
  108. if self.role is None:
  109. if self.email == current_app.config['ALBUMY_ADMIN_EMAIL']:
  110. self.role = Role.query.filter_by(name='Administrator').first()
  111. else:
  112. self.role = Role.query.filter_by(name='User').first()
  113. db.session.commit()
  114. def validate_password(self, password):
  115. return check_password_hash(self.password_hash, password)
  116. def follow(self, user):
  117. if not self.is_following(user):
  118. follow = Follow(follower=self, followed=user)
  119. db.session.add(follow)
  120. db.session.commit()
  121. def unfollow(self, user):
  122. follow = self.following.filter_by(followed_id=user.id).first()
  123. if follow:
  124. db.session.delete(follow)
  125. db.session.commit()
  126. def is_following(self, user):
  127. if user.id is None: # when follow self, user.id will be None
  128. return False
  129. return self.following.filter_by(followed_id=user.id).first() is not None
  130. def is_followed_by(self, user):
  131. return self.followers.filter_by(follower_id=user.id).first() is not None
  132. @property
  133. def followed_photos(self):
  134. return Photo.query.join(Follow, Follow.followed_id == Photo.author_id).filter(Follow.follower_id == self.id)
  135. def collect(self, photo):
  136. if not self.is_collecting(photo):
  137. collect = Collect(collector=self, collected=photo)
  138. db.session.add(collect)
  139. db.session.commit()
  140. def uncollect(self, photo):
  141. collect = Collect.query.with_parent(self).filter_by(collected_id=photo.id).first()
  142. if collect:
  143. db.session.delete(collect)
  144. db.session.commit()
  145. def is_collecting(self, photo):
  146. return Collect.query.with_parent(self).filter_by(collected_id=photo.id).first() is not None
  147. def lock(self):
  148. self.locked = True
  149. self.role = Role.query.filter_by(name='Locked').first()
  150. db.session.commit()
  151. def unlock(self):
  152. self.locked = False
  153. self.role = Role.query.filter_by(name='User').first()
  154. db.session.commit()
  155. def block(self):
  156. self.active = False
  157. db.session.commit()
  158. def unblock(self):
  159. self.active = True
  160. db.session.commit()
  161. def generate_avatar(self):
  162. avatar = Identicon()
  163. filenames = avatar.generate(text=self.username)
  164. self.avatar_s = filenames[0]
  165. self.avatar_m = filenames[1]
  166. self.avatar_l = filenames[2]
  167. db.session.commit()
  168. @property
  169. def is_admin(self):
  170. return self.role.name == 'Administrator'
  171. @property
  172. def is_active(self):
  173. return self.active
  174. def can(self, permission_name):
  175. permission = Permission.query.filter_by(name=permission_name).first()
  176. return permission is not None and self.role is not None and permission in self.role.permissions
  177. tagging = db.Table('tagging',
  178. db.Column('photo_id', db.Integer, db.ForeignKey('photo.id')),
  179. db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
  180. )
  181. @whooshee.register_model('description')
  182. class Photo(db.Model):
  183. id = db.Column(db.Integer, primary_key=True)
  184. description = db.Column(db.String(500))
  185. filename = db.Column(db.String(64))
  186. filename_s = db.Column(db.String(64))
  187. filename_m = db.Column(db.String(64))
  188. timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
  189. can_comment = db.Column(db.Boolean, default=True)
  190. flag = db.Column(db.Integer, default=0)
  191. author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  192. author = db.relationship('User', back_populates='photos')
  193. comments = db.relationship('Comment', back_populates='photo', cascade='all')
  194. collectors = db.relationship('Collect', back_populates='collected', cascade='all')
  195. tags = db.relationship('Tag', secondary=tagging, back_populates='photos')
  196. @whooshee.register_model('name')
  197. class Tag(db.Model):
  198. id = db.Column(db.Integer, primary_key=True)
  199. name = db.Column(db.String(64), index=True, unique=True)
  200. photos = db.relationship('Photo', secondary=tagging, back_populates='tags')
  201. class Comment(db.Model):
  202. id = db.Column(db.Integer, primary_key=True)
  203. body = db.Column(db.Text)
  204. timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
  205. flag = db.Column(db.Integer, default=0)
  206. replied_id = db.Column(db.Integer, db.ForeignKey('comment.id'))
  207. author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  208. photo_id = db.Column(db.Integer, db.ForeignKey('photo.id'))
  209. photo = db.relationship('Photo', back_populates='comments')
  210. author = db.relationship('User', back_populates='comments')
  211. replies = db.relationship('Comment', back_populates='replied', cascade='all')
  212. replied = db.relationship('Comment', back_populates='replies', remote_side=[id])
  213. class Notification(db.Model):
  214. id = db.Column(db.Integer, primary_key=True)
  215. message = db.Column(db.Text, nullable=False)
  216. is_read = db.Column(db.Boolean, default=False)
  217. timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
  218. receiver_id = db.Column(db.Integer, db.ForeignKey('user.id'))
  219. receiver = db.relationship('User', back_populates='notifications')
  220. @db.event.listens_for(User, 'after_delete', named=True)
  221. def delete_avatars(**kwargs):
  222. target = kwargs['target']
  223. for filename in [target.avatar_s, target.avatar_m, target.avatar_l, target.avatar_raw]:
  224. if filename is not None: # avatar_raw may be None
  225. path = os.path.join(current_app.config['AVATARS_SAVE_PATH'], filename)
  226. if os.path.exists(path): # not every filename map a unique file
  227. os.remove(path)
  228. @db.event.listens_for(Photo, 'after_delete', named=True)
  229. def delete_photos(**kwargs):
  230. target = kwargs['target']
  231. for filename in [target.filename, target.filename_s, target.filename_m]:
  232. path = os.path.join(current_app.config['ALBUMY_UPLOAD_PATH'], filename)
  233. if os.path.exists(path): # not every filename map a unique file
  234. os.remove(path)