views.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.message.views
  4. ~~~~~~~~~~~~~~~~~~~~~
  5. The views for the conversations and messages are located in this module.
  6. :copyright: (c) 2014 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import uuid
  10. from functools import wraps
  11. from flask import Blueprint, abort, flash, redirect, request, url_for
  12. from flask.views import MethodView
  13. from flask_babelplus import gettext as _
  14. from flask_login import current_user, login_required
  15. from flaskbb.extensions import db
  16. from flaskbb.message.forms import ConversationForm, MessageForm
  17. from flaskbb.message.models import Conversation, Message
  18. from flaskbb.user.models import User
  19. from flaskbb.utils.helpers import (format_quote, register_view,
  20. render_template, time_utcnow)
  21. from flaskbb.utils.settings import flaskbb_config
  22. message = Blueprint("message", __name__)
  23. def requires_message_box_space(f):
  24. @wraps(f)
  25. def wrapper(*a, **k):
  26. message_count = Conversation.query.filter(Conversation.user_id == current_user.id).count()
  27. if message_count >= flaskbb_config["MESSAGE_QUOTA"]:
  28. flash(
  29. _(
  30. "You cannot send any messages anymore because you have "
  31. "reached your message limit."
  32. ), "danger"
  33. )
  34. return redirect(url_for("message.inbox"))
  35. return f(*a, **k)
  36. return wrapper
  37. class Inbox(MethodView):
  38. decorators = [login_required]
  39. def get(self):
  40. page = request.args.get('page', 1, type=int)
  41. # the inbox will display both, the recieved and the sent messages
  42. conversations = Conversation.query.filter(
  43. Conversation.user_id == current_user.id,
  44. Conversation.draft == False,
  45. Conversation.trash == False,
  46. ).order_by(Conversation.date_modified.desc()).paginate(
  47. page, flaskbb_config['TOPICS_PER_PAGE'], False
  48. )
  49. # we can't simply do conversations.total because it would ignore
  50. # drafted and trashed messages
  51. message_count = Conversation.query.filter(Conversation.user_id == current_user.id).count()
  52. return render_template(
  53. "message/inbox.html", conversations=conversations, message_count=message_count
  54. )
  55. class ViewConversation(MethodView):
  56. decorators = [login_required]
  57. form = MessageForm
  58. def get(self, conversation_id):
  59. conversation = Conversation.query.filter_by(
  60. id=conversation_id, user_id=current_user.id
  61. ).first_or_404()
  62. if conversation.unread:
  63. conversation.unread = False
  64. current_user.invalidate_cache(permissions=False)
  65. conversation.save()
  66. form = self.form()
  67. return render_template("message/conversation.html", conversation=conversation, form=form)
  68. @requires_message_box_space
  69. def post(self, conversation_id):
  70. conversation = Conversation.query.filter_by(
  71. id=conversation_id, user_id=current_user.id
  72. ).first_or_404()
  73. form = self.form()
  74. if form.validate_on_submit():
  75. to_user_id = None
  76. # If the current_user is the user who recieved the message
  77. # then we have to change the id's a bit.
  78. if current_user.id == conversation.to_user_id:
  79. to_user_id = conversation.from_user_id
  80. else:
  81. to_user_id = conversation.to_user_id
  82. form.save(conversation=conversation, user_id=current_user.id)
  83. # save the message in the recievers conversation
  84. old_conv = conversation
  85. conversation = Conversation.query.filter(
  86. Conversation.user_id == to_user_id,
  87. Conversation.shared_id == conversation.shared_id
  88. ).first()
  89. # user deleted the conversation, start a new conversation with just
  90. # the recieving message
  91. if conversation is None:
  92. conversation = Conversation(
  93. subject=old_conv.subject,
  94. from_user_id=current_user.id,
  95. to_user=to_user_id,
  96. user_id=to_user_id,
  97. shared_id=old_conv.shared_id
  98. )
  99. conversation.save()
  100. form.save(conversation=conversation, user_id=current_user.id, unread=True)
  101. conversation.to_user.invalidate_cache(permissions=False)
  102. return redirect(url_for("message.view_conversation", conversation_id=old_conv.id))
  103. return render_template("message/conversation.html", conversation=conversation, form=form)
  104. class NewConversation(MethodView):
  105. decorators = [requires_message_box_space, login_required]
  106. form = ConversationForm
  107. def get(self):
  108. form = self.form()
  109. form.to_user.data = request.args.get("to_user")
  110. return render_template("message/message_form.html", form=form, title=_("Compose Message"))
  111. def post(self):
  112. form = self.form()
  113. if "save_message" in request.form and form.validate():
  114. to_user = User.query.filter_by(username=form.to_user.data).first()
  115. shared_id = uuid.uuid4()
  116. form.save(
  117. from_user=current_user.id,
  118. to_user=to_user.id,
  119. user_id=current_user.id,
  120. unread=False,
  121. as_draft=True,
  122. shared_id=shared_id
  123. )
  124. flash(_("Message saved."), "success")
  125. return redirect(url_for("message.drafts"))
  126. if "send_message" in request.form and form.validate():
  127. to_user = User.query.filter_by(username=form.to_user.data).first()
  128. # this is the shared id between conversations because the messages
  129. # are saved on both ends
  130. shared_id = uuid.uuid4()
  131. # Save the message in the current users inbox
  132. form.save(
  133. from_user=current_user.id,
  134. to_user=to_user.id,
  135. user_id=current_user.id,
  136. unread=False,
  137. shared_id=shared_id
  138. )
  139. # Save the message in the recievers inbox
  140. form.save(
  141. from_user=current_user.id,
  142. to_user=to_user.id,
  143. user_id=to_user.id,
  144. unread=True,
  145. shared_id=shared_id
  146. )
  147. to_user.invalidate_cache(permissions=False)
  148. flash(_("Message sent."), "success")
  149. return redirect(url_for("message.sent"))
  150. return render_template("message/message_form.html", form=form, title=_("Compose Message"))
  151. class EditConversation(MethodView):
  152. decorators = [login_required]
  153. form = ConversationForm
  154. def get(self, conversation_id):
  155. conversation = Conversation.query.filter_by(
  156. id=conversation_id, user_id=current_user.id
  157. ).first_or_404()
  158. if not conversation.draft:
  159. flash(_("You cannot edit a sent message."), "danger")
  160. return redirect(url_for("message.inbox"))
  161. form = self.form()
  162. form.to_user.data = conversation.to_user.username
  163. form.subject.data = conversation.subject
  164. form.message.data = conversation.first_message.message
  165. return render_template("message/message_form.html", form=form, title=_("Edit Message"))
  166. def post(self, conversation_id):
  167. conversation = Conversation.query.filter_by(
  168. id=conversation_id, user_id=current_user.id
  169. ).first_or_404()
  170. if not conversation.draft:
  171. flash(_("You cannot edit a sent message."), "danger")
  172. return redirect(url_for("message.inbox"))
  173. form = self.form()
  174. if request.method == "POST":
  175. if "save_message" in request.form:
  176. to_user = User.query.filter_by(username=form.to_user.data).first()
  177. conversation.draft = True
  178. conversation.to_user_id = to_user.id
  179. conversation.first_message.message = form.message.data
  180. conversation.save()
  181. flash(_("Message saved."), "success")
  182. return redirect(url_for("message.drafts"))
  183. if "send_message" in request.form and form.validate():
  184. to_user = User.query.filter_by(username=form.to_user.data).first()
  185. # Save the message in the recievers inbox
  186. form.save(
  187. from_user=current_user.id,
  188. to_user=to_user.id,
  189. user_id=to_user.id,
  190. unread=True,
  191. shared_id=conversation.shared_id
  192. )
  193. # Move the message from ``Drafts`` to ``Sent``.
  194. conversation.draft = False
  195. conversation.to_user = to_user
  196. conversation.date_created = time_utcnow()
  197. conversation.save()
  198. flash(_("Message sent."), "success")
  199. return redirect(url_for("message.sent"))
  200. else:
  201. form.to_user.data = conversation.to_user.username
  202. form.subject.data = conversation.subject
  203. form.message.data = conversation.first_message.message
  204. return render_template("message/message_form.html", form=form, title=_("Edit Message"))
  205. class RawMessage(MethodView):
  206. decorators = [login_required]
  207. def get(self, message_id):
  208. message = Message.query.filter_by(id=message_id).first_or_404()
  209. # abort if the message was not the current_user's one or the one of the
  210. # recieved ones
  211. if not (message.conversation.from_user_id == current_user.id
  212. or message.conversation.to_user_id == current_user.id):
  213. abort(404)
  214. return format_quote(username=message.user.username, content=message.message)
  215. class MoveConversation(MethodView):
  216. decorators = [login_required]
  217. def post(self, conversation_id):
  218. conversation = Conversation.query.filter_by(
  219. id=conversation_id, user_id=current_user.id
  220. ).first_or_404()
  221. conversation.trash = True
  222. conversation.save()
  223. return redirect(url_for("message.inbox"))
  224. class RestoreConversation(MethodView):
  225. decorators = [login_required]
  226. def post(self, conversation_id):
  227. conversation = Conversation.query.filter_by(
  228. id=conversation_id, user_id=current_user.id
  229. ).first_or_404()
  230. conversation.trash = False
  231. conversation.save()
  232. return redirect(url_for("message.inbox"))
  233. class DeleteConversation(MethodView):
  234. decorators = [login_required]
  235. def post(self, conversation_id):
  236. conversation = Conversation.query.filter_by(
  237. id=conversation_id, user_id=current_user.id
  238. ).first_or_404()
  239. conversation.delete()
  240. return redirect(url_for("message.inbox"))
  241. class SentMessages(MethodView):
  242. decorators = [login_required]
  243. def get(self):
  244. page = request.args.get('page', 1, type=int)
  245. conversations = Conversation.query. \
  246. filter(
  247. Conversation.user_id == current_user.id,
  248. Conversation.draft == False,
  249. Conversation.trash == False,
  250. db.not_(Conversation.to_user_id == current_user.id)
  251. ).\
  252. order_by(Conversation.date_modified.desc()). \
  253. paginate(page, flaskbb_config['TOPICS_PER_PAGE'], False)
  254. message_count = Conversation.query. \
  255. filter(Conversation.user_id == current_user.id).\
  256. count()
  257. return render_template(
  258. "message/sent.html", conversations=conversations, message_count=message_count
  259. )
  260. class DraftMessages(MethodView):
  261. decorators = [login_required]
  262. def get(self):
  263. page = request.args.get('page', 1, type=int)
  264. conversations = Conversation.query. \
  265. filter(
  266. Conversation.user_id == current_user.id,
  267. Conversation.draft == True,
  268. Conversation.trash == False
  269. ).\
  270. order_by(Conversation.date_modified.desc()). \
  271. paginate(page, flaskbb_config['TOPICS_PER_PAGE'], False)
  272. message_count = Conversation.query. \
  273. filter(Conversation.user_id == current_user.id).\
  274. count()
  275. return render_template(
  276. "message/drafts.html", conversations=conversations, message_count=message_count
  277. )
  278. class TrashedMessages(MethodView):
  279. decorators = [login_required]
  280. def get(self):
  281. page = request.args.get('page', 1, type=int)
  282. conversations = Conversation.query. \
  283. filter(
  284. Conversation.user_id == current_user.id,
  285. Conversation.trash == True,
  286. ).\
  287. order_by(Conversation.date_modified.desc()). \
  288. paginate(page, flaskbb_config['TOPICS_PER_PAGE'], False)
  289. message_count = Conversation.query. \
  290. filter(Conversation.user_id == current_user.id).\
  291. count()
  292. return render_template(
  293. "message/trash.html", conversations=conversations, message_count=message_count
  294. )
  295. register_view(message, routes=['/drafts'], view_func=DraftMessages.as_view('drafts'))
  296. register_view(message, routes=['/', '/inbox'], view_func=Inbox.as_view('inbox'))
  297. register_view(
  298. message,
  299. routes=['/<int:conversation_id>/delete'],
  300. view_func=DeleteConversation.as_view('delete_conversation')
  301. )
  302. register_view(
  303. message,
  304. routes=["/<int:conversation_id>/edit"],
  305. view_func=EditConversation.as_view('edit_conversation')
  306. )
  307. register_view(
  308. message,
  309. routes=['/<int:conversation_id>/move'],
  310. view_func=MoveConversation.as_view('move_conversation')
  311. )
  312. register_view(
  313. message,
  314. routes=['/<int:conversation_id>/restore'],
  315. view_func=RestoreConversation.as_view('restore_conversation')
  316. )
  317. register_view(
  318. message,
  319. routes=["/<int:conversation_id>/view"],
  320. view_func=ViewConversation.as_view('view_conversation')
  321. )
  322. register_view(
  323. message, routes=['/message/<int:message_id>/raw'], view_func=RawMessage.as_view('raw_message')
  324. )
  325. register_view(message, routes=["/new"], view_func=NewConversation.as_view('new_conversation'))
  326. register_view(message, routes=['/sent'], view_func=SentMessages.as_view('sent'))
  327. register_view(message, routes=['/trash'], view_func=TrashedMessages.as_view('trash'))