-# Content DB
+# ContentDB
# Copyright (C) 2018 rubenwardy
#
# This program is free software: you can redistribute it and/or modify
from flask_user import *
from app.models import *
-from app.utils import addNotification, clearNotifications
+from app.utils import addNotification, clearNotifications, isYes, addAuditLog
import datetime
thread.watchers.append(current_user)
db.session.commit()
- return redirect(url_for("threads.view", id=id))
+ return redirect(thread.getViewURL())
@bp.route("/threads/<int:id>/unsubscribe/", methods=["POST"])
thread.watchers.remove(current_user)
db.session.commit()
else:
- flash("Not subscribed to thread", "success")
+ flash("Already not subscribed!", "success")
- return redirect(url_for("threads.view", id=id))
+ return redirect(thread.getViewURL())
+
+
+@bp.route("/threads/<int:id>/set-lock/", methods=["POST"])
+@login_required
+def set_lock(id):
+ thread = Thread.query.get(id)
+ if thread is None or not thread.checkPerm(current_user, Permission.LOCK_THREAD):
+ abort(404)
+
+ thread.locked = isYes(request.args.get("lock"))
+ if thread.locked is None:
+ abort(400)
+
+ msg = None
+ if thread.locked:
+ msg = "Locked thread '{}'".format(thread.title)
+ flash("Locked thread", "success")
+ else:
+ msg = "Unlocked thread '{}'".format(thread.title)
+ flash("Unlocked thread", "success")
+
+ addNotification(thread.watchers, current_user, msg, thread.getViewURL(), thread.package)
+ addAuditLog(AuditSeverity.MODERATION, current_user, msg, thread.getViewURL(), thread.package)
+
+ db.session.commit()
+
+ 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 reply.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())
+
+
+class CommentForm(FlaskForm):
+ comment = TextAreaField("Comment", [InputRequired(), Length(10, 2000)])
+ submit = SubmitField("Comment")
+
+
+@bp.route("/threads/<int:id>/edit/", methods=["GET", "POST"])
+@login_required
+def edit_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 not reply.checkPerm(current_user, Permission.EDIT_REPLY):
+ abort(403)
+
+ form = CommentForm(formdata=request.form, obj=reply)
+ if request.method == "POST" and form.validate():
+ comment = form.comment.data
+
+ msg = "Edited reply by {}".format(reply.author.display_name)
+ severity = AuditSeverity.NORMAL if current_user == reply.author else AuditSeverity.MODERATION
+ addNotification(reply.author, current_user, msg, thread.getViewURL(), thread.package)
+ addAuditLog(severity, current_user, msg, thread.getViewURL(), thread.package, reply.comment)
+
+ reply.comment = comment
+
+ db.session.commit()
+
+ return redirect(thread.getViewURL())
+
+ return render_template("threads/edit_reply.html", thread=thread, reply=reply, form=form)
@bp.route("/threads/<int:id>/", methods=["GET", "POST"])
def view(id):
- clearNotifications(url_for("threads.view", id=id))
-
thread = Thread.query.get(id)
if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD):
abort(404)
if current_user.is_authenticated and request.method == "POST":
comment = request.form["comment"]
+ if not thread.checkPerm(current_user, Permission.COMMENT_THREAD):
+ flash("You cannot comment on this thread", "danger")
+ return redirect(thread.getViewURL())
+
if not current_user.canCommentRL():
flash("Please wait before commenting again", "danger")
- if package:
- return redirect(package.getDetailsURL())
- else:
- return redirect(url_for("homepage.home"))
+ return redirect(thread.getViewURL())
- if len(comment) <= 500 and len(comment) > 3:
+ if len(comment) <= 2000 and len(comment) > 3:
reply = ThreadReply()
reply.author = current_user
reply.comment = comment
if not current_user in thread.watchers:
thread.watchers.append(current_user)
- msg = None
- if thread.package is None:
- msg = "New comment on '{}'".format(thread.title)
- else:
- msg = "New comment on '{}' on package {}".format(thread.title, thread.package.title)
-
-
- addNotification(thread.watchers, current_user, msg, url_for("threads.view", id=thread.id))
+ msg = "New comment on '{}'".format(thread.title)
+ addNotification(thread.watchers, current_user, msg, thread.getViewURL(), thread.package)
db.session.commit()
- return redirect(url_for("threads.view", id=id))
+ return redirect(thread.getViewURL())
else:
- flash("Comment needs to be between 3 and 500 characters.")
+ flash("Comment needs to be between 3 and 2000 characters.")
return render_template("threads/view.html", thread=thread)
class ThreadForm(FlaskForm):
title = StringField("Title", [InputRequired(), Length(3,100)])
- comment = TextAreaField("Comment", [InputRequired(), Length(10, 500)])
+ comment = TextAreaField("Comment", [InputRequired(), Length(10, 2000)])
private = BooleanField("Private")
submit = SubmitField("Open Thread")
+
@bp.route("/threads/new/", methods=["GET", "POST"])
@login_required
def new():
# Only allow creating one thread when not approved
elif is_review_thread and package.review_thread is not None:
flash("A review thread already exists!", "danger")
- return redirect(url_for("threads.view", id=package.review_thread.id))
+ return redirect(package.review_thread.getViewURL())
elif not current_user.canOpenThreadRL():
flash("Please wait before opening another thread", "danger")
if is_review_thread:
package.review_thread = thread
- notif_msg = None
+ if package.state == PackageState.READY_FOR_REVIEW and current_user not in package.maintainers:
+ package.state = PackageState.CHANGES_NEEDED
+
+
+ notif_msg = "New thread '{}'".format(thread.title)
if package is not None:
- notif_msg = "New thread '{}' on package {}".format(thread.title, package.title)
- addNotification(package.maintainers, current_user, notif_msg, url_for("threads.view", id=thread.id))
- else:
- notif_msg = "New thread '{}'".format(thread.title)
+ addNotification(package.maintainers, current_user, notif_msg, thread.getViewURL(), package)
editors = User.query.filter(User.rank >= UserRank.EDITOR).all()
- addNotification(editors, current_user, notif_msg, url_for("threads.view", id=thread.id))
+ addNotification(editors, current_user, notif_msg, thread.getViewURL(), package)
db.session.commit()
- return redirect(url_for("threads.view", id=thread.id))
+ return redirect(thread.getViewURL())
return render_template("threads/new.html", form=form, allow_private_change=allow_change, package=package)