Foro: editor CKEditor 5 en los mensajes y moderación por POST+CSRF
- forum/forms.py: TopicForm/ReplyForm/EditPostForm con CKEditor5Widget (rich text) - vistas crear/responder/editar pasan el form; el HTML del editor se sanea con nh3 - acciones de moderación (bloquear, fijar, mover, borrar/restaurar temas y posts) ahora exigen POST y van con CSRF; las plantillas usan formularios en vez de enlaces GET - docs/FORO.md actualizado Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Formularios del foro con el editor enriquecido CKEditor 5 (el mismo que usa el
|
||||
resto de NovaWoW). El HTML resultante se sanea con nh3 al guardar (ver views).
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django_ckeditor_5.widgets import CKEditor5Widget
|
||||
|
||||
|
||||
class TopicForm(forms.Form):
|
||||
name = forms.CharField(
|
||||
max_length=45,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'Título del tema', 'maxlength': 45}),
|
||||
)
|
||||
text = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
|
||||
|
||||
class ReplyForm(forms.Form):
|
||||
text = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
|
||||
|
||||
class EditPostForm(forms.Form):
|
||||
text = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
@@ -32,6 +32,9 @@
|
||||
.forum-msg.error { background: rgba(196,30,58,.2); color: #ff9a9a; }
|
||||
.forum-msg.success { background: rgba(46,160,67,.2); color: #9affb0; }
|
||||
.forum-deleted { opacity: .55; }
|
||||
.forum-inline { display: inline; }
|
||||
.link-btn { background: none; border: none; color: #d79602; cursor: pointer; padding: 0; font: inherit; text-decoration: underline; }
|
||||
.link-btn.danger { color: #ff9a9a; }
|
||||
</style>
|
||||
{% if messages %}
|
||||
<div class="forum-messages forum-wrap">
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
<p style="color:#b1997f">Foro: <strong>{{ forum.name }}</strong></p>
|
||||
<form action="{% url 'forum_create_topic_submit' forum_id=forum.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="name" maxlength="45" placeholder="Título del tema" required>
|
||||
<textarea name="text" placeholder="Contenido del tema..." required></textarea>
|
||||
{{ form.name }}
|
||||
{{ form.text }}
|
||||
<div class="forum-actions">
|
||||
<button type="submit" class="forum-btn">Publicar tema</button>
|
||||
<a class="forum-btn" href="{% url 'forum_view_p1' forum_id=forum.id %}">Cancelar</a>
|
||||
</div>
|
||||
</form>
|
||||
{{ form.media }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
<div class="forum-wrap forum-form">
|
||||
<form action="{% url 'forum_update_post' post_id=post.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<textarea name="text" required>{{ post.text|safe }}</textarea>
|
||||
{{ form.text }}
|
||||
<div class="forum-actions">
|
||||
<button type="submit" class="forum-btn">Guardar cambios</button>
|
||||
<a class="forum-btn" href="{% url 'forum_topic_p1' topic_id=post.topic_id %}">Cancelar</a>
|
||||
</div>
|
||||
</form>
|
||||
{{ form.media }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
<span>
|
||||
{% if post.can_edit and not post.deleted %}
|
||||
<a href="{% url 'forum_edit_post' post_id=post.id %}">Editar</a>
|
||||
· <a href="{% url 'forum_delete_post' post_id=post.id %}" onclick="return confirm('¿Borrar este mensaje?')">Borrar</a>
|
||||
· <form class="forum-inline" action="{% url 'forum_delete_post' post_id=post.id %}" method="POST" onsubmit="return confirm('¿Borrar este mensaje?')">{% csrf_token %}<button type="submit" class="link-btn danger">Borrar</button></form>
|
||||
{% endif %}
|
||||
{% if post.deleted and forum_is_moderator %}
|
||||
<a href="{% url 'forum_restore_post' post_id=post.id %}">Restaurar</a>
|
||||
<form class="forum-inline" action="{% url 'forum_restore_post' post_id=post.id %}" method="POST">{% csrf_token %}<button type="submit" class="link-btn">Restaurar</button></form>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
@@ -69,11 +69,12 @@
|
||||
<h3 style="color:#d79602">Responder</h3>
|
||||
<form action="{% url 'forum_post_reply' topic_id=topic.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<textarea name="text" placeholder="Escribe tu respuesta..." required></textarea>
|
||||
{{ reply_form.text }}
|
||||
<div class="forum-actions">
|
||||
<button type="submit" class="forum-btn">Publicar respuesta</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ reply_form.media }}
|
||||
</div>
|
||||
{% elif topic.locked %}
|
||||
<p style="color:#b1997f">Este tema está bloqueado. No se admiten nuevas respuestas.</p>
|
||||
@@ -86,15 +87,15 @@
|
||||
<h3 style="color:#d79602">Moderación</h3>
|
||||
<div class="forum-actions">
|
||||
{% if topic.locked %}
|
||||
<a class="forum-btn" href="{% url 'forum_unlock_topic' topic_id=topic.id %}">Desbloquear</a>
|
||||
<form class="forum-inline" action="{% url 'forum_unlock_topic' topic_id=topic.id %}" method="POST">{% csrf_token %}<button type="submit" class="forum-btn">Desbloquear</button></form>
|
||||
{% else %}
|
||||
<a class="forum-btn" href="{% url 'forum_lock_topic' topic_id=topic.id %}">Bloquear</a>
|
||||
<form class="forum-inline" action="{% url 'forum_lock_topic' topic_id=topic.id %}" method="POST">{% csrf_token %}<button type="submit" class="forum-btn">Bloquear</button></form>
|
||||
{% endif %}
|
||||
<a class="forum-btn" href="{% url 'forum_toggle_sticky' topic_id=topic.id %}">{% if topic.sticky %}Quitar fijado{% else %}Fijar{% endif %}</a>
|
||||
<form class="forum-inline" action="{% url 'forum_toggle_sticky' topic_id=topic.id %}" method="POST">{% csrf_token %}<button type="submit" class="forum-btn">{% if topic.sticky %}Quitar fijado{% else %}Fijar{% endif %}</button></form>
|
||||
{% if topic.deleted %}
|
||||
<a class="forum-btn" href="{% url 'forum_restore_topic' topic_id=topic.id %}">Restaurar tema</a>
|
||||
<form class="forum-inline" action="{% url 'forum_restore_topic' topic_id=topic.id %}" method="POST">{% csrf_token %}<button type="submit" class="forum-btn">Restaurar tema</button></form>
|
||||
{% else %}
|
||||
<a class="forum-btn danger" href="{% url 'forum_delete_topic' topic_id=topic.id %}" onclick="return confirm('¿Borrar el tema completo?')">Borrar tema</a>
|
||||
<form class="forum-inline" action="{% url 'forum_delete_topic' topic_id=topic.id %}" method="POST" onsubmit="return confirm('¿Borrar el tema completo?')">{% csrf_token %}<button type="submit" class="forum-btn danger">Borrar tema</button></form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if move_forums %}
|
||||
|
||||
+23
-5
@@ -16,6 +16,7 @@ from django.urls import reverse
|
||||
from . import db
|
||||
from . import permissions as perm
|
||||
from . import sanitize
|
||||
from .forms import TopicForm, ReplyForm, EditPostForm
|
||||
|
||||
MIN_TEXT_LEN = 5
|
||||
|
||||
@@ -110,6 +111,7 @@ def view_topic(request, topic_id, page=1):
|
||||
'pagination': pag,
|
||||
'can_reply': perm.can_post(request) and not topic['locked'],
|
||||
'move_forums': db.list_forums_for_move(topic['forum_id']) if perm.is_moderator(request) else [],
|
||||
'reply_form': ReplyForm(),
|
||||
}))
|
||||
|
||||
|
||||
@@ -127,6 +129,7 @@ def create_topic(request, forum_id):
|
||||
return redirect('app_forum')
|
||||
return render(request, 'forum/create_topic.html', _base_context(request, {
|
||||
'forum': forum,
|
||||
'form': TopicForm(),
|
||||
}))
|
||||
|
||||
|
||||
@@ -204,6 +207,7 @@ def edit_post(request, post_id):
|
||||
return redirect('forum_topic_p1', topic_id=post['topic_id'])
|
||||
return render(request, 'forum/edit_post.html', _base_context(request, {
|
||||
'post': post,
|
||||
'form': EditPostForm(initial={'text': post['text']}),
|
||||
}))
|
||||
|
||||
|
||||
@@ -242,6 +246,8 @@ def delete_post(request, post_id):
|
||||
if not perm.can_edit_post(request, post):
|
||||
messages.error(request, 'No tienes permiso.')
|
||||
return redirect('forum_topic_p1', topic_id=post['topic_id'])
|
||||
if request.method != 'POST':
|
||||
return redirect('forum_topic_p1', topic_id=post['topic_id'])
|
||||
db.set_post_deleted(post_id, True)
|
||||
messages.success(request, 'Post borrado.')
|
||||
return redirect('forum_topic_p1', topic_id=post['topic_id'])
|
||||
@@ -253,6 +259,8 @@ def restore_post(request, post_id):
|
||||
post = db.get_post(post_id)
|
||||
if not post:
|
||||
return redirect('app_forum')
|
||||
if request.method != 'POST':
|
||||
return redirect('forum_topic_p1', topic_id=post['topic_id'])
|
||||
db.set_post_deleted(post_id, False)
|
||||
messages.success(request, 'Post restaurado.')
|
||||
return redirect('forum_topic_p1', topic_id=post['topic_id'])
|
||||
@@ -269,8 +277,18 @@ def _mod_only(request):
|
||||
return None
|
||||
|
||||
|
||||
def lock_topic(request, topic_id):
|
||||
def _mod_post_only(request):
|
||||
"""Exige moderador y método POST (CSRF)."""
|
||||
block = _mod_only(request)
|
||||
if block:
|
||||
return block
|
||||
if request.method != 'POST':
|
||||
return redirect('app_forum')
|
||||
return None
|
||||
|
||||
|
||||
def lock_topic(request, topic_id):
|
||||
block = _mod_post_only(request)
|
||||
if block:
|
||||
return block
|
||||
db.set_topic_flag(topic_id, 'locked', True)
|
||||
@@ -279,7 +297,7 @@ def lock_topic(request, topic_id):
|
||||
|
||||
|
||||
def unlock_topic(request, topic_id):
|
||||
block = _mod_only(request)
|
||||
block = _mod_post_only(request)
|
||||
if block:
|
||||
return block
|
||||
db.set_topic_flag(topic_id, 'locked', False)
|
||||
@@ -288,7 +306,7 @@ def unlock_topic(request, topic_id):
|
||||
|
||||
|
||||
def toggle_sticky(request, topic_id):
|
||||
block = _mod_only(request)
|
||||
block = _mod_post_only(request)
|
||||
if block:
|
||||
return block
|
||||
topic = db.get_topic(topic_id, include_hidden=True)
|
||||
@@ -299,7 +317,7 @@ def toggle_sticky(request, topic_id):
|
||||
|
||||
|
||||
def delete_topic(request, topic_id):
|
||||
block = _mod_only(request)
|
||||
block = _mod_post_only(request)
|
||||
if block:
|
||||
return block
|
||||
topic = db.get_topic(topic_id, include_hidden=True)
|
||||
@@ -311,7 +329,7 @@ def delete_topic(request, topic_id):
|
||||
|
||||
|
||||
def restore_topic(request, topic_id):
|
||||
block = _mod_only(request)
|
||||
block = _mod_post_only(request)
|
||||
if block:
|
||||
return block
|
||||
db.set_topic_flag(topic_id, 'deleted', False)
|
||||
|
||||
Reference in New Issue
Block a user