views.py 32 KB

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