diff options
author | rubenwardy <rw@rubenwardy.com> | 2020-07-11 03:29:33 +0100 |
---|---|---|
committer | rubenwardy <rw@rubenwardy.com> | 2020-07-11 03:29:38 +0100 |
commit | 31b8a7931bdb95b296e236c11705206507b035d8 (patch) | |
tree | dafcdca8f5dea95a326c08125f7d035be812ddd9 | |
parent | a4dd4f04293b6ad6dab5d3dc0a4c52a3290b4394 (diff) | |
download | cheatdb-31b8a7931bdb95b296e236c11705206507b035d8.tar.xz |
Add ability for moderators to delete comments
-rw-r--r-- | app/blueprints/admin/audit.py | 11 | ||||
-rw-r--r-- | app/blueprints/threads/__init__.py | 35 | ||||
-rw-r--r-- | app/models.py | 8 | ||||
-rw-r--r-- | app/templates/admin/audit.html | 14 | ||||
-rw-r--r-- | app/templates/admin/audit_view.html | 19 | ||||
-rw-r--r-- | app/templates/macros/threads.html | 7 | ||||
-rw-r--r-- | app/templates/threads/delete_reply.html | 22 | ||||
-rw-r--r-- | app/utils.py | 4 | ||||
-rw-r--r-- | migrations/versions/86512692b770_.py | 28 |
9 files changed, 140 insertions, 8 deletions
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/<int:id>/") +@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/<int:id>/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/<int:id>/", 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 <div class="list-group mt-3"> {% for entry in log %} - <a class="list-group-item list-group-item-action" href="{{ entry.url }}"> + <a class="list-group-item list-group-item-action" + {% if entry.description %} + href="{{ url_for('admin.audit_view', id=entry.id) }}"> + {% else %} + href="{{ entry.url }}"> + {% endif %} + <div class="row {% if entry.severity == entry.severity.NORMAL %}text-muted{% endif %}"> <div class="col-sm-auto text-center" style="width: 50px;"> {% if entry.severity == entry.severity.MODERATION %} @@ -30,6 +36,10 @@ Audit Log <div class="col-sm"> {{ entry.title}} + + {% if entry.description %} + <i class="fas fa-paperclip ml-3"></i> + {% endif %} </div> {% if entry.package %} @@ -54,5 +64,5 @@ Audit Log {% else %} <p class="list-group-item"><i>No audit log entires.</i></p> {% endfor %} - </ul> + </div> {% endblock %} diff --git a/app/templates/admin/audit_view.html b/app/templates/admin/audit_view.html new file mode 100644 index 0000000..72e0f27 --- /dev/null +++ b/app/templates/admin/audit_view.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %} +{{ entry.title }} +{% endblock %} + +{% block content %} + {% if entry.url %} + <a class="float-right btn btn-primary" href="{{ entry.url }}">View</a> + {% endif %} + + <h1>{{ entry.title }}</h1> + <p class="text-muted mb-4"> + {{ _("Caused by %(author)s.", author=entry.causer.display_name) }} + </p> + + <pre><code>{{ entry.description }}</code></pre> + +{% endblock %} diff --git a/app/templates/macros/threads.html b/app/templates/macros/threads.html index f9f298a..32acbc4 100644 --- a/app/templates/macros/threads.html +++ b/app/templates/macros/threads.html @@ -22,6 +22,13 @@ </div> <div class="card-body"> + {% if r != thread.replies[0] and thread.checkPerm(current_user, "DELETE_REPLY") %} + <a class="float-right btn btn-secondary btn-sm" + href="{{ url_for('threads.delete_reply', id=thread.id, reply=r.id) }}"> + <i class="fas fa-trash"></i> + </a> + {% endif %} + {{ r.comment | markdown }} </div> </div> 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 %} + <form method="POST" action="" class="card box_grey"> + <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> + + <h3 class="card-header">Delete reply by {{ reply.author.display_name }}</h3> + <div class="card-body"> + {{ reply.comment | markdown }} + </div> + <div class="card-body"> + <p>Deleting is permanent</p> + + <a class="btn btn-secondary mr-3" href="{{ thread.getViewURL() }}">Cancel</a> + <input type="submit" value="Delete" class="btn btn-danger" /> + </div> + </form> +{% 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 ### |