- Migrate from SQLite to PostgreSQL with normalized schema - Add 11 lookup tables (fuel_type, body_type, drivetrain, transmission, materials, position_part, manufacture_type, quality_tier, countries, reference_type, shapes) - Rewrite dashboard/server.py (76 routes) using SQLAlchemy text() queries - Rewrite console/db.py (27 methods) using SQLAlchemy ORM - Add models.py with 27 SQLAlchemy model definitions - Add config.py for centralized DB_URL configuration - Add migrate_to_postgres.py migration script - Add docs/METABASE_GUIDE.md with complete data entry guide - Rebrand from "AUTOPARTS DB" to "NEXUS AUTOPARTS" - Fill vehicle data gaps via NHTSA API + heuristics: engines (cylinders, power, torque), brands (country, founded_year), models (body_type, production years), MYE (drivetrain, transmission, trim) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
"""
|
|
Abstract base renderer interface for the NEXUS AUTOPARTS console application.
|
|
|
|
Every renderer (curses VT220, Textual/Rich, etc.) must subclass
|
|
:class:`BaseRenderer` and implement all of its methods. Screens call
|
|
these methods without knowing which backend is active.
|
|
"""
|
|
|
|
|
|
class BaseRenderer:
|
|
"""Abstract interface that all renderers must implement.
|
|
|
|
Methods raise :exc:`NotImplementedError` so that missing overrides
|
|
are caught immediately at runtime.
|
|
"""
|
|
|
|
# ── Lifecycle ────────────────────────────────────────────────────
|
|
|
|
def init_screen(self):
|
|
"""Initialise the terminal / display backend."""
|
|
raise NotImplementedError
|
|
|
|
def cleanup(self):
|
|
"""Restore the terminal to its original state."""
|
|
raise NotImplementedError
|
|
|
|
# ── Screen queries ───────────────────────────────────────────────
|
|
|
|
def get_size(self) -> tuple:
|
|
"""Return ``(height, width)`` of the usable display area."""
|
|
raise NotImplementedError
|
|
|
|
# ── Primitive operations ─────────────────────────────────────────
|
|
|
|
def clear(self):
|
|
"""Clear the entire screen buffer."""
|
|
raise NotImplementedError
|
|
|
|
def refresh(self):
|
|
"""Flush the screen buffer to the terminal."""
|
|
raise NotImplementedError
|
|
|
|
def get_key(self) -> int:
|
|
"""Block until a key is pressed and return its key code."""
|
|
raise NotImplementedError
|
|
|
|
# ── High-level widgets ───────────────────────────────────────────
|
|
|
|
def draw_header(self, title, subtitle=''):
|
|
"""Draw the application header bar on the top two rows.
|
|
|
|
*title* is left-aligned; *subtitle* is right-aligned.
|
|
Row 1 is a horizontal separator.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_footer(self, key_labels):
|
|
"""Draw the footer bar on the bottom two rows.
|
|
|
|
*key_labels* is a list of ``(key, description)`` tuples,
|
|
e.g. ``[("F1", "Ayuda"), ("ESC", "Atras")]``.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_menu(self, items, selected_index=0, title=''):
|
|
"""Draw a numbered menu list starting at row 3.
|
|
|
|
*items* is a list of ``(number, label)`` tuples.
|
|
Separator items have ``number == '---'``.
|
|
The item at *selected_index* is highlighted.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_table(self, headers, rows, widths, page_info=None,
|
|
selected_row=-1):
|
|
"""Draw a columnar data table.
|
|
|
|
*headers*: list of column header strings.
|
|
*rows*: list of row tuples (each tuple matches *headers*).
|
|
*widths*: list of int column widths.
|
|
*page_info*: optional dict ``{page, total_pages, total_rows}``.
|
|
*selected_row*: index of the highlighted row (-1 = none).
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_detail(self, fields, title=''):
|
|
"""Draw a detail view with label-value pairs.
|
|
|
|
*fields* is a list of ``(label, value)`` tuples displayed as
|
|
``Label........: Value``.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_form(self, fields, focused_index=0, title=''):
|
|
"""Draw an editable form.
|
|
|
|
*fields* is a list of dicts with keys:
|
|
``label``, ``value``, ``width``, ``type``, ``hint``.
|
|
The field at *focused_index* uses the active style.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_filter_list(self, items, filter_text, selected_index,
|
|
title=''):
|
|
"""Draw a filterable list with a text input at the top.
|
|
|
|
*items*: list of ``(number, label)`` tuples.
|
|
*filter_text*: current filter string.
|
|
*selected_index*: highlighted item index.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_comparison(self, columns, title=''):
|
|
"""Draw a side-by-side comparison view.
|
|
|
|
*columns* is a list of dicts, each with:
|
|
``header`` (str) and ``rows`` (list of ``(label, value)``).
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
# ── Low-level drawing ────────────────────────────────────────────
|
|
|
|
def draw_text(self, row, col, text, style='normal'):
|
|
"""Draw *text* at ``(row, col)`` using the named *style*."""
|
|
raise NotImplementedError
|
|
|
|
def draw_box(self, top, left, height, width, title=''):
|
|
"""Draw a box with Unicode line-drawing characters.
|
|
|
|
Optional *title* is rendered in the top border.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
# ── Dialogs ──────────────────────────────────────────────────────
|
|
|
|
def show_message(self, text, msg_type='info') -> bool:
|
|
"""Show a centred message box.
|
|
|
|
*msg_type* is one of ``'info'``, ``'error'``, or ``'confirm'``.
|
|
For ``'confirm'`` the user must press S (si) or N (no);
|
|
returns ``True`` for S, ``False`` for N.
|
|
For other types, waits for any key and returns ``True``.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def show_input(self, prompt, max_len=40):
|
|
"""Show a centred input dialog.
|
|
|
|
Returns the entered string, or ``None`` if the user pressed
|
|
Escape to cancel.
|
|
"""
|
|
raise NotImplementedError
|