views.py 34 KB

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