I can apply formulas
This commit is contained in:
@@ -233,7 +233,7 @@ class DataGrid(MultipleInstance):
|
|||||||
# add columns manager
|
# add columns manager
|
||||||
self._columns_manager = DataGridColumnsManager(self)
|
self._columns_manager = DataGridColumnsManager(self)
|
||||||
self._columns_manager.bind_command("ToggleColumn", self.commands.on_column_changed())
|
self._columns_manager.bind_command("ToggleColumn", self.commands.on_column_changed())
|
||||||
self._columns_manager.bind_command("UpdateColumn", self.commands.on_column_changed())
|
self._columns_manager.bind_command("SaveColumnDetails", self.commands.on_column_changed())
|
||||||
|
|
||||||
if self._settings.enable_formatting:
|
if self._settings.enable_formatting:
|
||||||
completion_engine = FormattingCompletionEngine(self._parent, self.get_table_name())
|
completion_engine = FormattingCompletionEngine(self._parent, self.get_table_name())
|
||||||
@@ -418,8 +418,7 @@ class DataGrid(MultipleInstance):
|
|||||||
self._df_store.ns_fast_access = _init_fast_access(self._df)
|
self._df_store.ns_fast_access = _init_fast_access(self._df)
|
||||||
self._df_store.ns_row_data = _init_row_data(self._df)
|
self._df_store.ns_row_data = _init_row_data(self._df)
|
||||||
self._df_store.ns_total_rows = len(self._df) if self._df is not None else 0
|
self._df_store.ns_total_rows = len(self._df) if self._df is not None else 0
|
||||||
if init_state:
|
self._register_existing_formulas()
|
||||||
self._register_existing_formulas()
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -677,8 +676,27 @@ class DataGrid(MultipleInstance):
|
|||||||
return Span(*res, cls=f"{css_class} truncate", style=style) if len(res) > 1 else res[0]
|
return Span(*res, cls=f"{css_class} truncate", style=style) if len(res) > 1 else res[0]
|
||||||
|
|
||||||
column_type = col_def.type
|
column_type = col_def.type
|
||||||
value = self._df_store.ns_fast_access[col_def.col_id][row_index]
|
|
||||||
|
# Formula column: safe read — ns_fast_access entry may not exist yet if the
|
||||||
|
# engine hasn't run its first recalculation pass.
|
||||||
|
if column_type == ColumnType.Formula:
|
||||||
|
col_array = self._df_store.ns_fast_access.get(col_def.col_id)
|
||||||
|
if col_array is None or row_index >= len(col_array):
|
||||||
|
return NotStr('<span class="dt2-cell-content-text truncate">—</span>')
|
||||||
|
value = col_array[row_index]
|
||||||
|
# Display error strings produced by the evaluator (#ERR, #DIV/0!, …)
|
||||||
|
if isinstance(value, str) and value.startswith("#"):
|
||||||
|
return NotStr(
|
||||||
|
f'<span class="dt2-cell-content-error truncate">{html.escape(value)}</span>'
|
||||||
|
)
|
||||||
|
# Infer number vs text style from the actual computed value type
|
||||||
|
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
||||||
|
column_type = ColumnType.Number
|
||||||
|
else:
|
||||||
|
column_type = ColumnType.Text
|
||||||
|
else:
|
||||||
|
value = self._df_store.ns_fast_access[col_def.col_id][row_index]
|
||||||
|
|
||||||
# Boolean type - uses cached HTML (only 2 possible values)
|
# Boolean type - uses cached HTML (only 2 possible values)
|
||||||
if column_type == ColumnType.Bool:
|
if column_type == ColumnType.Bool:
|
||||||
return _mk_bool_cached(value)
|
return _mk_bool_cached(value)
|
||||||
@@ -722,8 +740,9 @@ class DataGrid(MultipleInstance):
|
|||||||
"""
|
"""
|
||||||
if not col_def.visible:
|
if not col_def.visible:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
value = self._df_store.ns_fast_access[col_def.col_id][row_index]
|
col_array = self._df_store.ns_fast_access.get(col_def.col_id)
|
||||||
|
value = col_array[row_index] if col_array is not None and row_index < len(col_array) else None
|
||||||
content = self.mk_body_cell_content(col_pos, row_index, col_def, filter_keyword_lower)
|
content = self.mk_body_cell_content(col_pos, row_index, col_def, filter_keyword_lower)
|
||||||
|
|
||||||
return OptimizedDiv(content,
|
return OptimizedDiv(content,
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class Commands(BaseCommands):
|
|||||||
self._owner.show_all_columns).htmx(target=f"#{self._id}", swap="innerHTML")
|
self._owner.show_all_columns).htmx(target=f"#{self._id}", swap="innerHTML")
|
||||||
|
|
||||||
def save_column_details(self, col_id):
|
def save_column_details(self, col_id):
|
||||||
return Command(f"UpdateColumn",
|
return Command(f"SaveColumnDetails",
|
||||||
f"Update column {col_id}",
|
f"Save column {col_id}",
|
||||||
self._owner,
|
self._owner,
|
||||||
self._owner.save_column_details,
|
self._owner.save_column_details,
|
||||||
kwargs={"col_id": col_id}
|
kwargs={"col_id": col_id}
|
||||||
@@ -71,7 +71,7 @@ class DataGridColumnsManager(MultipleInstance):
|
|||||||
self._parent._parent,
|
self._parent._parent,
|
||||||
self._parent.get_table_name(),
|
self._parent.get_table_name(),
|
||||||
)
|
)
|
||||||
conf = DslEditorConf(save_button=False, line_numbers=False, engine_id=completion_engine.get_id())
|
conf = DslEditorConf(name="formula", save_button=False, line_numbers=False, engine_id=completion_engine.get_id())
|
||||||
self._formula_editor = DataGridFormulaEditor(self, conf=conf, _id=f"{self._id}-formula-editor")
|
self._formula_editor = DataGridFormulaEditor(self, conf=conf, _id=f"{self._id}-formula-editor")
|
||||||
DslsManager.register(completion_engine, FormulaParser())
|
DslsManager.register(completion_engine, FormulaParser())
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class DataGridColumnsManager(MultipleInstance):
|
|||||||
col_def.width = int(v)
|
col_def.width = int(v)
|
||||||
elif k == "formula":
|
elif k == "formula":
|
||||||
col_def.formula = v or ""
|
col_def.formula = v or ""
|
||||||
self._register_formula(col_def)
|
# self._register_formula(col_def), Will be done in save_column_details()
|
||||||
else:
|
else:
|
||||||
setattr(col_def, k, v)
|
setattr(col_def, k, v)
|
||||||
|
|
||||||
@@ -133,7 +133,8 @@ class DataGridColumnsManager(MultipleInstance):
|
|||||||
|
|
||||||
def save_column_details(self, col_id, client_response):
|
def save_column_details(self, col_id, client_response):
|
||||||
logger.debug(f"save_column_details {col_id=}, {client_response=}")
|
logger.debug(f"save_column_details {col_id=}, {client_response=}")
|
||||||
self._get_updated_col_def_from_col_id(col_id, client_response, copy=False)
|
col_def = self._get_updated_col_def_from_col_id(col_id, client_response, copy=False)
|
||||||
|
self._register_formula(col_def)
|
||||||
self._parent.save_state()
|
self._parent.save_state()
|
||||||
|
|
||||||
return self._mk_inner_content()
|
return self._mk_inner_content()
|
||||||
@@ -149,12 +150,17 @@ class DataGridColumnsManager(MultipleInstance):
|
|||||||
return self.mk_column_details(col_def)
|
return self.mk_column_details(col_def)
|
||||||
|
|
||||||
def _register_formula(self, col_def) -> None:
|
def _register_formula(self, col_def) -> None:
|
||||||
"""Register or remove a formula column with the FormulaEngine."""
|
"""Register or remove a formula column with the FormulaEngine.
|
||||||
|
|
||||||
|
Registers only when col_def.type is Formula and the formula text is
|
||||||
|
non-empty. Removes the formula in all other cases so the engine stays
|
||||||
|
consistent with the column definition.
|
||||||
|
"""
|
||||||
engine = self._parent.get_formula_engine()
|
engine = self._parent.get_formula_engine()
|
||||||
if engine is None:
|
if engine is None:
|
||||||
return
|
return
|
||||||
table = self._parent.get_table_name()
|
table = self._parent.get_table_name()
|
||||||
if col_def.formula:
|
if col_def.type == ColumnType.Formula and col_def.formula:
|
||||||
try:
|
try:
|
||||||
engine.set_formula(table, col_def.col_id, col_def.formula)
|
engine.set_formula(table, col_def.col_id, col_def.formula)
|
||||||
logger.debug("Registered formula for %s.%s", table, col_def.col_id)
|
logger.debug("Registered formula for %s.%s", table, col_def.col_id)
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class DslEditor(MultipleInstance):
|
|||||||
return Textarea(
|
return Textarea(
|
||||||
self._state.content,
|
self._state.content,
|
||||||
id=f"ta_{self._id}",
|
id=f"ta_{self._id}",
|
||||||
name=f"ta_{self._id}",
|
name=self.conf.name if (self.conf and self.conf.name) else f"ta_{self._id}",
|
||||||
cls="hidden",
|
cls="hidden",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from myfasthtml.core.utils import merge_classes
|
|||||||
from myfasthtml.icons.fluent import question20_regular, brain_circuit20_regular, number_row20_regular, \
|
from myfasthtml.icons.fluent import question20_regular, brain_circuit20_regular, number_row20_regular, \
|
||||||
number_symbol20_regular
|
number_symbol20_regular
|
||||||
from myfasthtml.icons.fluent_p1 import checkbox_checked20_regular, checkbox_unchecked20_regular, \
|
from myfasthtml.icons.fluent_p1 import checkbox_checked20_regular, checkbox_unchecked20_regular, \
|
||||||
checkbox_checked20_filled
|
checkbox_checked20_filled, math_formula16_regular
|
||||||
from myfasthtml.icons.fluent_p2 import text_bullet_list_square20_regular, text_field20_regular
|
from myfasthtml.icons.fluent_p2 import text_bullet_list_square20_regular, text_field20_regular
|
||||||
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
|
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
|
||||||
|
|
||||||
@@ -160,4 +160,5 @@ icons = {
|
|||||||
ColumnType.Datetime: calendar_ltr20_regular,
|
ColumnType.Datetime: calendar_ltr20_regular,
|
||||||
ColumnType.Bool: checkbox_checked20_filled,
|
ColumnType.Bool: checkbox_checked20_filled,
|
||||||
ColumnType.Enum: text_bullet_list_square20_regular,
|
ColumnType.Enum: text_bullet_list_square20_regular,
|
||||||
|
ColumnType.Formula: math_formula16_regular,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ class FormulaEngine:
|
|||||||
# Registers in DAG and raises FormulaCycleError if cycle detected
|
# Registers in DAG and raises FormulaCycleError if cycle detected
|
||||||
self._graph.add_formula(table, col, formula)
|
self._graph.add_formula(table, col, formula)
|
||||||
self._formulas[(table, col)] = formula
|
self._formulas[(table, col)] = formula
|
||||||
|
# Mark dirty so the column is computed on the next render
|
||||||
|
self._graph.mark_dirty(table, col)
|
||||||
|
|
||||||
logger.debug("Formula set for %s.%s: %s", table, col, formula_text)
|
logger.debug("Formula set for %s.%s: %s", table, col, formula_text)
|
||||||
|
|
||||||
def remove_formula(self, table: str, col: str) -> None:
|
def remove_formula(self, table: str, col: str) -> None:
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ class MockDataGrid(MultipleInstance):
|
|||||||
def get_table_name(self):
|
def get_table_name(self):
|
||||||
return "mock_table"
|
return "mock_table"
|
||||||
|
|
||||||
|
def get_formula_engine(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_datagrid(root_instance):
|
def mock_datagrid(root_instance):
|
||||||
|
|||||||
Reference in New Issue
Block a user