models.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. #!/usr/bin/env python3
  2. # -*- coding: UTF-8 -*-
  3. import datetime
  4. import hashlib
  5. import logging
  6. import time
  7. import json
  8. import os
  9. from gettext import gettext as _
  10. from social_sqlalchemy.storage import JSONType, SQLAlchemyMixin
  11. from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
  12. from sqlalchemy.ext.declarative import declarative_base
  13. from sqlalchemy.ext.mutable import Mutable
  14. from sqlalchemy.orm import relationship
  15. def mksalt():
  16. import random
  17. import string
  18. # for python3, just use: crypt.mksalt(crypt.METHOD_SHA512)
  19. saltchars = string.ascii_letters + string.digits + "./"
  20. salt = []
  21. for c in range(32):
  22. idx = int(random.random() * 10000) % len(saltchars)
  23. salt.append(saltchars[idx])
  24. return "".join(salt)
  25. Base = declarative_base()
  26. def bind_session(session):
  27. def _session(self):
  28. return session
  29. Base._session = classmethod(_session)
  30. SQLAlchemyMixin._session = classmethod(_session)
  31. logging.info("Bind modles._session()")
  32. def to_dict(self):
  33. return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}
  34. Base.to_dict = to_dict
  35. class MutableDict(Mutable, dict):
  36. @classmethod
  37. def coerce(cls, key, value):
  38. "Convert plain dictionaries to MutableDict."
  39. if isinstance(value, MutableDict):
  40. return value
  41. if isinstance(value, dict):
  42. return MutableDict(value)
  43. return Mutable.coerce(key, value)
  44. def __setitem__(self, key, value):
  45. "Detect dictionary set events and emit change events."
  46. dict.__setitem__(self, key, value)
  47. self.changed()
  48. def __delitem__(self, key):
  49. "Detect dictionary del events and emit change events."
  50. dict.__delitem__(self, key)
  51. self.changed()
  52. def __getitem__(self, key):
  53. if not dict.__contains__(self, key):
  54. return ""
  55. return dict.__getitem__(self, key)
  56. class Reader(Base, SQLAlchemyMixin):
  57. # 权限位
  58. SPECIAL = 0b00000001 # 未开启说明是默认权限
  59. LOGIN = 0b00000010 # 登录
  60. VIEW = 0b00000100 # 浏览
  61. READ = 0b00001000 # 阅读
  62. UPLOAD = 0b00010000 # 上传
  63. DOWNLOAD = 0b00100000 # 下载
  64. OVERSIZE_SHRINK_RATE = 0.8
  65. SQLITE_MAX_LENGTH = 32 * 1024.0
  66. RE_EMAIL = r"[^@]+@[^@]+\.[^@]+"
  67. RE_USERNAME = r"[a-z][a-z0-9_]*"
  68. RE_PASSWORD = r'[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};\':",./<>?\|]*'
  69. __tablename__ = "readers"
  70. id = Column(Integer, primary_key=True)
  71. username = Column(String(200))
  72. password = Column(String(200), default="")
  73. salt = Column(String(200))
  74. name = Column(String(100))
  75. email = Column(String(200))
  76. avatar = Column(String(200))
  77. admin = Column(Boolean, default=False)
  78. active = Column(Boolean, default=True)
  79. permission = Column(String(100), default="")
  80. create_time = Column(DateTime)
  81. update_time = Column(DateTime)
  82. access_time = Column(DateTime)
  83. extra = Column(MutableDict.as_mutable(JSONType), default={})
  84. def __str__(self):
  85. return "<id=%d, username=%s, email=%s>" % (self.id, self.username, self.email)
  86. def shrink_column_extra(self):
  87. # check whether the length of `extra` column is out of limit 32KB
  88. text = json.dumps(self.extra)
  89. shrink = min(self.OVERSIZE_SHRINK_RATE, self.SQLITE_MAX_LENGTH / len(text))
  90. if len(text) > self.SQLITE_MAX_LENGTH:
  91. for k, v in self.extra.items():
  92. if k.endswith("_history") and isinstance(v, list):
  93. new_length = int(len(v) * shrink)
  94. self.extra[k] = v[:new_length]
  95. def save(self):
  96. self.shrink_column_extra()
  97. return super().save()
  98. def init_default_user(self):
  99. class DefaultUserInfo:
  100. extra_data = {"username": _(u"默认用户")}
  101. provider = "qq"
  102. uid = 123456789
  103. self.init(DefaultUserInfo())
  104. def init(self, social_user):
  105. self.username = self.get_social_username(social_user)
  106. self.create_time = datetime.datetime.now()
  107. self.update_time = datetime.datetime.now()
  108. self.access_time = datetime.datetime.now()
  109. self.extra = {"kindle_email": ""}
  110. self.init_avatar(social_user)
  111. def reset_password(self):
  112. s = "%s%s%s" % (self.username, self.create_time.strftime("%s"), time.time())
  113. p = hashlib.md5(s.encode("UTF-8")).hexdigest()[:16]
  114. self.set_secure_password(p)
  115. return p
  116. def get_secure_password(self, raw_password):
  117. p1 = hashlib.sha256(raw_password.encode("UTF-8")).hexdigest()
  118. p2 = hashlib.sha256((self.salt + p1).encode("UTF-8")).hexdigest()
  119. return p2
  120. def set_secure_password(self, raw_password):
  121. self.salt = mksalt()
  122. self.password = self.get_secure_password(raw_password)
  123. def init_avatar(self, social_user):
  124. anyone = "http://tva1.sinaimg.cn/default/images/default_avatar_male_50.gif"
  125. url = social_user.extra_data.get("profile_image_url", anyone)
  126. self.avatar = url.replace("http://q.qlogo.cn", "//q.qlogo.cn")
  127. if social_user.provider == "github":
  128. self.avatar = "https://avatars.githubusercontent.com/u/%s" % social_user.extra_data["id"]
  129. def get_active_code(self):
  130. return self.get_secure_password(self.create_time.strftime("%Y-%m-%d %H:%M:%S"))
  131. def get_social_username(self, si):
  132. for k in ["username", "login"]:
  133. if k in si.extra_data:
  134. return si.extra_data[k]
  135. return "%s_%s" % (si.provider, si.uid)
  136. def check_and_update(self, social_user):
  137. name = self.get_social_username(social_user)
  138. if self.username != name:
  139. logging.info("userid[%s] username needs update to [%s]" % (self.id, name))
  140. self.username = name
  141. def set_permission(self, operations):
  142. ALL = "delprsuv"
  143. if not isinstance(operations, str):
  144. raise "bug"
  145. v = list(self.permission)
  146. for p in operations:
  147. if p.lower() not in ALL:
  148. continue
  149. r = p.upper() if p.islower() else p.lower()
  150. try:
  151. v.remove(r)
  152. except:
  153. pass
  154. v.append(p)
  155. self.permission = "".join(sorted(v))
  156. def has_permission(self, operation, default=True):
  157. if operation.lower() in self.permission:
  158. return True
  159. if operation.upper() in self.permission:
  160. return False
  161. return default
  162. def can_delete(self):
  163. return self.has_permission("d")
  164. def can_edit(self):
  165. return self.has_permission("e")
  166. def can_login(self):
  167. return self.has_permission("l")
  168. def can_push(self):
  169. return self.has_permission("p")
  170. def can_read(self):
  171. return self.has_permission("r")
  172. def can_save(self):
  173. return self.has_permission("s")
  174. def can_upload(self):
  175. return self.has_permission("u")
  176. def can_view(self):
  177. return self.has_permission("v")
  178. def is_active(self):
  179. return self.active
  180. def is_admin(self):
  181. return self.admin
  182. class Message(Base, SQLAlchemyMixin):
  183. __tablename__ = "messages"
  184. id = Column(Integer, primary_key=True)
  185. title = Column(String(200))
  186. status = Column(String(100))
  187. unread = Column(Boolean, default=True)
  188. create_time = Column(DateTime)
  189. update_time = Column(DateTime)
  190. data = Column(MutableDict.as_mutable(JSONType), default={})
  191. reader_id = Column(Integer, ForeignKey("readers.id"))
  192. reader = relationship(Reader, backref="messages")
  193. def __init__(self, user_id, status, msg):
  194. super(Message, self).__init__()
  195. self.reader_id = user_id
  196. self.status = status
  197. self.create_time = datetime.datetime.now()
  198. self.update_time = datetime.datetime.now()
  199. self.data = {"message": msg}
  200. class Item(Base, SQLAlchemyMixin):
  201. __tablename__ = "items"
  202. book_id = Column(Integer, default=0, primary_key=True)
  203. count_guest = Column(Integer, default=0, nullable=False)
  204. count_visit = Column(Integer, default=0, nullable=False)
  205. count_download = Column(Integer, default=0, nullable=False)
  206. website = Column(String(255), default="", nullable=False)
  207. collector_id = Column(Integer, ForeignKey("readers.id"))
  208. collector = relationship(Reader, backref="items")
  209. def __init__(self):
  210. super(Item, self).__init__()
  211. self.count_guest = 0
  212. self.count_visit = 0
  213. self.count_download = 0
  214. self.collector_id = 1
  215. class ScanFile(Base, SQLAlchemyMixin):
  216. __tablename__ = "scanfiles"
  217. id = Column(Integer, primary_key=True)
  218. scan_id = Column(Integer, default=0)
  219. import_id = Column(Integer, default=0)
  220. name = Column(String(512))
  221. path = Column(String(1024))
  222. hash = Column(String(512), unique=True)
  223. status = Column(String(24))
  224. title = Column(String(100))
  225. author = Column(String(100))
  226. publisher = Column(String(100))
  227. tags = Column(String(100))
  228. create_time = Column(DateTime)
  229. update_time = Column(DateTime)
  230. book_id = Column(Integer, default=0)
  231. data = Column(MutableDict.as_mutable(JSONType), default={})
  232. # STATUS
  233. NEW = "new"
  234. DROP = "drop"
  235. READY = "ready"
  236. EXIST = "exist"
  237. IMPORTED = "imported"
  238. def __init__(self, path, hash_value, scan_id):
  239. super(ScanFile, self).__init__()
  240. self.name = os.path.basename(path)
  241. self.path = path
  242. self.hash = hash_value
  243. self.scan_id = scan_id
  244. self.status = self.NEW
  245. self.create_time = datetime.datetime.now()
  246. self.update_time = datetime.datetime.now()
  247. def user_syncdb(engine):
  248. Base.metadata.create_all(engine)