moderation: added locking capability to topics and programs

This commit is contained in:
Darks 2023-06-06 21:35:29 +02:00
parent b9becbf21f
commit 3c671da85c
Signed by: Darks
GPG Key ID: 7515644268BE1433
13 changed files with 101 additions and 13 deletions

View File

@ -25,6 +25,7 @@
# delete.accounts
# delete.shared-files
# move.posts
# lock.threads
#
# Shoutbox:
# shoutbox.kick
@ -58,7 +59,7 @@
publish.schedule-posts publish.pin-posts publish.shared-files
edit.posts edit.tests edit.accounts edit.trophies
delete.posts delete.tests delete.accounts delete.shared-files
move.posts
move.posts lock.threads
shoutbox.kick shoutbox.ban
misc.unlimited-pms misc.dev-infos misc.admin-panel
misc.no-upload-limits misc.arbitrary-login
@ -69,7 +70,7 @@
privs: forum.access.admin
edit.posts edit.tests
delete.posts delete.tests
move.posts
move.posts lock.threads
shoutbox.kick shoutbox.ban
misc.unlimited-pms misc.no-upload-limits
-

View File

@ -28,6 +28,12 @@ class Comment(Post):
def is_top_comment(self):
return self.id == self.thread.top_comment_id
@property
def is_metacontent(self):
"""Whether if this post is metacontent (topic, program) or actual content"""
return False
def __init__(self, author, text, thread):
"""
Create a new Comment in a thread.

View File

@ -29,6 +29,12 @@ class Post(db.Model):
'polymorphic_on': type
}
@property
def is_metacontent(self):
"""Whether if this post is metacontent (topic, program) or actual content"""
return True
def __init__(self, author):
"""
Create a new Post.

View File

@ -18,6 +18,9 @@ class Thread(db.Model):
owner_topic = db.relationship('Topic')
owner_program = db.relationship('Program')
# Whether the thread is locked
locked = db.Column(db.Boolean, default=False)
# Other fields populated automatically through relations:
# <comments> The list of comments (of type Comment)

View File

@ -263,6 +263,13 @@ class Member(User):
post = comment.thread.owner_post
return self.can_edit_post(post) and (comment.author == post.author)
def can_lock_thread(self, post):
"""Whether this member can lock the thread associated with the post"""
print(post.id, post.is_metacontent)
if not post.is_metacontent:
return False
return self.priv("lock.threads")
def can_access_file(self, file):
"""Whether this member can access the file."""
return self.can_access_post(file.comment)

View File

@ -31,7 +31,7 @@ def forum_topic(f, page):
else:
form = AnonymousCommentForm()
if form.validate_on_submit() and (
if form.validate_on_submit() and not t.thread.locked and (
V5Config.ENABLE_GUEST_POST or \
(current_user.is_authenticated and current_user.can_post_in_forum(f))):

View File

@ -9,6 +9,7 @@ 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
from wtforms import BooleanField
@ -24,7 +25,7 @@ def edit_post(postid):
referrer = urlparse(request.args.get('r', default = '/', type = str)).path
print(referrer)
p = Post.query.filter_by(id=postid).first_or_404()
p = Post.query.get_or_404(postid)
# Check permissions
if not current_user.can_edit_post(p):
@ -104,7 +105,7 @@ def edit_post(postid):
@check_csrf
def delete_post(postid):
next_page = request.referrer
p = Post.query.filter_by(id=postid).first_or_404()
p = Post.query.get_or_404(postid)
xp = -1
if not current_user.can_delete_post(p):
@ -142,7 +143,7 @@ def delete_post(postid):
@login_required
@check_csrf
def set_post_topcomment(postid):
comment = Post.query.filter_by(id=postid).first_or_404()
comment = Post.query.get_or_404(postid)
if current_user.can_set_topcomment(comment):
comment.thread.top_comment = comment
@ -155,7 +156,7 @@ def set_post_topcomment(postid):
@app.route('/post/deplacer/<int:postid>', methods=['GET', 'POST'])
@login_required
def move_post(postid):
comment = Post.query.filter_by(id=postid).first_or_404()
comment = Post.query.get_or_404(postid)
if not current_user.can_edit_post(comment):
abort(403)
@ -194,3 +195,25 @@ def move_post(postid):
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é")
else:
flash(f"Le thread a été déverrouillé")
return redirect(request.referrer)

View File

@ -19,7 +19,7 @@ def program_view(page):
else:
form = AnonymousCommentForm()
if form.validate_on_submit() and (
if form.validate_on_submit() and not p.thread.locked and (
V5Config.ENABLE_GUEST_POST or current_user.is_authenticated):
# Manage author

View File

@ -36,7 +36,9 @@
</div>
{% endif %}
{% if V5Config.ENABLE_GUEST_POST
{% if t.thread.locked %}
<div class="locked">Les commentaires sont verrouillés</div>
{% elif V5Config.ENABLE_GUEST_POST
or (current_user.is_authenticated and current_user.can_post_in_forum(t.forum)) %}
<div class=form>
<h3>Commenter le sujet</h3>

View File

@ -60,7 +60,10 @@
{{ widget_pagination.paginate(comments, 'program_view', p) }}
{% if V5Config.ENABLE_GUEST_POST or current_user.is_authenticated %}
{% if p.thread.locked %}
<div class="locked">Les commentaires sont verrouillés</div>
{% elif V5Config.ENABLE_GUEST_POST or current_user.is_authenticated %}
<div class=form>
<h3>Commenter le programme</h3>
<form action="" method="post" enctype="multipart/form-data">

View File

@ -12,6 +12,7 @@
{% set can_punish = auth and current_user.can_punish_post(post) %}
{% set can_topcomm = auth and current_user.can_set_topcomment(post) %}
{% set can_move = auth and current_user.can_edit_post(post) and post.type == "comment" %}
{% set can_lock = auth and current_user.can_lock_thread(post) %}
{% if post.type == "topic" %}
{% set suffix = " le sujet" %}
@ -19,7 +20,7 @@
{% set suffix = " le programme" %}
{% endif %}
{% if can_edit or can_move or can_delete or can_punish or can_topcomm %}
{% if can_edit or can_move or can_delete or can_punish or can_topcomm or can_lock %}
<details>
<summary><b></b></summary>
<div class='context-menu'>
@ -41,6 +42,11 @@
{% if can_topcomm %}
<a href="{{ url_for('set_post_topcomment', postid=post.id, csrf_token=csrf_token()) }}">Utiliser comme en-tête</a>
{% endif %}
{% if can_lock %}
{% set prefix = "Déverrouiller" if post.thread.locked else "Verrouiller" %}
<a href="{{ url_for('lock_thread', postid=post.id, csrf_token=csrf_token()) }}">{{ prefix }}{{ suffix }}</a>
{% endif %}
</div>
</details>
{% endif %}

View File

@ -73,8 +73,7 @@ def run_migrations_online():
context.configure(connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args,
compare_type=True)
**current_app.extensions['migrate'].configure_args)
try:
with context.begin_transaction():

View File

@ -0,0 +1,32 @@
"""Threads: add locking capability
Revision ID: 5ffc4e562ed8
Revises: ba47de949e59
Create Date: 2023-06-06 21:19:27.950523
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5ffc4e562ed8'
down_revision = 'ba47de949e59'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('thread', schema=None) as batch_op:
batch_op.add_column(sa.Column('locked', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('thread', schema=None) as batch_op:
batch_op.drop_column('locked')
# ### end Alembic commands ###