2 # Copyright (C) 2018 rubenwardy
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <https://www.gnu.org/licenses/>.
20 bp = Blueprint("threads", __name__)
22 from flask_user import *
23 from app.models import *
24 from app.utils import triggerNotif, clearNotifications
28 from flask_wtf import FlaskForm
30 from wtforms.validators import *
31 from app.utils import get_int_or_abort
33 @bp.route("/threads/")
36 if not Permission.SEE_THREAD.check(current_user):
37 query = query.filter_by(private=False)
39 pid = request.args.get("pid")
41 pid = get_int_or_abort(pid)
42 query = query.filter_by(package_id=pid)
44 return render_template("threads/list.html", threads=query.all())
47 @bp.route("/threads/<int:id>/subscribe/", methods=["POST"])
50 thread = Thread.query.get(id)
51 if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD):
54 if current_user in thread.watchers:
55 flash("Already subscribed!", "success")
57 flash("Subscribed to thread", "success")
58 thread.watchers.append(current_user)
61 return redirect(url_for("threads.view", id=id))
64 @bp.route("/threads/<int:id>/unsubscribe/", methods=["POST"])
67 thread = Thread.query.get(id)
68 if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD):
71 if current_user in thread.watchers:
72 flash("Unsubscribed!", "success")
73 thread.watchers.remove(current_user)
76 flash("Not subscribed to thread", "success")
78 return redirect(url_for("threads.view", id=id))
81 @bp.route("/threads/<int:id>/", methods=["GET", "POST"])
83 clearNotifications(url_for("threads.view", id=id))
85 thread = Thread.query.get(id)
86 if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD):
89 if current_user.is_authenticated and request.method == "POST":
90 comment = request.form["comment"]
92 if not current_user.canCommentRL():
93 flash("Please wait before commenting again", "danger")
95 return redirect(package.getDetailsURL())
97 return redirect(url_for("homepage.home"))
99 if len(comment) <= 500 and len(comment) > 3:
100 reply = ThreadReply()
101 reply.author = current_user
102 reply.comment = comment
103 db.session.add(reply)
105 thread.replies.append(reply)
106 if not current_user in thread.watchers:
107 thread.watchers.append(current_user)
110 if thread.package is None:
111 msg = "New comment on '{}'".format(thread.title)
113 msg = "New comment on '{}' on package {}".format(thread.title, thread.package.title)
116 for user in thread.watchers:
117 if user != current_user:
118 triggerNotif(user, current_user, msg, url_for("threads.view", id=thread.id))
122 return redirect(url_for("threads.view", id=id))
125 flash("Comment needs to be between 3 and 500 characters.")
127 return render_template("threads/view.html", thread=thread)
130 class ThreadForm(FlaskForm):
131 title = StringField("Title", [InputRequired(), Length(3,100)])
132 comment = TextAreaField("Comment", [InputRequired(), Length(10, 500)])
133 private = BooleanField("Private")
134 submit = SubmitField("Open Thread")
136 @bp.route("/threads/new/", methods=["GET", "POST"])
139 form = ThreadForm(formdata=request.form)
142 if "pid" in request.args:
143 package = Package.query.get(int(request.args.get("pid")))
145 flash("Unable to find that package!", "danger")
147 # Don't allow making orphan threads on approved packages for now
151 def_is_private = request.args.get("private") or False
153 def_is_private = True
154 allow_change = package and package.approved
155 is_review_thread = package and not package.approved
157 # Check that user can make the thread
158 if not package.checkPerm(current_user, Permission.CREATE_THREAD):
159 flash("Unable to create thread!", "danger")
160 return redirect(url_for("homepage.home"))
162 # Only allow creating one thread when not approved
163 elif is_review_thread and package.review_thread is not None:
164 flash("A review thread already exists!", "danger")
165 return redirect(url_for("threads.view", id=package.review_thread.id))
167 elif not current_user.canOpenThreadRL():
168 flash("Please wait before opening another thread", "danger")
171 return redirect(package.getDetailsURL())
173 return redirect(url_for("homepage.home"))
176 elif request.method == "GET":
177 form.private.data = def_is_private
178 form.title.data = request.args.get("title") or ""
180 # Validate and submit
181 elif request.method == "POST" and form.validate():
183 thread.author = current_user
184 thread.title = form.title.data
185 thread.private = form.private.data if allow_change else def_is_private
186 thread.package = package
187 db.session.add(thread)
189 thread.watchers.append(current_user)
190 if package is not None and package.author != current_user:
191 thread.watchers.append(package.author)
193 reply = ThreadReply()
194 reply.thread = thread
195 reply.author = current_user
196 reply.comment = form.comment.data
197 db.session.add(reply)
199 thread.replies.append(reply)
204 package.review_thread = thread
207 if package is not None:
208 notif_msg = "New thread '{}' on package {}".format(thread.title, package.title)
209 for maintainer in package.maintainers:
210 triggerNotif(maintainer, current_user, notif_msg, url_for("threads.view", id=thread.id))
212 notif_msg = "New thread '{}'".format(thread.title)
214 for user in User.query.filter(User.rank >= UserRank.EDITOR).all():
215 triggerNotif(user, current_user, notif_msg, url_for("threads.view", id=thread.id))
219 return redirect(url_for("threads.view", id=thread.id))
222 return render_template("threads/new.html", form=form, allow_private_change=allow_change, package=package)