From e8f3d08d77ec7b27e88938eee72a03a871056696 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Sun, 28 Apr 2024 19:33:22 +0100 Subject: [PATCH] Use the same layout adjusment everywhere (#264) --- docs/changelog.md | 1 + itables/javascript.py | 87 +++++++++++++++++--------------------- tests/test_pandas_style.py | 36 ++++++++++++++++ 3 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 tests/test_pandas_style.py diff --git a/docs/changelog.md b/docs/changelog.md index 25ed0a1..e613178 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -14,6 +14,7 @@ ITables ChangeLog **Fixed** - We have improve the compatibility with dark themes ([#255](https://github.com/mwouts/itables/issues/255)) - We now enforce non-sparse index when displaying Pandas Style objects with a multiindex ([#254](https://github.com/mwouts/itables/issues/254)) +- Export buttons are shown when using `show(df, buttons=...)` on a Pandas Style object ([#259](https://github.com/mwouts/itables/issues/259)) 2.0.0 (2024-03-16) diff --git a/itables/javascript.py b/itables/javascript.py index b36c7fe..895a052 100644 --- a/itables/javascript.py +++ b/itables/javascript.py @@ -80,7 +80,6 @@ def init_notebook_mode( all_interactive=False, connected=GOOGLE_COLAB, - warn_if_call_is_superfluous=True, dt_bundle=None, ): """Load the DataTables library and the corresponding css (if connected=False), @@ -98,20 +97,6 @@ def init_notebook_mode( "This is because HTML outputs in Google Colab are encapsulated in iframes." ) - if ( - all_interactive is False - and pd.DataFrame._repr_html_ == _ORIGINAL_DATAFRAME_REPR_HTML - and connected is True - and _CONNECTED == connected - ): - if warn_if_call_is_superfluous: - warnings.warn( - "Did you know? " - "init_notebook_mode(all_interactive=False, connected=True) does nothing. " - "Feel free to remove this line, or pass warn_if_call_is_superfluous=False." - ) - return - _CONNECTED = connected if all_interactive: @@ -385,38 +370,7 @@ def to_html_datatable( ) ) - has_default_layout = kwargs["layout"] == DEFAULT_LAYOUT - - if "dom" in kwargs: - if opt.warn_on_dom: - warnings.warn( - "The 'dom' argument has been deprecated in DataTables==2.0.", - DeprecationWarning, - ) - if not has_default_layout: - raise ValueError("You can pass both 'dom' and 'layout'") - del kwargs["layout"] - has_default_layout = False - - if has_default_layout and _df_fits_in_one_page(df, kwargs): - - def filter_control(control): - if control == "info" and downsampling_warning: - return control - if control not in DEFAULT_LAYOUT_CONTROLS: - return control - return None - - kwargs["layout"] = { - key: filter_control(control) for key, control in kwargs["layout"].items() - } - - if ( - "buttons" in kwargs - and "layout" in kwargs - and "buttons" not in kwargs["layout"].values() - ): - kwargs["layout"] = {**kwargs["layout"], "topStart": "buttons"} + _adjust_layout(df, kwargs, downsampling_warning) footer = kwargs.pop("footer") column_filters = kwargs.pop("column_filters") @@ -549,8 +503,7 @@ def to_html_datatable_using_to_html( # Polars DataFrame showIndex = False - if "dom" not in kwargs and _df_fits_in_one_page(df, kwargs): - kwargs["dom"] = "t" + _adjust_layout(df, kwargs, downsampling_warning="") tableId = ( tableId @@ -713,6 +666,34 @@ def _min_rows(kwargs): return min_rows[0] +def _adjust_layout(df, kwargs, downsampling_warning): + has_default_layout = kwargs["layout"] == DEFAULT_LAYOUT + + if "dom" in kwargs: + if opt.warn_on_dom: + warnings.warn( + "The 'dom' argument has been deprecated in DataTables==2.0.", + DeprecationWarning, + ) + if not has_default_layout: + raise ValueError("You cannot pass both 'dom' and 'layout'") + del kwargs["layout"] + has_default_layout = False + + if has_default_layout and _df_fits_in_one_page(df, kwargs): + kwargs["layout"] = { + key: _filter_control(control, downsampling_warning) + for key, control in kwargs["layout"].items() + } + + if ( + "buttons" in kwargs + and "layout" in kwargs + and "buttons" not in kwargs["layout"].values() + ): + kwargs["layout"] = {**kwargs["layout"], "topStart": "buttons"} + + def _df_fits_in_one_page(df, kwargs): """Display just the table (not the search box, etc...) if the rows fit on one 'page'""" try: @@ -723,6 +704,14 @@ def _df_fits_in_one_page(df, kwargs): return len(df) <= _min_rows(kwargs) +def _filter_control(control, downsampling_warning): + if control == "info" and downsampling_warning: + return control + if control not in DEFAULT_LAYOUT_CONTROLS: + return control + return None + + def safe_reset_index(df): try: return df.reset_index() diff --git a/tests/test_pandas_style.py b/tests/test_pandas_style.py new file mode 100644 index 0000000..0c22e35 --- /dev/null +++ b/tests/test_pandas_style.py @@ -0,0 +1,36 @@ +import json + +import pandas as pd +import pytest + +from itables import to_html_datatable + +pytest.importorskip("jinja2") + + +@pytest.mark.skipif( + pd.__version__.startswith("0."), + reason="AttributeError: 'Styler' object has no attribute 'to_html'", +) +def test_buttons_are_shown_on_pd_style_objects(): + df = pd.DataFrame({"A": ["a"]}).style + html = to_html_datatable( + df, buttons=["pageLength", "copyHtml5", "csvHtml5", "excelHtml5"] + ) + + # Extract the dt_args passed to datatables + dt_args = "" + for line in html.splitlines(): + line = line.strip() + if line.startswith("let dt_args"): + dt_args = line.split("=", 1)[1] + break + + assert dt_args.endswith(";"), dt_args + dt_args = dt_args[:-1] + dt_args = json.loads(dt_args) + + print(dt_args) + assert "dom" not in dt_args + assert "buttons" in dt_args + assert "buttons" in dt_args["layout"].values()