diff --git a/app/data/groups.yaml b/app/data/groups.yaml index f81d603..a2eab56 100644 --- a/app/data/groups.yaml +++ b/app/data/groups.yaml @@ -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 - diff --git a/app/models/comment.py b/app/models/comment.py index e502362..285b148 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -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. diff --git a/app/models/post.py b/app/models/post.py index c272a0e..e17c8a2 100644 --- a/app/models/post.py +++ b/app/models/post.py @@ -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. diff --git a/app/models/thread.py b/app/models/thread.py index cedb4d6..a431a77 100644 --- a/app/models/thread.py +++ b/app/models/thread.py @@ -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: # The list of comments (of type Comment) diff --git a/app/models/user.py b/app/models/user.py index afbab8c..61f7a9c 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -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) diff --git a/app/routes/forum/topic.py b/app/routes/forum/topic.py index 0e63a9a..10dab56 100644 --- a/app/routes/forum/topic.py +++ b/app/routes/forum/topic.py @@ -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))): diff --git a/app/routes/posts/edit.py b/app/routes/posts/edit.py index 93ac5a3..1a3a12a 100644 --- a/app/routes/posts/edit.py +++ b/app/routes/posts/edit.py @@ -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/', 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/', 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) \ No newline at end of file diff --git a/app/routes/programs/program.py b/app/routes/programs/program.py index 286538a..30bde3d 100644 --- a/app/routes/programs/program.py +++ b/app/routes/programs/program.py @@ -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 diff --git a/app/templates/forum/topic.html b/app/templates/forum/topic.html index f4bcbd7..69a94cc 100644 --- a/app/templates/forum/topic.html +++ b/app/templates/forum/topic.html @@ -36,7 +36,9 @@ {% endif %} - {% if V5Config.ENABLE_GUEST_POST + {% if t.thread.locked %} +
Les commentaires sont verrouillés
+ {% elif V5Config.ENABLE_GUEST_POST or (current_user.is_authenticated and current_user.can_post_in_forum(t.forum)) %}

Commenter le sujet

diff --git a/app/templates/programs/program.html b/app/templates/programs/program.html index 1e5b5d0..e53535a 100644 --- a/app/templates/programs/program.html +++ b/app/templates/programs/program.html @@ -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 %} +
Les commentaires sont verrouillés
+ {% elif V5Config.ENABLE_GUEST_POST or current_user.is_authenticated %}

Commenter le programme

diff --git a/app/templates/widgets/thread.html b/app/templates/widgets/thread.html index 9d6dc16..827c2d2 100644 --- a/app/templates/widgets/thread.html +++ b/app/templates/widgets/thread.html @@ -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 %}
@@ -41,6 +42,11 @@ {% if can_topcomm %} Utiliser comme en-tête {% endif %} + + {% if can_lock %} + {% set prefix = "Déverrouiller" if post.thread.locked else "Verrouiller" %} + {{ prefix }}{{ suffix }} + {% endif %}
{% endif %} diff --git a/migrations/env.py b/migrations/env.py index 1b619d0..9a8d775 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -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(): diff --git a/migrations/versions/5ffc4e562ed8_threads_add_locking_capability.py b/migrations/versions/5ffc4e562ed8_threads_add_locking_capability.py new file mode 100644 index 0000000..1be84a3 --- /dev/null +++ b/migrations/versions/5ffc4e562ed8_threads_add_locking_capability.py @@ -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 ###