views.py 34 KB

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