Merge branch 'dev' of git.miarma.net:/Gallardo7761/aii-monorepo into dev

This commit is contained in:
2026-03-07 22:09:39 +01:00
3 changed files with 152 additions and 21 deletions

View File

@@ -7,7 +7,7 @@ from datetime import datetime
import locale import locale
from db import DBManager, DBAttr from db import DBManager, DBAttr
#from ui import RecipesUI from ui import RecipesUI
from __ssl import init_ssl from __ssl import init_ssl
from config import * from config import *
@@ -28,53 +28,105 @@ def create_tables():
dbm.create_table("recipes", recipes_attr) dbm.create_table("recipes", recipes_attr)
def parse_duration(duration):
if not duration:
return None
duration = duration.strip().lower()
hours = 0
minutes = 0
h_match = re.search(r"(\d+)h", duration)
m_match = re.search(r"(\d+)m", duration)
if h_match:
hours = int(h_match.group(1))
if m_match:
minutes = int(m_match.group(1))
return hours * 60 + minutes
def parse_duration_inverse(minutes):
if minutes is None:
return None
m = minutes % 60
h = (minutes - m) // 60
return f"{h}h {m}m" if h != 0 else f"{m}m"
def persist_recipes(): def persist_recipes():
f = urllib.request.urlopen(RECIPES_URL) f = urllib.request.urlopen(RECIPES_URL)
bs = BeautifulSoup(f, "lxml") bs = BeautifulSoup(f, "lxml")
results = bs.find_all("div", attrs={"data-js-selector": "resultado"}) results = bs.find_all("div", attrs={"data-js-selector": "resultado"})
for div in results: for div in results:
print(div)
title_a = div.a title_a = div.a
title = title_a.string.strip() title = title_a.string.strip()
info_div = div.find("div", class_="info_snippet") info_div = div.find("div", class_="info_snippet")
difficulty = info_div.find("span").get_text(strip=True) if info_div and info_div.find("span") else None difficulty = info_div.find("span").get_text(strip=True) if info_div and info_div.find("span") else None
properties = div.find("div", class_="properties") properties = div.find("div", class_="properties")
duration = properties.find("span", class_=["property", "duracion"]).string.strip() if properties and properties.find("span", class_=["property", "duracion"]) else None duration = properties.find("span", class_="duracion").string.strip() if properties and properties.find("span", class_="duracion") else None
units = properties.find("span", class_=["property", "unidades"]).string.strip() if properties and properties.find("span", class_=["property", "unidades"]) else None units = properties.find("span", class_="unidades").string.strip() if properties and properties.find("span", class_="unidades") else None
details_link = title_a["href"] details_link = title_a["href"]
f2 = urllib.request.urlopen(details_link) f2 = urllib.request.urlopen(details_link)
bs2 = BeautifulSoup(f2, "lxml") bs2 = BeautifulSoup(f2, "lxml")
details = bs2.find("div", class_="autor").find("div", class_="nombre_autor") details = bs2.find("div", class_="autor").find("div", class_="nombre_autor")
author = details.find("a").string author = details.find("a").string
date_str = details.find("span").string date_str = details.find("span").string.replace("Actualizado: ", "")
updated_at = datetime.strptime(date_str, "%d %B %Y") updated_at = datetime.strptime(date_str, "%d %B %Y")
dbm.insert("recipes", { dbm.insert("recipes", {
"title": title, "title": title,
"difficulty": difficulty, "difficulty": difficulty,
"units": units, "units": units,
"duration": duration, "duration": parse_duration(duration),
"author": author, "author": author,
"updated_at": updated_at "updated_at": updated_at
}) })
return dbm.count("recipes") return dbm.count("recipes")
def main(): def main():
create_tables() create_tables()
recipes_count = persist_recipes() root = Tk()
print(recipes_count) ui = RecipesUI(root)
#root = Tk()
#ui = RecipesUI(root)
# def handle_action(action): def handle_action(action):
match(action):
case "cargar":
resp = messagebox.askyesno(title="Cargar", message="Quieres cargar todos los datos de nuevo?")
if resp:
dbm.clear("recipes")
recipes_count = persist_recipes()
ui.info(f"Hay {recipes_count} recetas")
case "listar_recetas":
recipes = dbm.get_all("recipes")
for r in recipes:
r["units"] = str(r["units"]) + " personas" if r["units"] is not None else "Unknown personas"
r["duration"] = parse_duration_inverse(r["duration"])
ui.show_list(recipes, ["title", "difficulty", "units", "duration"])
case "buscar_autor":
def search_author(author):
recipes = [recipe for recipe in dbm.get_all("recipes") if author.lower() in recipe["author"].lower()]
for r in recipes:
r["units"] = str(r["units"]) + " personas" if r["units"] is not None else "Unknown personas"
r["duration"] = parse_duration_inverse(r["duration"])
ui.show_list(recipes, ["title", "difficulty", "units", "duration", "author"])
ui.ask_text("Buscar por autor: ", search_author)
case "buscar_fecha":
def search_date(date):
d = datetime.strptime(date, "%d/%m/%Y")
recipes = [recipe for recipe in dbm.get_all("recipes")
if d > datetime.strptime(recipe["updated_at"], "%Y-%m-%d %H:%M:%S")]
for r in recipes:
r["units"] = str(r["units"]) + " personas" if r["units"] is not None else "Unknown personas"
r["duration"] = parse_duration_inverse(r["duration"])
ui.show_list(recipes, ["title", "difficulty", "units", "duration", "updated_at"])
ui.ask_text("Buscar por fecha: ", search_date)
#ui.callback = handle_action ui.callback = handle_action
#root.mainloop() root.mainloop()
#dbm.close() dbm.close()
print(dbm.get_all("recipes"))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -0,0 +1,79 @@
import tkinter as tk
from tkinter import ttk, messagebox
from tkinter.scrolledtext import ScrolledText
class RecipesUI():
def __init__(self, root, title = "AII"):
self.root = root
self.root.title(title)
self.root.geometry("900x600")
# Menu Principal
self.menu = tk.Menu(self.root)
self.root.config(menu=self.menu)
# Menu Datos
datos_menu = tk.Menu(self.menu, tearoff=0)
datos_menu.add_command(label="Cargar", command=lambda: self.callback("cargar"))
datos_menu.add_separator()
datos_menu.add_command(label="Salir", command=self.root.quit)
self.menu.add_cascade(label="Datos", menu=datos_menu)
# Menu Listar
listar_menu = tk.Menu(self.menu, tearoff=0)
listar_menu.add_command(label= "Recetas", command = lambda: self.callback("listar_recetas"))
self.menu.add_cascade(label="Listar", menu=listar_menu)
# Menu Buscar
buscar_menu = tk.Menu(self.menu, tearoff=0)
buscar_menu.add_command(label="Receta por autor", command=lambda: self.callback("buscar_autor"))
buscar_menu.add_command(label="Receta por fecha", command=lambda: self.callback("buscar_fecha"))
self.menu.add_cascade(label="Buscar", menu=buscar_menu)
# Callback externo desde el punto de entrada
self.callback = None
def show_list(self, items, fields, title="Listado"):
mw = tk.Toplevel(self.root)
mw.title(title)
listbox = tk.Listbox(mw, width=80, height=20)
listbox.pack(side="left", fill="both", expand=True)
scrollbar = tk.Scrollbar(mw)
scrollbar.pack(side="right", fill="y")
listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=listbox.yview)
for item in items:
row = " | ".join(str(item[field]) for field in fields)
listbox.insert("end", row)
def ask_text(self, label, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
entry = ttk.Entry(mw)
entry.pack(pady=5)
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(entry.get()), mw.destroy()]).pack(pady=10)
def ask_spinbox(self, label, options, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
spinbox = ttk.Spinbox(mw, values=options, state="readonly", width=40)
spinbox.pack(pady=5)
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(spinbox.get()), mw.destroy()]).pack(pady=10)
def ask_radiobutton(self, label, options, callback):
mw = tk.Toplevel(self.root)
mw.title(label)
tk.Label(mw, text=label).pack(pady=5)
sv = tk.StringVar(value=options[0])
for option in options:
tk.Radiobutton(mw, text=option, variable=sv, value=option).pack(anchor="w")
ttk.Button(mw, text="Aceptar", command=
lambda: [callback(sv.get()), mw.destroy()]).pack(pady=10)
def info(slef, message):
messagebox.showinfo("Información", message)