views.py 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.management.views
  4. ~~~~~~~~~~~~~~~~~~~~~~~~
  5. This module handles the management views.
  6. :copyright: (c) 2014 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import logging
  10. import sys
  11. from celery import __version__ as celery_version
  12. from flask import __version__ as flask_version
  13. from flask import (Blueprint, current_app, flash, jsonify, redirect, request,
  14. url_for)
  15. from flask.views import MethodView
  16. from flask_allows import Not, Permission
  17. from flask_babelplus import gettext as _
  18. from flask_login import current_user, login_fresh
  19. from pluggy import HookimplMarker
  20. from flaskbb import __version__ as flaskbb_version
  21. from flaskbb.extensions import allows, celery, db
  22. from flaskbb.forum.forms import UserSearchForm
  23. from flaskbb.forum.models import Category, Forum, Post, Report, Topic
  24. from flaskbb.management.forms import (AddForumForm, AddGroupForm, AddUserForm,
  25. CategoryForm, EditForumForm,
  26. EditGroupForm, EditUserForm)
  27. from flaskbb.management.models import Setting, SettingsGroup
  28. from flaskbb.plugins.models import PluginRegistry, PluginStore
  29. from flaskbb.plugins.utils import validate_plugin
  30. from flaskbb.user.models import Group, Guest, User
  31. from flaskbb.utils.forms import populate_settings_dict, populate_settings_form
  32. from flaskbb.utils.helpers import (get_online_users, register_view,
  33. render_template, time_diff, time_utcnow)
  34. from flaskbb.utils.requirements import (CanBanUser, CanEditUser, IsAdmin,
  35. IsAtleastModerator,
  36. IsAtleastSuperModerator)
  37. from flaskbb.utils.settings import flaskbb_config
  38. impl = HookimplMarker('flaskbb')
  39. logger = logging.getLogger(__name__)
  40. class ManagementSettings(MethodView):
  41. decorators = [allows.requires(IsAdmin)]
  42. def _determine_active_settings(self, slug, plugin):
  43. """Determines which settings are active.
  44. Returns a tuple in following order:
  45. ``form``, ``old_settings``, ``plugin_obj``, ``active_nav``
  46. """
  47. # Any ideas how to do this better?
  48. slug = slug if slug else 'general'
  49. active_nav = {} # used to build the navigation
  50. plugin_obj = None
  51. if plugin is not None:
  52. plugin_obj = PluginRegistry.query.filter_by(name=plugin
  53. ).first_or_404()
  54. active_nav.update(
  55. {
  56. 'key': plugin_obj.name,
  57. 'title': plugin_obj.name.title()
  58. }
  59. )
  60. form = plugin_obj.get_settings_form()
  61. old_settings = plugin_obj.settings
  62. elif slug is not None:
  63. group_obj = SettingsGroup.query.filter_by(key=slug).first_or_404()
  64. active_nav.update({'key': group_obj.key, 'title': group_obj.name})
  65. form = Setting.get_form(group_obj)()
  66. old_settings = Setting.get_settings(group_obj)
  67. return form, old_settings, plugin_obj, active_nav
  68. def get(self, slug=None, plugin=None):
  69. form, old_settings, plugin_obj, active_nav = \
  70. self._determine_active_settings(slug, plugin)
  71. # get all groups and plugins - used to build the navigation
  72. all_groups = SettingsGroup.query.all()
  73. all_plugins = PluginRegistry.query.filter(db.and_(
  74. PluginRegistry.values != None,
  75. PluginRegistry.enabled == True
  76. )).all()
  77. form = populate_settings_form(form, old_settings)
  78. return render_template(
  79. "management/settings.html",
  80. form=form,
  81. all_groups=all_groups,
  82. all_plugins=all_plugins,
  83. active_nav=active_nav
  84. )
  85. def post(self, slug=None, plugin=None):
  86. form, old_settings, plugin_obj, active_nav = \
  87. self._determine_active_settings(slug, plugin)
  88. all_groups = SettingsGroup.query.all()
  89. all_plugins = PluginRegistry.query.filter(db.and_(
  90. PluginRegistry.values != None,
  91. PluginRegistry.enabled == True
  92. )).all()
  93. if form.validate_on_submit():
  94. new_settings = populate_settings_dict(form, old_settings)
  95. if plugin_obj is not None:
  96. plugin_obj.update_settings(new_settings)
  97. else:
  98. Setting.update(settings=new_settings, app=current_app)
  99. flash(_("Settings saved."), "success")
  100. return render_template(
  101. "management/settings.html",
  102. form=form,
  103. all_groups=all_groups,
  104. all_plugins=all_plugins,
  105. active_nav=active_nav
  106. )
  107. class ManageUsers(MethodView):
  108. decorators = [allows.requires(IsAtleastModerator)]
  109. form = UserSearchForm
  110. def get(self):
  111. page = request.args.get('page', 1, type=int)
  112. form = self.form()
  113. users = User.query.order_by(User.id.asc()).paginate(
  114. page, flaskbb_config['USERS_PER_PAGE'], False
  115. )
  116. return render_template(
  117. 'management/users.html', users=users, search_form=form
  118. )
  119. def post(self):
  120. page = request.args.get('page', 1, type=int)
  121. form = self.form()
  122. if form.validate():
  123. users = form.get_results().\
  124. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  125. return render_template(
  126. 'management/users.html', users=users, search_form=form
  127. )
  128. users = User.query.order_by(User.id.asc()).paginate(
  129. page, flaskbb_config['USERS_PER_PAGE'], False
  130. )
  131. return render_template(
  132. 'management/users.html', users=users, search_form=form
  133. )
  134. class EditUser(MethodView):
  135. decorators = [allows.requires(IsAtleastModerator & CanEditUser)]
  136. form = EditUserForm
  137. def get(self, user_id):
  138. user = User.query.filter_by(id=user_id).first_or_404()
  139. form = self.form(user)
  140. member_group = db.and_(
  141. * [
  142. db.not_(getattr(Group, p))
  143. for p in ['admin', 'mod', 'super_mod', 'banned', 'guest']
  144. ]
  145. )
  146. filt = db.or_(
  147. Group.id.in_(g.id for g in current_user.groups), member_group
  148. )
  149. if Permission(IsAtleastSuperModerator, identity=current_user):
  150. filt = db.or_(filt, Group.mod)
  151. if Permission(IsAdmin, identity=current_user):
  152. filt = db.or_(filt, Group.admin, Group.super_mod)
  153. if Permission(CanBanUser, identity=current_user):
  154. filt = db.or_(filt, Group.banned)
  155. group_query = Group.query.filter(filt)
  156. form.primary_group.query = group_query
  157. form.secondary_groups.query = group_query
  158. return render_template(
  159. 'management/user_form.html', form=form, title=_('Edit User')
  160. )
  161. def post(self, user_id):
  162. user = User.query.filter_by(id=user_id).first_or_404()
  163. member_group = db.and_(
  164. * [
  165. db.not_(getattr(Group, p))
  166. for p in ['admin', 'mod', 'super_mod', 'banned', 'guest']
  167. ]
  168. )
  169. filt = db.or_(
  170. Group.id.in_(g.id for g in current_user.groups), member_group
  171. )
  172. if Permission(IsAtleastSuperModerator, identity=current_user):
  173. filt = db.or_(filt, Group.mod)
  174. if Permission(IsAdmin, identity=current_user):
  175. filt = db.or_(filt, Group.admin, Group.super_mod)
  176. if Permission(CanBanUser, identity=current_user):
  177. filt = db.or_(filt, Group.banned)
  178. group_query = Group.query.filter(filt)
  179. form = EditUserForm(user)
  180. form.primary_group.query = group_query
  181. form.secondary_groups.query = group_query
  182. if form.validate_on_submit():
  183. form.populate_obj(user)
  184. user.primary_group_id = form.primary_group.data.id
  185. # Don't override the password
  186. if form.password.data:
  187. user.password = form.password.data
  188. user.save(groups=form.secondary_groups.data)
  189. flash(_('User updated.'), 'success')
  190. return redirect(url_for('management.edit_user', user_id=user.id))
  191. return render_template(
  192. 'management/user_form.html', form=form, title=_('Edit User')
  193. )
  194. class DeleteUser(MethodView):
  195. decorators = [allows.requires(IsAdmin)]
  196. def post(self, user_id=None):
  197. # ajax request
  198. if request.is_xhr:
  199. ids = request.get_json()["ids"]
  200. data = []
  201. for user in User.query.filter(User.id.in_(ids)).all():
  202. # do not delete current user
  203. if current_user.id == user.id:
  204. continue
  205. if user.delete():
  206. data.append(
  207. {
  208. "id": user.id,
  209. "type": "delete",
  210. "reverse": False,
  211. "reverse_name": None,
  212. "reverse_url": None
  213. }
  214. )
  215. return jsonify(
  216. message="{} users deleted.".format(len(data)),
  217. category="success",
  218. data=data,
  219. status=200
  220. )
  221. user = User.query.filter_by(id=user_id).first_or_404()
  222. if current_user.id == user.id:
  223. flash(_("You cannot delete yourself.", "danger"))
  224. return redirect(url_for("management.users"))
  225. user.delete()
  226. flash(_("User deleted."), "success")
  227. return redirect(url_for("management.users"))
  228. class AddUser(MethodView):
  229. decorators = [allows.requires(IsAdmin)]
  230. form = AddUserForm
  231. def get(self):
  232. return render_template(
  233. 'management/user_form.html', form=self.form(), title=_('Add User')
  234. )
  235. def post(self):
  236. form = self.form()
  237. if form.validate_on_submit():
  238. form.save()
  239. flash(_('User added.'), 'success')
  240. return redirect(url_for('management.users'))
  241. return render_template(
  242. 'management/user_form.html', form=form, title=_('Add User')
  243. )
  244. class BannedUsers(MethodView):
  245. decorators = [allows.requires(IsAtleastModerator)]
  246. form = UserSearchForm
  247. def get(self):
  248. page = request.args.get('page', 1, type=int)
  249. search_form = self.form()
  250. users = User.query.filter(
  251. Group.banned == True, Group.id == User.primary_group_id
  252. ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  253. return render_template(
  254. 'management/banned_users.html',
  255. users=users,
  256. search_form=search_form
  257. )
  258. def post(self):
  259. page = request.args.get('page', 1, type=int)
  260. search_form = self.form()
  261. users = User.query.filter(
  262. Group.banned == True, Group.id == User.primary_group_id
  263. ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  264. if search_form.validate():
  265. users = search_form.get_results().\
  266. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  267. return render_template(
  268. 'management/banned_users.html',
  269. users=users,
  270. search_form=search_form
  271. )
  272. return render_template(
  273. 'management/banned_users.html',
  274. users=users,
  275. search_form=search_form
  276. )
  277. class BanUser(MethodView):
  278. decorators = [allows.requires(IsAtleastModerator)]
  279. def post(self, user_id=None):
  280. if not Permission(CanBanUser, identity=current_user):
  281. flash(
  282. _("You do not have the permissions to ban this user."),
  283. "danger"
  284. )
  285. return redirect(url_for("management.overview"))
  286. # ajax request
  287. if request.is_xhr:
  288. ids = request.get_json()["ids"]
  289. data = []
  290. users = User.query.filter(User.id.in_(ids)).all()
  291. for user in users:
  292. # don't let a user ban himself and do not allow a moderator
  293. # to ban a admin user
  294. if (current_user.id == user.id or
  295. Permission(IsAdmin, identity=user) and
  296. Permission(Not(IsAdmin), current_user)):
  297. continue
  298. elif user.ban():
  299. data.append(
  300. {
  301. "id":
  302. user.id,
  303. "type":
  304. "ban",
  305. "reverse":
  306. "unban",
  307. "reverse_name":
  308. _("Unban"),
  309. "reverse_url":
  310. url_for("management.unban_user", user_id=user.id)
  311. }
  312. )
  313. return jsonify(
  314. message="{} users banned.".format(len(data)),
  315. category="success",
  316. data=data,
  317. status=200
  318. )
  319. user = User.query.filter_by(id=user_id).first_or_404()
  320. # Do not allow moderators to ban admins
  321. if Permission(IsAdmin, identity=user) and Permission(
  322. Not(IsAdmin), identity=current_user):
  323. flash(_("A moderator cannot ban an admin user."), "danger")
  324. return redirect(url_for("management.overview"))
  325. if not current_user.id == user.id and user.ban():
  326. flash(_("User is now banned."), "success")
  327. else:
  328. flash(_("Could not ban user."), "danger")
  329. return redirect(url_for("management.banned_users"))
  330. class UnbanUser(MethodView):
  331. decorators = [allows.requires(IsAtleastModerator)]
  332. def post(self, user_id=None):
  333. if not Permission(CanBanUser, identity=current_user):
  334. flash(
  335. _("You do not have the permissions to unban this user."),
  336. "danger"
  337. )
  338. return redirect(url_for("management.overview"))
  339. # ajax request
  340. if request.is_xhr:
  341. ids = request.get_json()["ids"]
  342. data = []
  343. for user in User.query.filter(User.id.in_(ids)).all():
  344. if user.unban():
  345. data.append(
  346. {
  347. "id": user.id,
  348. "type": "unban",
  349. "reverse": "ban",
  350. "reverse_name": _("Ban"),
  351. "reverse_url": url_for("management.ban_user",
  352. user_id=user.id)
  353. }
  354. )
  355. return jsonify(
  356. message="{} users unbanned.".format(len(data)),
  357. category="success",
  358. data=data,
  359. status=200
  360. )
  361. user = User.query.filter_by(id=user_id).first_or_404()
  362. if user.unban():
  363. flash(_("User is now unbanned."), "success")
  364. else:
  365. flash(_("Could not unban user."), "danger")
  366. return redirect(url_for("management.banned_users"))
  367. class Groups(MethodView):
  368. decorators = [allows.requires(IsAdmin)]
  369. def get(self):
  370. page = request.args.get("page", 1, type=int)
  371. groups = Group.query.\
  372. order_by(Group.id.asc()).\
  373. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  374. return render_template("management/groups.html", groups=groups)
  375. class AddGroup(MethodView):
  376. decorators = [allows.requires(IsAdmin)]
  377. form = AddGroupForm
  378. def get(self):
  379. return render_template(
  380. 'management/group_form.html',
  381. form=self.form(),
  382. title=_('Add Group')
  383. )
  384. def post(self):
  385. form = AddGroupForm()
  386. if form.validate_on_submit():
  387. form.save()
  388. flash(_('Group added.'), 'success')
  389. return redirect(url_for('management.groups'))
  390. return render_template(
  391. 'management/group_form.html', form=form, title=_('Add Group')
  392. )
  393. class EditGroup(MethodView):
  394. decorators = [allows.requires(IsAdmin)]
  395. form = EditGroupForm
  396. def get(self, group_id):
  397. group = Group.query.filter_by(id=group_id).first_or_404()
  398. form = self.form(group)
  399. return render_template(
  400. 'management/group_form.html', form=form, title=_('Edit Group')
  401. )
  402. def post(self, group_id):
  403. group = Group.query.filter_by(id=group_id).first_or_404()
  404. form = EditGroupForm(group)
  405. if form.validate_on_submit():
  406. form.populate_obj(group)
  407. group.save()
  408. if group.guest:
  409. Guest.invalidate_cache()
  410. flash(_('Group updated.'), 'success')
  411. return redirect(url_for('management.groups', group_id=group.id))
  412. return render_template(
  413. 'management/group_form.html', form=form, title=_('Edit Group')
  414. )
  415. class DeleteGroup(MethodView):
  416. decorators = [allows.requires(IsAdmin)]
  417. def post(self, group_id=None):
  418. if request.is_xhr:
  419. ids = request.get_json()["ids"]
  420. # TODO: Get rid of magic numbers
  421. if not (set(ids) & set(["1", "2", "3", "4", "5", "6"])):
  422. data = []
  423. for group in Group.query.filter(Group.id.in_(ids)).all():
  424. group.delete()
  425. data.append(
  426. {
  427. "id": group.id,
  428. "type": "delete",
  429. "reverse": False,
  430. "reverse_name": None,
  431. "reverse_url": None
  432. }
  433. )
  434. return jsonify(
  435. message="{} groups deleted.".format(len(data)),
  436. category="success",
  437. data=data,
  438. status=200
  439. )
  440. return jsonify(
  441. message=_("You cannot delete one of the standard groups."),
  442. category="danger",
  443. data=None,
  444. status=404
  445. )
  446. if group_id is not None:
  447. if group_id <= 6: # there are 6 standard groups
  448. flash(
  449. _(
  450. "You cannot delete the standard groups. "
  451. "Try renaming it instead.", "danger"
  452. )
  453. )
  454. return redirect(url_for("management.groups"))
  455. group = Group.query.filter_by(id=group_id).first_or_404()
  456. group.delete()
  457. flash(_("Group deleted."), "success")
  458. return redirect(url_for("management.groups"))
  459. flash(_("No group chosen."), "danger")
  460. return redirect(url_for("management.groups"))
  461. class Forums(MethodView):
  462. decorators = [allows.requires(IsAdmin)]
  463. def get(self):
  464. categories = Category.query.order_by(Category.position.asc()).all()
  465. return render_template("management/forums.html", categories=categories)
  466. class EditForum(MethodView):
  467. decorators = [allows.requires(IsAdmin)]
  468. form = EditForumForm
  469. def get(self, forum_id):
  470. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  471. form = self.form(forum)
  472. if forum.moderators:
  473. form.moderators.data = ','.join(
  474. [user.username for user in forum.moderators]
  475. )
  476. else:
  477. form.moderators.data = None
  478. return render_template(
  479. 'management/forum_form.html', form=form, title=_('Edit Forum')
  480. )
  481. def post(self, forum_id):
  482. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  483. form = self.form(forum)
  484. if form.validate_on_submit():
  485. form.save()
  486. flash(_('Forum updated.'), 'success')
  487. return redirect(url_for('management.edit_forum',
  488. forum_id=forum.id))
  489. else:
  490. if forum.moderators:
  491. form.moderators.data = ','.join(
  492. [user.username for user in forum.moderators]
  493. )
  494. else:
  495. form.moderators.data = None
  496. return render_template(
  497. 'management/forum_form.html', form=form, title=_('Edit Forum')
  498. )
  499. class AddForum(MethodView):
  500. decorators = [allows.requires(IsAdmin)]
  501. form = AddForumForm
  502. def get(self, category_id=None):
  503. form = self.form()
  504. form.groups.data = Group.query.order_by(Group.id.asc()).all()
  505. if category_id:
  506. category = Category.query.filter_by(id=category_id).first()
  507. form.category.data = category
  508. return render_template(
  509. 'management/forum_form.html', form=form, title=_('Add Forum')
  510. )
  511. def post(self, category_id=None):
  512. form = self.form()
  513. if form.validate_on_submit():
  514. form.save()
  515. flash(_('Forum added.'), 'success')
  516. return redirect(url_for('management.forums'))
  517. else:
  518. form.groups.data = Group.query.order_by(Group.id.asc()).all()
  519. if category_id:
  520. category = Category.query.filter_by(id=category_id).first()
  521. form.category.data = category
  522. return render_template(
  523. 'management/forum_form.html', form=form, title=_('Add Forum')
  524. )
  525. class DeleteForum(MethodView):
  526. decorators = [allows.requires(IsAdmin)]
  527. def post(self, forum_id):
  528. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  529. involved_users = User.query.filter(
  530. Topic.forum_id == forum.id, Post.user_id == User.id
  531. ).all()
  532. forum.delete(involved_users)
  533. flash(_("Forum deleted."), "success")
  534. return redirect(url_for("management.forums"))
  535. class AddCategory(MethodView):
  536. decorators = [allows.requires(IsAdmin)]
  537. form = CategoryForm
  538. def get(self):
  539. return render_template(
  540. 'management/category_form.html',
  541. form=self.form(),
  542. title=_('Add Category')
  543. )
  544. def post(self):
  545. form = self.form()
  546. if form.validate_on_submit():
  547. form.save()
  548. flash(_('Category added.'), 'success')
  549. return redirect(url_for('management.forums'))
  550. return render_template(
  551. 'management/category_form.html', form=form, title=_('Add Category')
  552. )
  553. class EditCategory(MethodView):
  554. decorators = [allows.requires(IsAdmin)]
  555. form = CategoryForm
  556. def get(self, category_id):
  557. category = Category.query.filter_by(id=category_id).first_or_404()
  558. form = self.form(obj=category)
  559. return render_template(
  560. 'management/category_form.html',
  561. form=form,
  562. title=_('Edit Category')
  563. )
  564. def post(self, category_id):
  565. category = Category.query.filter_by(id=category_id).first_or_404()
  566. form = self.form(obj=category)
  567. if form.validate_on_submit():
  568. form.populate_obj(category)
  569. flash(_('Category updated.'), 'success')
  570. category.save()
  571. return render_template(
  572. 'management/category_form.html',
  573. form=form,
  574. title=_('Edit Category')
  575. )
  576. class DeleteCategory(MethodView):
  577. decorators = [allows.requires(IsAdmin)]
  578. def post(self, category_id):
  579. category = Category.query.filter_by(id=category_id).first_or_404()
  580. involved_users = User.query.filter(
  581. Forum.category_id == category.id, Topic.forum_id == Forum.id,
  582. Post.user_id == User.id
  583. ).all()
  584. category.delete(involved_users)
  585. flash(_("Category with all associated forums deleted."), "success")
  586. return redirect(url_for("management.forums"))
  587. class Reports(MethodView):
  588. decorators = [allows.requires(IsAtleastModerator)]
  589. def get(self):
  590. page = request.args.get("page", 1, type=int)
  591. reports = Report.query.\
  592. order_by(Report.id.asc()).\
  593. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  594. return render_template("management/reports.html", reports=reports)
  595. class UnreadReports(MethodView):
  596. decorators = [allows.requires(IsAtleastModerator)]
  597. def get(self):
  598. page = request.args.get("page", 1, type=int)
  599. reports = Report.query.\
  600. filter(Report.zapped == None).\
  601. order_by(Report.id.desc()).\
  602. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  603. return render_template("management/reports.html", reports=reports)
  604. class MarkReportRead(MethodView):
  605. decorators = [allows.requires(IsAtleastModerator)]
  606. def post(self, report_id=None):
  607. # AJAX request
  608. if request.is_xhr:
  609. ids = request.get_json()["ids"]
  610. data = []
  611. for report in Report.query.filter(Report.id.in_(ids)).all():
  612. report.zapped_by = current_user.id
  613. report.zapped = time_utcnow()
  614. report.save()
  615. data.append(
  616. {
  617. "id": report.id,
  618. "type": "read",
  619. "reverse": False,
  620. "reverse_name": None,
  621. "reverse_url": None
  622. }
  623. )
  624. return jsonify(
  625. message="{} reports marked as read.".format(len(data)),
  626. category="success",
  627. data=data,
  628. status=200
  629. )
  630. # mark single report as read
  631. if report_id:
  632. report = Report.query.filter_by(id=report_id).first_or_404()
  633. if report.zapped:
  634. flash(
  635. _("Report %(id)s is already marked as read.", id=report.id),
  636. "success"
  637. )
  638. return redirect(url_for("management.reports"))
  639. report.zapped_by = current_user.id
  640. report.zapped = time_utcnow()
  641. report.save()
  642. flash(_("Report %(id)s marked as read.", id=report.id), "success")
  643. return redirect(url_for("management.reports"))
  644. # mark all as read
  645. reports = Report.query.filter(Report.zapped == None).all()
  646. report_list = []
  647. for report in reports:
  648. report.zapped_by = current_user.id
  649. report.zapped = time_utcnow()
  650. report_list.append(report)
  651. db.session.add_all(report_list)
  652. db.session.commit()
  653. flash(_("All reports were marked as read."), "success")
  654. return redirect(url_for("management.reports"))
  655. class DeleteReport(MethodView):
  656. decorators = [allows.requires(IsAtleastModerator)]
  657. def post(self, report_id=None):
  658. if request.is_xhr:
  659. ids = request.get_json()["ids"]
  660. data = []
  661. for report in Report.query.filter(Report.id.in_(ids)).all():
  662. if report.delete():
  663. data.append(
  664. {
  665. "id": report.id,
  666. "type": "delete",
  667. "reverse": False,
  668. "reverse_name": None,
  669. "reverse_url": None
  670. }
  671. )
  672. return jsonify(
  673. message="{} reports deleted.".format(len(data)),
  674. category="success",
  675. data=data,
  676. status=200
  677. )
  678. report = Report.query.filter_by(id=report_id).first_or_404()
  679. report.delete()
  680. flash(_("Report deleted."), "success")
  681. return redirect(url_for("management.reports"))
  682. class CeleryStatus(MethodView):
  683. decorators = [allows.requires(IsAtleastModerator)]
  684. def get(self):
  685. celery_inspect = celery.control.inspect()
  686. try:
  687. celery_running = True if celery_inspect.ping() else False
  688. except Exception:
  689. # catching Exception is bad, and just catching ConnectionError
  690. # from redis is also bad because you can run celery with other
  691. # brokers as well.
  692. celery_running = False
  693. return jsonify(celery_running=celery_running, status=200)
  694. class ManagementOverview(MethodView):
  695. decorators = [allows.requires(IsAtleastModerator)]
  696. def get(self):
  697. # user and group stats
  698. banned_users = User.query.filter(
  699. Group.banned == True, Group.id == User.primary_group_id
  700. ).count()
  701. if not current_app.config["REDIS_ENABLED"]:
  702. online_users = User.query.filter(User.lastseen >= time_diff()
  703. ).count()
  704. else:
  705. online_users = len(get_online_users())
  706. unread_reports = Report.query.\
  707. filter(Report.zapped == None).\
  708. order_by(Report.id.desc()).\
  709. count()
  710. python_version = "{}.{}.{}".format(
  711. sys.version_info[0], sys.version_info[1], sys.version_info[2]
  712. )
  713. stats = {
  714. "current_app": current_app,
  715. "unread_reports": unread_reports,
  716. # stats stats
  717. "all_users": User.query.count(),
  718. "banned_users": banned_users,
  719. "online_users": online_users,
  720. "all_groups": Group.query.count(),
  721. "report_count": Report.query.count(),
  722. "topic_count": Topic.query.count(),
  723. "post_count": Post.query.count(),
  724. # components
  725. "python_version": python_version,
  726. "celery_version": celery_version,
  727. "flask_version": flask_version,
  728. "flaskbb_version": flaskbb_version,
  729. # plugins
  730. "plugins": PluginRegistry.query.all()
  731. }
  732. return render_template("management/overview.html", **stats)
  733. class PluginsView(MethodView):
  734. decorators = [allows.requires(IsAdmin)]
  735. def get(self):
  736. plugins = PluginRegistry.query.all()
  737. return render_template("management/plugins.html", plugins=plugins)
  738. class EnablePlugin(MethodView):
  739. decorators = [allows.requires(IsAdmin)]
  740. def post(self, name):
  741. validate_plugin(name)
  742. plugin = PluginRegistry.query.filter_by(name=name).first_or_404()
  743. if plugin.enabled:
  744. flash(
  745. _("Plugin %(plugin)s is already enabled.", plugin=plugin.name),
  746. "info"
  747. )
  748. return redirect(url_for("management.plugins"))
  749. plugin.enabled = True
  750. plugin.save()
  751. flash(
  752. _(
  753. "Plugin %(plugin)s enabled. Please restart FlaskBB now.",
  754. plugin=plugin.name
  755. ), "success"
  756. )
  757. return redirect(url_for("management.plugins"))
  758. class DisablePlugin(MethodView):
  759. decorators = [allows.requires(IsAdmin)]
  760. def post(self, name):
  761. validate_plugin(name)
  762. plugin = PluginRegistry.query.filter_by(name=name).first_or_404()
  763. if not plugin.enabled:
  764. flash(
  765. _("Plugin %(plugin)s is already disabled.", plugin=plugin.name),
  766. "info"
  767. )
  768. return redirect(url_for("management.plugins"))
  769. plugin.enabled = False
  770. plugin.save()
  771. flash(
  772. _(
  773. "Plugin %(plugin)s disabled. Please restart FlaskBB now.",
  774. plugin=plugin.name
  775. ), "success"
  776. )
  777. return redirect(url_for("management.plugins"))
  778. class UninstallPlugin(MethodView):
  779. decorators = [allows.requires(IsAdmin)]
  780. def post(self, name):
  781. validate_plugin(name)
  782. plugin = PluginRegistry.query.filter_by(name=name).first_or_404()
  783. PluginStore.query.filter_by(plugin_id=plugin.id).delete()
  784. db.session.commit()
  785. flash(_("Plugin has been uninstalled."), "success")
  786. return redirect(url_for("management.plugins"))
  787. class InstallPlugin(MethodView):
  788. decorators = [allows.requires(IsAdmin)]
  789. def post(self, name):
  790. plugin_module = validate_plugin(name)
  791. plugin = PluginRegistry.query.filter_by(name=name).first_or_404()
  792. if not plugin.enabled:
  793. flash(
  794. _(
  795. "Can't install plugin. Enable '%(plugin)s' plugin first.",
  796. plugin=plugin.name
  797. ), "danger"
  798. )
  799. return redirect(url_for("management.plugins"))
  800. plugin.add_settings(plugin_module.SETTINGS)
  801. flash(_("Plugin has been installed."), "success")
  802. return redirect(url_for("management.plugins"))
  803. @impl(tryfirst=True)
  804. def flaskbb_load_blueprints(app):
  805. management = Blueprint("management", __name__)
  806. @management.before_request
  807. def check_fresh_login():
  808. """Checks if the login is fresh for the current user, otherwise the user
  809. has to reauthenticate."""
  810. if not login_fresh():
  811. return current_app.login_manager.needs_refresh()
  812. # Categories
  813. register_view(
  814. management,
  815. routes=['/category/add'],
  816. view_func=AddCategory.as_view('add_category')
  817. )
  818. register_view(
  819. management,
  820. routes=["/category/<int:category_id>/delete"],
  821. view_func=DeleteCategory.as_view('delete_category')
  822. )
  823. register_view(
  824. management,
  825. routes=['/category/<int:category_id>/edit'],
  826. view_func=EditCategory.as_view('edit_category')
  827. )
  828. # Forums
  829. register_view(
  830. management,
  831. routes=['/forums/add', '/forums/<int:category_id>/add'],
  832. view_func=AddForum.as_view('add_forum')
  833. )
  834. register_view(
  835. management,
  836. routes=['/forums/<int:forum_id>/delete'],
  837. view_func=DeleteForum.as_view('delete_forum')
  838. )
  839. register_view(
  840. management,
  841. routes=['/forums/<int:forum_id>/edit'],
  842. view_func=EditForum.as_view('edit_forum')
  843. )
  844. register_view(
  845. management, routes=['/forums'], view_func=Forums.as_view('forums')
  846. )
  847. # Groups
  848. register_view(
  849. management,
  850. routes=['/groups/add'],
  851. view_func=AddGroup.as_view('add_group')
  852. )
  853. register_view(
  854. management,
  855. routes=['/groups/<int:group_id>/delete', '/groups/delete'],
  856. view_func=DeleteGroup.as_view('delete_group')
  857. )
  858. register_view(
  859. management,
  860. routes=['/groups/<int:group_id>/edit'],
  861. view_func=EditGroup.as_view('edit_group')
  862. )
  863. register_view(
  864. management, routes=['/groups'], view_func=Groups.as_view('groups')
  865. )
  866. # Plugins
  867. register_view(
  868. management,
  869. routes=['/plugins/<path:name>/disable'],
  870. view_func=DisablePlugin.as_view('disable_plugin')
  871. )
  872. register_view(
  873. management,
  874. routes=['/plugins/<path:name>/enable'],
  875. view_func=EnablePlugin.as_view('enable_plugin')
  876. )
  877. register_view(
  878. management,
  879. routes=['/plugins/<path:name>/install'],
  880. view_func=InstallPlugin.as_view('install_plugin')
  881. )
  882. register_view(
  883. management,
  884. routes=['/plugins/<path:name>/uninstall'],
  885. view_func=UninstallPlugin.as_view('uninstall_plugin')
  886. )
  887. register_view(
  888. management,
  889. routes=['/plugins'],
  890. view_func=PluginsView.as_view('plugins')
  891. )
  892. # Reports
  893. register_view(
  894. management,
  895. routes=['/reports/<int:report_id>/delete', '/reports/delete'],
  896. view_func=DeleteReport.as_view('delete_report')
  897. )
  898. register_view(
  899. management,
  900. routes=['/reports/<int:report_id>/markread', '/reports/markread'],
  901. view_func=MarkReportRead.as_view('report_markread')
  902. )
  903. register_view(
  904. management,
  905. routes=['/reports/unread'],
  906. view_func=UnreadReports.as_view('unread_reports')
  907. )
  908. register_view(
  909. management, routes=['/reports'], view_func=Reports.as_view('reports')
  910. )
  911. # Settings
  912. register_view(
  913. management,
  914. routes=[
  915. '/settings', '/settings/<path:slug>',
  916. '/settings/plugin/<path:plugin>'
  917. ],
  918. view_func=ManagementSettings.as_view('settings')
  919. )
  920. # Users
  921. register_view(
  922. management,
  923. routes=['/users/add'],
  924. view_func=AddUser.as_view('add_user')
  925. )
  926. register_view(
  927. management,
  928. routes=['/users/banned'],
  929. view_func=BannedUsers.as_view('banned_users')
  930. )
  931. register_view(
  932. management,
  933. routes=['/users/ban', '/users/<int:user_id>/ban'],
  934. view_func=BanUser.as_view('ban_user')
  935. )
  936. register_view(
  937. management,
  938. routes=['/users/delete', '/users/<int:user_id>/delete'],
  939. view_func=DeleteUser.as_view('delete_user')
  940. )
  941. register_view(
  942. management,
  943. routes=['/users/<int:user_id>/edit'],
  944. view_func=EditUser.as_view('edit_user')
  945. )
  946. register_view(
  947. management,
  948. routes=['/users/unban', '/users/<int:user_id>/unban'],
  949. view_func=UnbanUser.as_view('unban_user')
  950. )
  951. register_view(
  952. management, routes=['/users'], view_func=ManageUsers.as_view('users')
  953. )
  954. register_view(
  955. management,
  956. routes=['/celerystatus'],
  957. view_func=CeleryStatus.as_view('celery_status')
  958. )
  959. register_view(
  960. management,
  961. routes=['/'],
  962. view_func=ManagementOverview.as_view('overview')
  963. )
  964. app.register_blueprint(
  965. management, url_prefix=app.config["ADMIN_URL_PREFIX"]
  966. )