views.py 44 KB

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