views.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.forum.views
  4. ~~~~~~~~~~~~~~~~~~~~
  5. This module handles the forum logic like creating and viewing
  6. topics and posts.
  7. :copyright: (c) 2014 by the FlaskBB Team.
  8. :license: BSD, see LICENSE for more details.
  9. """
  10. import datetime
  11. import math
  12. from flask import (Blueprint, redirect, url_for, current_app,
  13. request, flash)
  14. from flask.ext.login import login_required, current_user
  15. from flaskbb.extensions import db
  16. from flaskbb.utils.helpers import get_online_users, time_diff, render_template
  17. from flaskbb.utils.permissions import (can_post_reply, can_post_topic,
  18. can_delete_topic, can_delete_post,
  19. can_edit_post, can_lock_topic,
  20. can_move_topic)
  21. from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
  22. TopicsRead)
  23. from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
  24. ReportForm, UserSearchForm, SearchPageForm)
  25. from flaskbb.user.models import User
  26. from flaskbb.plugins import hooks
  27. forum = Blueprint("forum", __name__)
  28. @forum.route("/")
  29. def index():
  30. categories = Category.get_all(user=current_user)
  31. # Fetch a few stats about the forum
  32. user_count = User.query.count()
  33. topic_count = Topic.query.count()
  34. post_count = Post.query.count()
  35. newest_user = User.query.order_by(User.id.desc()).first()
  36. current_app.logger.debug("Runnnig beforeIndex hook...")
  37. hooks.runHook(hooks.registered.beforeIndex)
  38. # Check if we use redis or not
  39. if not current_app.config["REDIS_ENABLED"]:
  40. online_users = User.query.filter(User.lastseen >= time_diff()).count()
  41. # Because we do not have server side sessions, we cannot check if there
  42. # are online guests
  43. online_guests = None
  44. else:
  45. online_users = len(get_online_users())
  46. online_guests = len(get_online_users(guest=True))
  47. return render_template("forum/index.html",
  48. categories=categories,
  49. user_count=user_count,
  50. topic_count=topic_count,
  51. post_count=post_count,
  52. newest_user=newest_user,
  53. online_users=online_users,
  54. online_guests=online_guests)
  55. @forum.route("/category/<int:category_id>")
  56. @forum.route("/category/<int:category_id>-<slug>")
  57. def view_category(category_id, slug=None):
  58. category, forums = Category.\
  59. get_forums(category_id=category_id, user=current_user)
  60. return render_template("forum/category.html", forums=forums,
  61. category=category)
  62. @forum.route("/forum/<int:forum_id>")
  63. @forum.route("/forum/<int:forum_id>-<slug>")
  64. def view_forum(forum_id, slug=None):
  65. page = request.args.get('page', 1, type=int)
  66. forum, forumsread = Forum.get_forum(forum_id=forum_id, user=current_user)
  67. topics = Forum.get_topics(forum_id=forum.id, user=current_user, page=page,
  68. per_page=current_app.config["TOPICS_PER_PAGE"])
  69. return render_template("forum/forum.html", forum=forum, topics=topics,
  70. forumsread=forumsread,)
  71. @forum.route("/topic/<int:topic_id>", methods=["POST", "GET"])
  72. @forum.route("/topic/<int:topic_id>-<slug>", methods=["POST", "GET"])
  73. def view_topic(topic_id, slug=None):
  74. page = request.args.get('page', 1, type=int)
  75. topic = Topic.query.filter_by(id=topic_id).first()
  76. posts = Post.query.filter_by(topic_id=topic.id).\
  77. paginate(page, current_app.config['POSTS_PER_PAGE'], False)
  78. # Count the topic views
  79. topic.views += 1
  80. # Update the topicsread status if the user hasn't read it
  81. forumsread = None
  82. if current_user.is_authenticated():
  83. forumsread = ForumsRead.query.\
  84. filter_by(user_id=current_user.id,
  85. forum_id=topic.forum.id).first()
  86. topic.update_read(current_user, topic.forum, forumsread)
  87. topic.save()
  88. form = None
  89. if not topic.locked \
  90. and not topic.forum.locked \
  91. and can_post_reply(user=current_user,
  92. forum=topic.forum):
  93. form = QuickreplyForm()
  94. if form.validate_on_submit():
  95. post = form.save(current_user, topic)
  96. return view_post(post.id)
  97. return render_template("forum/topic.html", topic=topic, posts=posts,
  98. last_seen=time_diff(), form=form)
  99. @forum.route("/post/<int:post_id>")
  100. def view_post(post_id):
  101. post = Post.query.filter_by(id=post_id).first_or_404()
  102. count = post.topic.post_count
  103. page = math.ceil(count / current_app.config["POSTS_PER_PAGE"])
  104. if count > 10:
  105. page += 1
  106. else:
  107. page = 1
  108. return redirect(post.topic.url + "?page=%d#pid%s" % (page, post.id))
  109. @forum.route("/<int:forum_id>/topic/new", methods=["POST", "GET"])
  110. @forum.route("/<int:forum_id>-<slug>/topic/new", methods=["POST", "GET"])
  111. @login_required
  112. def new_topic(forum_id, slug=None):
  113. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  114. if forum.locked:
  115. flash("This forum is locked; you cannot submit new topics or posts.",
  116. "danger")
  117. return redirect(forum.url)
  118. if not can_post_topic(user=current_user, forum=forum):
  119. flash("You do not have the permissions to create a new topic.",
  120. "danger")
  121. return redirect(forum.url)
  122. form = NewTopicForm()
  123. if form.validate_on_submit():
  124. topic = form.save(current_user, forum)
  125. # redirect to the new topic
  126. return redirect(url_for('forum.view_topic', topic_id=topic.id))
  127. return render_template("forum/new_topic.html", forum=forum, form=form)
  128. @forum.route("/topic/<int:topic_id>/delete")
  129. @forum.route("/topic/<int:topic_id>-<slug>/delete")
  130. @login_required
  131. def delete_topic(topic_id, slug=None):
  132. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  133. if not can_delete_topic(user=current_user, forum=topic.forum,
  134. post_user_id=topic.first_post.user_id):
  135. flash("You do not have the permissions to delete the topic", "danger")
  136. return redirect(topic.forum.url)
  137. involved_users = User.query.filter(Post.topic_id == topic.id,
  138. User.id == Post.user_id).all()
  139. topic.delete(users=involved_users)
  140. return redirect(url_for("forum.view_forum", forum_id=topic.forum_id))
  141. @forum.route("/topic/<int:topic_id>/lock")
  142. @forum.route("/topic/<int:topic_id>-<slug>/lock")
  143. @login_required
  144. def lock_topic(topic_id, slug=None):
  145. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  146. if not can_lock_topic(user=current_user, forum=topic.forum):
  147. flash("Yo do not have the permissions to lock this topic", "danger")
  148. return redirect(topic.url)
  149. topic.locked = True
  150. topic.save()
  151. return redirect(topic.url)
  152. @forum.route("/topic/<int:topic_id>/unlock")
  153. @forum.route("/topic/<int:topic_id>-<slug>/unlock")
  154. @login_required
  155. def unlock_topic(topic_id, slug=None):
  156. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  157. # Unlock is basically the same as lock
  158. if not can_lock_topic(user=current_user, forum=topic.forum):
  159. flash("Yo do not have the permissions to unlock this topic", "danger")
  160. return redirect(topic.url)
  161. topic.locked = False
  162. topic.save()
  163. return redirect(topic.url)
  164. @forum.route("/topic/<int:topic_id>/move/<int:forum_id>")
  165. @forum.route("/topic/<int:topic_id>-<topic_slug>/move/<int:forum_id>-<forum_slug>")
  166. @login_required
  167. def move_topic(topic_id, forum_id, topic_slug=None, forum_slug=None):
  168. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  169. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  170. if not topic.move(forum):
  171. flash("Could not move the topic to forum %s" % forum.title, "danger")
  172. return redirect(topic.url)
  173. flash("Topic was moved to forum %s" % forum.title, "success")
  174. return redirect(topic.url)
  175. @forum.route("/topic/<int:old_id>/merge/<int:new_id>")
  176. @forum.route("/topic/<int:old_id>-<old_slug>/merge/<int:new_id>-<new_slug>")
  177. @login_required
  178. def merge_topic(old_id, new_id, old_slug=None, new_slug=None):
  179. old_topic = Topic.query.filter_by(id=old_id).first_or_404()
  180. new_topic = Topic.query.filter_by(id=new_id).first_or_404()
  181. if not old_topic.merge(new_topic):
  182. flash("Could not merge the topic.", "danger")
  183. return redirect(old_topic.url)
  184. flash("Topic succesfully merged.", "success")
  185. return redirect(new_topic.url)
  186. @forum.route("/topic/<int:topic_id>/post/new", methods=["POST", "GET"])
  187. @forum.route("/topic/<int:topic_id>-<slug>/post/new", methods=["POST", "GET"])
  188. @login_required
  189. def new_post(topic_id, slug=None):
  190. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  191. if topic.forum.locked:
  192. flash("This forum is locked; you cannot submit new topics or posts.",
  193. "danger")
  194. return redirect(topic.forum.url)
  195. if topic.locked:
  196. flash("The topic is locked.", "danger")
  197. return redirect(topic.forum.url)
  198. if not can_post_reply(user=current_user, forum=topic.forum):
  199. flash("You do not have the permissions to delete the topic", "danger")
  200. return redirect(topic.forum.url)
  201. form = ReplyForm()
  202. if form.validate_on_submit():
  203. post = form.save(current_user, topic)
  204. return view_post(post.id)
  205. return render_template("forum/new_post.html", topic=topic, form=form)
  206. @forum.route("/topic/<int:topic_id>/post/<int:post_id>/reply", methods=["POST", "GET"])
  207. @login_required
  208. def reply_post(topic_id, post_id):
  209. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  210. post = Post.query.filter_by(id=post_id).first_or_404()
  211. if post.topic.forum.locked:
  212. flash("This forum is locked; you cannot submit new topics or posts.",
  213. "danger")
  214. return redirect(post.topic.forum.url)
  215. if post.topic.locked:
  216. flash("The topic is locked.", "danger")
  217. return redirect(post.topic.forum.url)
  218. if not can_post_reply(user=current_user, forum=topic.forum):
  219. flash("You do not have the permissions to post in this topic", "danger")
  220. return redirect(topic.forum.url)
  221. form = ReplyForm()
  222. if form.validate_on_submit():
  223. form.save(current_user, topic)
  224. return redirect(post.topic.url)
  225. else:
  226. form.content.data = '[quote]{}[/quote]'.format(post.content)
  227. return render_template("forum/new_post.html", topic=post.topic, form=form)
  228. @forum.route("/post/<int:post_id>/edit", methods=["POST", "GET"])
  229. @login_required
  230. def edit_post(post_id):
  231. post = Post.query.filter_by(id=post_id).first_or_404()
  232. if post.topic.forum.locked:
  233. flash("This forum is locked; you cannot submit new topics or posts.",
  234. "danger")
  235. return redirect(post.topic.forum.url)
  236. if post.topic.locked:
  237. flash("The topic is locked.", "danger")
  238. return redirect(post.topic.forum.url)
  239. if not can_edit_post(user=current_user, forum=post.topic.forum,
  240. post_user_id=post.user_id):
  241. flash("You do not have the permissions to edit this post", "danger")
  242. return redirect(post.topic.url)
  243. form = ReplyForm()
  244. if form.validate_on_submit():
  245. form.populate_obj(post)
  246. post.date_modified = datetime.datetime.utcnow()
  247. post.modified_by = current_user.username
  248. post.save()
  249. return redirect(post.topic.url)
  250. else:
  251. form.content.data = post.content
  252. return render_template("forum/new_post.html", topic=post.topic, form=form)
  253. @forum.route("/post/<int:post_id>/delete")
  254. @login_required
  255. def delete_post(post_id, slug=None):
  256. post = Post.query.filter_by(id=post_id).first_or_404()
  257. if not can_delete_post(user=current_user, forum=post.topic.forum,
  258. post_user_id=post.user_id):
  259. flash("You do not have the permissions to edit this post", "danger")
  260. return redirect(post.topic.url)
  261. post.delete()
  262. # If the post was the first post in the topic, redirect to the forums
  263. if post.first_post:
  264. return redirect(post.topic.forum.url)
  265. return redirect(post.topic.url)
  266. @forum.route("/post/<int:post_id>/report", methods=["GET", "POST"])
  267. @login_required
  268. def report_post(post_id):
  269. post = Post.query.filter_by(id=post_id).first_or_404()
  270. form = ReportForm()
  271. if form.validate_on_submit():
  272. form.save(current_user, post)
  273. flash("Thanks for reporting!", "success")
  274. return render_template("forum/report_post.html", form=form)
  275. @forum.route("/markread")
  276. @forum.route("/<int:forum_id>/markread")
  277. @forum.route("/<int:forum_id>-<slug>/markread")
  278. def markread(forum_id=None, slug=None):
  279. if not current_user.is_authenticated():
  280. flash("You need to be logged in for that feature.", "danger")
  281. return redirect(url_for("forum.index"))
  282. # Mark a single forum as read
  283. if forum_id:
  284. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  285. forumsread = ForumsRead.query.filter_by(user_id=current_user.id,
  286. forum_id=forum.id).first()
  287. TopicsRead.query.filter_by(user_id=current_user.id,
  288. forum_id=forum.id).delete()
  289. if not forumsread:
  290. forumsread = ForumsRead()
  291. forumsread.user_id = current_user.id
  292. forumsread.forum_id = forum.id
  293. forumsread.last_read = datetime.datetime.utcnow()
  294. forumsread.cleared = datetime.datetime.utcnow()
  295. db.session.add(forumsread)
  296. db.session.commit()
  297. return redirect(forum.url)
  298. # Mark all forums as read
  299. ForumsRead.query.filter_by(user_id=current_user.id).delete()
  300. TopicsRead.query.filter_by(user_id=current_user.id).delete()
  301. forums = Forum.query.all()
  302. forumsread_list = []
  303. for forum in forums:
  304. forumsread = ForumsRead()
  305. forumsread.user_id = current_user.id
  306. forumsread.forum_id = forum.id
  307. forumsread.last_read = datetime.datetime.utcnow()
  308. forumsread.cleared = datetime.datetime.utcnow()
  309. forumsread_list.append(forumsread)
  310. db.session.add_all(forumsread_list)
  311. db.session.commit()
  312. return redirect(url_for("forum.index"))
  313. @forum.route("/who_is_online")
  314. def who_is_online():
  315. if current_app.config['REDIS_ENABLED']:
  316. online_users = get_online_users()
  317. else:
  318. online_users = User.query.filter(User.lastseen >= time_diff()).all()
  319. return render_template("forum/online_users.html",
  320. online_users=online_users)
  321. @forum.route("/memberlist", methods=['GET', 'POST'])
  322. def memberlist():
  323. page = request.args.get('page', 1, type=int)
  324. search_form = UserSearchForm()
  325. if search_form.validate():
  326. users = search_form.get_results().\
  327. paginate(page, current_app.config['USERS_PER_PAGE'], False)
  328. return render_template("forum/memberlist.html", users=users,
  329. search_form=search_form)
  330. else:
  331. users = User.query.\
  332. paginate(page, current_app.config['USERS_PER_PAGE'], False)
  333. return render_template("forum/memberlist.html", users=users,
  334. search_form=search_form)
  335. @forum.route("/topictracker")
  336. def topictracker():
  337. page = request.args.get("page", 1, type=int)
  338. topics = current_user.tracked_topics.\
  339. outerjoin(TopicsRead,
  340. db.and_(TopicsRead.topic_id == Topic.id,
  341. TopicsRead.user_id == current_user.id)).\
  342. add_entity(TopicsRead).\
  343. order_by(Post.id.desc()).\
  344. paginate(page, current_app.config['TOPICS_PER_PAGE'], True)
  345. return render_template("forum/topictracker.html", topics=topics)
  346. @forum.route("/topictracker/<topic_id>/add")
  347. @forum.route("/topictracker/<topic_id>-<slug>/add")
  348. def track_topic(topic_id, slug=None):
  349. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  350. current_user.track_topic(topic)
  351. current_user.save()
  352. return redirect(topic.url)
  353. @forum.route("/topictracker/<topic_id>/delete")
  354. @forum.route("/topictracker/<topic_id>-<slug>/delete")
  355. def untrack_topic(topic_id, slug=None):
  356. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  357. current_user.untrack_topic(topic)
  358. current_user.save()
  359. return redirect(topic.url)
  360. @forum.route("/search", methods=['GET', 'POST'])
  361. def search():
  362. form = SearchPageForm()
  363. if form.validate_on_submit():
  364. result = form.get_results()
  365. return render_template('forum/search_result.html', form=form,
  366. result=result)
  367. return render_template('forum/search_form.html', form=form)