Foro: paginación en la búsqueda y contador de visitas por tema

- búsqueda paginada (count_search_topics + offset/limit) con controles en la plantilla
- contador de visitas: columna opcional forum_topics.views con detección automática
  (has_views_column), increment_views al ver el tema, mostrado en lista y cabecera
- sql/forum_views.sql (ALTER opcional) + docs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 17:22:05 +00:00
parent fd49d6cd96
commit eb93758c43
7 changed files with 90 additions and 12 deletions
+54 -7
View File
@@ -25,6 +25,35 @@ def _conn():
return connections['acore_web']
# Detección perezosa de la columna opcional forum_topics.views (contador de visitas)
_views_supported = None
def has_views_column():
global _views_supported
if _views_supported is None:
try:
with _conn().cursor() as cursor:
cursor.execute("SELECT views FROM forum_topics LIMIT 1")
_views_supported = True
except Exception:
_views_supported = False
return _views_supported
def increment_views(topic_id):
"""Suma 1 a las visitas del tema (si existe la columna)."""
if not has_views_column():
return
try:
with _conn().cursor() as cursor:
cursor.execute(
"UPDATE forum_topics SET views = views + 1 WHERE id = %s", [topic_id]
)
except Exception:
pass
# --------------------------------------------------------------------------
# Portada: categorías + foros (con estadísticas)
# --------------------------------------------------------------------------
@@ -110,11 +139,12 @@ def count_topics(forum_id):
def get_topics(forum_id, offset, limit):
views_sel = "t.views," if has_views_column() else "0 AS views,"
with _conn().cursor() as cursor:
cursor.execute(
"""
f"""
SELECT t.id, t.name, t.poster, t.poster_id, t.created, t.sticky,
t.locked, t.moved, t.updated_at,
t.locked, t.moved, t.updated_at, {views_sel}
(SELECT COUNT(*) FROM forum_posts p
WHERE p.topic_id = t.id AND p.deleted = 0) AS post_count,
(SELECT p2.poster FROM forum_posts p2
@@ -138,10 +168,11 @@ def get_topics(forum_id, offset, limit):
# --------------------------------------------------------------------------
def get_topic(topic_id, include_hidden=False):
views_sel = ", views" if has_views_column() else ", 0 AS views"
with _conn().cursor() as cursor:
cursor.execute(
"SELECT id, forum_id, name, poster, poster_id, created, sticky, "
"locked, deleted, moved, updated_at FROM forum_topics WHERE id = %s",
f"locked, deleted, moved, updated_at{views_sel} FROM forum_topics WHERE id = %s",
[topic_id],
)
topic = _dictfetchone(cursor)
@@ -333,22 +364,38 @@ def get_display_name(poster_id):
return None
def search_topics(query, limit=50):
def count_search_topics(query):
like = f"%{query}%"
with _conn().cursor() as cursor:
cursor.execute(
"""
SELECT COUNT(DISTINCT t.id)
FROM forum_topics t
JOIN forums f ON f.id = t.forum_id AND f.visibility = 1 AND f.deleted = 0
LEFT JOIN forum_posts p ON p.topic_id = t.id AND p.deleted = 0
WHERE t.deleted = 0 AND (t.name LIKE %s OR p.text LIKE %s)
""",
[like, like],
)
return cursor.fetchone()[0]
def search_topics(query, offset=0, limit=20):
"""Busca temas por título o por contenido de sus posts (foros visibles)."""
like = f"%{query}%"
with _conn().cursor() as cursor:
cursor.execute(
"""
SELECT DISTINCT t.id, t.name, t.poster, t.poster_id, t.forum_id,
t.created, f.name AS forum_name
t.created, t.updated_at, f.name AS forum_name
FROM forum_topics t
JOIN forums f ON f.id = t.forum_id AND f.visibility = 1 AND f.deleted = 0
LEFT JOIN forum_posts p ON p.topic_id = t.id AND p.deleted = 0
WHERE t.deleted = 0 AND (t.name LIKE %s OR p.text LIKE %s)
ORDER BY t.updated_at DESC
LIMIT %s
LIMIT %s OFFSET %s
""",
[like, like, limit],
[like, like, limit, offset],
)
return _dictfetchall(cursor)