I’m just playing around and learning/exploring. I was thinking about how form fields render just by putting the field variable in the template. I’ve created some custom form widgets in the past and I think it’s pretty cool.
I’ve been working to create a derived ListView
class that integrates bootstrap table functionality. I was lamenting how busy the template was, and I thought, what if I can add a widget to my BootstrapTableColumn
class so that all I need to do is reference the object in the template to generate the th
tag and possibly a default td
tag?
For example, I have this currently in a SampleListView
template that inherits from my BootstrapTableListView
class:
<th data-field="{{ name.name }}"
data-valign="top"
data-filter-control="{{ name.filter_control }}"
{{ name.filter_data_attr|safe }}
data-filter-default="{{ name.filter }}"
data-sortable="{{ name.sortable }}"
data-sorter="{{ name.sorter }}"
data-visible="{{ name.visible }}">Sample</th>
<th data-field="{{ animal__name.name }}"
data-valign="top"
data-filter-control="{{ animal__name.filter_control }}"
{{ animal__name.filter_data_attr|safe }}
data-filter-default="{{ animal__name.filter }}"
data-sortable="{{ animal__name.sortable }}"
data-sorter="{{ animal__name.sorter }}"
data-visible="{{ animal__name.visible }}">Animal</th>
<th data-field="{{ tissue__name.name }}"
data-valign="top"
data-filter-control="{{ tissue__name.filter_control }}"
{{ tissue__name.filter_data_attr|safe }}
data-filter-default="{{ tissue__name.filter }}"
data-sortable="{{ tissue__name.sortable }}"
data-sorter="{{ tissue__name.sorter }}"
data-visible="{{ tissue__name.visible }}">Tissue</th>
That’s just the first 3 of 22 columns. And I thought, what if it could look like this:
{{ name }}
{{ animal__name }}
{{ tissue__name }}
… like the way django form elements are rendered? It would be a lot cleaner.
I know I could use include
tags, which would be a huge improvement:
{{ include "DataRepo/templates/DataRepo/widgets/bstlistview_th.html" with column=name }}
{{ include "DataRepo/templates/DataRepo/widgets/bstlistview_th.html" with column=animal__name }}
{{ include "DataRepo/templates/DataRepo/widgets/bstlistview_th.html" with column=tissue__name }}
but I like how with a widget, you don’t have to supply the template path or the object. They’re there without having to pass them.
I perused the source of forms and widgets and managed to work it out, but I’m hijacking django.forms.widgets
to render these th
tags. Here’s a peek at the components:
widgets.py
class BSTHeader(Widget):
template_name = "DataRepo/widgets/bstlistview_th.html"
def get_context(self, name, column, attrs=None):
context = super().get_context(name, None, None)
column_attrs = attrs or {}
context["column"] = {
"name": column.name,
"filter_control": column.filter_control,
"sortable": column.sortable,
"sorter": column.sorter,
"visible": column_attrs.get("visible") or column.visible,
"filter": column_attrs.get("filter") or column.filter,
"FILTER_CONTROL_CHOICES": column.FILTER_CONTROL_CHOICES,
"many_related": column.many_related,
"strict_select": column.strict_select,
"header": column.header,
}
return context
bst_list_view.py
class BootstrapTableColumn:
def __init__(self):
self.th_widget = BSTHeader()
def __str__(self):
return self.as_th_widget()
def as_th_widget(self, attrs=None):
return self.th_widget.render(
self.name,
self,
attrs=attrs,
)
class BootstrapTableListView(ListView):
def get_context_data(self, **kwargs):
column: BootstrapTableColumn
for column in self.columns:
context[column.name] = column.as_th_widget(
attrs={
"visible": self.get_column_cookie(column, "visible", column.visible),
"filter": filter_term,
}
)
...
It actually works and I spent less than a work day on it. I’ve already updated 2 derived ListView
classes to inherit from this and I updated their templates and it’s pretty slick.
I’d like to know if there’s a better way to do this… something not quite so hacky/fragile (i.e. I wouldn’t have to have the inner workings of form stuff in the widget…)