From d7cabbb2c607ccc5d1df5300c929adab842ba9f0 Mon Sep 17 00:00:00 2001 From: jlimolina Date: Mon, 26 May 2025 17:50:55 +0200 Subject: [PATCH] =?UTF-8?q?Cambio=20de=20rutas:=20ahora=20portada=20muestr?= =?UTF-8?q?a=20noticias=20y=20gesti=C3=B3n=20de=20feeds=20pasa=20a=20/feed?= =?UTF-8?q?s.=20Mejoras=20en=20navegaci=C3=B3n=20y=20backup.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 206 ++++++++++++++++++++-------- contienentes.sql => continentes.sql | 0 templates/edit_feed.html | 50 +++++++ templates/index.html | 17 ++- templates/noticias.html | 10 +- 5 files changed, 222 insertions(+), 61 deletions(-) rename contienentes.sql => continentes.sql (100%) create mode 100644 templates/edit_feed.html diff --git a/app.py b/app.py index d8c837f..f62bd61 100644 --- a/app.py +++ b/app.py @@ -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 datetime import datetime import feedparser import hashlib import re import mysql.connector +import csv +from io import StringIO app = Flask(__name__) @@ -15,61 +17,11 @@ DB_CONFIG = { '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('/') -def index(): - 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(): +def home(): conn = None noticias = [] categorias = [] @@ -131,6 +83,150 @@ def show_noticias(): 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/', 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/') +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(): conn = None try: diff --git a/contienentes.sql b/continentes.sql similarity index 100% rename from contienentes.sql rename to continentes.sql diff --git a/templates/edit_feed.html b/templates/edit_feed.html new file mode 100644 index 0000000..7646c4d --- /dev/null +++ b/templates/edit_feed.html @@ -0,0 +1,50 @@ + + + + + Editar Feed RSS + + +

Editar Feed

+
+ +

+ +

+ +

+ +

+ +

+ + Cancelar +
+ + + diff --git a/templates/index.html b/templates/index.html index 4db5e3e..065df9b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,7 @@ - Mis Feeds RSS + Gestión de Feeds RSS -

Feeds configurados

-

📄 Ver últimas noticias

+

Gestión de Feeds RSS

+

← Volver a últimas noticias

+ + +

+ ⬇️ Descargar backup de feeds (CSV) +

Añadir un nuevo feed

@@ -66,11 +71,17 @@ — Categoría: {{ cat_nom or 'N/A' }} — País: {{ pais_nom or 'N/A' }} — Activo: {{ 'Sí' if activo else 'No' }} + + | Editar + + | Eliminar {% else %}
  • No hay feeds aún.
  • {% endfor %} + +

    ← Volver a últimas noticias

    diff --git a/templates/noticias.html b/templates/noticias.html index b4fd1de..be0ab82 100644 --- a/templates/noticias.html +++ b/templates/noticias.html @@ -27,9 +27,11 @@

    Últimas Noticias Recopiladas

    -

    ← Volver a feeds

    - + +

    ⚙️ Gestionar feeds RSS

    + + {% for pid, pnom, contid in paises %} -