views.py 17 KB

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