Repository: holoviz/panel

Issue:
In the Tabulator of panel 1.3.2, using HTMLTemplateFormatter for the Index now raises an exception.
It worked fine in panel 1.3.1

#### ALL software version info
Windows 10 python 3.11.6

bokeh                     3.3.1
holoviews                 1.18.1
panel                     1.3.2
param                     2.0.1
jupyter-bokeh             3.0.7
jupyter_client            7.4.9
jupyter_core              5.3.1
jupyter-events            0.6.3
jupyter-lsp               2.2.0
jupyter_server            2.5.0
jupyter_server_terminals  0.4.4
jupyterlab                4.0.9
jupyterlab-pygments       0.2.2
jupyterlab_server         2.23.0
jupyterlab-widgets        3.0.8
ipykernel                 6.24.0

#### Description of expected behavior and the observed behavior

#### Complete, minimal, self-contained example code that reproduces the issue

```

from bokeh.models.widgets.tables import HTMLTemplateFormatter
import panel as pn
pn.extension('tabulator')

df = pd.DataFrame(
    {
        'A':[1,2,3],
        'B':[1,2,3],
    }
)
df = df.set_index('A')

tabu = pn.widgets.Tabulator(
    df,
    formatters=dict(
        A=HTMLTemplateFormatter(template='<a href="https://www.google.com/search?code=<%= value %>"><%= value %></a>'),
    ),
)
tabu

```

#### Stack traceback and/or browser JavaScript console output

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\IPython\core\formatters.py:974](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/IPython/core/formatters.py:974), in MimeBundleFormatter.__call__(self, obj, include, exclude)
    [971](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/IPython/core/formatters.py?line=970)     method = get_real_method(obj, self.print_method)
    [973](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/IPython/core/formatters.py?line=972)     if method is not None:
--> [974](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/IPython/core/formatters.py?line=973)         return method(include=include, exclude=exclude)
    [975](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/IPython/core/formatters.py?line=974)     return None
    [976](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/IPython/core/formatters.py?line=975) else:

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\viewable.py:774](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py:774), in Viewable._repr_mimebundle_(self, include, exclude)
    [771](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=770)     loaded = hv.extension._loaded
    [773](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=772) if config.comms in ('vscode', 'ipywidgets'):
--> [774](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=773)     widget = ipywidget(self)
    [775](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=774)     if hasattr(widget, '_repr_mimebundle_'):
    [776](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=775)         return widget._repr_mimebundle_(include=include, exclude=exclude), {}

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\io\notebook.py:527](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/io/notebook.py:527), in ipywidget(obj, doc, **kwargs)
    [525](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/io/notebook.py?line=524) from ..pane import panel
    [526](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/io/notebook.py?line=525) doc = doc if doc else Document()
--> [527](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/io/notebook.py?line=526) model = panel(obj, **kwargs).get_root(doc=doc)
    [528](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/io/notebook.py?line=527) widget = BokehModel(model, combine_events=True)
    [529](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/io/notebook.py?line=528) if hasattr(widget, '_view_count'):

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\viewable.py:670](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py:670), in Renderable.get_root(self, doc, comm, preprocess)
    [668](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=667)     root_view = wrapper
    [669](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=668) else:
--> [670](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=669)     root = self._get_model(doc, comm=comm)
    [671](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=670)     root_view = self
    [672](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/viewable.py?line=671)     if preprocess:

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\widgets\tables.py:1679](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py:1679), in Tabulator._get_model(self, doc, root, parent, comm)
   [1672](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1671) def _get_model(
   [1673](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1672)     self, doc: Document, root: Optional[Model] = None,
   [1674](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1673)     parent: Optional[Model] = None, comm: Optional[Comm] = None
   [1675](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1674) ) -> Model:
   [1676](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1675)     Tabulator._widget_type = lazy_load(
   [1677](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1676)         'panel.models.tabulator', 'DataTabulator', isinstance(comm, JupyterComm), root
   [1678](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1677)     )
-> [1679](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1678)     model = super()._get_model(doc, root, parent, comm)
   [1680](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1679)     root = root or model
   [1681](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1680)     self._child_panels = child_panels = self._get_children()

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\widgets\tables.py:334](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py:334), in BaseTable._get_model(self, doc, root, parent, comm)
    [330](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=329) def _get_model(
    [331](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=330)     self, doc: Document, root: Optional[Model] = None,
    [332](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=331)     parent: Optional[Model] = None, comm: Optional[Comm] = None
    [333](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=332) ) -> Model:
--> [334](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=333)     properties = self._get_properties(doc)
    [335](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=334)     model = self._widget_type(**properties)
    [336](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=335)     root = root or model

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\widgets\tables.py:1637](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py:1637), in Tabulator._get_properties(self, doc)
   [1636](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1635) def _get_properties(self, doc: Document) -> Dict[str, Any]:
-> [1637](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1636)     properties = super()._get_properties(doc)
   [1638](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1637)     properties['configuration'] = self._get_configuration(properties['columns'])
   [1639](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=1638)     properties['cell_styles'] = self._get_style_data()

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\widgets\tables.py:325](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py:325), in BaseTable._get_properties(self, doc)
    [323](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=322) def _get_properties(self, doc: Document) -> Dict[str, Any]:
    [324](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=323)     properties = super()._get_properties(doc)
--> [325](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=324)     properties['columns'] = self._get_columns()
    [326](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=325)     properties['source']  = cds = ColumnDataSource(data=self._data)
    [327](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=326)     cds.selected.indices = self.selection

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\widgets\tables.py:161](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py:161), in BaseTable._get_columns(self)
    [159](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=158) fields = self._get_fields()
    [160](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=159) df = self.value.reset_index() if len(indexes) > 1 else self.value
--> [161](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=160) return self._get_column_definitions(fields, df)

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\panel\widgets\tables.py:238](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py:238), in BaseTable._get_column_definitions(self, col_names, df)
    [236](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=235)         warn(msg, RuntimeWarning)
    [237](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=236) elif col in self.indexes:
--> [238](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=237)     formatter.text_align = 'left'
    [240](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=239) if isinstance(self.widths, int):
    [241](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/panel/widgets/tables.py?line=240)     col_kwargs['width'] = self.widths

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\bokeh\core\has_props.py:340](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py:340), in HasProps.__setattr__(self, name, value)
    [337](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py?line=336) if isinstance(descriptor, property): # Python property
    [338](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py?line=337)     return super().__setattr__(name, value)
--> [340](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py?line=339) self._raise_attribute_error_with_matches(name, properties)

File [~\AppData\Local\Programs\Python\Python311\Lib\site-packages\bokeh\core\has_props.py:375](https://untitled+.vscode-resource.vscode-cdn.net//~/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py:375), in HasProps._raise_attribute_error_with_matches(self, name, properties)
    [372](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py?line=371) if not matches:
    [373](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py?line=372)     matches, text = sorted(properties), "possible"
--> [375](file:///c%3A/Users/nao/AppData/Local/Programs/Python/Python311/Lib/site-packages/bokeh/core/has_props.py?line=374) raise AttributeError(f"unexpected attribute {name!r} to {self.__class__.__name__}, {text} attributes are {nice_join(matches)}")

AttributeError: unexpected attribute 'text_align' to HTMLTemplateFormatter, possible attributes are js_event_callbacks, js_property_callbacks, name, subscribed_events, syncable, tags or template




Discussion:
Introduced in #5866

Are you up for trying to fix this regression?
I am unable to fix this regression.
May need a quick followup release (i.e. 1.3.3) for this one.

Resolving patch:
diff --git a/panel/widgets/tables.py b/panel/widgets/tables.py
--- a/panel/widgets/tables.py
+++ b/panel/widgets/tables.py
@@ -234,7 +234,7 @@ def _get_column_definitions(self, col_names: List[str], df: pd.DataFrame) -> Lis
                 if not default_text_align:
                     msg = f"The 'text_align' in Tabulator.formatters[{col!r}] is overridden by Tabulator.text_align[{col!r}]"
                     warn(msg, RuntimeWarning)
-            elif col in self.indexes:
+            elif col in self.indexes and hasattr(formatter, "text_align"):
                 formatter.text_align = 'left'
 
             if isinstance(self.widths, int):


Test patch:
diff --git a/panel/tests/widgets/test_tables.py b/panel/tests/widgets/test_tables.py
--- a/panel/tests/widgets/test_tables.py
+++ b/panel/tests/widgets/test_tables.py
@@ -6,8 +6,9 @@
 
 from bokeh.models.widgets.tables import (
     AvgAggregator, CellEditor, CheckboxEditor, DataCube, DateEditor,
-    DateFormatter, IntEditor, MinAggregator, NumberEditor, NumberFormatter,
-    SelectEditor, StringEditor, StringFormatter, SumAggregator,
+    DateFormatter, HTMLTemplateFormatter, IntEditor, MinAggregator,
+    NumberEditor, NumberFormatter, SelectEditor, StringEditor, StringFormatter,
+    SumAggregator,
 )
 from packaging.version import Version
 from pandas._testing import (
@@ -2136,3 +2137,15 @@ def test_bokeh_formatter_with_text_align_conflict(align):
         columns = model._get_column_definitions("x", data)
     output = columns[0].formatter.text_align
     assert output == "right"
+
+def test_bokeh_formatter_index_with_no_textalign():
+    df = pd.DataFrame({"A": [1, 2, 3], "B": [1, 2, 3]})
+    df = df.set_index("A")
+
+    index_format = HTMLTemplateFormatter(
+        template='<a href="https://www.google.com/search?code=<%= value %>"><%= value %></a>'
+    )
+
+    table = Tabulator(df, formatters={"A": index_format})
+    serve_and_request(table)
+    wait_until(lambda: bool(table._models))