models.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.user.models
  4. ~~~~~~~~~~~~~~~~~~~~
  5. This module provides the models for the user.
  6. :copyright: (c) 2013 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import sys
  10. from datetime import datetime
  11. from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
  12. from itsdangerous import SignatureExpired
  13. from werkzeug import generate_password_hash, check_password_hash
  14. from flask import current_app
  15. from flask.ext.login import UserMixin, AnonymousUserMixin
  16. from flaskbb.extensions import db, cache
  17. from flaskbb.forum.models import Post, Topic, topictracker
  18. groups_users = db.Table(
  19. 'groups_users',
  20. db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
  21. db.Column('group_id', db.Integer(), db.ForeignKey('groups.id')))
  22. class Group(db.Model):
  23. __tablename__ = "groups"
  24. id = db.Column(db.Integer, primary_key=True)
  25. name = db.Column(db.String, unique=True)
  26. description = db.Column(db.String(80))
  27. # I bet there is a nicer way for this :P
  28. admin = db.Column(db.Boolean, default=False)
  29. super_mod = db.Column(db.Boolean, default=False)
  30. mod = db.Column(db.Boolean, default=False)
  31. guest = db.Column(db.Boolean, default=False)
  32. banned = db.Column(db.Boolean, default=False)
  33. editpost = db.Column(db.Boolean, default=True)
  34. deletepost = db.Column(db.Boolean, default=False)
  35. deletetopic = db.Column(db.Boolean, default=False)
  36. locktopic = db.Column(db.Boolean, default=True)
  37. movetopic = db.Column(db.Boolean, default=True)
  38. posttopic = db.Column(db.Boolean, default=True)
  39. postreply = db.Column(db.Boolean, default=True)
  40. # Methods
  41. def __repr__(self):
  42. """
  43. Set to a unique key specific to the object in the database.
  44. Required for cache.memoize() to work across requests.
  45. """
  46. return "<{} {})>".format(self.__class__.__name__, self.id)
  47. def save(self):
  48. db.session.add(self)
  49. db.session.commit()
  50. return self
  51. def delete(self):
  52. db.session.delete(self)
  53. db.session.commit()
  54. return self
  55. class User(db.Model, UserMixin):
  56. __tablename__ = "users"
  57. id = db.Column(db.Integer, primary_key=True)
  58. username = db.Column(db.String, unique=True)
  59. email = db.Column(db.String, unique=True)
  60. _password = db.Column('password', db.String(80), nullable=False)
  61. date_joined = db.Column(db.DateTime, default=datetime.utcnow())
  62. lastseen = db.Column(db.DateTime, default=datetime.utcnow())
  63. birthday = db.Column(db.DateTime)
  64. gender = db.Column(db.String)
  65. website = db.Column(db.String)
  66. location = db.Column(db.String)
  67. signature = db.Column(db.String)
  68. avatar = db.Column(db.String)
  69. notes = db.Column(db.Text(5000))
  70. posts = db.relationship("Post", backref="user", lazy="dynamic",
  71. cascade="all, delete-orphan")
  72. topics = db.relationship("Topic", backref="user", lazy="dynamic",
  73. cascade="all, delete-orphan")
  74. post_count = db.Column(db.Integer, default=0)
  75. primary_group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
  76. primary_group = db.relationship('Group', lazy="joined",
  77. backref="user_group", uselist=False,
  78. foreign_keys=[primary_group_id])
  79. secondary_groups = \
  80. db.relationship('Group',
  81. secondary=groups_users,
  82. primaryjoin=(groups_users.c.user_id == id),
  83. backref=db.backref('users', lazy='dynamic'),
  84. lazy='dynamic')
  85. tracked_topics = \
  86. db.relationship("Topic", secondary=topictracker,
  87. primaryjoin=(topictracker.c.user_id == id),
  88. backref=db.backref("topicstracked", lazy="dynamic"),
  89. lazy="dynamic")
  90. # Properties
  91. @property
  92. def last_post(self):
  93. """
  94. Returns the latest post from the user
  95. """
  96. return Post.query.filter(Post.user_id == self.id).\
  97. order_by(Post.date_created.desc()).first()
  98. # Methods
  99. def __repr__(self):
  100. """
  101. Set to a unique key specific to the object in the database.
  102. Required for cache.memoize() to work across requests.
  103. """
  104. return "Username: %s" % self.username
  105. def _get_password(self):
  106. return self._password
  107. def _set_password(self, password):
  108. self._password = generate_password_hash(password)
  109. # Hide password encryption by exposing password field only.
  110. password = db.synonym('_password',
  111. descriptor=property(_get_password,
  112. _set_password))
  113. def check_password(self, password):
  114. """
  115. Check passwords. If passwords match it returns true, else false
  116. """
  117. if self.password is None:
  118. return False
  119. return check_password_hash(self.password, password)
  120. @classmethod
  121. def authenticate(cls, login, password):
  122. """
  123. A classmethod for authenticating users
  124. It returns true if the user exists and has entered a correct password
  125. """
  126. user = cls.query.filter(db.or_(User.username == login,
  127. User.email == login)).first()
  128. if user:
  129. authenticated = user.check_password(password)
  130. else:
  131. authenticated = False
  132. return user, authenticated
  133. def _make_token(self, data, timeout):
  134. s = Serializer(current_app.config['SECRET_KEY'], timeout)
  135. return s.dumps(data)
  136. def _verify_token(self, token):
  137. s = Serializer(current_app.config['SECRET_KEY'])
  138. data = None
  139. expired, invalid = False, False
  140. try:
  141. data = s.loads(token)
  142. except SignatureExpired:
  143. expired = True
  144. except Exception:
  145. invalid = True
  146. return expired, invalid, data
  147. def make_reset_token(self, expiration=3600):
  148. return self._make_token({'id': self.id, 'op': 'reset'}, expiration)
  149. def verify_reset_token(self, token):
  150. expired, invalid, data = self._verify_token(token)
  151. if data and data.get('id') == self.id and data.get('op') == 'reset':
  152. data = True
  153. else:
  154. data = False
  155. return expired, invalid, data
  156. def all_topics(self, page):
  157. """
  158. Returns a paginated query result with all topics the user has created.
  159. """
  160. return Topic.query.filter(Topic.user_id == self.id).\
  161. filter(Post.topic_id == Topic.id).\
  162. order_by(Post.id.desc()).\
  163. paginate(page, current_app.config['TOPICS_PER_PAGE'], False)
  164. def all_posts(self, page):
  165. """
  166. Returns a paginated query result with all posts the user has created.
  167. """
  168. return Post.query.filter(Post.user_id == self.id).\
  169. paginate(page, current_app.config['TOPICS_PER_PAGE'], False)
  170. def track_topic(self, topic):
  171. """
  172. Tracks the specified topic
  173. """
  174. if not self.is_tracking_topic(topic):
  175. self.tracked_topics.append(topic)
  176. return self
  177. def untrack_topic(self, topic):
  178. """
  179. Untracks the specified topic
  180. """
  181. if self.is_tracking_topic(topic):
  182. self.tracked_topics.remove(topic)
  183. return self
  184. def is_tracking_topic(self, topic):
  185. """
  186. Checks if the user is already tracking this topic
  187. """
  188. return self.tracked_topics.filter(
  189. topictracker.c.topic_id == topic.id).count() > 0
  190. def add_to_group(self, group):
  191. """
  192. Adds the user to the `group` if he isn't in it.
  193. """
  194. if not self.in_group(group):
  195. self.secondary_groups.append(group)
  196. return self
  197. def remove_from_group(self, group):
  198. """
  199. Removes the user from the `group` if he is in it.
  200. """
  201. if self.in_group(group):
  202. self.secondary_groups.remove(group)
  203. return self
  204. def in_group(self, group):
  205. """
  206. Returns True if the user is in the specified group
  207. """
  208. return self.secondary_groups.filter(
  209. groups_users.c.group_id == group.id).count() > 0
  210. @cache.memoize(timeout=sys.maxint)
  211. def get_permissions(self, exclude=None):
  212. """
  213. Returns a dictionary with all the permissions the user has.
  214. """
  215. exclude = exclude or []
  216. exclude.extend(['id', 'name', 'description'])
  217. perms = {}
  218. groups = self.secondary_groups.all()
  219. groups.append(self.primary_group)
  220. for group in groups:
  221. for c in group.__table__.columns:
  222. # try if the permission already exists in the dictionary
  223. # and if the permission is true, set it to True
  224. try:
  225. if not perms[c.name] and getattr(group, c.name):
  226. perms[c.name] = True
  227. # if the permission doesn't exist in the dictionary
  228. # add it to the dictionary
  229. except KeyError:
  230. # if the permission is in the exclude list,
  231. # skip to the next permission
  232. if c.name in exclude:
  233. continue
  234. perms[c.name] = getattr(group, c.name)
  235. return perms
  236. def save(self, groups=None):
  237. if groups:
  238. # TODO: Only remove/add groups that are selected
  239. secondary_groups = self.secondary_groups.all()
  240. for group in secondary_groups:
  241. self.remove_from_group(group)
  242. db.session.commit()
  243. for group in groups:
  244. # Do not add the primary group to the secondary groups
  245. if group.id == self.primary_group_id:
  246. continue
  247. self.add_to_group(group)
  248. db.session.add(self)
  249. db.session.commit()
  250. return self
  251. def delete(self):
  252. # TODO: Recount posts in the involved forums and topics
  253. db.session.delete(self)
  254. db.session.commit()
  255. return self
  256. class Guest(AnonymousUserMixin):
  257. @cache.memoize(60*5)
  258. def get_permissions(self, exclude=None):
  259. """
  260. Returns a dictionary with all permissions the user has
  261. """
  262. exclude = exclude or []
  263. exclude.extend(['id', 'name', 'description'])
  264. perms = {}
  265. # Get the Guest group
  266. group = Group.query.filter_by(guest=True).first()
  267. for c in group.__table__.columns:
  268. if c.name in exclude:
  269. continue
  270. perms[c.name] = getattr(group, c.name)
  271. return perms
  272. class PrivateMessage(db.Model):
  273. __tablename__ = "privatemessages"
  274. id = db.Column(db.Integer, primary_key=True)
  275. user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
  276. from_user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
  277. to_user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
  278. subject = db.Column(db.String)
  279. message = db.Column(db.Text)
  280. date_created = db.Column(db.DateTime, default=datetime.utcnow())
  281. trash = db.Column(db.Boolean, nullable=False, default=False)
  282. draft = db.Column(db.Boolean, nullable=False, default=False)
  283. unread = db.Column(db.Boolean, nullable=False, default=True)
  284. user = db.relationship("User", backref="pms", lazy="joined",
  285. foreign_keys=[user_id])
  286. from_user = db.relationship("User", lazy="joined",
  287. foreign_keys=[from_user_id])
  288. to_user = db.relationship("User", lazy="joined", foreign_keys=[to_user_id])
  289. def save(self, from_user=None, to_user=None, user_id=None, draft=False):
  290. if self.id:
  291. db.session.add(self)
  292. db.session.commit()
  293. return self
  294. if draft:
  295. self.draft = True
  296. # Add the message to the user's pm box
  297. self.user_id = user_id
  298. self.from_user_id = from_user
  299. self.to_user_id = to_user
  300. db.session.add(self)
  301. db.session.commit()
  302. return self
  303. def delete(self):
  304. db.session.delete(self)
  305. db.session.commit()
  306. return self