Finished
This commit is contained in:
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
def init_ssl():
|
|
||||||
import os, ssl
|
|
||||||
if (not os.environ.get('PYTHONHTTPSVERIFY', '') and
|
|
||||||
getattr(ssl, '_create_unverified_context', None)):
|
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
BASE_URL = "https://www.recetasgratis.net"
|
|
||||||
RECIPES_URL = BASE_URL + "/Recetas-de-Aperitivos-tapas-listado_receta-1_1.html/"
|
|
||||||
DATA_DIR = Path(__file__).parent.parent / "data"
|
|
||||||
DB_PATH = DATA_DIR / "recetas.bd"
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
class DBAttr:
|
|
||||||
def __init__(self, name, type_, modifier=""):
|
|
||||||
self.name = name
|
|
||||||
self.type_ = type_
|
|
||||||
self.modifier = modifier
|
|
||||||
|
|
||||||
def sql(self):
|
|
||||||
parts = [self.name, self.type_]
|
|
||||||
if self.modifier:
|
|
||||||
parts.append(self.modifier)
|
|
||||||
return " ".join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
class DBManager:
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super().__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = Path(path)
|
|
||||||
self.conn = sqlite3.connect(self.path)
|
|
||||||
self.conn.row_factory = sqlite3.Row
|
|
||||||
|
|
||||||
def create_table(self, table_name, attributes: list[DBAttr]):
|
|
||||||
columns_sql = ",\n ".join(attr.sql() for attr in attributes)
|
|
||||||
|
|
||||||
query = f"""
|
|
||||||
CREATE TABLE IF NOT EXISTS {table_name} (
|
|
||||||
{columns_sql}
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.conn:
|
|
||||||
self.conn.execute(query)
|
|
||||||
except Exception as e:
|
|
||||||
print("Error creating table:", e)
|
|
||||||
|
|
||||||
def get_all(self, table_name):
|
|
||||||
try:
|
|
||||||
cursor = self.conn.execute(f"SELECT * FROM {table_name};")
|
|
||||||
return [dict(row) for row in cursor.fetchall()]
|
|
||||||
except Exception as e:
|
|
||||||
print("Error selecting:", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_singleton(self, singleton_table):
|
|
||||||
try:
|
|
||||||
cursor = self.conn.execute(f"SELECT * FROM {singleton_table}")
|
|
||||||
return [row[0] for row in cursor.fetchall()]
|
|
||||||
except Exception as e:
|
|
||||||
print("Error selecting:", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_by(self, table_name, column, value):
|
|
||||||
try:
|
|
||||||
query = f"SELECT * FROM {table_name} WHERE {column} = ?;"
|
|
||||||
cursor = self.conn.execute(query, (value,))
|
|
||||||
return [dict(row) for row in cursor.fetchall()]
|
|
||||||
except Exception as e:
|
|
||||||
print("Error selecting:", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def insert(self, table_name, data: dict):
|
|
||||||
keys = ", ".join(data.keys())
|
|
||||||
placeholders = ", ".join("?" for _ in data)
|
|
||||||
values = tuple(data.values())
|
|
||||||
|
|
||||||
query = f"""
|
|
||||||
INSERT INTO {table_name} ({keys})
|
|
||||||
VALUES ({placeholders});
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.conn:
|
|
||||||
self.conn.execute(query, values)
|
|
||||||
except Exception as e:
|
|
||||||
print("Error inserting:", e)
|
|
||||||
|
|
||||||
def update(self, table_name, data: dict, where_column, where_value):
|
|
||||||
set_clause = ", ".join(f"{key} = ?" for key in data.keys())
|
|
||||||
values = list(data.values())
|
|
||||||
values.append(where_value)
|
|
||||||
|
|
||||||
query = f"""
|
|
||||||
UPDATE {table_name}
|
|
||||||
SET {set_clause}
|
|
||||||
WHERE {where_column} = ?;
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.conn:
|
|
||||||
self.conn.execute(query, tuple(values))
|
|
||||||
except Exception as e:
|
|
||||||
print("Error updating:", e)
|
|
||||||
|
|
||||||
def delete(self, table_name, where_column, where_value):
|
|
||||||
query = f"DELETE FROM {table_name} WHERE {where_column} = ?;"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.conn:
|
|
||||||
self.conn.execute(query, (where_value,))
|
|
||||||
except Exception as e:
|
|
||||||
print("Error deleting:", e)
|
|
||||||
|
|
||||||
def clear(self, table_name):
|
|
||||||
query = f"DELETE FROM {table_name};"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.conn:
|
|
||||||
self.conn.execute(query)
|
|
||||||
except Exception as e:
|
|
||||||
print("Error clearing table: ", e)
|
|
||||||
|
|
||||||
def exists(self, table_name, where_column, where_value):
|
|
||||||
query = f"SELECT 1 FROM {table_name} WHERE {where_column} = ? LIMIT 1;"
|
|
||||||
|
|
||||||
try:
|
|
||||||
cursor = self.conn.execute(query, (where_value,))
|
|
||||||
return cursor.fetchone() is not None
|
|
||||||
except Exception as e:
|
|
||||||
print("Error checking existence:", e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def count(self, table_name):
|
|
||||||
try:
|
|
||||||
cursor = self.conn.execute(f"SELECT COUNT(*) as total FROM {table_name};")
|
|
||||||
return cursor.fetchone()["total"]
|
|
||||||
except Exception as e:
|
|
||||||
print("Error counting:", e)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.conn.close()
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
from bs4 import BeautifulSoup
|
|
||||||
import re
|
|
||||||
from tkinter import Tk
|
|
||||||
from tkinter import messagebox
|
|
||||||
import urllib.request
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from db import DBManager, DBAttr
|
|
||||||
from ui import WinesUI
|
|
||||||
from __ssl import init_ssl
|
|
||||||
from config import *
|
|
||||||
|
|
||||||
init_ssl()
|
|
||||||
|
|
||||||
dbm = DBManager(DB_PATH)
|
|
||||||
|
|
||||||
def create_tables():
|
|
||||||
movies_attr = [
|
|
||||||
DBAttr("title", "TEXT", "NOT NULL"),
|
|
||||||
DBAttr("original_title", "TEXT", "NOT NULL"),
|
|
||||||
DBAttr("country", "TEXT", "NOT NULL"),
|
|
||||||
DBAttr("date", "DATE", "NOT NULL"),
|
|
||||||
DBAttr("director", "TEXT", "NOT NULL"),
|
|
||||||
DBAttr("genres", "TEXT", "NOT NULL")
|
|
||||||
]
|
|
||||||
|
|
||||||
genres_attr = [
|
|
||||||
DBAttr("genre", "TEXT")
|
|
||||||
]
|
|
||||||
|
|
||||||
dbm.create_table("movies", movies_attr)
|
|
||||||
dbm.create_table("genres", genres_attr)
|
|
||||||
|
|
||||||
def parse_duration(duration):
|
|
||||||
duration.strip()
|
|
||||||
res = 0
|
|
||||||
if duration[-1] == "h":
|
|
||||||
duration.replace("h","")
|
|
||||||
res = int(duration) * 60
|
|
||||||
elif "h" in duration:
|
|
||||||
duration.replace("h","")
|
|
||||||
duration.replace("m","")
|
|
||||||
res = int(duration[0]) + int(duration[1:])
|
|
||||||
else:
|
|
||||||
duration.replace("m","")
|
|
||||||
res = int(duration)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def main():
|
|
||||||
create_tables()
|
|
||||||
root = Tk()
|
|
||||||
ui = WinesUI(root)
|
|
||||||
|
|
||||||
|
|
||||||
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("movies")
|
|
||||||
dbm.clear("genres")
|
|
||||||
movies_count = persit_recetas()
|
|
||||||
ui.info(f"Hay {movies_count} recetas")
|
|
||||||
case "Recetas":
|
|
||||||
recetas = dbm.get_all("recetas")
|
|
||||||
ui.show_list(recetas, ["titulo", "dificultad", "comensales", "duracion", "autor", "fecha"])
|
|
||||||
case "Receta por autor":
|
|
||||||
def search_title(title):
|
|
||||||
movies = [movie for movie in dbm.get_all("recetas") if title.lower() in movie["titulo"].lower()]
|
|
||||||
ui.show_list(recetas, ["titulo", "dificultad", "comensales", "duracion", "autor", "fecha"])
|
|
||||||
ui.ask_text("Buscar por autor: ", search_title)
|
|
||||||
case "Receta por fecha":
|
|
||||||
def search_date(date):
|
|
||||||
d = datetime.strptime(date, "%d-%m-%Y")
|
|
||||||
movies = [movie for movie in dbm.get_all("movies")
|
|
||||||
if d < datetime.strptime(movie["date"], "%Y-%m-%d %H:%M:%S")]
|
|
||||||
ui.show_list(movies, ["title", "date"])
|
|
||||||
ui.ask_text("Buscar por fecha: ", search_date)
|
|
||||||
|
|
||||||
ui.callback = handle_action
|
|
||||||
root.mainloop()
|
|
||||||
dbm.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -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):
|
||||||
|
match(action):
|
||||||
# def handle_action(action):
|
case "cargar":
|
||||||
|
resp = messagebox.askyesno(title="Cargar", message="Quieres cargar todos los datos de nuevo?")
|
||||||
#ui.callback = handle_action
|
if resp:
|
||||||
#root.mainloop()
|
dbm.clear("recipes")
|
||||||
#dbm.close()
|
recipes_count = persist_recipes()
|
||||||
|
ui.info(f"Hay {recipes_count} recetas")
|
||||||
print(dbm.get_all("recipes"))
|
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
|
||||||
|
root.mainloop()
|
||||||
|
dbm.close()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -2,7 +2,7 @@ import tkinter as tk
|
|||||||
from tkinter import ttk, messagebox
|
from tkinter import ttk, messagebox
|
||||||
from tkinter.scrolledtext import ScrolledText
|
from tkinter.scrolledtext import ScrolledText
|
||||||
|
|
||||||
class WinesUI():
|
class RecipesUI():
|
||||||
def __init__(self, root, title = "AII"):
|
def __init__(self, root, title = "AII"):
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title(title)
|
self.root.title(title)
|
||||||
@@ -22,12 +22,12 @@ class WinesUI():
|
|||||||
# Menu Listar
|
# Menu Listar
|
||||||
listar_menu = tk.Menu(self.menu, tearoff=0)
|
listar_menu = tk.Menu(self.menu, tearoff=0)
|
||||||
listar_menu.add_command(label= "Recetas", command = lambda: self.callback("listar_recetas"))
|
listar_menu.add_command(label= "Recetas", command = lambda: self.callback("listar_recetas"))
|
||||||
|
self.menu.add_cascade(label="Listar", menu=listar_menu)
|
||||||
|
|
||||||
# Menu Buscar
|
# Menu Buscar
|
||||||
buscar_menu = tk.Menu(self.menu, tearoff=0)
|
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 autor", command=lambda: self.callback("buscar_autor"))
|
||||||
buscar_menu.add_command(label="Receta por fecha", command=lambda: self.callback("buscar_fecha"))
|
buscar_menu.add_command(label="Receta por fecha", command=lambda: self.callback("buscar_fecha"))
|
||||||
|
|
||||||
self.menu.add_cascade(label="Buscar", menu=buscar_menu)
|
self.menu.add_cascade(label="Buscar", menu=buscar_menu)
|
||||||
|
|
||||||
# Callback externo desde el punto de entrada
|
# Callback externo desde el punto de entrada
|
||||||
Reference in New Issue
Block a user