From 31b8a7931bdb95b296e236c11705206507b035d8 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sat, 11 Jul 2020 03:29:33 +0100 Subject: [PATCH] Add ability for moderators to delete comments --- app/blueprints/admin/audit.py | 11 ++++++-- app/blueprints/threads/__init__.py | 35 +++++++++++++++++++++++++ app/models.py | 8 ++++-- app/templates/admin/audit.html | 14 ++++++++-- app/templates/admin/audit_view.html | 19 ++++++++++++++ app/templates/macros/threads.html | 7 +++++ app/templates/threads/delete_reply.html | 22 ++++++++++++++++ app/utils.py | 4 +-- migrations/versions/86512692b770_.py | 28 ++++++++++++++++++++ 9 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 app/templates/admin/audit_view.html create mode 100644 app/templates/threads/delete_reply.html create mode 100644 migrations/versions/86512692b770_.py diff --git a/app/blueprints/admin/audit.py b/app/blueprints/admin/audit.py index 64dc3a7..5ccac56 100644 --- a/app/blueprints/admin/audit.py +++ b/app/blueprints/admin/audit.py @@ -16,15 +16,22 @@ from flask import Blueprint, render_template, redirect, url_for -from flask_user import current_user, login_required +from flask_user import current_user from app.models import db, AuditLogEntry, UserRank from app.utils import rank_required from . import bp + @bp.route("/admin/audit/") -@login_required @rank_required(UserRank.MODERATOR) def audit(): log = AuditLogEntry.query.order_by(db.desc(AuditLogEntry.created_at)).all() return render_template("admin/audit.html", log=log) + + +@bp.route("/admin/audit//") +@rank_required(UserRank.MODERATOR) +def audit_view(id): + entry = AuditLogEntry.query.get(id) + return render_template("admin/audit_view.html", entry=entry) diff --git a/app/blueprints/threads/__init__.py b/app/blueprints/threads/__init__.py index e3043c0..28a7fde 100644 --- a/app/blueprints/threads/__init__.py +++ b/app/blueprints/threads/__init__.py @@ -107,6 +107,40 @@ def set_lock(id): return redirect(thread.getViewURL()) +@bp.route("/threads//delete/", methods=["GET", "POST"]) +@login_required +def delete_reply(id): + thread = Thread.query.get(id) + if thread is None: + abort(404) + + reply_id = request.args.get("reply") + if reply_id is None: + abort(404) + + reply = ThreadReply.query.get(reply_id) + if reply is None or reply.thread != thread: + abort(404) + + if thread.replies[0] == reply: + flash("Cannot delete thread opening post!", "danger") + return redirect(thread.getViewURL()) + + if not thread.checkPerm(current_user, Permission.DELETE_REPLY): + abort(403) + + if request.method == "GET": + return render_template("threads/delete_reply.html", thread=thread, reply=reply) + + msg = "Deleted reply by {}".format(reply.author.display_name) + addAuditLog(AuditSeverity.MODERATION, current_user, msg, thread.getViewURL(), thread.package, reply.comment) + + db.session.delete(reply) + db.session.commit() + + return redirect(thread.getViewURL()) + + @bp.route("/threads//", methods=["GET", "POST"]) def view(id): thread = Thread.query.get(id) @@ -152,6 +186,7 @@ class ThreadForm(FlaskForm): private = BooleanField("Private") submit = SubmitField("Open Thread") + @bp.route("/threads/new/", methods=["GET", "POST"]) @login_required def new(): diff --git a/app/models.py b/app/models.py index 13d1fdd..9e1840e 100644 --- a/app/models.py +++ b/app/models.py @@ -92,6 +92,7 @@ class Permission(enum.Enum): CREATE_THREAD = "CREATE_THREAD" COMMENT_THREAD = "COMMENT_THREAD" LOCK_THREAD = "LOCK_THREAD" + DELETE_REPLY = "DELETE_REPLY" UNAPPROVE_PACKAGE = "UNAPPROVE_PACKAGE" TOPIC_DISCARD = "TOPIC_DISCARD" CREATE_TOKEN = "CREATE_TOKEN" @@ -1123,7 +1124,7 @@ class Thread(db.Model): elif perm == Permission.COMMENT_THREAD: return canSee and (not self.locked or user.rank.atLeast(UserRank.MODERATOR)) - elif perm == Permission.LOCK_THREAD: + elif perm == Permission.LOCK_THREAD or perm == Permission.DELETE_REPLY: return user.rank.atLeast(UserRank.MODERATOR) else: @@ -1201,7 +1202,9 @@ class AuditLogEntry(db.Model): package_id = db.Column(db.Integer, db.ForeignKey("package.id"), nullable=True) package = db.relationship("Package", foreign_keys=[package_id]) - def __init__(self, causer, severity, title, url, package=None): + description = db.Column(db.Text, nullable=True, default=None) + + def __init__(self, causer, severity, title, url, package=None, description=None): if len(title) > 100: title = title[:99] + "…" @@ -1210,6 +1213,7 @@ class AuditLogEntry(db.Model): self.title = title self.url = url self.package = package + self.description = description diff --git a/app/templates/admin/audit.html b/app/templates/admin/audit.html index 4255b72..1ac793b 100644 --- a/app/templates/admin/audit.html +++ b/app/templates/admin/audit.html @@ -9,7 +9,13 @@ Audit Log
{% for entry in log %} - + + {% else %} + href="{{ entry.url }}"> + {% endif %} +
+ {% if r != thread.replies[0] and thread.checkPerm(current_user, "DELETE_REPLY") %} + + + + {% endif %} + {{ r.comment | markdown }}
diff --git a/app/templates/threads/delete_reply.html b/app/templates/threads/delete_reply.html new file mode 100644 index 0000000..6c145df --- /dev/null +++ b/app/templates/threads/delete_reply.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %} + Delete reply in {{ thread.title }} +{% endblock %} + +{% block content %} +
+ + +

Delete reply by {{ reply.author.display_name }}

+
+ {{ reply.comment | markdown }} +
+
+

Deleting is permanent

+ + Cancel + +
+
+{% endblock %} diff --git a/app/utils.py b/app/utils.py index 0f5a916..ed369b7 100644 --- a/app/utils.py +++ b/app/utils.py @@ -204,8 +204,8 @@ def addNotification(target, causer, title, url, package=None): db.session.add(notif) -def addAuditLog(severity, causer, title, url, package=None): - entry = AuditLogEntry(causer, severity, title, url, package) +def addAuditLog(severity, causer, title, url, package=None, description=None): + entry = AuditLogEntry(causer, severity, title, url, package, description) db.session.add(entry) diff --git a/migrations/versions/86512692b770_.py b/migrations/versions/86512692b770_.py new file mode 100644 index 0000000..cbfc990 --- /dev/null +++ b/migrations/versions/86512692b770_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 86512692b770 +Revises: ba730ce1dc3e +Create Date: 2020-07-11 01:56:28.634661 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '86512692b770' +down_revision = 'ba730ce1dc3e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('audit_log_entry', sa.Column('description', sa.Text, nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('audit_log_entry', 'description') + # ### end Alembic commands ###