cambios en los feeds y mejora de velocidad

This commit is contained in:
jlimolina 2025-11-25 07:59:35 +01:00
parent 9a243db633
commit 239025cb83
3 changed files with 195 additions and 24 deletions

54
app.py
View file

@ -157,10 +157,17 @@ def _process_feed(feed_row):
finally: finally:
socket.setdefaulttimeout(old_timeout) socket.setdefaulttimeout(old_timeout)
if parsed.bozo and parsed.bozo_exception:
app.logger.warning(f"[ingesta] Feed {feed_id} bozo={parsed.bozo}: {parsed.bozo_exception}")
entries = parsed.entries or [] entries = parsed.entries or []
status = getattr(parsed, "status", None)
if parsed.bozo and getattr(parsed, "bozo_exception", None):
app.logger.warning(f"[ingesta] Feed {feed_id} bozo={parsed.bozo}: {parsed.bozo_exception}")
if (parsed.bozo and getattr(parsed, "bozo_exception", None)) or (status is not None and status >= 400):
if not entries:
raise RuntimeError(
f"Feed {feed_id} inválido o no es XML RSS/Atom (status={status}, bozo={parsed.bozo})"
)
nuevos = 0 nuevos = 0
with get_conn() as conn: with get_conn() as conn:
@ -607,13 +614,42 @@ def manage_feeds():
per_page = 50 per_page = 50
offset = (page - 1) * per_page offset = (page - 1) * per_page
pais_id = request.args.get("pais_id") or None
categoria_id = request.args.get("categoria_id") or None
estado = request.args.get("estado") or ""
where = []
params: list[object] = []
if pais_id:
where.append("f.pais_id = %s")
params.append(int(pais_id))
if categoria_id:
where.append("f.categoria_id = %s")
params.append(int(categoria_id))
if estado == "activos":
where.append("f.activo = TRUE")
elif estado == "inactivos":
where.append("f.activo = FALSE")
elif estado == "errores":
where.append("COALESCE(f.fallos, 0) > 0")
where_sql = ""
if where:
where_sql = "WHERE " + " AND ".join(where)
with get_conn() as conn, conn.cursor(cursor_factory=extras.DictCursor) as cur: with get_conn() as conn, conn.cursor(cursor_factory=extras.DictCursor) as cur:
cur.execute("SELECT COUNT(*) FROM feeds;") cur.execute(
f"SELECT COUNT(*) FROM feeds f {where_sql};",
params,
)
total_feeds = cur.fetchone()[0] if cur.rowcount else 0 total_feeds = cur.fetchone()[0] if cur.rowcount else 0
total_pages = (total_feeds // per_page) + (1 if total_feeds % per_page else 0) total_pages = (total_feeds // per_page) + (1 if total_feeds % per_page else 0)
cur.execute( cur.execute(
""" f"""
SELECT SELECT
f.id, f.id,
f.nombre, f.nombre,
@ -626,10 +662,11 @@ def manage_feeds():
FROM feeds f FROM feeds f
LEFT JOIN categorias c ON c.id = f.categoria_id LEFT JOIN categorias c ON c.id = f.categoria_id
LEFT JOIN paises p ON p.id = f.pais_id LEFT JOIN paises p ON p.id = f.pais_id
ORDER BY f.nombre {where_sql}
ORDER BY p.nombre NULLS LAST, c.nombre NULLS LAST, f.nombre
LIMIT %s OFFSET %s; LIMIT %s OFFSET %s;
""", """,
(per_page, offset), params + [per_page, offset],
) )
feeds = cur.fetchall() feeds = cur.fetchall()
@ -646,6 +683,9 @@ def manage_feeds():
page=page, page=page,
categorias=categorias, categorias=categorias,
paises=paises, paises=paises,
filtro_pais_id=pais_id,
filtro_categoria_id=categoria_id,
filtro_estado=estado,
) )

View file

@ -25,9 +25,38 @@
<h3>Gestión de Feeds RSS</h3> <h3>Gestión de Feeds RSS</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<p>Exporta tu lista de feeds RSS o restaura/importa desde un archivo CSV.</p> <p>
<a href="{{ url_for('backup_feeds') }}" class="btn"><i class="fas fa-download"></i> Exportar Feeds</a> Exporta tu lista de feeds RSS o restaura/importa desde un archivo CSV.
<a href="{{ url_for('restore_feeds') }}" class="btn btn-info"><i class="fas fa-upload"></i> Importar Feeds</a> Además, puedes ir al organizador avanzado de feeds para filtrarlos
por país, categoría y estado (activos, caídos, con errores).
</p>
<div style="display:flex; flex-wrap:wrap; gap:10px; margin-bottom:10px;">
<a href="{{ url_for('manage_feeds') }}" class="btn btn-secondary">
<i class="fas fa-list"></i> Ver / Gestionar Feeds
</a>
</div>
<div style="display:flex; flex-wrap:wrap; gap:8px; margin-bottom:15px;">
<a href="{{ url_for('manage_feeds', estado='activos') }}" class="btn btn-small">
<i class="fas fa-check-circle"></i> Feeds activos
</a>
<a href="{{ url_for('manage_feeds', estado='inactivos') }}" class="btn btn-small btn-danger">
<i class="fas fa-times-circle"></i> Feeds caídos/inactivos
</a>
<a href="{{ url_for('manage_feeds', estado='errores') }}" class="btn btn-small btn-info">
<i class="fas fa-exclamation-triangle"></i> Feeds con errores
</a>
</div>
<hr style="margin: 15px 0; border: 0; border-top: 1px solid var(--border-color);">
<a href="{{ url_for('backup_feeds') }}" class="btn">
<i class="fas fa-download"></i> Exportar Feeds
</a>
<a href="{{ url_for('restore_feeds') }}" class="btn btn-info">
<i class="fas fa-upload"></i> Importar Feeds
</a>
</div> </div>
</div> </div>
</div> </div>
@ -39,8 +68,12 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<p>Exporta tu lista de fuentes URL o restaura/importa desde un archivo CSV.</p> <p>Exporta tu lista de fuentes URL o restaura/importa desde un archivo CSV.</p>
<a href="{{ url_for('backup_urls') }}" class="btn"><i class="fas fa-download"></i> Exportar URLs</a> <a href="{{ url_for('backup_urls') }}" class="btn">
<a href="{{ url_for('restore_urls') }}" class="btn btn-info"><i class="fas fa-upload"></i> Importar Fuentes URL</a> <i class="fas fa-download"></i> Exportar URLs
</a>
<a href="{{ url_for('restore_urls') }}" class="btn btn-info">
<i class="fas fa-upload"></i> Importar Fuentes URL
</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -6,7 +6,7 @@
<div class="card feed-detail-card"> <div class="card feed-detail-card">
<div class="feed-header"> <div class="feed-header">
<h2>Lista de Feeds RSS ({{ total_feeds }})</h2> <h2>Lista de Feeds RSS</h2>
<div class="nav-actions"> <div class="nav-actions">
<a href="{{ url_for('add_feed') }}" class="btn btn-small"> <a href="{{ url_for('add_feed') }}" class="btn btn-small">
<i class="fas fa-plus"></i> Añadir Feed <i class="fas fa-plus"></i> Añadir Feed
@ -14,6 +14,66 @@
</div> </div>
</div> </div>
<!-- Filtros avanzados -->
<div class="feed-body" style="padding: 15px 15px 0 15px;">
<form class="feed-filters" method="get" action="{{ url_for('manage_feeds') }}">
<div class="filters-row" style="display:flex; flex-wrap:wrap; gap:10px;">
<div style="flex:1 1 200px;">
<label for="pais_id" class="form-label">País</label>
<select name="pais_id" id="pais_id" class="form-select">
<option value="">Todos los países</option>
{% for p in paises %}
<option value="{{ p.id }}"
{% if filtro_pais_id is not none and p.id == filtro_pais_id|int %}selected{% endif %}>
{{ p.nombre }}
</option>
{% endfor %}
</select>
</div>
<div style="flex:1 1 200px;">
<label for="categoria_id" class="form-label">Categoría</label>
<select name="categoria_id" id="categoria_id" class="form-select">
<option value="">Todas las categorías</option>
{% for c in categorias %}
<option value="{{ c.id }}"
{% if filtro_categoria_id is not none and c.id == filtro_categoria_id|int %}selected{% endif %}>
{{ c.nombre }}
</option>
{% endfor %}
</select>
</div>
<div style="flex:1 1 200px;">
<label for="estado" class="form-label">Estado</label>
<select name="estado" id="estado" class="form-select">
<option value="" {% if not filtro_estado %}selected{% endif %}>Todos</option>
<option value="activos" {% if filtro_estado == "activos" %}selected{% endif %}>Activos</option>
<option value="inactivos" {% if filtro_estado == "inactivos" %}selected{% endif %}>Inactivos</option>
<option value="errores" {% if filtro_estado == "errores" %}selected{% endif %}>Con errores</option>
</select>
</div>
<div style="flex:0 0 auto; align-self:flex-end;">
<button type="submit" class="btn btn-small">
<i class="fas fa-filter"></i> Filtrar
</button>
<a href="{{ url_for('manage_feeds') }}" class="btn btn-small btn-secondary">
Limpiar
</a>
</div>
</div>
</form>
<!-- Resumen resultados -->
<div class="mt-2" style="margin-top: 10px;">
<strong>{{ total_feeds }}</strong> feeds encontrados
{% if filtro_pais_id or filtro_categoria_id or filtro_estado %}
<span class="text-muted" style="font-size:0.9em;">(con filtros aplicados)</span>
{% endif %}
</div>
</div>
<div class="feed-body" style="padding: 0;"> <div class="feed-body" style="padding: 0;">
<table style="width:100%; border-collapse: collapse;"> <table style="width:100%; border-collapse: collapse;">
<thead> <thead>
@ -22,35 +82,57 @@
<th style="padding: 12px 15px; text-align: left;">Categoría</th> <th style="padding: 12px 15px; text-align: left;">Categoría</th>
<th style="padding: 12px 15px; text-align: left;">País</th> <th style="padding: 12px 15px; text-align: left;">País</th>
<th style="padding: 12px 15px; text-align: center;">Estado</th> <th style="padding: 12px 15px; text-align: center;">Estado</th>
<th style="padding: 12px 15px; text-align: center;">Fallos</th>
<th style="padding: 12px 15px; text-align: right;">Acciones</th> <th style="padding: 12px 15px; text-align: right;">Acciones</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for feed in feeds %} {% for feed in feeds %}
<tr> <tr {% if feed.fallos and feed.fallos > 0 %}style="background-color: rgba(192,57,43,0.05);" {% endif %}>
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color);"> <td style="padding: 12px 15px; border-top: 1px solid var(--border-color);">
<a href="{{ feed.url }}" target="_blank" title="{{ feed.url }}">{{ feed.nombre }}</a> <a href="{{ feed.url }}" target="_blank" title="{{ feed.url }}">{{ feed.nombre }}</a>
</td> </td>
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color);">{{ feed.categoria or 'N/A' }}</td> <td style="padding: 12px 15px; border-top: 1px solid var(--border-color);">
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color);">{{ feed.pais or 'Global' }}</td> {{ feed.categoria or 'N/A' }}
</td>
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color);">
{{ feed.pais or 'Global' }}
</td>
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color); text-align: center;"> <td style="padding: 12px 15px; border-top: 1px solid var(--border-color); text-align: center;">
{% if not feed.activo %} {% if not feed.activo %}
<span style="color: #c0392b; font-weight: bold;" title="Inactivo por {{ feed.fallos }} fallos">KO</span> <span style="color: #c0392b; font-weight: bold;"
title="Inactivo por {{ feed.fallos or 0 }} fallos">KO</span>
{% else %} {% else %}
<span style="color: #27ae60; font-weight:bold;">OK</span> <span style="color: #27ae60; font-weight:bold;">OK</span>
{% endif %} {% endif %}
</td> </td>
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color); text-align:center;">
{{ feed.fallos or 0 }}
</td>
<td style="padding: 12px 15px; text-align: right; border-top: 1px solid var(--border-color);"> <td style="padding: 12px 15px; text-align: right; border-top: 1px solid var(--border-color);">
<a href="{{ url_for('edit_feed', feed_id=feed.id) }}" class="btn btn-small btn-info" title="Editar"><i class="fas fa-edit"></i></a> <a href="{{ url_for('edit_feed', feed_id=feed.id) }}"
<a href="{{ url_for('delete_feed', feed_id=feed.id) }}" class="btn btn-small btn-danger" title="Eliminar" onclick="return confirm('¿Estás seguro de que quieres eliminar este feed?')"><i class="fas fa-trash"></i></a> class="btn btn-small btn-info" title="Editar">
<i class="fas fa-edit"></i>
</a>
<a href="{{ url_for('delete_feed', feed_id=feed.id) }}"
class="btn btn-small btn-danger" title="Eliminar"
onclick="return confirm('¿Estás seguro de que quieres eliminar este feed?')">
<i class="fas fa-trash"></i>
</a>
{% if not feed.activo %} {% if not feed.activo %}
<a href="{{ url_for('reactivar_feed', feed_id=feed.id) }}" class="btn btn-small" title="Reactivar"><i class="fas fa-sync-alt"></i></a> <a href="{{ url_for('reactivar_feed', feed_id=feed.id) }}"
class="btn btn-small" title="Reactivar">
<i class="fas fa-sync-alt"></i>
</a>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="5" style="padding: 20px; text-align: center;">No hay feeds para mostrar. <a href="{{ url_for('add_feed') }}">Añade el primero</a>.</td> <td colspan="6" style="padding: 20px; text-align: center;">
No hay feeds para mostrar.
<a href="{{ url_for('add_feed') }}">Añade el primero</a>.
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -61,21 +143,37 @@
{% if total_pages > 1 %} {% if total_pages > 1 %}
<nav class="pagination"> <nav class="pagination">
{% if page > 1 %} {% if page > 1 %}
<a href="{{ url_for('manage_feeds', page=page-1) }}" class="page-link">&laquo; Anterior</a> <a href="{{ url_for('manage_feeds',
page=page-1,
pais_id=filtro_pais_id,
categoria_id=filtro_categoria_id,
estado=filtro_estado) }}"
class="page-link">&laquo; Anterior</a>
{% endif %} {% endif %}
{% for p in range(1, total_pages + 1) %} {% for p in range(1, total_pages + 1) %}
{% if p == page %} {% if p == page %}
<a href="#" class="page-link active">{{ p }}</a> <a href="#" class="page-link active">{{ p }}</a>
{% else %} {% else %}
<a href="{{ url_for('manage_feeds', page=p) }}" class="page-link">{{ p }}</a> <a href="{{ url_for('manage_feeds',
page=p,
pais_id=filtro_pais_id,
categoria_id=filtro_categoria_id,
estado=filtro_estado) }}"
class="page-link">{{ p }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if page < total_pages %} {% if page < total_pages %}
<a href="{{ url_for('manage_feeds', page=page+1) }}" class="page-link">Siguiente &raquo;</a> <a href="{{ url_for('manage_feeds',
page=page+1,
pais_id=filtro_pais_id,
categoria_id=filtro_categoria_id,
estado=filtro_estado) }}"
class="page-link">Siguiente &raquo;</a>
{% endif %} {% endif %}
</nav> </nav>
{% endif %} {% endif %}
{% endblock %} {% endblock %}