Cambio de rutas: ahora portada muestra noticias y gestión de feeds pasa a /feeds. Mejoras en navegación y backup.

This commit is contained in:
jlimolina 2025-05-26 17:50:55 +02:00
parent b3dc6a4ac2
commit d7cabbb2c6
5 changed files with 222 additions and 61 deletions

206
app.py
View file

@ -1,10 +1,12 @@
from flask import Flask, render_template, request, redirect from flask import Flask, render_template, request, redirect, url_for, Response
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime from datetime import datetime
import feedparser import feedparser
import hashlib import hashlib
import re import re
import mysql.connector import mysql.connector
import csv
from io import StringIO
app = Flask(__name__) app = Flask(__name__)
@ -15,61 +17,11 @@ DB_CONFIG = {
'database': 'noticiasrss' 'database': 'noticiasrss'
} }
# Página principal: muestra los feeds y el formulario con selects de categoría, continente y país # ======================================
# Página principal: últimas noticias
# ======================================
@app.route('/') @app.route('/')
def index(): def home():
conn = None
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
# Feeds con país/categoría (incluye nombres)
cursor.execute("""
SELECT f.id, f.nombre, f.url, f.categoria_id, f.pais_id, f.activo, 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
""")
feeds = cursor.fetchall()
# Categorías, continentes y países para los selects
cursor.execute("SELECT id, nombre FROM categorias_estandar ORDER BY nombre")
categorias = cursor.fetchall()
cursor.execute("SELECT id, nombre FROM continentes ORDER BY nombre")
continentes = cursor.fetchall()
cursor.execute("SELECT id, nombre, continente_id FROM paises ORDER BY nombre")
paises = cursor.fetchall()
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al leer feeds/categorías/países: {db_err}", exc_info=True)
feeds, categorias, continentes, paises = [], [], [], []
finally:
if conn:
conn.close()
return render_template("index.html", feeds=feeds, categorias=categorias, continentes=continentes, paises=paises)
# Añadir feed: ahora requiere país y categoría
@app.route('/add', methods=['POST'])
def add_feed():
nombre = request.form.get('nombre')
url = request.form.get('url')
categoria_id = request.form.get('categoria_id')
pais_id = request.form.get('pais_id')
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
cursor.execute(
"INSERT INTO feeds (nombre, url, categoria_id, pais_id) VALUES (%s, %s, %s, %s)",
(nombre, url, categoria_id, pais_id)
)
conn.commit()
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al agregar feed: {db_err}", exc_info=True)
finally:
if conn:
conn.close()
return redirect('/')
# Mostrar noticias, con filtros por categoría, continente y país
@app.route('/noticias')
def show_noticias():
conn = None conn = None
noticias = [] noticias = []
categorias = [] categorias = []
@ -131,6 +83,150 @@ def show_noticias():
pais_id=int(pais_id) if pais_id else None pais_id=int(pais_id) if pais_id else None
) )
# ======================================
# Gestión de feeds en /feeds
# ======================================
@app.route('/feeds')
def feeds():
conn = None
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
# Feeds con país/categoría (incluye nombres)
cursor.execute("""
SELECT f.id, f.nombre, f.url, f.categoria_id, f.pais_id, f.activo, 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
""")
feeds = cursor.fetchall()
# Categorías, continentes y países para los selects
cursor.execute("SELECT id, nombre FROM categorias_estandar ORDER BY nombre")
categorias = cursor.fetchall()
cursor.execute("SELECT id, nombre FROM continentes ORDER BY nombre")
continentes = cursor.fetchall()
cursor.execute("SELECT id, nombre, continente_id FROM paises ORDER BY nombre")
paises = cursor.fetchall()
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al leer feeds/categorías/países: {db_err}", exc_info=True)
feeds, categorias, continentes, paises = [], [], [], []
finally:
if conn:
conn.close()
return render_template("index.html", feeds=feeds, categorias=categorias, continentes=continentes, paises=paises)
# Añadir feed
@app.route('/add', methods=['POST'])
def add_feed():
nombre = request.form.get('nombre')
url = request.form.get('url')
categoria_id = request.form.get('categoria_id')
pais_id = request.form.get('pais_id')
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
cursor.execute(
"INSERT INTO feeds (nombre, url, categoria_id, pais_id) VALUES (%s, %s, %s, %s)",
(nombre, url, categoria_id, pais_id)
)
conn.commit()
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al agregar feed: {db_err}", exc_info=True)
finally:
if conn:
conn.close()
return redirect(url_for('feeds'))
# Editar feed
@app.route('/edit/<int:feed_id>', methods=['GET', 'POST'])
def edit_feed(feed_id):
conn = None
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor(dictionary=True)
if request.method == 'POST':
nombre = request.form.get('nombre')
url_feed = request.form.get('url')
categoria_id = request.form.get('categoria_id')
pais_id = request.form.get('pais_id')
activo = 1 if request.form.get('activo') == 'on' else 0
cursor.execute(
"UPDATE feeds SET nombre=%s, url=%s, categoria_id=%s, pais_id=%s, activo=%s WHERE id=%s",
(nombre, url_feed, categoria_id, pais_id, activo, feed_id)
)
conn.commit()
return redirect(url_for('feeds'))
# GET: Obtener datos actuales del feed
cursor.execute("SELECT * FROM feeds WHERE id = %s", (feed_id,))
feed = cursor.fetchone()
cursor.execute("SELECT id, nombre FROM categorias_estandar ORDER BY nombre")
categorias = cursor.fetchall()
cursor.execute("SELECT id, nombre FROM paises ORDER BY nombre")
paises = cursor.fetchall()
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al editar feed: {db_err}", exc_info=True)
feed, categorias, paises = {}, [], []
finally:
if conn:
conn.close()
return render_template('edit_feed.html', feed=feed, categorias=categorias, paises=paises)
# Eliminar feed
@app.route('/delete/<int:feed_id>')
def delete_feed(feed_id):
conn = None
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
cursor.execute("DELETE FROM feeds WHERE id=%s", (feed_id,))
conn.commit()
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al eliminar feed: {db_err}", exc_info=True)
finally:
if conn:
conn.close()
return redirect(url_for('feeds'))
# Backup de feeds a CSV
@app.route('/backup_feeds')
def backup_feeds():
conn = None
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
cursor.execute("""
SELECT f.id, f.nombre, f.url, f.categoria_id, c.nombre AS categoria, f.pais_id, p.nombre AS pais, f.activo
FROM feeds f
LEFT JOIN categorias_estandar c ON f.categoria_id = c.id
LEFT JOIN paises p ON f.pais_id = p.id
""")
feeds = cursor.fetchall()
header = [desc[0] for desc in cursor.description]
except mysql.connector.Error as db_err:
app.logger.error(f"[DB ERROR] Al hacer backup de feeds: {db_err}", exc_info=True)
return "Error generando backup.", 500
finally:
if conn:
conn.close()
# CSV en memoria
si = StringIO()
cw = csv.writer(si)
cw.writerow(header)
cw.writerows(feeds)
output = si.getvalue()
si.close()
return Response(
output,
mimetype="text/csv",
headers={"Content-Disposition": "attachment;filename=feeds_backup.csv"}
)
# (Antiguo /noticias, por compatibilidad)
@app.route('/noticias')
def show_noticias():
return home()
def fetch_and_store(): def fetch_and_store():
conn = None conn = None
try: try:

50
templates/edit_feed.html Normal file
View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Editar Feed RSS</title>
</head>
<body>
<h1>Editar Feed</h1>
<form method="post">
<label>
Nombre del feed:<br>
<input name="nombre" placeholder="Nombre" value="{{ feed['nombre'] }}" required>
</label>
<br><br>
<label>
URL del RSS:<br>
<input name="url" placeholder="URL" value="{{ feed['url'] }}" required>
</label>
<br><br>
<label>
Categoría:<br>
<select name="categoria_id" required>
<option value="">— Elige categoría —</option>
{% for cat in categorias %}
<option value="{{ cat.id }}" {% if cat.id == feed['categoria_id'] %}selected{% endif %}>{{ cat.nombre }}</option>
{% endfor %}
</select>
</label>
<br><br>
<label>
País:<br>
<select name="pais_id" required>
<option value="">— Elige país —</option>
{% for p in paises %}
<option value="{{ p.id }}" {% if p.id == feed['pais_id'] %}selected{% endif %}>{{ p.nombre }}</option>
{% endfor %}
</select>
</label>
<br><br>
<label>
<input type="checkbox" name="activo" {% if feed['activo'] %}checked{% endif %}>
Activo
</label>
<br><br>
<button type="submit">Guardar cambios</button>
<a href="{{ url_for('index') }}">Cancelar</a>
</form>
</body>
</html>

View file

@ -2,7 +2,7 @@
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Mis Feeds RSS</title> <title>Gestión de Feeds RSS</title>
<script> <script>
// Script para filtrar países según continente elegido // Script para filtrar países según continente elegido
function filtrarPaisesPorContinente() { function filtrarPaisesPorContinente() {
@ -27,8 +27,13 @@
</script> </script>
</head> </head>
<body> <body>
<h1>Feeds configurados</h1> <h1>Gestión de Feeds RSS</h1>
<p><a href="/noticias">📄 Ver últimas noticias</a></p> <p><a href="/">← Volver a últimas noticias</a></p>
<!-- Enlace de backup de feeds -->
<p>
<a href="/backup_feeds" target="_blank">⬇️ Descargar backup de feeds (CSV)</a>
</p>
<h2>Añadir un nuevo feed</h2> <h2>Añadir un nuevo feed</h2>
<form action="/add" method="post"> <form action="/add" method="post">
@ -66,11 +71,17 @@
— Categoría: {{ cat_nom or 'N/A' }} — Categoría: {{ cat_nom or 'N/A' }}
— País: {{ pais_nom or 'N/A' }} — País: {{ pais_nom or 'N/A' }}
— Activo: {{ 'Sí' if activo else 'No' }} — Activo: {{ 'Sí' if activo else 'No' }}
<!-- Enlace de edición de feed -->
| <a href="/edit/{{ id }}">Editar</a>
<!-- Enlace para eliminar feed con confirmación -->
| <a href="/delete/{{ id }}" onclick="return confirm('¿Seguro que quieres eliminar este feed?');">Eliminar</a>
</li> </li>
{% else %} {% else %}
<li>No hay feeds aún.</li> <li>No hay feeds aún.</li>
{% endfor %} {% endfor %}
</ul> </ul>
<p><a href="/">← Volver a últimas noticias</a></p>
</body> </body>
</html> </html>

View file

@ -27,9 +27,11 @@
</head> </head>
<body> <body>
<h1>Últimas Noticias Recopiladas</h1> <h1>Últimas Noticias Recopiladas</h1>
<p><a href="/">← Volver a feeds</a></p>
<form method="get" action="/noticias"> <!-- Enlace de gestión de feeds -->
<p><a href="/feeds">⚙️ Gestionar feeds RSS</a></p>
<form method="get" action="">
<select name="categoria_id"> <select name="categoria_id">
<option value="">— Categoría —</option> <option value="">— Categoría —</option>
{% for cid, cnom in categorias %} {% for cid, cnom in categorias %}
@ -77,6 +79,8 @@
<li>No hay noticias que mostrar con estos filtros.</li> <li>No hay noticias que mostrar con estos filtros.</li>
{% endfor %} {% endfor %}
</ul> </ul>
<p><a href="/feeds">← Volver a gestión de feeds</a></p>
</body> </body>
</html> </html>