Files
Autoparts-DB/console/screens/vehiculo_nav.py
2026-02-15 01:47:23 +00:00

240 lines
8.4 KiB
Python

"""
Vehicle drill-down navigation screen for the AUTOPARTES console application.
Guides the user through a four-level hierarchy:
Brand -> Model -> Year -> Engine
Each level presents a filterable list. After engine selection the screen
navigates to the catalogue (``catalogo``) with the resolved
``model_year_engine`` id.
"""
from console.core.screens import Screen
from console.core.keybindings import Key
# Ordered sequence of drill-down levels.
_LEVELS = ("brand", "model", "year", "engine")
# Human-readable titles for each level (Spanish).
_LEVEL_TITLES = {
"brand": "Seleccione Marca",
"model": "Seleccione Modelo",
"year": "Seleccione Ano",
"engine": "Seleccione Motor",
}
class VehiculoNavScreen(Screen):
"""Four-level vehicle drill-down: Brand -> Model -> Year -> Engine."""
def __init__(self):
super().__init__("vehiculo_nav", "Consulta por Vehiculo")
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _get_title_for_level(self, context):
"""Return the title string for the current drill-down level."""
level = context.get("level", "brand")
return _LEVEL_TITLES.get(level, "Seleccione")
def _get_subtitle(self, context):
"""Build a breadcrumb subtitle from selections made so far.
Example: ``"TOYOTA > CAMRY > 2023 > Seleccione motor"``
"""
parts = []
if context.get("brand"):
parts.append(context["brand"])
if context.get("model"):
parts.append(context["model"])
if context.get("year") is not None:
parts.append(str(context["year"]))
level = context.get("level", "brand")
parts.append(_LEVEL_TITLES.get(level, ""))
return " > ".join(parts)
def _load_items(self, context, db):
"""Fetch the item list from the database for the current level."""
level = context.get("level", "brand")
if level == "brand":
context["all_items"] = db.get_brands()
elif level == "model":
context["all_items"] = db.get_models(brand=context.get("brand"))
elif level == "year":
context["all_items"] = db.get_years(
brand=context.get("brand"),
model=context.get("model"),
)
elif level == "engine":
context["all_items"] = db.get_engines(
brand=context.get("brand"),
model=context.get("model"),
year=context.get("year"),
)
else:
context["all_items"] = []
self._apply_filter(context)
def _apply_filter(self, context):
"""Reduce ``all_items`` to those matching ``filter_text``.
Matching is a case-insensitive substring test on the display name.
"""
level = context.get("level", "brand")
ft = context.get("filter_text", "").lower()
all_items = context.get("all_items", [])
if ft:
context["filtered_items"] = [
item for item in all_items
if ft in self._get_display_name(item, level).lower()
]
else:
context["filtered_items"] = list(all_items)
@staticmethod
def _get_display_name(item, level):
"""Extract the human-readable display string from an item dict."""
if level == "year":
return str(item.get("year", ""))
return item.get("name", "")
# ------------------------------------------------------------------
# Screen interface
# ------------------------------------------------------------------
def render(self, context, db, renderer):
# First-render initialisation
if "level" not in context:
context["level"] = "brand"
context["filter_text"] = ""
context["selected_index"] = 0
self._load_items(context, db)
level = context["level"]
title = self._get_title_for_level(context)
subtitle = self._get_subtitle(context)
renderer.draw_header(title, subtitle)
# Build the (number, label) tuples expected by draw_filter_list.
filtered = context.get("filtered_items", [])
display_items = [
(str(idx + 1), self._get_display_name(item, level))
for idx, item in enumerate(filtered)
]
renderer.draw_filter_list(
display_items,
context.get("filter_text", ""),
context.get("selected_index", 0),
title=f"SELECCIONAR {level.upper()}",
)
renderer.draw_footer([
("Escriba", "Filtrar"),
("ENTER", "Seleccionar"),
("\u2191\u2193", "Mover"),
("ESC", "Atras"),
])
def on_key(self, key, context, db, renderer, nav):
filtered = context.get("filtered_items", [])
level = context.get("level", "brand")
# -- ESC: go back one level, or return to menu ----------------
if key == Key.ESCAPE:
if level == "brand":
return "back"
prev = _LEVELS[_LEVELS.index(level) - 1]
context["level"] = prev
context["filter_text"] = ""
context["selected_index"] = 0
self._load_items(context, db)
return None
# -- ENTER: select item and advance ---------------------------
if key == Key.ENTER and filtered:
idx = context.get("selected_index", 0)
if idx >= len(filtered):
return None
selected = filtered[idx]
if level == "brand":
context["brand"] = selected["name"]
context["level"] = "model"
elif level == "model":
context["model"] = selected["name"]
context["level"] = "year"
elif level == "year":
context["year"] = selected["year"]
context["level"] = "engine"
elif level == "engine":
context["engine_id"] = selected["id"]
context["engine_name"] = selected["name"]
# Resolve the model_year_engine row
mye_list = db.get_model_year_engine(
context["brand"],
context["model"],
context["year"],
context["engine_id"],
)
if mye_list:
mye_id = mye_list[0]["id"]
return (
"catalogo",
{
"mye_id": mye_id,
"brand": context["brand"],
"model": context["model"],
"year": context["year"],
"engine": context["engine_name"],
},
f"{context['brand']} {context['model']} {context['year']}",
)
else:
renderer.show_message(
"No se encontro configuracion para este vehiculo",
"error",
)
return None
# Reset filter for the new level
context["filter_text"] = ""
context["selected_index"] = 0
self._load_items(context, db)
return None
# -- Arrow keys: move selection cursor ------------------------
if key == Key.UP:
if context.get("selected_index", 0) > 0:
context["selected_index"] -= 1
return None
if key == Key.DOWN:
if context.get("selected_index", 0) < len(filtered) - 1:
context["selected_index"] += 1
return None
# -- Backspace: trim filter text ------------------------------
if key in (Key.BACKSPACE, 8, 263):
if context.get("filter_text"):
context["filter_text"] = context["filter_text"][:-1]
self._apply_filter(context)
context["selected_index"] = 0
return None
# -- Printable characters: append to filter -------------------
if isinstance(key, int) and 32 <= key <= 126:
context["filter_text"] = context.get("filter_text", "") + chr(key)
self._apply_filter(context)
context["selected_index"] = 0
return None
return None