views.py 23 KB

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