views.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  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 datetime import datetime
  11. from flask import (Blueprint, current_app, request, redirect, url_for, flash,
  12. jsonify, __version__ as flask_version)
  13. from flask_login import current_user, login_fresh
  14. from flask_plugins import get_all_plugins, get_plugin, get_plugin_from_all
  15. from flask_babelplus import gettext as _
  16. from flask_allows import Permission, Not
  17. from flaskbb import __version__ as flaskbb_version
  18. from flaskbb._compat import iteritems
  19. from flaskbb.forum.forms import UserSearchForm
  20. from flaskbb.utils.settings import flaskbb_config
  21. from flaskbb.utils.requirements import (IsAtleastModerator, IsAdmin,
  22. CanBanUser, CanEditUser,
  23. IsAtleastSuperModerator)
  24. from flaskbb.extensions import db, allows
  25. from flaskbb.utils.helpers import render_template, time_diff, get_online_users
  26. from flaskbb.user.models import Guest, User, Group
  27. from flaskbb.forum.models import Post, Topic, Forum, Category, Report
  28. from flaskbb.management.models import Setting, SettingsGroup
  29. from flaskbb.management.forms import (AddUserForm, EditUserForm, AddGroupForm,
  30. EditGroupForm, EditForumForm,
  31. AddForumForm, CategoryForm)
  32. management = Blueprint("management", __name__)
  33. @management.before_request
  34. def check_fresh_login():
  35. """Checks if the login is fresh for the current user, otherwise the user
  36. has to reauthenticate."""
  37. if not login_fresh():
  38. return current_app.login_manager.needs_refresh()
  39. @management.route("/")
  40. @allows.requires(IsAtleastModerator)
  41. def overview():
  42. # user and group stats
  43. banned_users = User.query.filter(
  44. Group.banned == True,
  45. Group.id == User.primary_group_id
  46. ).count()
  47. if not current_app.config["REDIS_ENABLED"]:
  48. online_users = User.query.filter(User.lastseen >= time_diff()).count()
  49. else:
  50. online_users = len(get_online_users())
  51. stats = {
  52. # user stats
  53. "all_users": User.query.count(),
  54. "banned_users": banned_users,
  55. "online_users": online_users,
  56. "all_groups": Group.query.count(),
  57. # forum stats
  58. "report_count": Report.query.count(),
  59. "topic_count": Topic.query.count(),
  60. "post_count": Post.query.count(),
  61. # misc stats
  62. "plugins": get_all_plugins(),
  63. "python_version": "%s.%s" % (sys.version_info[0], sys.version_info[1]),
  64. "flask_version": flask_version,
  65. "flaskbb_version": flaskbb_version
  66. }
  67. return render_template("management/overview.html", **stats)
  68. @management.route("/settings", methods=["GET", "POST"])
  69. @management.route("/settings/<path:slug>", methods=["GET", "POST"])
  70. @allows.requires(IsAdmin)
  71. def settings(slug=None):
  72. slug = slug if slug else "general"
  73. # get the currently active group
  74. active_group = SettingsGroup.query.filter_by(key=slug).first_or_404()
  75. # get all groups - used to build the navigation
  76. all_groups = SettingsGroup.query.all()
  77. SettingsForm = Setting.get_form(active_group)
  78. old_settings = Setting.get_settings(active_group)
  79. new_settings = {}
  80. form = SettingsForm()
  81. if form.validate_on_submit():
  82. for key, values in iteritems(old_settings):
  83. try:
  84. # check if the value has changed
  85. if values['value'] == form[key].data:
  86. continue
  87. else:
  88. new_settings[key] = form[key].data
  89. except KeyError:
  90. pass
  91. Setting.update(settings=new_settings, app=current_app)
  92. flash(_("Settings saved."), "success")
  93. else:
  94. for key, values in iteritems(old_settings):
  95. try:
  96. form[key].data = values['value']
  97. except (KeyError, ValueError):
  98. pass
  99. return render_template("management/settings.html", form=form,
  100. all_groups=all_groups, active_group=active_group)
  101. # Users
  102. @management.route("/users", methods=['GET', 'POST'])
  103. @allows.requires(IsAtleastModerator)
  104. def users():
  105. page = request.args.get("page", 1, type=int)
  106. search_form = UserSearchForm()
  107. if search_form.validate():
  108. users = search_form.get_results().\
  109. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  110. return render_template("management/users.html", users=users,
  111. search_form=search_form)
  112. users = User.query. \
  113. order_by(User.id.asc()).\
  114. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  115. return render_template("management/users.html", users=users,
  116. search_form=search_form)
  117. @management.route("/users/<int:user_id>/edit", methods=["GET", "POST"])
  118. @allows.requires(IsAtleastModerator)
  119. def edit_user(user_id):
  120. user = User.query.filter_by(id=user_id).first_or_404()
  121. if not Permission(CanEditUser, identity=current_user):
  122. flash(_("You are not allowed to edit this user."), "danger")
  123. return redirect(url_for("management.users"))
  124. member_group = db.and_(*[db.not_(getattr(Group, p)) for p in
  125. ['admin', 'mod', 'super_mod', 'banned', 'guest']])
  126. filt = db.or_(
  127. Group.id.in_(g.id for g in current_user.groups), member_group
  128. )
  129. if Permission(IsAtleastSuperModerator, identity=current_user):
  130. filt = db.or_(filt, Group.mod)
  131. if Permission(IsAdmin, identity=current_user):
  132. filt = db.or_(filt, Group.admin, Group.super_mod)
  133. if Permission(CanBanUser, identity=current_user):
  134. filt = db.or_(filt, Group.banned)
  135. group_query = Group.query.filter(filt)
  136. form = EditUserForm(user)
  137. form.primary_group.query = group_query
  138. form.secondary_groups.query = group_query
  139. if form.validate_on_submit():
  140. form.populate_obj(user)
  141. user.primary_group_id = form.primary_group.data.id
  142. # Don't override the password
  143. if form.password.data:
  144. user.password = form.password.data
  145. user.save(groups=form.secondary_groups.data)
  146. flash(_("User updated."), "success")
  147. return redirect(url_for("management.edit_user", user_id=user.id))
  148. return render_template("management/user_form.html", form=form,
  149. title=_("Edit User"))
  150. @management.route("/users/delete", methods=["POST"])
  151. @management.route("/users/<int:user_id>/delete", methods=["POST"])
  152. @allows.requires(IsAdmin)
  153. def delete_user(user_id=None):
  154. # ajax request
  155. if request.is_xhr:
  156. ids = request.get_json()["ids"]
  157. data = []
  158. for user in User.query.filter(User.id.in_(ids)).all():
  159. # do not delete current user
  160. if current_user.id == user.id:
  161. continue
  162. if user.delete():
  163. data.append({
  164. "id": user.id,
  165. "type": "delete",
  166. "reverse": False,
  167. "reverse_name": None,
  168. "reverse_url": None
  169. })
  170. return jsonify(
  171. message="{} users deleted.".format(len(data)),
  172. category="success",
  173. data=data,
  174. status=200
  175. )
  176. user = User.query.filter_by(id=user_id).first_or_404()
  177. if current_user.id == user.id:
  178. flash(_("You cannot delete yourself.", "danger"))
  179. return redirect(url_for("management.users"))
  180. user.delete()
  181. flash(_("User deleted."), "success")
  182. return redirect(url_for("management.users"))
  183. @management.route("/users/add", methods=["GET", "POST"])
  184. @allows.requires(IsAdmin)
  185. def add_user():
  186. form = AddUserForm()
  187. if form.validate_on_submit():
  188. form.save()
  189. flash(_("User added."), "success")
  190. return redirect(url_for("management.users"))
  191. return render_template("management/user_form.html", form=form,
  192. title=_("Add User"))
  193. @management.route("/users/banned", methods=["GET", "POST"])
  194. @allows.requires(IsAtleastModerator)
  195. def banned_users():
  196. page = request.args.get("page", 1, type=int)
  197. search_form = UserSearchForm()
  198. users = User.query.filter(
  199. Group.banned == True,
  200. Group.id == User.primary_group_id
  201. ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  202. if search_form.validate():
  203. users = search_form.get_results().\
  204. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  205. return render_template("management/banned_users.html", users=users,
  206. search_form=search_form)
  207. return render_template("management/banned_users.html", users=users,
  208. search_form=search_form)
  209. @management.route("/users/ban", methods=["POST"])
  210. @management.route("/users/<int:user_id>/ban", methods=["POST"])
  211. @allows.requires(IsAtleastModerator)
  212. def ban_user(user_id=None):
  213. if not Permission(CanBanUser, identity=current_user):
  214. flash(_("You do not have the permissions to ban this user."), "danger")
  215. return redirect(url_for("management.overview"))
  216. # ajax request
  217. if request.is_xhr:
  218. ids = request.get_json()["ids"]
  219. data = []
  220. users = User.query.filter(User.id.in_(ids)).all()
  221. for user in users:
  222. # don't let a user ban himself and do not allow a moderator to ban
  223. # a admin user
  224. if (
  225. current_user.id == user.id or
  226. Permission(IsAdmin, identity=user) and
  227. Permission(Not(IsAdmin), current_user)
  228. ):
  229. continue
  230. elif user.ban():
  231. data.append({
  232. "id": user.id,
  233. "type": "ban",
  234. "reverse": "unban",
  235. "reverse_name": _("Unban"),
  236. "reverse_url": url_for("management.unban_user",
  237. user_id=user.id)
  238. })
  239. return jsonify(
  240. message="{} users banned.".format(len(data)),
  241. category="success",
  242. data=data,
  243. status=200
  244. )
  245. user = User.query.filter_by(id=user_id).first_or_404()
  246. # Do not allow moderators to ban admins
  247. if Permission(IsAdmin, identity=user) and \
  248. Permission(Not(IsAdmin), identity=current_user):
  249. flash(_("A moderator cannot ban an admin user."), "danger")
  250. return redirect(url_for("management.overview"))
  251. if not current_user.id == user.id and user.ban():
  252. flash(_("User is now banned."), "success")
  253. else:
  254. flash(_("Could not ban user."), "danger")
  255. return redirect(url_for("management.banned_users"))
  256. @management.route("/users/unban", methods=["POST"])
  257. @management.route("/users/<int:user_id>/unban", methods=["POST"])
  258. @allows.requires(IsAtleastModerator)
  259. def unban_user(user_id=None):
  260. if not Permission(CanBanUser, identity=current_user):
  261. flash(_("You do not have the permissions to unban this user."),
  262. "danger")
  263. return redirect(url_for("management.overview"))
  264. # ajax request
  265. if request.is_xhr:
  266. ids = request.get_json()["ids"]
  267. data = []
  268. for user in User.query.filter(User.id.in_(ids)).all():
  269. if user.unban():
  270. data.append({
  271. "id": user.id,
  272. "type": "unban",
  273. "reverse": "ban",
  274. "reverse_name": _("Ban"),
  275. "reverse_url": url_for("management.ban_user",
  276. user_id=user.id)
  277. })
  278. return jsonify(
  279. message="{} users unbanned.".format(len(data)),
  280. category="success",
  281. data=data,
  282. status=200
  283. )
  284. user = User.query.filter_by(id=user_id).first_or_404()
  285. if user.unban():
  286. flash(_("User is now unbanned."), "success")
  287. else:
  288. flash(_("Could not unban user."), "danger")
  289. return redirect(url_for("management.banned_users"))
  290. # Reports
  291. @management.route("/reports")
  292. @allows.requires(IsAtleastModerator)
  293. def reports():
  294. page = request.args.get("page", 1, type=int)
  295. reports = Report.query.\
  296. order_by(Report.id.asc()).\
  297. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  298. return render_template("management/reports.html", reports=reports)
  299. @management.route("/reports/unread")
  300. @allows.requires(IsAtleastModerator)
  301. def unread_reports():
  302. page = request.args.get("page", 1, type=int)
  303. reports = Report.query.\
  304. filter(Report.zapped == None).\
  305. order_by(Report.id.desc()).\
  306. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  307. return render_template("management/unread_reports.html", reports=reports)
  308. @management.route("/reports/<int:report_id>/markread", methods=["POST"])
  309. @management.route("/reports/markread", methods=["POST"])
  310. @allows.requires(IsAtleastModerator)
  311. def report_markread(report_id=None):
  312. # AJAX request
  313. if request.is_xhr:
  314. ids = request.get_json()["ids"]
  315. data = []
  316. for report in Report.query.filter(Report.id.in_(ids)).all():
  317. report.zapped_by = current_user.id
  318. report.zapped = datetime.utcnow()
  319. report.save()
  320. data.append({
  321. "id": report.id,
  322. "type": "read",
  323. "reverse": False,
  324. "reverse_name": None,
  325. "reverse_url": None
  326. })
  327. return jsonify(
  328. message="{} reports marked as read.".format(len(data)),
  329. category="success",
  330. data=data,
  331. status=200
  332. )
  333. # mark single report as read
  334. if report_id:
  335. report = Report.query.filter_by(id=report_id).first_or_404()
  336. if report.zapped:
  337. flash(_("Report %(id)s is already marked as read.", id=report.id),
  338. "success")
  339. return redirect(url_for("management.reports"))
  340. report.zapped_by = current_user.id
  341. report.zapped = datetime.utcnow()
  342. report.save()
  343. flash(_("Report %(id)s marked as read.", id=report.id), "success")
  344. return redirect(url_for("management.reports"))
  345. # mark all as read
  346. reports = Report.query.filter(Report.zapped == None).all()
  347. report_list = []
  348. for report in reports:
  349. report.zapped_by = current_user.id
  350. report.zapped = datetime.utcnow()
  351. report_list.append(report)
  352. db.session.add_all(report_list)
  353. db.session.commit()
  354. flash(_("All reports were marked as read."), "success")
  355. return redirect(url_for("management.reports"))
  356. # Groups
  357. @management.route("/groups")
  358. @allows.requires(IsAdmin)
  359. def groups():
  360. page = request.args.get("page", 1, type=int)
  361. groups = Group.query.\
  362. order_by(Group.id.asc()).\
  363. paginate(page, flaskbb_config['USERS_PER_PAGE'], False)
  364. return render_template("management/groups.html", groups=groups)
  365. @management.route("/groups/<int:group_id>/edit", methods=["GET", "POST"])
  366. @allows.requires(IsAdmin)
  367. def edit_group(group_id):
  368. group = Group.query.filter_by(id=group_id).first_or_404()
  369. form = EditGroupForm(group)
  370. if form.validate_on_submit():
  371. form.populate_obj(group)
  372. group.save()
  373. if group.guest:
  374. Guest.invalidate_cache()
  375. flash(_("Group updated."), "success")
  376. return redirect(url_for("management.groups", group_id=group.id))
  377. return render_template("management/group_form.html", form=form,
  378. title=_("Edit Group"))
  379. @management.route("/groups/<int:group_id>/delete", methods=["POST"])
  380. @management.route("/groups/delete", methods=["POST"])
  381. @allows.requires(IsAdmin)
  382. def delete_group(group_id=None):
  383. if request.is_xhr:
  384. ids = request.get_json()["ids"]
  385. if not (set(ids) & set(["1", "2", "3", "4", "5"])):
  386. data = []
  387. for group in Group.query.filter(Group.id.in_(ids)).all():
  388. group.delete()
  389. data.append({
  390. "id": group.id,
  391. "type": "delete",
  392. "reverse": False,
  393. "reverse_name": None,
  394. "reverse_url": None
  395. })
  396. return jsonify(
  397. message="{} groups deleted.".format(len(data)),
  398. category="success",
  399. data=data,
  400. status=200
  401. )
  402. return jsonify(
  403. message=_("You cannot delete one of the standard groups."),
  404. category="danger",
  405. data=None,
  406. status=404
  407. )
  408. if group_id is not None:
  409. if group_id <= 5: # there are 5 standard groups
  410. flash(_("You cannot delete the standard groups. "
  411. "Try renaming it instead.", "danger"))
  412. return redirect(url_for("management.groups"))
  413. group = Group.query.filter_by(id=group_id).first_or_404()
  414. group.delete()
  415. flash(_("Group deleted."), "success")
  416. return redirect(url_for("management.groups"))
  417. flash(_("No group chosen."), "danger")
  418. return redirect(url_for("management.groups"))
  419. @management.route("/groups/add", methods=["GET", "POST"])
  420. @allows.requires(IsAdmin)
  421. def add_group():
  422. form = AddGroupForm()
  423. if form.validate_on_submit():
  424. form.save()
  425. flash(_("Group added."), "success")
  426. return redirect(url_for("management.groups"))
  427. return render_template("management/group_form.html", form=form,
  428. title=_("Add Group"))
  429. # Forums and Categories
  430. @management.route("/forums")
  431. @allows.requires(IsAdmin)
  432. def forums():
  433. categories = Category.query.order_by(Category.position.asc()).all()
  434. return render_template("management/forums.html", categories=categories)
  435. @management.route("/forums/<int:forum_id>/edit", methods=["GET", "POST"])
  436. @allows.requires(IsAdmin)
  437. def edit_forum(forum_id):
  438. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  439. form = EditForumForm(forum)
  440. if form.validate_on_submit():
  441. form.save()
  442. flash(_("Forum updated."), "success")
  443. return redirect(url_for("management.edit_forum", forum_id=forum.id))
  444. else:
  445. if forum.moderators:
  446. form.moderators.data = ",".join([
  447. user.username for user in forum.moderators
  448. ])
  449. else:
  450. form.moderators.data = None
  451. return render_template("management/forum_form.html", form=form,
  452. title=_("Edit Forum"))
  453. @management.route("/forums/<int:forum_id>/delete", methods=["POST"])
  454. @allows.requires(IsAdmin)
  455. def delete_forum(forum_id):
  456. forum = Forum.query.filter_by(id=forum_id).first_or_404()
  457. involved_users = User.query.filter(Topic.forum_id == forum.id,
  458. Post.user_id == User.id).all()
  459. forum.delete(involved_users)
  460. flash(_("Forum deleted."), "success")
  461. return redirect(url_for("management.forums"))
  462. @management.route("/forums/add", methods=["GET", "POST"])
  463. @management.route("/forums/<int:category_id>/add", methods=["GET", "POST"])
  464. @allows.requires(IsAdmin)
  465. def add_forum(category_id=None):
  466. form = AddForumForm()
  467. if form.validate_on_submit():
  468. form.save()
  469. flash(_("Forum added."), "success")
  470. return redirect(url_for("management.forums"))
  471. else:
  472. form.groups.data = Group.query.order_by(Group.id.asc()).all()
  473. if category_id:
  474. category = Category.query.filter_by(id=category_id).first()
  475. form.category.data = category
  476. return render_template("management/forum_form.html", form=form,
  477. title=_("Add Forum"))
  478. @management.route("/category/add", methods=["GET", "POST"])
  479. @allows.requires(IsAdmin)
  480. def add_category():
  481. form = CategoryForm()
  482. if form.validate_on_submit():
  483. form.save()
  484. flash(_("Category added."), "success")
  485. return redirect(url_for("management.forums"))
  486. return render_template("management/category_form.html", form=form,
  487. title=_("Add Category"))
  488. @management.route("/category/<int:category_id>/edit", methods=["GET", "POST"])
  489. @allows.requires(IsAdmin)
  490. def edit_category(category_id):
  491. category = Category.query.filter_by(id=category_id).first_or_404()
  492. form = CategoryForm(obj=category)
  493. if form.validate_on_submit():
  494. form.populate_obj(category)
  495. flash(_("Category updated."), "success")
  496. category.save()
  497. return render_template("management/category_form.html", form=form,
  498. title=_("Edit Category"))
  499. @management.route("/category/<int:category_id>/delete", methods=["POST"])
  500. @allows.requires(IsAdmin)
  501. def delete_category(category_id):
  502. category = Category.query.filter_by(id=category_id).first_or_404()
  503. involved_users = User.query.filter(Forum.category_id == category.id,
  504. Topic.forum_id == Forum.id,
  505. Post.user_id == User.id).all()
  506. category.delete(involved_users)
  507. flash(_("Category with all associated forums deleted."), "success")
  508. return redirect(url_for("management.forums"))
  509. # Plugins
  510. @management.route("/plugins")
  511. @allows.requires(IsAdmin)
  512. def plugins():
  513. plugins = get_all_plugins()
  514. return render_template("management/plugins.html", plugins=plugins)
  515. @management.route("/plugins/<path:plugin>/enable", methods=["POST"])
  516. @allows.requires(IsAdmin)
  517. def enable_plugin(plugin):
  518. plugin = get_plugin_from_all(plugin)
  519. if plugin.enabled:
  520. flash(_("Plugin %(plugin)s is already enabled.", plugin=plugin.name),
  521. "info")
  522. return redirect(url_for("management.plugins"))
  523. try:
  524. plugin.enable()
  525. flash(_("Plugin %(plugin)s enabled. Please restart FlaskBB now.",
  526. plugin=plugin.name), "success")
  527. except OSError:
  528. flash(_("It seems that FlaskBB does not have enough filesystem "
  529. "permissions. Try removing the 'DISABLED' file by "
  530. "yourself instead."), "danger")
  531. return redirect(url_for("management.plugins"))
  532. @management.route("/plugins/<path:plugin>/disable", methods=["POST"])
  533. @allows.requires(IsAdmin)
  534. def disable_plugin(plugin):
  535. try:
  536. plugin = get_plugin(plugin)
  537. except KeyError:
  538. flash(_("Plugin %(plugin)s not found.", plugin=plugin.name), "danger")
  539. return redirect(url_for("management.plugins"))
  540. try:
  541. plugin.disable()
  542. flash(_("Plugin %(plugin)s disabled. Please restart FlaskBB now.",
  543. plugin=plugin.name), "success")
  544. except OSError:
  545. flash(_("It seems that FlaskBB does not have enough filesystem "
  546. "permissions. Try creating the 'DISABLED' file by "
  547. "yourself instead."), "danger")
  548. return redirect(url_for("management.plugins"))
  549. @management.route("/plugins/<path:plugin>/uninstall", methods=["POST"])
  550. @allows.requires(IsAdmin)
  551. def uninstall_plugin(plugin):
  552. plugin = get_plugin_from_all(plugin)
  553. if plugin.uninstallable:
  554. plugin.uninstall()
  555. Setting.invalidate_cache()
  556. flash(_("Plugin has been uninstalled."), "success")
  557. else:
  558. flash(_("Cannot uninstall plugin."), "danger")
  559. return redirect(url_for("management.plugins"))
  560. @management.route("/plugins/<path:plugin>/install", methods=["POST"])
  561. @allows.requires(IsAdmin)
  562. def install_plugin(plugin):
  563. plugin = get_plugin_from_all(plugin)
  564. if plugin.installable and not plugin.uninstallable:
  565. plugin.install()
  566. Setting.invalidate_cache()
  567. flash(_("Plugin has been installed."), "success")
  568. else:
  569. flash(_("Cannot install plugin."), "danger")
  570. return redirect(url_for("management.plugins"))