views.py 19 KB

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