update
This commit is contained in:
parent
91f2f409d6
commit
34f60011a3
3 changed files with 90 additions and 15 deletions
41
app.py
41
app.py
|
|
@ -17,6 +17,8 @@ DB_CONFIG = {
|
|||
'database': 'noticiasrss'
|
||||
}
|
||||
|
||||
MAX_FALLOS = 5 # Número máximo de fallos antes de desactivar el feed
|
||||
|
||||
# ======================================
|
||||
# Página principal: últimas noticias
|
||||
# ======================================
|
||||
|
|
@ -90,9 +92,9 @@ def feeds():
|
|||
try:
|
||||
conn = mysql.connector.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
# Feeds con descripción
|
||||
# Feeds con descripción y fallos
|
||||
cursor.execute("""
|
||||
SELECT f.id, f.nombre, f.descripcion, f.url, f.categoria_id, f.pais_id, f.activo, c.nombre, p.nombre
|
||||
SELECT f.id, f.nombre, f.descripcion, f.url, f.categoria_id, f.pais_id, f.activo, f.fallos, c.nombre, p.nombre
|
||||
FROM feeds f
|
||||
LEFT JOIN categorias_estandar c ON f.categoria_id = c.id
|
||||
LEFT JOIN paises p ON f.pais_id = p.id
|
||||
|
|
@ -193,7 +195,7 @@ def backup_feeds():
|
|||
conn = mysql.connector.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT f.id, f.nombre, f.descripcion, f.url, f.categoria_id, c.nombre AS categoria, f.pais_id, p.nombre AS pais, f.activo
|
||||
SELECT f.id, f.nombre, f.descripcion, f.url, f.categoria_id, c.nombre AS categoria, f.pais_id, p.nombre AS pais, f.activo, f.fallos
|
||||
FROM feeds f
|
||||
LEFT JOIN categorias_estandar c ON f.categoria_id = c.id
|
||||
LEFT JOIN paises p ON f.pais_id = p.id
|
||||
|
|
@ -236,25 +238,26 @@ def restore_feeds():
|
|||
n_ok = 0
|
||||
for row in rows:
|
||||
try:
|
||||
# Soporta CSV con o sin columna 'descripcion'
|
||||
descripcion = row.get('descripcion') or ""
|
||||
cursor.execute("""
|
||||
INSERT INTO feeds (nombre, descripcion, url, categoria_id, pais_id, activo)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
INSERT INTO feeds (nombre, descripcion, url, categoria_id, pais_id, activo, fallos)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
nombre=VALUES(nombre),
|
||||
descripcion=VALUES(descripcion),
|
||||
url=VALUES(url),
|
||||
categoria_id=VALUES(categoria_id),
|
||||
pais_id=VALUES(pais_id),
|
||||
activo=VALUES(activo)
|
||||
activo=VALUES(activo),
|
||||
fallos=VALUES(fallos)
|
||||
""", (
|
||||
row['nombre'],
|
||||
descripcion,
|
||||
row['url'],
|
||||
row['categoria_id'],
|
||||
row['pais_id'],
|
||||
int(row['activo'])
|
||||
int(row['activo']),
|
||||
int(row.get('fallos', 0))
|
||||
))
|
||||
n_ok += 1
|
||||
except Exception as e:
|
||||
|
|
@ -268,29 +271,47 @@ def restore_feeds():
|
|||
def show_noticias():
|
||||
return home()
|
||||
|
||||
# ================================
|
||||
# Lógica de procesado de feeds con control de fallos
|
||||
# ================================
|
||||
def sumar_fallo_feed(cursor, feed_id):
|
||||
cursor.execute("UPDATE feeds SET fallos = fallos + 1 WHERE id = %s", (feed_id,))
|
||||
cursor.execute("SELECT fallos FROM feeds WHERE id = %s", (feed_id,))
|
||||
fallos = cursor.fetchone()[0]
|
||||
if fallos >= MAX_FALLOS:
|
||||
cursor.execute("UPDATE feeds SET activo = 0 WHERE id = %s", (feed_id,))
|
||||
return fallos
|
||||
|
||||
def resetear_fallos_feed(cursor, feed_id):
|
||||
cursor.execute("UPDATE feeds SET fallos = 0 WHERE id = %s", (feed_id,))
|
||||
|
||||
def fetch_and_store():
|
||||
conn = None
|
||||
try:
|
||||
conn = mysql.connector.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT url, categoria_id, pais_id FROM feeds WHERE activo = TRUE")
|
||||
cursor.execute("SELECT id, url, categoria_id, pais_id FROM feeds WHERE activo = TRUE")
|
||||
feeds = cursor.fetchall()
|
||||
except mysql.connector.Error as db_err:
|
||||
app.logger.error(f"[DB ERROR] No se pudo conectar o leer feeds: {db_err}", exc_info=True)
|
||||
return
|
||||
|
||||
for rss_url, categoria_id, pais_id in feeds:
|
||||
for feed_id, rss_url, categoria_id, pais_id in feeds:
|
||||
try:
|
||||
app.logger.info(f"Procesando feed: {rss_url} [{categoria_id}] [{pais_id}]")
|
||||
parsed = feedparser.parse(rss_url)
|
||||
except Exception as e:
|
||||
app.logger.error(f"[PARSE ERROR] Al parsear {rss_url}: {e}", exc_info=True)
|
||||
sumar_fallo_feed(cursor, feed_id)
|
||||
continue
|
||||
|
||||
if getattr(parsed, 'bozo', False):
|
||||
bozo_exc = getattr(parsed, 'bozo_exception', 'Unknown')
|
||||
app.logger.warning(f"[BOZO] Feed mal formado: {rss_url} - {bozo_exc}")
|
||||
sumar_fallo_feed(cursor, feed_id)
|
||||
continue
|
||||
else:
|
||||
resetear_fallos_feed(cursor, feed_id)
|
||||
|
||||
for entry in parsed.entries:
|
||||
link = entry.get('link') or entry.get('id')
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
box-shadow: 0 1px 4px #0001;
|
||||
padding: 24px 20px 18px 20px;
|
||||
margin-bottom: 30px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
|
|
@ -152,6 +153,40 @@
|
|||
.noticia-item { flex-direction: column; align-items: flex-start; gap: 9px; }
|
||||
.noticia-imagen { align-self: flex-start; }
|
||||
}
|
||||
/* ------ BADGES DE ESTADO ------ */
|
||||
.badge-ok {
|
||||
background: #ddffdd;
|
||||
color: #1c8c1c;
|
||||
font-weight: bold;
|
||||
padding: 3px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
display: inline-block;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
}
|
||||
.badge-ko {
|
||||
background: #ffdddd;
|
||||
color: #b20000;
|
||||
font-weight: bold;
|
||||
padding: 3px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
display: inline-block;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
}
|
||||
.badge-warn {
|
||||
background: #fff7cc;
|
||||
color: #b68900;
|
||||
font-weight: bold;
|
||||
padding: 3px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
display: inline-block;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
}
|
||||
/* Scroll suave para tablas grandes */
|
||||
.card { overflow-x: auto; }
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -56,12 +56,13 @@
|
|||
<th>URL</th>
|
||||
<th>Categoría</th>
|
||||
<th>País</th>
|
||||
<th>Activo</th>
|
||||
<th style="min-width: 80px;">Estado</th>
|
||||
<th style="min-width: 60px;">Fallos</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for id, nombre, descripcion, url, categoria_id, pais_id, activo, cat_nom, pais_nom in feeds %}
|
||||
{% for id, nombre, descripcion, url, categoria_id, pais_id, activo, fallos, cat_nom, pais_nom in feeds %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ nombre }}</strong>
|
||||
|
|
@ -72,15 +73,33 @@
|
|||
<td><a href="{{ url }}" target="_blank">{{ url }}</a></td>
|
||||
<td>{{ cat_nom or 'N/A' }}</td>
|
||||
<td>{{ pais_nom or 'N/A' }}</td>
|
||||
<td>{{ 'Sí' if activo else 'No' }}</td>
|
||||
<td>
|
||||
{% if not activo %}
|
||||
<span class="badge-ko" title="Inactivo: {{ fallos }} fallos">KO</span>
|
||||
{% elif fallos > 0 %}
|
||||
<span class="badge-warn" title="{{ fallos }} fallos recientes">⚠️</span>
|
||||
{% else %}
|
||||
<span class="badge-ok">OK</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if fallos > 0 %}
|
||||
<span style="color:orange;">{{ fallos }}</span>
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="actions">
|
||||
<a href="/edit/{{ id }}">Editar</a> |
|
||||
<a href="/delete/{{ id }}" onclick="return confirm('¿Seguro que quieres eliminar este feed?');">Eliminar</a>
|
||||
{% if not activo %}
|
||||
| <a href="/reactivar_feed/{{ id }}" style="color:#198754;" title="Reactivar feed">Reactivar</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6">No hay feeds aún.</td>
|
||||
<td colspan="7">No hay feeds aún.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
@ -101,7 +120,7 @@
|
|||
optionNA.textContent = '— N/A —';
|
||||
selectPais.appendChild(optionNA);
|
||||
paises.forEach(([id, nombre, contId]) => {
|
||||
if (!continenteId || contId == continenteId) {
|
||||
if (!continenteId || contId == continenteId || contId == Number(continenteId)) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = id;
|
||||
opt.textContent = nombre;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue