Add ability for moderators to delete comments

This commit is contained in:
rubenwardy 2020-07-11 03:29:33 +01:00
parent a4dd4f0429
commit 31b8a7931b
9 changed files with 140 additions and 8 deletions

@ -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)

@ -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():

@ -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

@ -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 %}

@ -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 %}

@ -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>

@ -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 %}

@ -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)

@ -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 ###