cambios en los feeds y mejora de velocidad
This commit is contained in:
parent
9a243db633
commit
239025cb83
3 changed files with 195 additions and 24 deletions
54
app.py
54
app.py
|
|
@ -157,10 +157,17 @@ def _process_feed(feed_row):
|
|||
finally:
|
||||
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 []
|
||||
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
|
||||
|
||||
with get_conn() as conn:
|
||||
|
|
@ -607,13 +614,42 @@ def manage_feeds():
|
|||
per_page = 50
|
||||
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:
|
||||
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_pages = (total_feeds // per_page) + (1 if total_feeds % per_page else 0)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
f"""
|
||||
SELECT
|
||||
f.id,
|
||||
f.nombre,
|
||||
|
|
@ -626,10 +662,11 @@ def manage_feeds():
|
|||
FROM feeds f
|
||||
LEFT JOIN categorias c ON c.id = f.categoria_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;
|
||||
""",
|
||||
(per_page, offset),
|
||||
params + [per_page, offset],
|
||||
)
|
||||
feeds = cur.fetchall()
|
||||
|
||||
|
|
@ -646,6 +683,9 @@ def manage_feeds():
|
|||
page=page,
|
||||
categorias=categorias,
|
||||
paises=paises,
|
||||
filtro_pais_id=pais_id,
|
||||
filtro_categoria_id=categoria_id,
|
||||
filtro_estado=estado,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,38 @@
|
|||
<h3>Gestión de Feeds RSS</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Exporta tu lista de feeds RSS o restaura/importa desde un archivo CSV.</p>
|
||||
<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>
|
||||
<p>
|
||||
Exporta tu lista de feeds RSS o restaura/importa desde un archivo CSV.
|
||||
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>
|
||||
|
|
@ -39,8 +68,12 @@
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<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('restore_urls') }}" class="btn btn-info"><i class="fas fa-upload"></i> Importar Fuentes URL</a>
|
||||
<a href="{{ url_for('backup_urls') }}" class="btn">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<div class="card feed-detail-card">
|
||||
<div class="feed-header">
|
||||
<h2>Lista de Feeds RSS ({{ total_feeds }})</h2>
|
||||
<h2>Lista de Feeds RSS</h2>
|
||||
<div class="nav-actions">
|
||||
<a href="{{ url_for('add_feed') }}" class="btn btn-small">
|
||||
<i class="fas fa-plus"></i> Añadir Feed
|
||||
|
|
@ -14,6 +14,66 @@
|
|||
</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;">
|
||||
<table style="width:100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
|
|
@ -22,35 +82,57 @@
|
|||
<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: center;">Estado</th>
|
||||
<th style="padding: 12px 15px; text-align: center;">Fallos</th>
|
||||
<th style="padding: 12px 15px; text-align: right;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% 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);">
|
||||
<a href="{{ feed.url }}" target="_blank" title="{{ feed.url }}">{{ feed.nombre }}</a>
|
||||
</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);">{{ feed.pais or 'Global' }}</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);">
|
||||
{{ feed.pais or 'Global' }}
|
||||
</td>
|
||||
<td style="padding: 12px 15px; border-top: 1px solid var(--border-color); text-align: center;">
|
||||
{% 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 %}
|
||||
<span style="color: #27ae60; font-weight:bold;">OK</span>
|
||||
{% endif %}
|
||||
</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);">
|
||||
<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('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>
|
||||
<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('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 %}
|
||||
<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 %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
@ -61,21 +143,37 @@
|
|||
{% if total_pages > 1 %}
|
||||
<nav class="pagination">
|
||||
{% if page > 1 %}
|
||||
<a href="{{ url_for('manage_feeds', page=page-1) }}" class="page-link">« 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">« Anterior</a>
|
||||
{% endif %}
|
||||
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p == page %}
|
||||
<a href="#" class="page-link active">{{ p }}</a>
|
||||
{% 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 %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page < total_pages %}
|
||||
<a href="{{ url_for('manage_feeds', page=page+1) }}" class="page-link">Siguiente »</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 »</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue