views.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  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 logging
  11. import math
  12. from flask import (Blueprint, abort, current_app, flash, redirect, request,
  13. url_for)
  14. from flask.views import MethodView
  15. from flask_allows import And, Permission
  16. from flask_babelplus import gettext as _
  17. from flask_login import current_user, login_required
  18. from sqlalchemy import asc, desc
  19. from flaskbb.extensions import allows, db
  20. from flaskbb.forum.forms import (NewTopicForm, QuickreplyForm, ReplyForm,
  21. ReportForm, SearchPageForm, UserSearchForm)
  22. from flaskbb.forum.models import (Category, Forum, ForumsRead, Post, Topic,
  23. TopicsRead)
  24. from flaskbb.user.models import User
  25. from flaskbb.utils.helpers import (do_topic_action, format_quote,
  26. get_online_users, real, register_view,
  27. render_template, time_diff, time_utcnow)
  28. from flaskbb.utils.requirements import (CanAccessForum, CanAccessTopic,
  29. CanDeletePost, CanDeleteTopic,
  30. CanEditPost, CanPostReply,
  31. CanPostTopic, Has,
  32. IsAtleastModeratorInForum)
  33. from flaskbb.utils.settings import flaskbb_config
  34. logger = logging.getLogger(__name__)
  35. forum = Blueprint("forum", __name__)
  36. class ForumIndex(MethodView):
  37. def get(self):
  38. categories = Category.get_all(user=real(current_user))
  39. # Fetch a few stats about the forum
  40. user_count = User.query.count()
  41. topic_count = Topic.query.count()
  42. post_count = Post.query.count()
  43. newest_user = User.query.order_by(User.id.desc()).first()
  44. # Check if we use redis or not
  45. if not current_app.config['REDIS_ENABLED']:
  46. online_users = User.query.filter(User.lastseen >= time_diff()).count()
  47. # Because we do not have server side sessions, we cannot check if there
  48. # are online guests
  49. online_guests = None
  50. else:
  51. online_users = len(get_online_users())
  52. online_guests = len(get_online_users(guest=True))
  53. return render_template(
  54. 'forum/index.html',
  55. categories=categories,
  56. user_count=user_count,
  57. topic_count=topic_count,
  58. post_count=post_count,
  59. newest_user=newest_user,
  60. online_users=online_users,
  61. online_guests=online_guests
  62. )
  63. class ViewCategory(MethodView):
  64. def get(self, category_id, slug=None):
  65. category, forums = Category.get_forums(category_id=category_id, user=real(current_user))
  66. return render_template('forum/category.html', forums=forums, category=category)
  67. class ViewForum(MethodView):
  68. decorators = [allows.requires(CanAccessForum())]
  69. def get(self, forum_id, slug=None):
  70. page = request.args.get('page', 1, type=int)
  71. forum_instance, forumsread = Forum.get_forum(forum_id=forum_id, user=real(current_user))
  72. if forum_instance.external:
  73. return redirect(forum_instance.external)
  74. topics = Forum.get_topics(
  75. forum_id=forum_instance.id,
  76. user=real(current_user),
  77. page=page,
  78. per_page=flaskbb_config['TOPICS_PER_PAGE']
  79. )
  80. return render_template(
  81. 'forum/forum.html',
  82. forum=forum_instance,
  83. topics=topics,
  84. forumsread=forumsread,
  85. )
  86. class ViewPost(MethodView):
  87. def get(self, post_id):
  88. '''Redirects to a post in a topic.'''
  89. post = Post.query.filter_by(id=post_id).first_or_404()
  90. post_in_topic = Post.query.filter(Post.topic_id == post.topic_id,
  91. Post.id <= post_id).order_by(Post.id.asc()).count()
  92. page = int(math.ceil(post_in_topic / float(flaskbb_config['POSTS_PER_PAGE'])))
  93. return redirect(
  94. url_for(
  95. 'forum.view_topic',
  96. topic_id=post.topic.id,
  97. slug=post.topic.slug,
  98. page=page,
  99. _anchor='pid{}'.format(post.id)
  100. )
  101. )
  102. class ViewTopic(MethodView):
  103. decorators = [allows.requires(CanAccessTopic())]
  104. def get(self, topic_id, slug=None):
  105. page = request.args.get('page', 1, type=int)
  106. # Fetch some information about the topic
  107. topic = Topic.get_topic(topic_id=topic_id, user=real(current_user))
  108. # Count the topic views
  109. topic.views += 1
  110. topic.save()
  111. # Update the topicsread status if the user hasn't read it
  112. forumsread = None
  113. if current_user.is_authenticated:
  114. forumsread = ForumsRead.query.\
  115. filter_by(user_id=current_user.id,
  116. forum_id=topic.forum_id).first()
  117. topic.update_read(real(current_user), topic.forum, forumsread)
  118. # fetch the posts in the topic
  119. posts = Post.query.\
  120. outerjoin(User, Post.user_id == User.id).\
  121. filter(Post.topic_id == topic.id).\
  122. add_entity(User).\
  123. order_by(Post.id.asc()).\
  124. paginate(page, flaskbb_config['POSTS_PER_PAGE'], False)
  125. # Abort if there are no posts on this page
  126. if len(posts.items) == 0:
  127. abort(404)
  128. return render_template(
  129. 'forum/topic.html', topic=topic, posts=posts, last_seen=time_diff(), form=self.form()
  130. )
  131. @allows.requires(CanPostReply)
  132. def post(self, topic_id, slug=None):
  133. topic = Topic.get_topic(topic_id=topic_id, user=real(current_user))
  134. form = self.form()
  135. if not form:
  136. flash(_('Cannot post reply'), 'warning')
  137. return redirect('forum.view_topic', topic_id=topic_id, slug=slug)
  138. elif form.validate_on_submit():
  139. post = form.save(real(current_user), topic)
  140. return redirect(url_for('forum.view_post', post_id=post.id))
  141. else:
  142. for e in form.errors.get('content', []):
  143. flash(e, 'danger')
  144. return redirect(url_for('forum.view_topic', topic_id=topic_id, slug=slug))
  145. def form(self):
  146. if Permission(CanPostReply):
  147. return QuickreplyForm()
  148. return None
  149. class NewTopic(MethodView):
  150. decorators = [login_required]
  151. form = NewTopicForm
  152. def get(self, forum_id, slug=None):
  153. forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
  154. return render_template('forum/new_topic.html', forum=forum_instance, form=self.form())
  155. @allows.requires(CanPostTopic)
  156. def post(self, forum_id, slug=None):
  157. forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
  158. form = self.form()
  159. if 'preview' in request.form and form.validate():
  160. return render_template(
  161. 'forum/new_topic.html', forum=forum_instance, form=form, preview=form.content.data
  162. )
  163. elif 'submit' in request.form and form.validate():
  164. topic = form.save(real(current_user), forum_instance)
  165. # redirect to the new topic
  166. return redirect(url_for('forum.view_topic', topic_id=topic.id))
  167. else:
  168. return render_template('forum/new_topic.html', forum=forum_instance, form=form)
  169. class ManageForum(MethodView):
  170. decorators = [allows.requires(IsAtleastModeratorInForum()), login_required]
  171. def get(self, forum_id, slug=None):
  172. forum_instance, forumsread = Forum.get_forum(forum_id=forum_id, user=real(current_user))
  173. if forum_instance.external:
  174. return redirect(forum_instance.external)
  175. # remove the current forum from the select field (move).
  176. available_forums = Forum.query.order_by(Forum.position).all()
  177. available_forums.remove(forum_instance)
  178. page = request.args.get('page', 1, type=int)
  179. topics = Forum.get_topics(
  180. forum_id=forum_instance.id,
  181. user=real(current_user),
  182. page=page,
  183. per_page=flaskbb_config['TOPICS_PER_PAGE']
  184. )
  185. return render_template(
  186. 'forum/edit_forum.html',
  187. forum=forum_instance,
  188. topics=topics,
  189. available_forums=available_forums,
  190. forumsread=forumsread,
  191. )
  192. def post(self, forum_id, slug=None):
  193. forum_instance, __ = Forum.get_forum(forum_id=forum_id, user=real(current_user))
  194. mod_forum_url = url_for(
  195. 'forum.manage_forum', forum_id=forum_instance.id, slug=forum_instance.slug
  196. )
  197. ids = request.form.getlist('rowid')
  198. tmp_topics = Topic.query.filter(Topic.id.in_(ids)).all()
  199. if not len(tmp_topics) > 0:
  200. flash(
  201. _('In order to perform this action you have to select at '
  202. 'least one topic.'), 'danger'
  203. )
  204. return redirect(mod_forum_url)
  205. # locking/unlocking
  206. if 'lock' in request.form:
  207. changed = do_topic_action(
  208. topics=tmp_topics, user=real(current_user), action='locked', reverse=False
  209. )
  210. flash(_('%(count)s topics locked.', count=changed), 'success')
  211. return redirect(mod_forum_url)
  212. elif 'unlock' in request.form:
  213. changed = do_topic_action(
  214. topics=tmp_topics, user=real(current_user), action='locked', reverse=True
  215. )
  216. flash(_('%(count)s topics unlocked.', count=changed), 'success')
  217. return redirect(mod_forum_url)
  218. # highlighting/trivializing
  219. elif 'highlight' in request.form:
  220. changed = do_topic_action(
  221. topics=tmp_topics, user=real(current_user), action='important', reverse=False
  222. )
  223. flash(_('%(count)s topics highlighted.', count=changed), 'success')
  224. return redirect(mod_forum_url)
  225. elif 'trivialize' in request.form:
  226. changed = do_topic_action(
  227. topics=tmp_topics, user=real(current_user), action='important', reverse=True
  228. )
  229. flash(_('%(count)s topics trivialized.', count=changed), 'success')
  230. return redirect(mod_forum_url)
  231. # deleting
  232. elif 'delete' in request.form:
  233. changed = do_topic_action(
  234. topics=tmp_topics, user=real(current_user), action='delete', reverse=False
  235. )
  236. flash(_('%(count)s topics deleted.', count=changed), 'success')
  237. return redirect(mod_forum_url)
  238. # moving
  239. elif 'move' in request.form:
  240. new_forum_id = request.form.get('forum')
  241. if not new_forum_id:
  242. flash(_('Please choose a new forum for the topics.'), 'info')
  243. return redirect(mod_forum_url)
  244. new_forum = Forum.query.filter_by(id=new_forum_id).first_or_404()
  245. # check the permission in the current forum and in the new forum
  246. if not Permission(And(IsAtleastModeratorInForum(forum_id=new_forum_id),
  247. IsAtleastModeratorInForum(forum=forum_instance))):
  248. flash(_('You do not have the permissions to move this topic.'), 'danger')
  249. return redirect(mod_forum_url)
  250. if new_forum.move_topics_to(tmp_topics):
  251. flash(_('Topics moved.'), 'success')
  252. else:
  253. flash(_('Failed to move topics.'), 'danger')
  254. return redirect(mod_forum_url)
  255. # hiding/unhiding
  256. elif "hide" in request.form:
  257. changed = do_topic_action(
  258. topics=tmp_topics, user=real(current_user), action="hide", reverse=False
  259. )
  260. flash(_("%(count)s topics hidden.", count=changed), "success")
  261. return redirect(mod_forum_url)
  262. elif "unhide" in request.form:
  263. changed = do_topic_action(
  264. topics=tmp_topics, user=real(current_user), action="unhide", reverse=False
  265. )
  266. flash(_("%(count)s topics unhidden.", count=changed), "success")
  267. return redirect(mod_forum_url)
  268. else:
  269. flash(_('Unknown action requested'), 'danger')
  270. return redirect(mod_forum_url)
  271. class NewPost(MethodView):
  272. decorators = [allows.requires(CanPostReply), login_required]
  273. def get(self, topic_id, slug=None):
  274. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  275. return render_template(
  276. 'forum/new_post.html', topic=topic, form=self.form()
  277. )
  278. def post(self, topic_id, slug=None):
  279. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  280. form = self.form()
  281. if form.validate_on_submit():
  282. if 'preview' in request.form:
  283. return render_template(
  284. 'forum/new_post.html', topic=topic, form=form,
  285. preview=form.content.data
  286. )
  287. else:
  288. post = form.save(real(current_user), topic)
  289. return redirect(url_for('forum.view_post', post_id=post.id))
  290. return render_template('forum/new_post.html', topic=topic, form=form)
  291. def form(self):
  292. current_app.pluggy.hook.flaskbb_form_new_post(form=ReplyForm)
  293. return ReplyForm()
  294. class ReplyPost(MethodView):
  295. decorators = [allows.requires(CanPostReply), login_required]
  296. form = ReplyForm
  297. def get(self, topic_id, post_id):
  298. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  299. return render_template('forum/new_post.html', topic=topic, form=self.form())
  300. def post(self, topic_id, post_id):
  301. form = self.form()
  302. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  303. if form.validate_on_submit():
  304. if 'preview' in request.form:
  305. return render_template(
  306. 'forum/new_post.html', topic=topic, form=form, preview=form.content.data
  307. )
  308. else:
  309. post = form.save(real(current_user), topic)
  310. return redirect(url_for('forum.view_post', post_id=post.id))
  311. else:
  312. form.content.data = format_quote(post.username, post.content)
  313. return render_template('forum/new_post.html', topic=post.topic, form=form)
  314. class EditPost(MethodView):
  315. decorators = [allows.requires(CanEditPost), login_required]
  316. form = ReplyForm
  317. def get(self, post_id):
  318. post = Post.query.filter_by(id=post_id).first_or_404()
  319. form = self.form(obj=post)
  320. return render_template('forum/new_post.html', topic=post.topic, form=form, edit_mode=True)
  321. def post(self, post_id):
  322. post = Post.query.filter_by(id=post_id).first_or_404()
  323. form = self.form(obj=post)
  324. if form.validate_on_submit():
  325. if 'preview' in request.form:
  326. return render_template(
  327. 'forum/new_post.html',
  328. topic=post.topic,
  329. form=form,
  330. preview=form.content.data,
  331. edit_mode=True
  332. )
  333. else:
  334. form.populate_obj(post)
  335. post.date_modified = time_utcnow()
  336. post.modified_by = real(current_user).username
  337. post.save()
  338. return redirect(url_for('forum.view_post', post_id=post.id))
  339. return render_template('forum/new_post.html', topic=post.topic, form=form, edit_mode=True)
  340. class ReportView(MethodView):
  341. decorators = [login_required]
  342. form = ReportForm
  343. def get(self, post_id):
  344. return render_template('forum/report_post.html', form=self.form())
  345. def post(self, post_id):
  346. form = self.form()
  347. if form.validate_on_submit():
  348. post = Post.query.filter_by(id=post_id).first_or_404()
  349. form.save(real(current_user), post)
  350. flash(_('Thanks for reporting.'), 'success')
  351. return render_template('forum/report_post.html', form=form)
  352. class MemberList(MethodView):
  353. form = UserSearchForm
  354. def get(self):
  355. page = request.args.get('page', 1, type=int)
  356. sort_by = request.args.get('sort_by', 'reg_date')
  357. order_by = request.args.get('order_by', 'asc')
  358. if order_by == 'asc':
  359. order_func = asc
  360. else:
  361. order_func = desc
  362. if sort_by == 'reg_date':
  363. sort_obj = User.id
  364. elif sort_by == 'post_count':
  365. sort_obj = User.post_count
  366. else:
  367. sort_obj = User.username
  368. users = User.query.order_by(order_func(sort_obj)).paginate(
  369. page, flaskbb_config['USERS_PER_PAGE'], False
  370. )
  371. return render_template('forum/memberlist.html', users=users, search_form=self.form())
  372. def post(self):
  373. page = request.args.get('page', 1, type=int)
  374. sort_by = request.args.get('sort_by', 'reg_date')
  375. order_by = request.args.get('order_by', 'asc')
  376. if order_by == 'asc':
  377. order_func = asc
  378. else:
  379. order_func = desc
  380. if sort_by == 'reg_date':
  381. sort_obj = User.id
  382. elif sort_by == 'post_count':
  383. sort_obj = User.post_count
  384. else:
  385. sort_obj = User.username
  386. form = self.form()
  387. if form.validate():
  388. users = form.get_results().paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  389. return render_template('forum/memberlist.html', users=users, search_form=form)
  390. users = User.query.order_by(order_func(sort_obj)).paginate(
  391. page, flaskbb_config['USERS_PER_PAGE'], False
  392. )
  393. return render_template('forum/memberlist.html', users=users, search_form=form)
  394. class TopicTracker(MethodView):
  395. decorators = [login_required]
  396. def get(self):
  397. page = request.args.get('page', 1, type=int)
  398. topics = real(current_user).tracked_topics.outerjoin(
  399. TopicsRead,
  400. db.and_(TopicsRead.topic_id == Topic.id, TopicsRead.user_id == real(current_user).id)
  401. ).add_entity(TopicsRead).order_by(Topic.last_updated.desc()).paginate(
  402. page, flaskbb_config['TOPICS_PER_PAGE'], True
  403. )
  404. return render_template('forum/topictracker.html', topics=topics)
  405. def post(self):
  406. topic_ids = request.form.getlist('rowid')
  407. tmp_topics = Topic.query.filter(Topic.id.in_(topic_ids)).all()
  408. for topic in tmp_topics:
  409. real(current_user).untrack_topic(topic)
  410. real(current_user).save()
  411. flash(_('%(topic_count)s topics untracked.', topic_count=len(tmp_topics)), 'success')
  412. return redirect(url_for('forum.topictracker'))
  413. class Search(MethodView):
  414. form = SearchPageForm
  415. def get(self):
  416. return render_template('forum/search_form.html', form=self.form())
  417. def post(self):
  418. form = self.form()
  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)
  423. class DeleteTopic(MethodView):
  424. decorators = [allows.requires(CanDeleteTopic), login_required]
  425. def post(self, topic_id, slug=None):
  426. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  427. involved_users = User.query.filter(Post.topic_id == topic.id,
  428. User.id == Post.user_id).all()
  429. topic.delete(users=involved_users)
  430. return redirect(url_for('forum.view_forum', forum_id=topic.forum_id))
  431. class LockTopic(MethodView):
  432. decorators = [allows.requires(IsAtleastModeratorInForum()), login_required]
  433. def post(self, topic_id, slug=None):
  434. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  435. topic.locked = True
  436. topic.save()
  437. return redirect(topic.url)
  438. class UnlockTopic(MethodView):
  439. decorators = [allows.requires(IsAtleastModeratorInForum()), login_required]
  440. def post(self, topic_id, slug=None):
  441. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  442. topic.locked = False
  443. topic.save()
  444. return redirect(topic.url)
  445. class HighlightTopic(MethodView):
  446. decorators = [allows.requires(IsAtleastModeratorInForum()), login_required]
  447. def post(self, topic_id, slug=None):
  448. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  449. topic.important = True
  450. topic.save()
  451. return redirect(topic.url)
  452. class TrivializeTopic(MethodView):
  453. decorators = [allows.requires(IsAtleastModeratorInForum()), login_required]
  454. def post(self, topic_id=None, slug=None):
  455. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  456. topic.important = False
  457. topic.save()
  458. return redirect(topic.url)
  459. class DeletePost(MethodView):
  460. decorators = [allows.requires(CanDeletePost), login_required]
  461. def post(self, post_id):
  462. post = Post.query.filter_by(id=post_id).first_or_404()
  463. first_post = post.first_post
  464. topic_url = post.topic.url
  465. forum_url = post.topic.forum.url
  466. post.delete()
  467. # If the post was the first post in the topic, redirect to the forums
  468. if first_post:
  469. return redirect(forum_url)
  470. return redirect(topic_url)
  471. class RawPost(MethodView):
  472. decorators = [login_required]
  473. def get(self, post_id):
  474. post = Post.query.filter_by(id=post_id).first_or_404()
  475. return format_quote(username=post.username, content=post.content)
  476. class MarkRead(MethodView):
  477. decorators = [login_required]
  478. def post(self, forum_id=None, slug=None):
  479. # Mark a single forum as read
  480. if forum_id is not None:
  481. forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
  482. forumsread = ForumsRead.query.filter_by(
  483. user_id=real(current_user).id, forum_id=forum_instance.id
  484. ).first()
  485. TopicsRead.query.filter_by(
  486. user_id=real(current_user).id, forum_id=forum_instance.id
  487. ).delete()
  488. if not forumsread:
  489. forumsread = ForumsRead()
  490. forumsread.user = real(current_user)
  491. forumsread.forum = forum_instance
  492. forumsread.last_read = time_utcnow()
  493. forumsread.cleared = time_utcnow()
  494. db.session.add(forumsread)
  495. db.session.commit()
  496. flash(_('Forum %(forum)s marked as read.', forum=forum_instance.title), 'success')
  497. return redirect(forum_instance.url)
  498. # Mark all forums as read
  499. ForumsRead.query.filter_by(user_id=real(current_user).id).delete()
  500. TopicsRead.query.filter_by(user_id=real(current_user).id).delete()
  501. forums = Forum.query.all()
  502. forumsread_list = []
  503. for forum_instance in forums:
  504. forumsread = ForumsRead()
  505. forumsread.user = real(current_user)
  506. forumsread.forum = forum_instance
  507. forumsread.last_read = time_utcnow()
  508. forumsread.cleared = time_utcnow()
  509. forumsread_list.append(forumsread)
  510. db.session.add_all(forumsread_list)
  511. db.session.commit()
  512. flash(_('All forums marked as read.'), 'success')
  513. return redirect(url_for('forum.index'))
  514. class WhoIsOnline(MethodView):
  515. def get(self):
  516. if current_app.config['REDIS_ENABLED']:
  517. online_users = get_online_users()
  518. else:
  519. online_users = User.query.filter(User.lastseen >= time_diff()).all()
  520. return render_template('forum/online_users.html', online_users=online_users)
  521. class TrackTopic(MethodView):
  522. decorators = [login_required]
  523. def post(self, topic_id, slug=None):
  524. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  525. real(current_user).track_topic(topic)
  526. real(current_user).save()
  527. return redirect(topic.url)
  528. class UntrackTopic(MethodView):
  529. decorators = [login_required]
  530. def post(self, topic_id, slug=None):
  531. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  532. real(current_user).untrack_topic(topic)
  533. real(current_user).save()
  534. return redirect(topic.url)
  535. class HideTopic(MethodView):
  536. decorators = [login_required]
  537. def post(self, topic_id, slug=None):
  538. topic = Topic.query.with_hidden().filter_by(id=topic_id).first_or_404()
  539. if not Permission(Has('makehidden'), IsAtleastModeratorInForum(forum=topic.forum)):
  540. flash(_("You do not have permission to hide this topic"), "danger")
  541. return redirect(topic.url)
  542. topic.hide(user=current_user)
  543. topic.save()
  544. if Permission(Has('viewhidden')):
  545. return redirect(topic.url)
  546. return redirect(topic.forum.url)
  547. class UnhideTopic(MethodView):
  548. decorators = [login_required]
  549. def post(self, topic_id, slug=None):
  550. topic = Topic.query.filter_by(id=topic_id).first_or_404()
  551. if not Permission(Has('makehidden'), IsAtleastModeratorInForum(forum=topic.forum)):
  552. flash(_("You do not have permission to unhide this topic"), "danger")
  553. return redirect(topic.url)
  554. topic.unhide()
  555. topic.save()
  556. return redirect(topic.url)
  557. class HidePost(MethodView):
  558. decorators = [login_required]
  559. def post(self, post_id):
  560. post = Post.query.filter(Post.id == post_id).first_or_404()
  561. if not Permission(Has('makehidden'), IsAtleastModeratorInForum(forum=post.topic.forum)):
  562. flash(_("You do not have permission to hide this post"), "danger")
  563. return redirect(post.topic.url)
  564. if post.hidden:
  565. flash(_("Post is already hidden"), "warning")
  566. return redirect(post.topic.url)
  567. first_post = post.first_post
  568. post.hide(current_user)
  569. post.save()
  570. if first_post:
  571. flash(_("Topic hidden"), "success")
  572. else:
  573. flash(_("Post hidden"), "success")
  574. if post.first_post and not Permission(Has("viewhidden")):
  575. return redirect(post.topic.forum.url)
  576. return redirect(post.topic.url)
  577. class UnhidePost(MethodView):
  578. decorators = [login_required]
  579. def post(self, post_id):
  580. post = Post.query.filter(Post.id == post_id).first_or_404()
  581. if not Permission(Has('makehidden'), IsAtleastModeratorInForum(forum=post.topic.forum)):
  582. flash(_("You do not have permission to unhide this post"), "danger")
  583. return redirect(post.topic.url)
  584. if not post.hidden:
  585. flash(_("Post is already unhidden"), "warning")
  586. redirect(post.topic.url)
  587. post.unhide()
  588. post.save()
  589. flash(_("Post unhidden"), "success")
  590. return redirect(post.topic.url)
  591. register_view(
  592. forum,
  593. routes=['/category/<int:category_id>', '/category/<int:category_id>-<slug>'],
  594. view_func=ViewCategory.as_view('view_category')
  595. )
  596. register_view(
  597. forum,
  598. routes=['/forum/<int:forum_id>/edit', '/forum/<int:forum_id>-<slug>/edit'],
  599. view_func=ManageForum.as_view('manage_forum')
  600. )
  601. register_view(
  602. forum,
  603. routes=['/forum/<int:forum_id>', '/forum/<int:forum_id>-<slug>'],
  604. view_func=ViewForum.as_view('view_forum')
  605. )
  606. register_view(
  607. forum,
  608. routes=['/<int:forum_id>/markread', '/<int:forum_id>-<slug>/markread'],
  609. view_func=MarkRead.as_view('markread')
  610. )
  611. register_view(
  612. forum,
  613. routes=['/<int:forum_id>/topic/new', '/<int:forum_id>-<slug>/topic/new'],
  614. view_func=NewTopic.as_view('new_topic')
  615. )
  616. register_view(forum, routes=['/memberlist'], view_func=MemberList.as_view('memberlist'))
  617. register_view(
  618. forum, routes=['/post/<int:post_id>/delete'], view_func=DeletePost.as_view('delete_post')
  619. )
  620. register_view(forum, routes=['/post/<int:post_id>/edit'], view_func=EditPost.as_view('edit_post'))
  621. register_view(forum, routes=['/post/<int:post_id>/raw'], view_func=RawPost.as_view('raw_post'))
  622. register_view(
  623. forum, routes=['/post/<int:post_id>/report'], view_func=ReportView.as_view('report_post')
  624. )
  625. register_view(forum, routes=['/post/<int:post_id>'], view_func=ViewPost.as_view('view_post'))
  626. register_view(forum, routes=['/search'], view_func=Search.as_view('search'))
  627. register_view(
  628. forum,
  629. routes=['/topic/<int:topic_id>/delete', '/topic/<int:topic_id>-<slug>/delete'],
  630. view_func=DeleteTopic.as_view('delete_topic')
  631. )
  632. register_view(
  633. forum,
  634. routes=['/topic/<int:topic_id>/highlight', '/topic/<int:topic_id>-<slug>/highlight'],
  635. view_func=HighlightTopic.as_view('highlight_topic')
  636. )
  637. register_view(
  638. forum,
  639. routes=['/topic/<int:topic_id>/lock', '/topic/<int:topic_id>-<slug>/lock'],
  640. view_func=LockTopic.as_view('lock_topic')
  641. )
  642. register_view(
  643. forum,
  644. routes=['/topic/<int:topic_id>/post/<int:post_id>/reply'],
  645. view_func=ReplyPost.as_view('reply_post')
  646. )
  647. register_view(
  648. forum,
  649. routes=['/topic/<int:topic_id>/post/new', '/topic/<int:topic_id>-<slug>/post/new'],
  650. view_func=NewPost.as_view('new_post')
  651. )
  652. register_view(
  653. forum,
  654. routes=['/topic/<int:topic_id>', '/topic/<int:topic_id>-<slug>'],
  655. view_func=ViewTopic.as_view('view_topic')
  656. )
  657. register_view(
  658. forum,
  659. routes=['/topic/<int:topic_id>/trivialize', '/topic/<int:topic_id>-<slug>/trivialize'],
  660. view_func=TrivializeTopic.as_view('trivialize_topic')
  661. )
  662. register_view(
  663. forum,
  664. routes=['/topic/<int:topic_id>/unlock', '/topic/<int:topic_id>-<slug>/unlock'],
  665. view_func=UnlockTopic.as_view('unlock_topic')
  666. )
  667. register_view(
  668. forum,
  669. routes=['/topictracker/<int:topic_id>/add', '/topictracker/<int:topic_id>-<slug>/add'],
  670. view_func=TrackTopic.as_view('track_topic')
  671. )
  672. register_view(
  673. forum,
  674. routes=['/topictracker/<int:topic_id>/delete', '/topictracker/<int:topic_id>-<slug>/delete'],
  675. view_func=UntrackTopic.as_view('untrack_topic')
  676. )
  677. register_view(forum, routes=['/topictracker'], view_func=TopicTracker.as_view('topictracker'))
  678. register_view(forum, routes=['/'], view_func=ForumIndex.as_view('index'))
  679. register_view(forum, routes=['/who-is-online'], view_func=WhoIsOnline.as_view('who_is_online'))
  680. register_view(
  681. forum,
  682. routes=["/topic/<int:topic_id>/hide", "/topic/<int:topic_id>-<slug>/hide"],
  683. view_func=HideTopic.as_view('hide_topic')
  684. )
  685. register_view(
  686. forum,
  687. routes=["/topic/<int:topic_id>/unhide", "/topic/<int:topic_id>-<slug>/unhide"],
  688. view_func=UnhideTopic.as_view('unhide_topic')
  689. )
  690. register_view(forum, routes=["/post/<int:post_id>/hide"], view_func=HidePost.as_view('hide_post'))
  691. register_view(
  692. forum, routes=["/post/<int:post_id>/unhide"], view_func=UnhidePost.as_view('unhide_post')
  693. )