From f08ae4a90be6f406bcca734e8eaad8dc554712b6 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sat, 23 Aug 2025 00:29:52 +0200 Subject: [PATCH] Added RowIndex in GridState. Fixed content escaping --- .../datagrid_new/components/DataGrid.py | 25 +++++++++++++------ src/components/datagrid_new/constants.py | 2 ++ src/components/datagrid_new/settings.py | 1 + tests/test_datagrid_new.py | 15 +++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/components/datagrid_new/components/DataGrid.py b/src/components/datagrid_new/components/DataGrid.py index 57d267c..e08b503 100644 --- a/src/components/datagrid_new/components/DataGrid.py +++ b/src/components/datagrid_new/components/DataGrid.py @@ -1,8 +1,10 @@ import copy +import html import logging from io import BytesIO from typing import Literal, Any +import numpy as np import pandas as pd from fasthtml.components import * from fasthtml.xtend import Script @@ -121,14 +123,21 @@ class DataGrid(BaseComponent): else: return ColumnType.Text # Default to Text if no match + def _init_columns(_df): + columns = [DataGridColumnState(make_safe_id(col_id), + col_index, + col_id, + _get_column_type(_df[make_safe_id(col_id)].dtype)) + for col_index, col_id in enumerate(_df.columns)] + if self._state.row_index: + columns.insert(0, DataGridColumnState(make_safe_id(ROW_INDEX_ID), -1, " ", ColumnType.RowIndex)) + + return columns + self._df = df.copy() self._df.columns = self._df.columns.map(make_safe_id) # make sure column names are trimmed self._state.rows = [DataGridRowState(row_id) for row_id in self._df.index] - self._state.columns = [DataGridColumnState(make_safe_id(col_id), - col_index, - col_id, - _get_column_type(self._df[make_safe_id(col_id)].dtype)) - for col_index, col_id in enumerate(df.columns)] + self._state.columns = _init_columns(self._df) self._fast_access = self._init_fast_access(self._df) self._total_rows = len(self._df) if self._df is not None else 0 @@ -560,7 +569,7 @@ class DataGrid(BaseComponent): return mk_my_ellipsis(_value, cls="dt2-cell-content-number") def process_cell_content(_value): - value_str = str(_value) + value_str = html.escape(str(_value)) if FILTER_INPUT_CID not in self._state.filtered or ( keyword := self._state.filtered[FILTER_INPUT_CID]) is None: @@ -869,7 +878,9 @@ class DataGrid(BaseComponent): if df is None: return {} - return {col: df[col].to_numpy() for col in df.columns} + res = {col: df[col].to_numpy() for col in df.columns} + res[ROW_INDEX_ID] = df.index.to_numpy() + return res @timed def __ft__(self): diff --git a/src/components/datagrid_new/constants.py b/src/components/datagrid_new/constants.py index e79b55d..5cd5620 100644 --- a/src/components/datagrid_new/constants.py +++ b/src/components/datagrid_new/constants.py @@ -19,6 +19,8 @@ DATAGRID_STATE_FOOTER = "footer" DATAGRID_PAGE_SIZE = 50 +ROW_INDEX_ID = "__row_index__" + class Routes: Filter = "/filter" # request the filtering in the grid ResetFilter = "/reset_filter" # diff --git a/src/components/datagrid_new/settings.py b/src/components/datagrid_new/settings.py index 4a036af..1971fdd 100644 --- a/src/components/datagrid_new/settings.py +++ b/src/components/datagrid_new/settings.py @@ -69,6 +69,7 @@ class DataGridSettings: class DataGridState: sidebar_visible: bool = False selected_view: str = None + row_index: bool = False columns: list[DataGridColumnState] = dataclasses.field(default_factory=list) rows: list[DataGridRowState] = dataclasses.field(default_factory=list) # only the rows that have a specific state footers: list[DataGridFooterConf] = dataclasses.field(default_factory=list) diff --git a/tests/test_datagrid_new.py b/tests/test_datagrid_new.py index 7747d4c..03e2469 100644 --- a/tests/test_datagrid_new.py +++ b/tests/test_datagrid_new.py @@ -509,3 +509,18 @@ def test_i_can_compute_footer_menu_position_when_not_enough_space(dg): ) assert matches(menu, expected) + + +def test_the_content_of_the_cell_is_escaped(empty_dg): + df = pd.DataFrame({ + 'value': ['
My Content
'], + 'value2': ['{My Content}'], + }) + my_dg = empty_dg.init_from_dataframe(df) + + actual = my_dg.__ft__() + table_content = extract_table_values_new(actual, header=True) + + assert table_content == OrderedDict({ + 'value': ['<div> My Content </div>'], + 'value2': ['{My Content}']})