arreglada la visualizacion, añadido busqueda por fechas y palabras clave
This commit is contained in:
parent
0433b3b004
commit
ce19d301e6
3 changed files with 179 additions and 112 deletions
63
app.py
63
app.py
|
|
@ -43,30 +43,46 @@ def safe_html(text):
|
||||||
allowed_attrs = {'a': ['href', 'title'], 'img': ['src', 'alt']}
|
allowed_attrs = {'a': ['href', 'title'], 'img': ['src', 'alt']}
|
||||||
return bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs, strip=True)
|
return bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs, strip=True)
|
||||||
|
|
||||||
|
# En app.py, reemplaza tu función home() con esta:
|
||||||
|
|
||||||
|
# En app.py
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
noticias, categorias, continentes, paises = [], [], [], []
|
# ... (el código inicial para obtener los parámetros no cambia) ...
|
||||||
cat_id = request.args.get("categoria_id")
|
cat_id = request.args.get("categoria_id")
|
||||||
cont_id = request.args.get("continente_id")
|
cont_id = request.args.get("continente_id")
|
||||||
pais_id = request.args.get("pais_id")
|
pais_id = request.args.get("pais_id")
|
||||||
fecha_filtro = request.args.get("fecha")
|
fecha_filtro = request.args.get("fecha")
|
||||||
|
q = request.args.get("q", "").strip() # <--- NUEVO: Obtenemos el término de búsqueda
|
||||||
|
|
||||||
|
# ... (el resto del código hasta la construcción de la consulta) ...
|
||||||
|
# (se omite por brevedad, es idéntico)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor:
|
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor:
|
||||||
|
# ... (las consultas para obtener categorías, continentes y países no cambian) ...
|
||||||
cursor.execute("SELECT id, nombre FROM categorias ORDER BY nombre")
|
cursor.execute("SELECT id, nombre FROM categorias ORDER BY nombre")
|
||||||
categorias = cursor.fetchall()
|
categorias = cursor.fetchall()
|
||||||
cursor.execute("SELECT id, nombre FROM continentes ORDER BY nombre")
|
cursor.execute("SELECT id, nombre FROM continentes ORDER BY nombre")
|
||||||
continentes = cursor.fetchall()
|
continentes = cursor.fetchall()
|
||||||
if cont_id:
|
|
||||||
cursor.execute("SELECT id, nombre, continente_id FROM paises WHERE continente_id = %s ORDER BY nombre", (cont_id,))
|
|
||||||
else:
|
|
||||||
cursor.execute("SELECT id, nombre, continente_id FROM paises ORDER BY nombre")
|
cursor.execute("SELECT id, nombre, continente_id FROM paises ORDER BY nombre")
|
||||||
paises = cursor.fetchall()
|
paises = cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
sql_params, conditions = [], []
|
sql_params, conditions = [], []
|
||||||
|
# El SELECT ahora puede incluir el ranking de la búsqueda
|
||||||
sql_base = "SELECT n.fecha, n.titulo, n.resumen, n.url, n.imagen_url, c.nombre AS categoria, p.nombre AS pais, co.nombre AS continente FROM noticias n LEFT JOIN categorias c ON n.categoria_id = c.id LEFT JOIN paises p ON n.pais_id = p.id LEFT JOIN continentes co ON p.continente_id = co.id"
|
sql_base = "SELECT n.fecha, n.titulo, n.resumen, n.url, n.imagen_url, c.nombre AS categoria, p.nombre AS pais, co.nombre AS continente FROM noticias n LEFT JOIN categorias c ON n.categoria_id = c.id LEFT JOIN paises p ON n.pais_id = p.id LEFT JOIN continentes co ON p.continente_id = co.id"
|
||||||
|
|
||||||
|
# === INICIO DE LA MODIFICACIÓN DE LA CONSULTA ===
|
||||||
|
if q:
|
||||||
|
# Convierte el término de búsqueda para que busque palabras completas
|
||||||
|
# Ejemplo: "guerra ucrania" -> "guerra & ucrania"
|
||||||
|
search_query = " & ".join(q.split())
|
||||||
|
conditions.append("n.tsv @@ to_tsquery('spanish', %s)")
|
||||||
|
sql_params.append(search_query)
|
||||||
|
|
||||||
if cat_id: conditions.append("n.categoria_id = %s"); sql_params.append(cat_id)
|
if cat_id: conditions.append("n.categoria_id = %s"); sql_params.append(cat_id)
|
||||||
if pais_id: conditions.append("n.pais_id = %s"); sql_params.append(pais_id)
|
if pais_id: conditions.append("n.pais_id = %s"); sql_params.append(pais_id)
|
||||||
elif cont_id: conditions.append("p.continente_id = %s"); sql_params.append(cont_id)
|
elif cont_id: conditions.append("p.continente_id = %s"); sql_params.append(cont_id)
|
||||||
|
|
@ -74,25 +90,46 @@ def home():
|
||||||
if fecha_filtro:
|
if fecha_filtro:
|
||||||
try:
|
try:
|
||||||
fecha_obj = datetime.strptime(fecha_filtro, '%Y-%m-%d')
|
fecha_obj = datetime.strptime(fecha_filtro, '%Y-%m-%d')
|
||||||
fecha_inicio = fecha_obj
|
conditions.append("n.fecha::date = %s")
|
||||||
fecha_fin = fecha_obj + timedelta(days=1)
|
sql_params.append(fecha_obj.date())
|
||||||
conditions.append("n.fecha >= %s AND n.fecha < %s")
|
|
||||||
sql_params.extend([fecha_inicio, fecha_fin])
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
flash("Formato de fecha no válido. Use AAAA-MM-DD.", "error")
|
flash("Formato de fecha no válido. Use AAAA-MM-DD.", "error")
|
||||||
fecha_filtro = None
|
fecha_filtro = None
|
||||||
|
|
||||||
if conditions: sql_base += " WHERE " + " AND ".join(conditions)
|
if conditions:
|
||||||
sql_final = sql_base + " ORDER BY n.fecha DESC NULLS LAST LIMIT 50"
|
sql_base += " WHERE " + " AND ".join(conditions)
|
||||||
|
|
||||||
|
# Si hay búsqueda, ordena por relevancia. Si no, por fecha.
|
||||||
|
if q:
|
||||||
|
search_query_ts = " & ".join(q.split())
|
||||||
|
sql_base += " ORDER BY ts_rank(n.tsv, to_tsquery('spanish', %s)) DESC, n.fecha DESC"
|
||||||
|
sql_params.append(search_query_ts)
|
||||||
|
else:
|
||||||
|
sql_base += " ORDER BY n.fecha DESC NULLS LAST"
|
||||||
|
|
||||||
|
sql_final = sql_base + " LIMIT 50"
|
||||||
|
# === FIN DE LA MODIFICACIÓN DE LA CONSULTA ===
|
||||||
|
|
||||||
cursor.execute(sql_final, tuple(sql_params))
|
cursor.execute(sql_final, tuple(sql_params))
|
||||||
noticias = cursor.fetchall()
|
noticias = cursor.fetchall()
|
||||||
except psycopg2.Error as db_err:
|
except psycopg2.Error as db_err:
|
||||||
app.logger.error(f"[DB ERROR] Al leer noticias: {db_err}", exc_info=True)
|
app.logger.error(f"[DB ERROR] Al leer noticias: {db_err}", exc_info=True)
|
||||||
flash("Error de base de datos al cargar las noticias.", "error")
|
flash("Error de base de datos al cargar las noticias.", "error")
|
||||||
|
noticias, categorias, continentes, paises = [], [], [], [] # Resetea en caso de error
|
||||||
|
|
||||||
return render_template("noticias.html", noticias=noticias, categorias=categorias, continentes=continentes, paises=paises,
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
cat_id=int(cat_id) if cat_id else None, cont_id=int(cont_id) if cont_id else None,
|
return render_template('_noticias_list.html', noticias=noticias)
|
||||||
pais_id=int(pais_id) if pais_id else None, fecha_filtro=fecha_filtro)
|
|
||||||
|
return render_template("noticias.html",
|
||||||
|
noticias=noticias,
|
||||||
|
categorias=categorias,
|
||||||
|
continentes=continentes,
|
||||||
|
paises=paises,
|
||||||
|
cat_id=int(cat_id) if cat_id else None,
|
||||||
|
cont_id=int(cont_id) if cont_id else None,
|
||||||
|
pais_id=int(pais_id) if pais_id else None,
|
||||||
|
fecha_filtro=fecha_filtro,
|
||||||
|
q=q) # <--- NUEVO: Pasamos el término de búsqueda a la plantilla
|
||||||
|
|
||||||
@app.route("/feeds")
|
@app.route("/feeds")
|
||||||
def dashboard():
|
def dashboard():
|
||||||
|
|
|
||||||
24
templates/_noticias_list.html
Normal file
24
templates/_noticias_list.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<div class="noticias-list">
|
||||||
|
{% for noticia in noticias %}
|
||||||
|
<article class="noticia-item">
|
||||||
|
{% if noticia.imagen_url %}
|
||||||
|
<div class="noticia-imagen">
|
||||||
|
<a href="{{ noticia.url }}" target="_blank" rel="noopener noreferrer"><img src="{{ noticia.imagen_url }}" alt="{{ noticia.titulo }}" loading="lazy"></a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="noticia-texto">
|
||||||
|
<h3><a href="{{ noticia.url }}" target="_blank" rel="noopener noreferrer">{{ noticia.titulo }}</a></h3>
|
||||||
|
<div class="noticia-meta">
|
||||||
|
<span><i class="far fa-calendar-alt"></i> {{ noticia.fecha.strftime('%d-%m-%Y %H:%M') if noticia.fecha else 'N/D' }}</span> |
|
||||||
|
<span><i class="fas fa-tag"></i> {{ noticia.categoria or 'N/A' }}</span> |
|
||||||
|
<span><i class="fas fa-globe-americas"></i> {{ noticia.pais or 'Global' }}</span>
|
||||||
|
</div>
|
||||||
|
<p>{{ noticia.resumen | striptags | safe_html | truncate(280) }}</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<div class="card" style="text-align:center;">
|
||||||
|
<p><i class="fas fa-info-circle"></i> No hay noticias que mostrar con los filtros seleccionados.</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
@ -3,16 +3,23 @@
|
||||||
{% block title %}Últimas Noticias RSS{% endblock %}
|
{% block title %}Últimas Noticias RSS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<header>
|
<header>
|
||||||
<h1>Agregador de Noticias</h1>
|
<h1>Agregador de Noticias</h1>
|
||||||
<p class="subtitle">Tus fuentes de información, en un solo lugar.</p>
|
<p class="subtitle">Tus fuentes de información, en un solo lugar.</p>
|
||||||
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">⚙️ Gestionar Feeds</a>
|
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">
|
||||||
</header>
|
<i class="fas fa-cogs"></i> Gestionar Feeds
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Filtrar Noticias</h2>
|
<h2><i class="fas fa-filter" style="color: var(--secondary-color); margin-right: 10px;"></i>Filtrar Noticias</h2>
|
||||||
<form method="get" action="{{ url_for('home') }}">
|
<form method="get" action="{{ url_for('home') }}" id="filter-form">
|
||||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; align-items: flex-end;">
|
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<label for="q">Buscar por palabra clave</label>
|
||||||
|
<input type="search" name="q" id="q" placeholder="Ej: Trump, California, IA..." value="{{ q or '' }}">
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; align-items: flex-end; border-top: 1px solid var(--border-color); padding-top: 20px;">
|
||||||
<div>
|
<div>
|
||||||
<label for="categoria_id">Categoría</label>
|
<label for="categoria_id">Categoría</label>
|
||||||
<select name="categoria_id" id="categoria_id">
|
<select name="categoria_id" id="categoria_id">
|
||||||
|
|
@ -24,7 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="continente_id">Continente</label>
|
<label for="continente_id">Continente</label>
|
||||||
<select name="continente_id" id="continente_id" onchange="filtrarPaises()">
|
<select name="continente_id" id="continente_id">
|
||||||
<option value="">— Todos —</option>
|
<option value="">— Todos —</option>
|
||||||
{% for cont in continentes %}
|
{% for cont in continentes %}
|
||||||
<option value="{{ cont.id }}" {% if cont_id == cont.id %}selected{% endif %}>{{ cont.nombre }}</option>
|
<option value="{{ cont.id }}" {% if cont_id == cont.id %}selected{% endif %}>{{ cont.nombre }}</option>
|
||||||
|
|
@ -35,78 +42,77 @@
|
||||||
<label for="pais_id">País</label>
|
<label for="pais_id">País</label>
|
||||||
<select name="pais_id" id="pais_id">
|
<select name="pais_id" id="pais_id">
|
||||||
<option value="">— Todos —</option>
|
<option value="">— Todos —</option>
|
||||||
{# El JavaScript llenará esto dinámicamente, pero mostramos el seleccionado si existe #}
|
|
||||||
{% for pais in paises %}
|
{% for pais in paises %}
|
||||||
{% if pais_id == pais.id %}
|
<option value="{{ pais.id }}" data-continente-id="{{ pais.continente_id }}" {% if pais_id == pais.id %}selected{% endif %}>
|
||||||
<option value="{{ pais.id }}" selected>{{ pais.nombre }}</option>
|
{{ pais.nombre }}
|
||||||
{% endif %}
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" class="btn">Filtrar</button>
|
<label for="fecha">Fecha</label>
|
||||||
|
<input type="date" name="fecha" id="fecha" value="{{ fecha_filtro or '' }}">
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;">
|
||||||
|
<button type="submit" class="btn"><i class="fas fa-search"></i> Filtrar</button>
|
||||||
|
<a href="{{ url_for('home') }}" class="btn btn-secondary"><i class="fas fa-eraser"></i> Limpiar</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noticias-list">
|
<div id="noticias-container">
|
||||||
{% for noticia in noticias %}
|
{% include '_noticias_list.html' %}
|
||||||
<article class="noticia-item">
|
</div>
|
||||||
{% if noticia.imagen_url %}
|
|
||||||
<div class="noticia-imagen">
|
|
||||||
<a href="{{ noticia.url }}" target="_blank" rel="noopener noreferrer"><img src="{{ noticia.imagen_url }}" alt="{{ noticia.titulo }}"></a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="noticia-texto">
|
|
||||||
<h3><a href="{{ noticia.url }}" target="_blank" rel="noopener noreferrer">{{ noticia.titulo }}</a></h3>
|
|
||||||
<div class="noticia-meta">
|
|
||||||
<span>🗓️ {{ noticia.fecha.strftime('%d-%m-%Y %H:%M') if noticia.fecha else 'Fecha no disponible' }}</span> |
|
|
||||||
<span>{{ noticia.categoria or 'N/A' }}</span> |
|
|
||||||
<span>{{ noticia.pais or 'Global' }}</span>
|
|
||||||
</div>
|
|
||||||
<p>{{ noticia.resumen | striptags | safe_html | truncate(280) }}</p>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{% else %}
|
|
||||||
<div class="card" style="text-align:center;">
|
|
||||||
<p>No hay noticias que mostrar con los filtros seleccionados.</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="application/json" id="paises-data">{{ paises | tojson }}</script>
|
<script>
|
||||||
<script>
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const todosLosPaises = JSON.parse(document.getElementById('paises-data').textContent);
|
const form = document.getElementById('filter-form');
|
||||||
const continenteSelect = document.getElementById('continente_id');
|
const continenteSelect = document.getElementById('continente_id');
|
||||||
const paisSelect = document.getElementById('pais_id');
|
const paisSelect = document.getElementById('pais_id');
|
||||||
const paisSeleccionadoId = '{{ pais_id or '' }}';
|
|
||||||
|
|
||||||
|
// Lógica para filtrar países al cambiar el continente
|
||||||
function filtrarPaises() {
|
function filtrarPaises() {
|
||||||
const continenteId = continenteSelect.value;
|
const continenteId = continenteSelect.value;
|
||||||
// Guardamos el valor actual por si necesitamos restaurarlo
|
for (let i = 1; i < paisSelect.options.length; i++) {
|
||||||
const valorActualPais = paisSelect.value;
|
const option = paisSelect.options[i];
|
||||||
|
const paisContinenteId = option.getAttribute('data-continente-id');
|
||||||
paisSelect.innerHTML = '<option value="">— Todos —</option>'; // Opción por defecto
|
option.style.display = (!continenteId || paisContinenteId === continenteId) ? '' : 'none';
|
||||||
|
|
||||||
if (todosLosPaises) {
|
|
||||||
todosLosPaises.forEach(pais => {
|
|
||||||
if (!continenteId || pais.continente_id == continenteId) {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = pais.id;
|
|
||||||
option.textContent = pais.nombre;
|
|
||||||
// Si el ID del país coincide con el que estaba seleccionado, lo volvemos a seleccionar
|
|
||||||
if (pais.id == valorActualPais || pais.id == paisSeleccionadoId) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
}
|
||||||
paisSelect.appendChild(option);
|
const opcionSeleccionada = paisSelect.options[paisSelect.selectedIndex];
|
||||||
|
if (opcionSeleccionada && opcionSeleccionada.style.display === 'none') {
|
||||||
|
paisSelect.value = '';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lógica para enviar el formulario con AJAX
|
||||||
|
async function actualizarNoticias(event) {
|
||||||
|
if (event) event.preventDefault();
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const params = new URLSearchParams(formData);
|
||||||
|
const newUrl = `${form.action}?${params.toString()}`;
|
||||||
|
const container = document.getElementById('noticias-container');
|
||||||
|
container.style.opacity = '0.5';
|
||||||
|
container.innerHTML = '<div style="text-align:center; padding: 40px;"><i class="fas fa-spinner fa-spin fa-2x"></i></div>';
|
||||||
|
try {
|
||||||
|
const response = await fetch(newUrl, {
|
||||||
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||||
});
|
});
|
||||||
|
const html = await response.text();
|
||||||
|
container.innerHTML = html;
|
||||||
|
window.history.pushState({path: newUrl}, '', newUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al filtrar noticias:', error);
|
||||||
|
container.innerHTML = '<p style="color:var(--error-color); text-align:center;">Error al cargar las noticias.</p>';
|
||||||
|
} finally {
|
||||||
|
container.style.opacity = '1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ejecutamos la función una vez al cargar la página para inicializar el select de países
|
// Asignar los eventos
|
||||||
// según el continente que ya esté seleccionado.
|
continenteSelect.addEventListener('change', filtrarPaises);
|
||||||
document.addEventListener('DOMContentLoaded', filtrarPaises);
|
form.addEventListener('submit', actualizarNoticias);
|
||||||
</script>
|
filtrarPaises(); // Ejecutar al inicio
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue