293 lines
10 KiB
Python
293 lines
10 KiB
Python
from app import app, db, V5Config
|
|
from app.models.attachment import Attachment
|
|
from app.models.comment import Comment
|
|
from app.models.forum import Forum
|
|
from app.models.post import Post
|
|
from app.models.program import Program
|
|
from app.models.thread import Thread
|
|
from app.models.topic import Topic
|
|
from app.models.user import Member
|
|
from app.utils.render import render
|
|
from app.utils.check_csrf import check_csrf
|
|
from app.utils.priv_required import priv_required
|
|
from app.forms.forum import CommentEditForm, AnonymousCommentEditForm, TopicEditForm
|
|
from app.forms.post import MovePost, SearchThread, MergePost
|
|
from wtforms import BooleanField
|
|
from urllib.parse import urlparse
|
|
from flask import redirect, url_for, abort, request, flash
|
|
from flask_login import login_required, current_user
|
|
from sqlalchemy import text, and_
|
|
from datetime import timedelta
|
|
|
|
@app.route('/post/editer/<int:postid>', methods=['GET','POST'])
|
|
@login_required
|
|
def edit_post(postid):
|
|
# FIXME: Maybe not safe
|
|
referrer = urlparse(request.args.get('r', default = '/', type = str)).path
|
|
print(referrer)
|
|
|
|
p = Post.query.get_or_404(postid)
|
|
|
|
# Check permissions
|
|
if not current_user.can_edit_post(p):
|
|
abort(403)
|
|
|
|
if isinstance(p, Comment):
|
|
base = CommentEditForm
|
|
comment = p
|
|
elif isinstance(p, Topic):
|
|
base = TopicEditForm
|
|
comment = p.thread.top_comment
|
|
else:
|
|
abort(404)
|
|
|
|
class TheForm(base):
|
|
pass
|
|
for a in comment.attachments:
|
|
setattr(TheForm, f'a{a.id}', BooleanField(f'a{a.id}'))
|
|
setattr(TheForm, 'attachment_list',
|
|
{ f'a{a.id}': a for a in comment.attachments })
|
|
form = TheForm()
|
|
|
|
if isinstance(p, Topic):
|
|
forums = sorted(Forum.query.all(), key=lambda f: f.url)
|
|
forums = [f for f in forums if current_user.can_post_in_forum(f)]
|
|
form.forum.choices = [(f.url, f"{f.url}: {f.name}") for f in forums]
|
|
|
|
if form.validate_on_submit():
|
|
comment.text = form.message.data
|
|
|
|
# Remove attachments
|
|
for id, a in form.attachment_list.items():
|
|
if form[id].data:
|
|
a.delete()
|
|
|
|
# Add new attachments
|
|
attachments = []
|
|
for file in form.attachments.data:
|
|
if file.filename != "":
|
|
a = Attachment(file, comment)
|
|
attachments.append((a, file))
|
|
db.session.add(a)
|
|
|
|
comment.touch()
|
|
db.session.add(comment)
|
|
|
|
if isinstance(p, Topic):
|
|
p.title = form.title.data
|
|
p.summary = form.summary.data
|
|
# If there's a thumbnail, set it
|
|
if form.thumbnail.data:
|
|
p.thumbnail = comment.attachments[int(form.thumbnail.data)-1]
|
|
else:
|
|
p.thumbnail = None
|
|
f = Forum.query.filter_by(url=form.forum.data).first_or_404()
|
|
if current_user.can_post_in_forum(f):
|
|
p.forum = f
|
|
db.session.merge(p)
|
|
|
|
db.session.commit()
|
|
|
|
for a, file in attachments:
|
|
a.set_file(file)
|
|
|
|
flash('Modifications enregistrées', 'ok')
|
|
admin_msg = "[admin] " if current_user != p.author else ""
|
|
app.v5logger.info(f"{admin_msg}<{current_user.name}> has edited the post #{p.id}")
|
|
|
|
# Determine topic URL now, in case forum was changed
|
|
if isinstance(p, Topic):
|
|
return redirect(url_for('forum_topic', f=p.forum, page=(p,1)))
|
|
else:
|
|
return redirect(referrer)
|
|
|
|
# Non-submitted form
|
|
if isinstance(p, Comment):
|
|
form.message.data = p.text
|
|
return render('forum/edit_comment.html', comment=p, form=form)
|
|
elif isinstance(p, Topic):
|
|
form.message.data = p.thread.top_comment.text
|
|
form.title.data = p.title
|
|
form.forum.data = p.forum.url
|
|
form.summary.data = p.summary
|
|
return render('forum/edit_topic.html', t=p, form=form)
|
|
|
|
@app.route('/post/supprimer/<int:postid>', methods=['GET','POST'])
|
|
@login_required
|
|
@check_csrf
|
|
def delete_post(postid):
|
|
next_page = request.referrer
|
|
p = Post.query.get_or_404(postid)
|
|
xp = -1
|
|
|
|
if not current_user.can_delete_post(p):
|
|
abort(403)
|
|
|
|
# Is a penalty deletion
|
|
is_penalty = request.args.get('penalty') == 'True' \
|
|
and current_user.priv('delete.posts')
|
|
|
|
# Users who need to have their trophies updated
|
|
authors = set()
|
|
|
|
# When deleting topics, return to forum page
|
|
if isinstance(p, Topic):
|
|
next_page = url_for('forum_page', f=p.forum)
|
|
xp = -2
|
|
|
|
for comment in p.thread.comments:
|
|
if isinstance(comment.author, Member):
|
|
comment.author.add_xp(-1)
|
|
db.session.merge(comment.author)
|
|
authors.add(comment.author)
|
|
|
|
if isinstance(p.author, Member):
|
|
factor = 3 if is_penalty else 1
|
|
p.author.add_xp(xp * factor)
|
|
db.session.merge(p.author)
|
|
authors.add(p.author)
|
|
|
|
admin_msg = "[admin] " if current_user != p.author else ""
|
|
p.delete()
|
|
db.session.commit()
|
|
|
|
for author in authors:
|
|
author.update_trophies("new-post")
|
|
|
|
flash("Le contenu a été supprimé", 'ok')
|
|
penalty_msg = " (with penalty)" if is_penalty else ""
|
|
app.v5logger.info(f"{admin_msg}<{current_user.name}> has deleted the post #{p.id}{penalty_msg}")
|
|
|
|
return redirect(next_page)
|
|
|
|
@app.route('/post/entete/<int:postid>', methods=['GET'])
|
|
@login_required
|
|
@check_csrf
|
|
def set_post_topcomment(postid):
|
|
comment = Post.query.get_or_404(postid)
|
|
|
|
if current_user.can_set_topcomment(comment):
|
|
comment.thread.top_comment = comment
|
|
db.session.add(comment.thread)
|
|
db.session.commit()
|
|
flash("Le post a été défini comme nouvel en-tête", 'ok')
|
|
admin_msg = "[admin] " if current_user != comment.author else ""
|
|
app.v5logger.info(f"{admin_msg}<{current_user.name}> has set a new top comment on thread #{comment.thread.id}")
|
|
|
|
return redirect(request.referrer)
|
|
|
|
|
|
@app.route('/post/deplacer/<int:postid>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def move_post(postid):
|
|
comment = Post.query.get_or_404(postid)
|
|
|
|
if not current_user.can_edit_post(comment):
|
|
abort(403)
|
|
|
|
if not isinstance(comment, Comment):
|
|
flash("Vous ne pouvez pas déplacer un message principal", 'error')
|
|
abort(403)
|
|
|
|
move_form = MovePost(prefix="move_")
|
|
search_form = SearchThread(prefix="thread_")
|
|
|
|
# There is a bug with validate_on_submit
|
|
keyword = search_form.name.data if search_form.search.data else ""
|
|
|
|
# Get 10 last corresponding threads
|
|
# TODO: add support for every MainPost
|
|
req = text("""SELECT thread.id, topic.title FROM thread
|
|
INNER JOIN topic ON topic.thread_id = thread.id
|
|
WHERE lower(topic.title) LIKE lower(:keyword)
|
|
ORDER BY thread.id DESC LIMIT 10""")
|
|
threads = list(db.session.execute(req, {'keyword': '%'+keyword+'%'}))
|
|
move_form.thread.choices = [(t[0], f"{t[1]}") for t in threads]
|
|
|
|
if move_form.validate_on_submit():
|
|
thread = Thread.query.get_or_404(move_form.thread.data)
|
|
owner_post = thread.owner_post
|
|
|
|
if isinstance(owner_post, Topic):
|
|
t = owner_post
|
|
if not current_user.can_access_forum(t.forum):
|
|
abort(403)
|
|
comment.thread = thread
|
|
db.session.add(comment)
|
|
db.session.commit()
|
|
flash("Le topic a été déplacé", 'ok')
|
|
admin_msg = "[admin] " if current_user != comment.author else ""
|
|
app.v5logger.info(f"{admin_msg}<{current_user.name}> has moved the comment #{comment.id} to thread #{thread.id}")
|
|
return redirect(url_for('forum_topic', f=t.forum, page=(t,1)))
|
|
|
|
return render('post/move_post.html', comment=comment,
|
|
search_form=search_form, move_form=move_form)
|
|
|
|
@app.route('/post/verrouiller/<int:postid>', methods=['GET'])
|
|
@priv_required("lock.threads")
|
|
@check_csrf
|
|
def lock_thread(postid):
|
|
post = Post.query.get_or_404(postid)
|
|
|
|
if not post.is_metacontent:
|
|
flash("Vous ne pouvez pas verrouiller ce contenu (n'est pas de type metacontenu)", 'error')
|
|
abort(403)
|
|
|
|
post.thread.locked = not post.thread.locked
|
|
|
|
db.session.add(post.thread)
|
|
db.session.commit()
|
|
|
|
if post.thread.locked:
|
|
flash(f"Le thread a été verrouillé", 'ok')
|
|
app.v5logger.info(f"[admin] <{current_user.name}> has locked the thread #{post.thread.id}")
|
|
else:
|
|
flash(f"Le thread a été déverrouillé", 'ok')
|
|
app.v5logger.info(f"[admin] <{current_user.name}> has unlocked the thread #{post.thread.id}")
|
|
|
|
return redirect(request.referrer)
|
|
|
|
@app.route('/post/fusionner/<int:postid>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def merge_post(postid):
|
|
comment = Comment.query.get_or_404(postid)
|
|
|
|
# Get the posts from the same user in the same topic that are separated
|
|
# by less than V5Config.MERGE_AGE_THRESHOLD
|
|
compatible_comments = Comment.query.filter(and_(
|
|
Comment.thread_id == comment.thread_id,
|
|
and_(
|
|
Post.author_id == comment.author_id,
|
|
and_(
|
|
Post.date_created > comment.date_created,
|
|
Post.date_created <= comment.date_created + timedelta(0, V5Config.MERGE_AGE_THRESHOLD)
|
|
)
|
|
)
|
|
))
|
|
|
|
merge_form = MergePost()
|
|
merge_form.post.choices = [(t.id, f"{t.text[:30]}[…]") for t in list(compatible_comments)]
|
|
|
|
if merge_form.validate_on_submit():
|
|
merge_comment = Comment.query.filter_by(id=merge_form.post.data).first_or_404()
|
|
|
|
comment.text += f"\n\n---\n\n{merge_comment.text}"
|
|
|
|
# Change the modification date only if the other post is more recent
|
|
if merge_comment.date_created > comment.date_modified:
|
|
comment.date_modified = merge_comment.date_created
|
|
|
|
if merge_comment.is_top_comment:
|
|
comment.thread.set_top_comment(comment)
|
|
|
|
db.session.add(comment)
|
|
merge_comment.delete()
|
|
db.session.commit()
|
|
|
|
if isinstance(comment.thread.owner_post, Topic):
|
|
return redirect(url_for('forum_topic', f=comment.thread.owner_post.forum, page=[comment.thread.owner_post, -1]))
|
|
elif isinstance(comment.thread.owner_post, Program):
|
|
return redirect(url_for('program_view', page=[comment.thread.owner_post, -1]))
|
|
|
|
return render('post/merge_post.html', comment=comment, merge_form=merge_form)
|