Añade foro de comunidad integrado (app forum) con el diseño de NovaWoW

- app 'forum': portada (categorías/foros), ver foro y tema (paginado), crear tema,
  responder, editar/borrar posts y moderación (fijar, bloquear, mover, borrar/restaurar)
- lee/escribe la BD acore_web (5ª conexión, DB_NAME_WEB) por SQL directo, tablas
  forum_categories/forums/forum_topics/forum_posts/forum_reads (portado de NovaWeb-main)
- identidad = cuenta de juego en sesión; permisos simplificados por gmlevel
  (FORUM_MOD_GMLEVEL) en vez de la matriz group_level×permission del original
- saneado del HTML de posts con nh3 (evita el XSS del original); POST+CSRF en formularios
- plantillas con los partials del sitio; enlace 'FOROS' del menú apunta al foro interno
- sql/forum_schema.sql y docs/FORO.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 17:10:55 +00:00
parent 2162fe34d0
commit 2c30844bf7
21 changed files with 1385 additions and 1 deletions
+71
View File
@@ -0,0 +1,71 @@
"""
Modelo de permisos del foro (versión simplificada y mantenible).
En lugar de la matriz group_level×permission del proyecto original, usamos
reglas claras basadas en:
* si el usuario ha iniciado sesión y tiene una cuenta de juego seleccionada
(``request.session['account_id']``),
* su nivel GM (``acore_auth.account_access.gmlevel``),
* y la propiedad del contenido (autor == usuario).
"""
from django.conf import settings
from django.db import connections
def get_account_id(request):
"""Id de la cuenta de juego seleccionada en sesión, o None."""
return request.session.get('account_id')
def is_authenticated(request):
return bool(request.session.get('account_id'))
def get_gmlevel(account_id):
"""Nivel GM máximo de una cuenta (acore_auth.account_access). 0 si no tiene."""
if not account_id:
return 0
try:
with connections['acore_auth'].cursor() as cursor:
cursor.execute(
"SELECT MAX(gmlevel) FROM account_access WHERE id = %s",
[account_id],
)
row = cursor.fetchone()
return int(row[0]) if row and row[0] is not None else 0
except Exception:
return 0
def is_moderator(request):
"""¿El usuario puede moderar el foro?"""
account_id = get_account_id(request)
if not account_id:
return False
return get_gmlevel(account_id) >= settings.FORUM_MOD_GMLEVEL
def can_post(request):
"""¿Puede crear temas / responder? Cualquier usuario autenticado."""
return is_authenticated(request)
def can_edit_post(request, post):
"""Puede editar/borrar un post: su autor o un moderador."""
account_id = get_account_id(request)
if not account_id:
return False
if post and int(post.get('poster_id') or 0) == int(account_id):
return True
return is_moderator(request)
def forum_context(request):
"""Datos comunes de permisos para las plantillas."""
return {
'forum_is_authenticated': is_authenticated(request),
'forum_is_moderator': is_moderator(request),
'forum_account_id': get_account_id(request),
'forum_username': request.session.get('username'),
}