frontend añadido y backend de creacion de notas
This commit is contained in:
@@ -0,0 +1,521 @@
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.15.5"
|
||||
app = marimo.App(width="columns")
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import marimo as mo
|
||||
return (mo,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
from backend.db.session import engine
|
||||
import pandas as pd
|
||||
return engine, pd
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(engine, mo, pd):
|
||||
# Query directo a information_schema
|
||||
query_tables = """
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name;
|
||||
"""
|
||||
tables_df = pd.read_sql(query_tables, engine)
|
||||
|
||||
# Dropdown para elegir tabla
|
||||
table_selector = mo.ui.dropdown(
|
||||
options=tables_df["table_name"].tolist(),
|
||||
label="Selecciona una tabla",
|
||||
allow_select_none= False,
|
||||
value=tables_df["table_name"][0]
|
||||
)
|
||||
table_selector
|
||||
return (table_selector,)
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(mo):
|
||||
mo.md(r"""## Leer Datos""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
delete_status,
|
||||
engine,
|
||||
final_status,
|
||||
insert_button,
|
||||
inserted_data,
|
||||
mo,
|
||||
pd,
|
||||
soft_delete_status,
|
||||
soft_restore_status,
|
||||
table_selector,
|
||||
):
|
||||
inserted_data
|
||||
final_status
|
||||
delete_status
|
||||
soft_delete_status
|
||||
soft_restore_status
|
||||
insert_button
|
||||
|
||||
query_data = f'SELECT * FROM "{table_selector.value}" LIMIT 1000;'
|
||||
df_preview = pd.read_sql(query_data, engine)
|
||||
mo.ui.table(df_preview) # editor visual
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
get_obj_id_input = mo.ui.number(label="ID a recuperar", value=1)
|
||||
get_obj_button = mo.ui.run_button(label="Recuperar objeto", kind="neutral")
|
||||
|
||||
mo.hstack([get_obj_id_input, get_obj_button])
|
||||
return get_obj_button, get_obj_id_input
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(get_obj_button, get_obj_id_input, mo, repo, session, table_selector):
|
||||
obj_status = "ℹ️ Introduce un ID y pulsa *Recuperar*."
|
||||
obj_python = None
|
||||
|
||||
if get_obj_button.value and table_selector.value:
|
||||
try:
|
||||
# Usamos el repo → devuelve un dominio (no un dict)
|
||||
_fetched_obj = repo.get_by_id(get_obj_id_input.value)
|
||||
if _fetched_obj:
|
||||
obj_status = f"✅ Objeto recuperado con ID {get_obj_id_input.value}"
|
||||
obj_python = _fetched_obj
|
||||
else:
|
||||
obj_status = f"⚠️ No se encontró registro con ID {get_obj_id_input.value}"
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
obj_status = f"❌ Error al recuperar: {exc!s}"
|
||||
|
||||
mo.vstack([
|
||||
mo.md(f"**Estado:** {obj_status}"),
|
||||
mo.inspect(obj_python)
|
||||
])
|
||||
return
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(mo):
|
||||
mo.md(r"""## Crear datos""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(engine, pd, table_selector):
|
||||
from sqlalchemy import text
|
||||
|
||||
table_selector
|
||||
|
||||
query_columns = text("""
|
||||
SELECT column_name, data_type, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = :table_name
|
||||
AND column_name NOT LIKE 'sys_%'
|
||||
ORDER BY ordinal_position;
|
||||
""")
|
||||
|
||||
columns_df = pd.read_sql(query_columns, engine, params={"table_name": table_selector.value})
|
||||
|
||||
columns_df = columns_df[
|
||||
~(
|
||||
(columns_df["column_name"] == "id") &
|
||||
(columns_df["column_default"].notnull()) &
|
||||
(columns_df["column_default"].str.contains("nextval"))
|
||||
)
|
||||
]
|
||||
return (columns_df,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(columns_df, mo, table_selector):
|
||||
# Generamos inputs dinámicamente con soporte para fechas
|
||||
ui_inputs = {}
|
||||
for _, row in columns_df.iterrows():
|
||||
col, dtype = row["column_name"], row["data_type"]
|
||||
|
||||
if dtype in ("integer", "numeric", "bigint", "smallint"):
|
||||
ui_inputs[col] = mo.ui.number(label=col)
|
||||
elif dtype in ("boolean",):
|
||||
ui_inputs[col] = mo.ui.checkbox(label=col)
|
||||
elif dtype in ("date", "timestamp", "timestamp without time zone", "timestamp with time zone"):
|
||||
ui_inputs[col] = mo.ui.date(label=col) # 👉 usar date picker de Marimo
|
||||
else:
|
||||
ui_inputs[col] = mo.ui.text(label=col)
|
||||
|
||||
# Botón
|
||||
run_button_add = mo.ui.run_button(
|
||||
label=f"Agregar fila a {table_selector.value}",
|
||||
kind="neutral"
|
||||
)
|
||||
|
||||
# Layout de inputs + botón
|
||||
mo.vstack(list(ui_inputs.values()) + [run_button_add])
|
||||
return run_button_add, ui_inputs
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(MapperCls, mo, repo, run_button_add, table_selector, ui_inputs):
|
||||
_status_message = "ℹ️ Completa los campos y pulsa *Agregar fila*."
|
||||
inserted_id = None
|
||||
inserted_data = None
|
||||
|
||||
if run_button_add.value and table_selector.value:
|
||||
try:
|
||||
# Recoger inputs y normalizar fechas a ISO
|
||||
raw_data = {}
|
||||
for _col, ui in ui_inputs.items():
|
||||
value = ui.value
|
||||
if hasattr(value, "isoformat"): # 👉 si es date/datetime
|
||||
raw_data[_col] = value.isoformat()
|
||||
elif value == "" or value is None: # 👉 convertir '' → None
|
||||
raw_data[_col] = None
|
||||
else:
|
||||
raw_data[_col] = value
|
||||
|
||||
# Convertir dict → dominio con el mapper correcto
|
||||
dominio_obj = MapperCls.from_dict(raw_data)
|
||||
|
||||
# Insertar en la tabla usando el repo correcto
|
||||
inserted_id = repo.add(dominio_obj, created_by="admin")
|
||||
|
||||
_status_message = f"✅ Registro insertado en {table_selector.value} con ID: {inserted_id}"
|
||||
inserted_data = raw_data
|
||||
except Exception as exc:
|
||||
_status_message = f"❌ Error al insertar: {exc!s}"
|
||||
|
||||
|
||||
mo.vstack([
|
||||
mo.md(f"**Estado:** {_status_message}"),
|
||||
mo.json(inserted_data) if inserted_data else mo.md("")
|
||||
])
|
||||
|
||||
return (inserted_data,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
mo.md(r"""Para añadir un objeto dentro de la base de datos:""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
from domains.nota import notaDom
|
||||
|
||||
# Aqui genera tu objeto de python
|
||||
|
||||
# objeto_python = notaDom(
|
||||
# nombre="Prueba",
|
||||
# descripcion="Registro de prueba insertado desde Python",
|
||||
# activo=True,
|
||||
|
||||
# )
|
||||
|
||||
insert_button = mo.ui.run_button(label="Insertar objeto en la bbdd", kind="neutral")
|
||||
|
||||
mo.vstack([
|
||||
# mo.inspect(objeto_python),
|
||||
insert_button])
|
||||
return (insert_button,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(ejemploRepo, insert_button, mo, objeto_python, session):
|
||||
insert_status = "ℹ️ Pulsa el botón para insertar el objeto."
|
||||
nuevo_id = None
|
||||
|
||||
if insert_button.value:
|
||||
try:
|
||||
# Instancia del repo
|
||||
repo_objeto = ejemploRepo(session)
|
||||
|
||||
# Inserción en la BD
|
||||
nuevo_id = repo_objeto.add(
|
||||
objeto_python,
|
||||
created_by="admin",
|
||||
notes="Inserción desde código Python"
|
||||
)
|
||||
insert_status = f"✅ Registro insertado con ID: {nuevo_id}"
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
insert_status = f"❌ Error al insertar: {exc!s}"
|
||||
|
||||
mo.md(insert_status)
|
||||
return
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(mo):
|
||||
mo.md(r"""## Actualizar datos""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(mo):
|
||||
update_id_input = mo.ui.number(label="ID a actualizar", value=1)
|
||||
update_fetch_button = mo.ui.run_button(label="Buscar registro", kind="neutral")
|
||||
confirm_update_button = mo.ui.run_button(label="Confirmar actualización", kind="success")
|
||||
|
||||
mo.hstack([update_id_input, update_fetch_button, confirm_update_button])
|
||||
return confirm_update_button, update_fetch_button, update_id_input
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
MapperCls,
|
||||
mo,
|
||||
repo,
|
||||
session,
|
||||
table_selector,
|
||||
update_fetch_button,
|
||||
update_id_input,
|
||||
):
|
||||
# Valores iniciales
|
||||
_update_status = "ℹ️ Introduce un ID y pulsa *Buscar*."
|
||||
update_dic = None
|
||||
|
||||
if update_fetch_button.value and table_selector.value:
|
||||
try:
|
||||
fetched_obj = repo.get_by_id(update_id_input.value)
|
||||
if fetched_obj:
|
||||
fetched_dict = MapperCls.to_dict(fetched_obj)
|
||||
|
||||
update_dic = {
|
||||
col: (
|
||||
mo.ui.checkbox(value=bool(fetched_dict[col]), label=col)
|
||||
if isinstance(fetched_dict[col], bool) else
|
||||
mo.ui.number(value=fetched_dict[col], label=col)
|
||||
if isinstance(fetched_dict[col], (int, float)) else
|
||||
mo.ui.date(value=fetched_dict[col], label=col)
|
||||
if hasattr(fetched_dict[col], "isoformat") else
|
||||
mo.ui.text(value=str(fetched_dict[col]) if fetched_dict[col] is not None else "", label=col)
|
||||
)
|
||||
for col in fetched_dict.keys()
|
||||
if not col.startswith("sys_") and col != "id"
|
||||
}
|
||||
|
||||
_update_status = f"✅ Registro encontrado con ID: {update_id_input.value}"
|
||||
else:
|
||||
_update_status = f"⚠️ No se encontró registro con ID: {update_id_input.value}"
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
_update_status = f"❌ Error al buscar: {exc!s}"
|
||||
|
||||
mo.vstack([
|
||||
mo.md(f"**Estado:** {_update_status}"),
|
||||
mo.vstack(list(update_dic.values())) if update_dic else mo.md("")
|
||||
])
|
||||
return (update_dic,)
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(confirm_update_button, mo, repo, session, update_dic, update_id_input):
|
||||
# Estado por defecto
|
||||
final_status = "ℹ️ Esperando confirmación de actualización."
|
||||
|
||||
if confirm_update_button.value and update_dic is not None:
|
||||
try:
|
||||
updated_data = {}
|
||||
for _col, _ui in update_dic.items():
|
||||
val = _ui.value
|
||||
# Normalización: "" → None
|
||||
if val == "":
|
||||
updated_data[_col] = None
|
||||
# Normalización: fechas → ISO string
|
||||
elif hasattr(val, "isoformat"):
|
||||
updated_data[_col] = val.isoformat()
|
||||
else:
|
||||
updated_data[_col] = val
|
||||
|
||||
ok = repo.update(
|
||||
update_id_input.value,
|
||||
updated_data,
|
||||
updated_by="admin",
|
||||
notes="Actualización desde UI"
|
||||
)
|
||||
if ok:
|
||||
final_status = f"✅ Registro con ID {update_id_input.value} actualizado correctamente."
|
||||
else:
|
||||
final_status = f"⚠️ No se pudo actualizar el registro con ID {update_id_input.value}."
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
final_status = f"❌ Error al actualizar: {exc!s}"
|
||||
|
||||
mo.md(f"**Estado final:** {final_status}")
|
||||
return (final_status,)
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(mo):
|
||||
mo.md(r"""## Eliminar datos""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
delete_id_input = mo.ui.number(label="ID a eliminar", value=1)
|
||||
delete_button = mo.ui.run_button(label="Hard delete", kind="danger")
|
||||
|
||||
mo.hstack([delete_id_input, delete_button])
|
||||
return delete_button, delete_id_input
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(delete_button, delete_id_input, mo, repo, session, table_selector):
|
||||
delete_status = "ℹ️ Introduce un ID y pulsa *Eliminar*."
|
||||
|
||||
if delete_button.value and table_selector.value:
|
||||
try:
|
||||
_ok = repo.delete_by_id(delete_id_input.value)
|
||||
if _ok:
|
||||
delete_status = f"✅ Registro con ID {delete_id_input.value} eliminado permanentemente."
|
||||
else:
|
||||
delete_status = f"⚠️ No se encontró registro con ID {delete_id_input.value}."
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
delete_status = f"❌ Error al eliminar: {exc!s}"
|
||||
|
||||
mo.md(f"**Estado:** {delete_status}")
|
||||
return (delete_status,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
soft_delete_id_input = mo.ui.number(label="ID a eliminar (soft)", value=1)
|
||||
soft_delete_button = mo.ui.run_button(label="Soft delete", kind="warn")
|
||||
|
||||
mo.hstack([soft_delete_id_input, soft_delete_button])
|
||||
return soft_delete_button, soft_delete_id_input
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
mo,
|
||||
repo,
|
||||
session,
|
||||
soft_delete_button,
|
||||
soft_delete_id_input,
|
||||
table_selector,
|
||||
):
|
||||
soft_delete_status = "ℹ️ Introduce un ID y pulsa *Soft delete*."
|
||||
|
||||
if soft_delete_button.value and table_selector.value:
|
||||
try:
|
||||
_ok = repo.soft_delete(
|
||||
soft_delete_id_input.value,
|
||||
deleted_by="admin",
|
||||
notes="Eliminado desde UI"
|
||||
)
|
||||
if _ok:
|
||||
soft_delete_status = f"✅ Registro con ID {soft_delete_id_input.value} marcado como eliminado (soft delete)."
|
||||
else:
|
||||
soft_delete_status = f"⚠️ No se encontró registro con ID {soft_delete_id_input.value} o ya estaba eliminado."
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
soft_delete_status = f"❌ Error al aplicar soft delete: {exc!s}"
|
||||
|
||||
mo.md(f"**Estado:** {soft_delete_status}")
|
||||
return (soft_delete_status,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
soft_restore_id_input = mo.ui.number(label="ID a restaurar (soft)", value=1)
|
||||
soft_restore_button = mo.ui.run_button(label="Restaurar registro", kind="success")
|
||||
|
||||
mo.hstack([soft_restore_id_input, soft_restore_button])
|
||||
return soft_restore_button, soft_restore_id_input
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
mo,
|
||||
repo,
|
||||
session,
|
||||
soft_restore_button,
|
||||
soft_restore_id_input,
|
||||
table_selector,
|
||||
):
|
||||
soft_restore_status = "ℹ️ Introduce un ID y pulsa *Restaurar*."
|
||||
|
||||
if soft_restore_button.value and table_selector.value:
|
||||
try:
|
||||
_ok = repo.soft_restore(
|
||||
soft_restore_id_input.value,
|
||||
restored_by="admin",
|
||||
notes="Restaurado desde UI"
|
||||
)
|
||||
if _ok:
|
||||
soft_restore_status = f"✅ Registro con ID {soft_restore_id_input.value} restaurado correctamente."
|
||||
else:
|
||||
soft_restore_status = f"⚠️ No se encontró registro con ID {soft_restore_id_input.value} o no estaba eliminado."
|
||||
except Exception as exc:
|
||||
session.rollback()
|
||||
soft_restore_status = f"❌ Error al restaurar: {exc!s}"
|
||||
|
||||
mo.md(f"**Estado:** {soft_restore_status}")
|
||||
return (soft_restore_status,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
|
||||
# # Arregla transacciones rotas
|
||||
|
||||
# try:
|
||||
# _dominio_obj = MapperCls.from_dict(raw_data)
|
||||
# _inserted_id = repo.add(_dominio_obj, created_by="admin")
|
||||
# _status_message = f"✅ Insertado con ID {_inserted_id}"
|
||||
# except Exception as exc:
|
||||
# session.rollback() # 👈 limpia la transacción rota
|
||||
# _status_message = f"❌ Error al insertar: {exc!s}"
|
||||
return
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(table_selector):
|
||||
import importlib
|
||||
from backend.db.session import SessionLocal
|
||||
|
||||
def load_classes(entidad: str):
|
||||
"""
|
||||
Importa dinámicamente Repo, Mapper y Dom según convención:
|
||||
- Módulo: domains.{entidad} (minúsculas)
|
||||
- Clases: {EntidadCapitalizada}Repo, {EntidadCapitalizada}Mapper, {EntidadCapitalizada}Dom
|
||||
"""
|
||||
entidad = entidad.lower() # siempre minúscula para el módulo
|
||||
module_name = f"domains.{entidad}"
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
# Nombre base: capitalizar primera letra
|
||||
base_name = entidad.lower()
|
||||
|
||||
repo_class = getattr(module, f"{base_name}Repo")
|
||||
mapper_class = getattr(module, f"{base_name}Mapper")
|
||||
dom_class = getattr(module, f"{base_name}Dom")
|
||||
|
||||
return repo_class, mapper_class, dom_class
|
||||
|
||||
|
||||
# 👉 Uso interactivo en Marimo
|
||||
session = SessionLocal()
|
||||
if table_selector.value: # valor que escribiste en el textarea
|
||||
RepoCls, MapperCls, DomCls = load_classes(table_selector.value)
|
||||
repo = RepoCls(session)
|
||||
return MapperCls, repo, session
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
Reference in New Issue
Block a user