views.py 34 KB

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