diff --git a/apps/showcase/examples/example_html_grid.py b/apps/showcase/examples/example_html_grid.py
index 45345497b..52000459d 100644
--- a/apps/showcase/examples/example_html_grid.py
+++ b/apps/showcase/examples/example_html_grid.py
@@ -50,8 +50,8 @@ def example_html_grid(path=None):
**grid_param
)
- grid.formatters["thing.color"] = lambda color: I(
- _class="fa fa-circle", _style="color:" + color
+ grid.columns[3].represent = lambda row: I(
+ _class="fa fa-circle", _style="color:" + row.color
)
return dict(grid=grid)
diff --git a/docs/chapter-16.rst b/docs/chapter-16.rst
index f7881fc7b..1fd4aaf24 100644
--- a/docs/chapter-16.rst
+++ b/docs/chapter-16.rst
@@ -81,7 +81,7 @@ As en example of application of the above,
Consider the case of wanting to send emails asynchronously from a background task.
In this example we send them using SendGrid from Twilio (https://www.twilio.com/docs/sendgrid/for-developers/sending-email/quickstart-python)
-Here is an example of scheduler task to send the email:
+Here is a possible scheduler task to send the email:
.. code:: python
diff --git a/py4web/utils/grid.py b/py4web/utils/grid.py
index 4a78335f4..a981f5461 100644
--- a/py4web/utils/grid.py
+++ b/py4web/utils/grid.py
@@ -5,6 +5,7 @@
import base64
import copy
import datetime
+import functools
from urllib.parse import urlparse
from pydal.objects import Expression, Field, FieldVirtual
@@ -49,15 +50,6 @@ def safe_int(text, default):
return default
-def join_styles(items):
- return "".join(items) if isinstance(items, (list, tuple)) else " %s" % items
-
-
-def clean_sc(**kwargs):
- """returns a clean dict with _class and _style only if value"""
- return {key: value for key, value in kwargs.items() if value}
-
-
class GridClassStyle:
"""
Default grid style
@@ -114,61 +106,10 @@ class GridClassStyle:
"grid-footer-element": "grid-footer-element info",
}
- styles = {
- "grid-wrapper": "",
- "grid-header": "display: table; width: 100%;",
- "grid-new-button": "margin-top:4px; height:34px; line-height:34px;",
- "grid-search": "display: table-cell; float:right;",
- "grid-table-wrapper": "overflow-x: auto; width:100%;",
- "grid-table": "",
- "grid-sorter-icon-up": "",
- "grid-sorter-icon-down": "",
- "grid-thead": "",
- "grid-tr": "",
- "grid-th": "text-align:left; white-space: nowrap; vertical-align: top;",
- "grid-td": "text-align: left; vertical-align: top;",
- "grid-td-buttons": "text-align: left; white-space: nowrap; vertical-align: top;",
- "grid-button": "margin-bottom: 0;",
- "grid-details-button": "margin-bottom: 0;",
- "grid-edit-button": "margin-bottom: 0;",
- "grid-delete-button": "margin-bottom: 0;",
- "grid-search-button": "height: 34px;",
- "grid-clear-button": "height: 34px;",
- "grid-footer": "display: table; width:100%;",
- "grid-info": "display: table-cell;",
- "grid-pagination": "display: table-cell; text-align:right;",
- "grid-pagination-button": "min-width: 20px;",
- "grid-pagination-button-current": "min-width: 20px; pointer-events:none; opacity: 0.7;",
- "grid-cell-type-string": "white-space: nowrap; vertical-align: top; text-align: left; text-overflow: ellipsis; max-width: 200px;",
- "grid-cell-type-text": "vertical-align: top; text-align: left; text-overflow: ellipsis; max-width: 200px;",
- "grid-cell-type-boolean": "white-space: nowrap; vertical-align: top; text-align: center;",
- "grid-cell-type-float": "white-space: nowrap; vertical-align: top; text-align: right;",
- "grid-cell-type-decimal": "white-space: nowrap; vertical-align: top; text-align: right;",
- "grid-cell-type-int": "white-space: nowrap; vertical-align: top; text-align: right;",
- "grid-cell-type-date": "white-space: nowrap; vertical-align: top; text-align: right;",
- "grid-cell-type-time": "white-space: nowrap; vertical-align: top; text-align: right;",
- "grid-cell-type-datetime": "white-space: nowrap; vertical-align: top; text-align: right;",
- "grid-cell-type-upload": "white-space: nowrap; vertical-align: top; text-align: center;",
- "grid-cell-type-list": "white-space: nowrap; vertical-align: top; text-align: left;",
- # specific for custom form
- "grid-search-form": "",
- "grid-search-form-table": "",
- "grid-search-form-tr": "border-bottom: none;",
- "grid-search-form-td": "",
- "grid-search-form-input": "",
- "grid-search-form-select": "",
- "grid-search-boolean": "",
- "grid-header-element": "margin-top:4px; height:34px; line-height:34px;",
- "grid-footer-element": "margin-top:4px; height:34px; line-height:34px;",
- }
-
@classmethod
- def get(cls, element):
+ def get(cls, element, default=None):
"""returns a dict with _class and _style for the element name"""
- return clean_sc(
- _class=cls.classes.get(element),
- _style=cls.styles.get(element),
- )
+ return cls.classes.get(element, element if default is None else default)
class GridClassStyleBulma(GridClassStyle):
@@ -223,55 +164,6 @@ class GridClassStyleBulma(GridClassStyle):
"grid-footer-element": "grid-footer-element button",
}
- styles = {
- "grid-wrapper": "",
- "grid-header": "",
- "grid-new-button": "",
- "grid-search": "",
- "grid-table-wrapper": "",
- "grid-table": "",
- "grid-sorter-icon-up": "",
- "grid-sorter-icon-down": "",
- "grid-thead": "",
- "grid-tr": "",
- "grid-th": "text-align: center; text-transform: uppercase; vertical-align: bottom;",
- "grid-td": "",
- "grid-td-buttons": "",
- "grid-button": "",
- "grid-details-button": "",
- "grid-edit-button": "",
- "grid-delete-button": "",
- "grid-search-button": "",
- "grid-clear-button": "",
- "grid-footer": "padding-top: .5em; padding-bottom: 2em;",
- "grid-info": "",
- "grid-pagination": "",
- "grid-pagination-button": "margin-left: .25em;",
- "grid-pagination-button-current": "margin-left: .25em;",
- "grid-cell-type-string": "vertical-align: top; text-overflow: ellipsis;",
- "grid-cell-type-text": "vertical-align: top; text-overflow: ellipsis;",
- "grid-cell-type-boolean": "vertical-align: top; text-align: center",
- "grid-cell-type-float": "vertical-align: top; text-align: right",
- "grid-cell-type-decimal": "vertical-align: top; text-align: right",
- "grid-cell-type-int": "vertical-align: top; text-align: center;",
- "grid-cell-type-date": "vertical-align: top; text-align: center;",
- "grid-cell-type-time": "vertical-align: top; text-align: center;",
- "grid-cell-type-datetime": "vertical-align: top; text-align: center;",
- "grid-cell-type-upload": "vertical-align: top; text-align: center;",
- "grid-cell-type-list": "vertical-align: top; text-align: left;",
- "grid-cell-type-id": "",
- # specific for custom form
- "grid-search-form": "",
- "grid-search-form-table": "",
- "grid-search-form-tr": "",
- "grid-search-form-td": "",
- "grid-search-form-input": "",
- "grid-search-form-select": "",
- "grid-search-boolean": "padding-top: .5rem;",
- "grid-header-element": "",
- "grid-footer-element": "",
- }
-
class GridClassStyleBootstrap5(GridClassStyle):
"""The Grid style for Bootstrap 5"""
@@ -325,55 +217,6 @@ class GridClassStyleBootstrap5(GridClassStyle):
"grid-footer-element": "grid-footer-element btn btn-sm",
}
- styles = {
- "grid-wrapper": "",
- "grid-header": "",
- "grid-new-button": "",
- "grid-search": "",
- "grid-table-wrapper": "",
- "grid-table": "",
- "grid-sorter-icon-up": "",
- "grid-sorter-icon-down": "",
- "grid-thead": "",
- "grid-tr": "",
- "grid-th": "text-align: center; text-transform: uppercase; vertical-align: bottom; text-decoration: none;",
- "grid-td": "",
- "grid-td-buttons": "white-space: nowrap; width: 1%;",
- "grid-button": "",
- "grid-details-button": "border-radius: 0 !important;",
- "grid-edit-button": "border-radius: 0 !important;",
- "grid-delete-button": "border-radius: 0 !important;",
- "grid-search-button": "border-radius: 0 !important;",
- "grid-clear-button": "border-radius: 0 !important;",
- "grid-footer": "padding-top: .5em; padding-bottom: 2em;",
- "grid-info": "",
- "grid-pagination": "",
- "grid-pagination-button": "margin-left: .25em; border-radius: 0 !important;",
- "grid-pagination-button-current": "margin-left: .25em; border-radius: 0 !important;",
- "grid-cell-type-string": "vertical-align: top; text-overflow: ellipsis;",
- "grid-cell-type-text": "vertical-align: top; text-overflow: ellipsis;",
- "grid-cell-type-boolean": "vertical-align: top; text-align: center",
- "grid-cell-type-float": "vertical-align: top; text-align: right",
- "grid-cell-type-decimal": "vertical-align: top; text-align: right",
- "grid-cell-type-int": "vertical-align: top; text-align: center;",
- "grid-cell-type-date": "vertical-align: top; text-align: center;",
- "grid-cell-type-time": "vertical-align: top; text-align: center;",
- "grid-cell-type-datetime": "vertical-align: top; text-align: center;",
- "grid-cell-type-upload": "vertical-align: top; text-align: center;",
- "grid-cell-type-list": "vertical-align: top; text-align: left;",
- "grid-cell-type-id": "",
- # specific for custom form
- "grid-search-form": "",
- "grid-search-form-table": "",
- "grid-search-form-tr": "",
- "grid-search-form-td": "",
- "grid-search-form-input": "",
- "grid-search-form-select": "",
- "grid-search-boolean": "padding-top: .5rem;",
- "grid-header-element": "",
- "grid-footer-element": "",
- }
-
class Column:
"""class used to represent a column in a grid"""
@@ -382,34 +225,27 @@ def __init__(
self,
name,
represent,
- required_fields=None,
+ key=None,
+ required_fields=None, # must be a list or none
orderby=None,
+ col_type="string",
td_class_style=None,
):
self.name = name
self.represent = represent
self.orderby = orderby
- self.required_fields = []
- if required_fields:
- if isinstance(required_fields, list):
- self.required_fields = required_fields
- else:
- self.required_fields = [required_fields]
-
+ self.required_fields = required_fields or []
+ self.key = key
+ self.type = (col_type,)
self.td_class_style = td_class_style
- def render(self, row, index=None):
- """renders a row al position index (optional)"""
- return self.represent(row)
-
class Grid:
FORMATTERS_BY_TYPE = {
- "boolean": lambda value: (
- INPUT(_type="checkbox", _checked=value, _disabled="disabled")
- if value
- else ""
- ),
+ "NoneType": lambda value: "",
+ "bool": lambda value: "☑" if value else "☐" if value is False else "",
+ "float": lambda value: "%.2f" % value,
+ "double": lambda value: "%.2f" % value,
"datetime": lambda value: (
XML(
""
@@ -451,9 +287,7 @@ class Grid:
if value
else ""
),
- "list:string": lambda value: ", ".join(str(x) for x in value) if value else "",
- "list:integer": lambda value: ", ".join(x for x in value) if value else "",
- "default": lambda value: str(value) if value is not None else "",
+ "list": lambda value: ", ".join(x for x in value) if value else "",
}
def __init__(
@@ -583,8 +417,6 @@ def __init__(
self.rows = None
self.tablename = None
self.total_number_of_rows = None
- self.use_tablename = self.is_join()
- self.formatters = {}
self.formatters_by_type = copy.copy(Grid.FORMATTERS_BY_TYPE)
self.attributes_plugin = AttributesPlugin(request)
@@ -653,33 +485,75 @@ def process(self):
self.record_id = safe_int(parts[1] if len(parts) > 1 else None, default=None)
table = db[self.tablename]
+ # if no column specified use all fields
if not self.param.columns:
- # if no column specified use all fields
self.param.columns = [field for field in table if field.readable]
+ # convert to column object
+ self.columns = []
+
+ def title(col):
+ return str(col).replace('"', "")
+
+ def col2key(col):
+ return str(col).lower().replace(".", "-")
+
+ for index, col in enumerate(self.param.columns):
+ if isinstance(col, Column):
+ if not col.key:
+ col.key = f"column-{index}"
+ self.columns.append(col)
+ elif isinstance(col, Field):
+ # what about represe
+ def compute(row, col=col):
+ value = row(str(col))
+ return col.represent(value) if col.represent else value
+
+ self.columns.append(
+ Column(
+ col.label,
+ compute,
+ orderby=col,
+ required_fields=[col],
+ key=col2key(col),
+ col_type=col.type,
+ )
+ )
+ elif isinstance(col, FieldVirtual):
+
+ def compute(row, col=col):
+ return col.f(row) if "id" in row else col.f(row[col.tablename])
+
+ self.columns.append(
+ Column(
+ col.label,
+ compute,
+ orderby=None,
+ required_fields=db[col.tablename],
+ key=col2key(col),
+ )
+ )
+ elif isinstance(col, Expression):
+
+ def compute(row, name=str(col)):
+ return row._extra(name)
+
+ self.columns.append(
+ Column(
+ title(col),
+ compute,
+ orderby=None,
+ required_fields=[col],
+ key=f"column-{index}",
+ )
+ )
+ else:
+ raise RuntimeError(f"Column not support {col}")
- if not self.param.columns:
- self.needed_fields = self.param.columns[:]
- else:
- needed_fields = set()
- for col in self.param.columns:
- print("Column", col)
- if isinstance(col, Column):
- if col.required_fields:
- needed_fields |= set(col.required_fields)
- else:
- needed_fields |= set(db[self.tablename])
- elif isinstance(col, FieldVirtual):
- # if virtual fields are specified the fields may come from a join
- needed_fields |= set(db[col.tablename])
- else:
- needed_fields.add(col)
- self.needed_fields = list(needed_fields)
-
- print(self.needed_fields)
-
- # except the primary key may be missing and must be fetched even if not displayed
- if not any(getattr(col, "name", None) == table._id.name for col in self.needed_fields):
- self.needed_fields.insert(0, table._id)
+ # join the set of all required fields
+ sets = (set(col.required_fields) for col in self.columns)
+ self.needed_fields = list(
+ functools.reduce(lambda a, b: a | b, sets) | set([table._id])
+ )
self.referrer = None
@@ -769,21 +643,6 @@ def process(self):
request.url.encode("utf8")
).decode("utf8")
- # find the primary key of the primary table
- pt = db[self.tablename]
- key_is_missing = True
- for field in self.param.columns:
- if (
- isinstance(field, Field)
- and field.table._tablename == pt._tablename
- and field.name == pt._id.name
- ):
- key_is_missing = False
- if key_is_missing:
- # primary key wasn't included, add it and set show_id to False so it doesn't display
- self.param.columns.append(pt._id)
- self.param.show_id = False
-
self.current_page_number = safe_int(request.query.get("page"), default=1)
select_params = dict()
@@ -836,8 +695,6 @@ def process(self):
self.page_end = self.total_number_of_rows
# get the data
- print(self.needed_fields)
- print(select_params)
self.rows = db(query).select(*self.needed_fields, **select_params)
self.number_of_pages = self.total_number_of_rows // self.param.rows_per_page
@@ -851,8 +708,14 @@ def process(self):
or self.param.deletable
or self.param.post_action_buttons
):
- self.param.columns.append(
- Column("", self.make_action_buttons, td_class_style="grid-td-buttons")
+ key = f"column-{len(self.columns)}"
+ self.columns.append(
+ Column(
+ "",
+ self.make_action_buttons,
+ key=key,
+ td_class_style=self.param.grid_class_style.get("grid-td-buttons"),
+ )
)
def iter_pages(
@@ -889,9 +752,7 @@ def _make_action_button(
icon,
icon_size="small", # deprecated
additional_classes=None,
- additional_styles=None,
override_classes=None,
- override_styles=None,
message=None,
onclick=None, # deprecated
row_id=None,
@@ -903,29 +764,18 @@ def _make_action_button(
if row_id:
url += "/%s" % row_id
- classes = self.param.grid_class_style.classes.get(name, "")
- styles = self.param.grid_class_style.styles.get(name, "")
+ classes = self.param.grid_class_style.get(name)
if callable(additional_classes):
additional_classes = additional_classes(row)
- if callable(additional_styles):
- additional_styles = additional_styles(row)
-
if callable(override_classes):
override_classes = override_classes(row)
- if callable(override_styles):
- override_styles = override_styles(row)
-
if override_classes:
classes = join_classes(override_classes)
elif additional_classes:
classes = join_classes(classes, additional_classes)
- if override_styles:
- styles = join_styles(override_styles)
- elif additional_styles:
- styles += join_styles(additional_styles)
if callable(url):
url = url(row)
@@ -940,7 +790,7 @@ def _make_action_button(
_role="button",
_message=message,
_title=button_text,
- **clean_sc(_class=classes, _style=styles),
+ _class=classes,
**attrs,
)
if self.param.include_action_button_text:
@@ -950,6 +800,12 @@ def _make_action_button(
return link
+ def reformat(self, value):
+ type_name = type(value).__name__
+ if type_name in self.formatters_by_type:
+ return self.formatters_by_type[type_name](value)
+ return value
+
def _make_default_form(self):
search_type = safe_int(request.query.get("search_type", 0), default=0)
search_string = request.query.get("search_string")
@@ -964,51 +820,51 @@ def _make_default_form(self):
]
attrs = self.attributes_plugin.link(url=self.endpoint)
form = FORM(*hidden_fields, **attrs)
- sc = self.param.grid_class_style.get("grid-search-form-select")
- select = SELECT(
- *options,
- **dict(
- _name="search_type",
- ),
- **sc,
- )
- sc = self.param.grid_class_style.get("grid-search-form-input")
+ classes = self.param.grid_class_style.get("grid-search-form-select")
+ select = SELECT(*options, **dict(_name="search_type", _class=classes))
+ classes = self.param.grid_class_style.get("grid-search-form-input")
input = INPUT(
_type="text",
_name="search_string",
_value=search_string,
- **sc,
+ _class=classes,
)
- sc = self.param.grid_class_style.get("grid-search-button")
- submit = INPUT(_type="submit", _value=self.T("Search"), **sc)
+ classes = self.param.grid_class_style.get("grid-search-button")
+ submit = INPUT(_type="submit", _value=self.T("Search"), _class=classes)
clear_script = "document.querySelector('[name=search_string]').value='';"
- sc = self.param.grid_class_style.get("grid-clear-button")
+ classes = self.param.grid_class_style.get("grid-clear-button")
clear = INPUT(
- _type="submit", _value=self.T("Clear"), _onclick=clear_script, **sc
+ _type="submit",
+ _value=self.T("Clear"),
+ _onclick=clear_script,
+ _class=classes,
+ )
+ div = DIV(
+ _id="grid-search", _classes=self.param.grid_class_style.get("grid-search")
)
- div = DIV(_id="grid-search", **self.param.grid_class_style.get("grid-search"))
- sc = self.param.grid_class_style.get("grid-search-form-tr")
- tr = TR(**sc)
- sc = self.param.grid_class_style.get("grid-search-form-td")
+ tr = TR(_class=self.param.grid_class_style.get("grid-search-form-tr"))
+ classes = self.param.grid_class_style.get("grid-search-form-td")
if len(options) > 1:
- tr.append(TD(select, **sc))
- tr.append(TD(input, **sc))
- tr.append(TD(submit, clear, **sc))
- sc = self.param.grid_class_style.get("grid-search-form-table")
- form.append(TABLE(tr, **sc))
+ tr.append(TD(select, _class=classes))
+ tr.append(TD(input, _class=classes))
+ tr.append(TD(submit, clear, _class=classes))
+ classes = self.param.grid_class_style.get("grid-search-form-table")
+ form.append(TABLE(tr, _class=classes))
div.append(form)
return div
def _make_search_form(self):
# TODO: Do we need this?
- div = DIV(_id="grid-search", **self.param.grid_class_style.get("grid-search"))
+ div = DIV(
+ _id="grid-search", _class=self.param.grid_class_style.get("grid-search")
+ )
div.append(self.param.search_form.custom["begin"])
- tr = TR(**self.param.grid_class_style.get("grid-search-form-tr"))
+ tr = TR(_class=self.param.grid_class_style.get("grid-search-form-tr"))
for field in self.param.search_form.table:
- td = TD(**self.param.grid_class_style.get("grid-search-form-td"))
+ td = TD(_class=self.param.grid_class_style.get("grid-search-form-td"))
if field.type == "boolean":
- sb = DIV(**self.param.grid_class_style.get("grid-search-boolean"))
+ sb = DIV(_class=self.param.grid_class_style.get("grid-search-boolean"))
sb.append(self.param.search_form.custom["widgets"][field.name])
sb.append(field.label)
td.append(sb)
@@ -1018,12 +874,7 @@ def _make_search_form(self):
field.name in self.param.search_form.custom["errors"]
and self.param.search_form.custom["errors"][field.name]
):
- td.append(
- DIV(
- self.param.search_form.custom["errors"][field.name],
- _style="color:#ff0000",
- )
- )
+ td.append(DIV(self.param.search_form.custom["errors"][field.name]))
tr.append(td)
if self.param.search_button_text:
tr.append(
@@ -1033,18 +884,18 @@ def _make_search_form(self):
_type="submit",
_value=self.T(self.param.search_button_text),
),
- **self.param.grid_class_style.get("grid-search-form-td"),
+ _class=self.param.grid_class_style.get("grid-search-form-td"),
)
)
else:
tr.append(
TD(
self.param.search_form.custom["submit"],
- **self.param.grid_class_style.get("grid-search-form-td"),
+ _class=self.param.grid_class_style.get("grid-search-form-td"),
)
)
div.append(
- TABLE(tr, **self.param.grid_class_style.get("grid-search-form-table"))
+ TABLE(tr, _class=self.param.grid_class_style.get("grid-search-form-table"))
)
for hidden_widget in self.param.search_form.custom["hidden_widgets"].keys():
if hidden_widget not in ("formname", "formkey"):
@@ -1059,173 +910,73 @@ def _make_search_form(self):
def _make_table_header(self):
sort_order = request.query.get("orderby", "")
- thead = THEAD(
- **clean_sc(_class=self.param.grid_class_style.classes.get("grid-thead", ""))
- )
- for index, column in enumerate(self.param.columns):
- col = None
- key = None
- if isinstance(column, (Field, FieldVirtual)):
- field = column
- if field.readable and (field.type != "id" or self.param.show_id):
- key, col = self._make_field_header(column, index, sort_order)
- elif isinstance(column, Column):
- key = column.name.lower().replace(" ", "-")
- col = column.name
- if column.orderby:
- key, col = self._make_field_header(column, index, sort_order)
- else:
- raise RuntimeError("Invalid Grid Column type")
- if col is not None:
- classes = join_classes(
- self.param.grid_class_style.classes.get("grid-th"),
- "grid-col-%s" % key,
- )
- style = self.param.grid_class_style.styles.get("grid-th")
- thead.append(TH(col, **clean_sc(_class=classes, _style=style)))
+ thead = THEAD(_class=self.param.grid_class_style.get("grid-thead"))
+ for index, col in enumerate(self.columns):
+ col_header = self._make_col_header(col, index, sort_order)
+ classes = join_classes(
+ self.param.grid_class_style.get("grid-th"),
+ "grid-col-%s" % col.key,
+ )
+ thead.append(TH(col_header, _class=classes))
return thead
- def _make_field_header(self, field, field_index, sort_order):
- up = I(**self.param.grid_class_style.get("grid-sorter-icon-up"))
- dw = I(**self.param.grid_class_style.get("grid-sorter-icon-down"))
+ def _make_col_header(self, col, index, sort_order):
+ up = I(_class=self.param.grid_class_style.get("grid-sorter-icon-up"))
+ dw = I(_class=self.param.grid_class_style.get("grid-sorter-icon-down"))
- if isinstance(field, Column):
- key = str(field.orderby)
- else:
- key = "%s.%s" % (field.tablename, field.name)
+ orderby = col.orderby and str(col.orderby)
heading = (
- self.param.headings[field_index]
- if field_index < len(self.param.headings)
- else getattr(field, "label", title(field.name))
+ self.param.headings[index] if index < len(self.param.headings) else col.name
)
# add the sort order query parm
sort_query_parms = dict(self.query_parms)
attrs = {}
- if isinstance(field, FieldVirtual):
- col = SPAN(heading)
- elif key == sort_order:
- sort_query_parms["orderby"] = "~" + key
- url = URL(self.endpoint, "select", vars=sort_query_parms)
- attrs = self.attributes_plugin.link(url=url)
- col = A(heading, up, **attrs)
- else:
- sort_query_parms["orderby"] = key
- url = URL(self.endpoint, "select", vars=sort_query_parms)
- attrs = self.attributes_plugin.link(url=url)
- col = A(heading, dw if "~" + key == sort_order else "", **attrs)
- return key, col
-
- def _make_field(self, row, field, field_index):
- """
- Render a field
-
- if only 1 table in the query, the no table name needed when getting the row value - however, if there
- are multiple tables in the query (self.use_tablename == True) then we need to use the tablename as well
- when accessing the value in the row object
-
- the row object sent in can take
- :param row:
- :param field:
- :return:
- """
- if self.use_tablename:
- row = row[field.tablename]
- if isinstance(field, FieldVirtual):
- field_value = field.f(row)
- else:
- field_value = row[field.name]
- key = "%s.%s" % (field.tablename, field.name)
- # custom formatter overwrites represent
- if key in self.formatters:
- formatter = self.formatters.get(key)
- # else if represent provided use it
- elif field.represent:
- formatter = field.represent
- # else fallback on the formatter for the type
- elif field.type in self.formatters_by_type:
- formatter = self.formatters_by_type.get(field.type)
- # else fallback to default
+ if orderby:
+ if orderby == sort_order:
+ sort_query_parms["orderby"] = "~" + orderby
+ url = URL(self.endpoint, "select", vars=sort_query_parms)
+ attrs = self.attributes_plugin.link(url=url)
+ col_header = A(heading, up, **attrs)
+ else:
+ sort_query_parms["orderby"] = orderby
+ url = URL(self.endpoint, "select", vars=sort_query_parms)
+ attrs = self.attributes_plugin.link(url=url)
+ col_header = A(
+ heading, dw if "~" + orderby == sort_order else "", **attrs
+ )
else:
- formatter = self.formatters_by_type.get("default")
- # allow 1 (value) or 2 (value + row) args
- try:
- formatted_value = formatter(field_value, row)
- except TypeError:
- formatted_value = formatter(field_value)
-
- class_type = "grid-cell-type-%s" % str(field.type).split(":")[0].split("(")[0]
- class_col = " grid-col-%s" % key.replace(".", "_")
- classes = join_classes(
- self.param.grid_class_style.classes.get("grid-td"),
- self.param.grid_class_style.classes.get(class_type),
- class_col,
- )
- td = TD(
- formatted_value,
- **clean_sc(
- _class=classes,
- _style=(
- self.param.grid_class_style.styles.get(class_type)
- or self.param.grid_class_style.styles.get("grid-td")
- ),
- ),
- )
-
- return td
+ col_header = heading
+ return col_header
def _make_table_body(self):
tbody = TBODY()
- for row in self.rows:
+ for index, row in enumerate(self.rows):
# find the row id - there may be nested tables....
- if not (self.use_tablename and self.tablename in row and "id" not in row):
- self.use_tablename = False
- key = "%s.%s" % (self.tablename, "__row")
- if self.formatters.get(key):
- extra_class = self.formatters.get(key)(row)["_class"]
- extra_style = self.formatters.get(key)(row)["_style"]
- else:
- extra_class = ""
- extra_style = ""
tr = TR(
_role="row",
- **clean_sc(
- _class=join_classes(
- self.param.grid_class_style.classes.get("grid-tr"), extra_class
- ),
- _style=join_styles(
- [self.param.grid_class_style.styles.get("grid-tr"), extra_style]
- ),
- ),
+ _class=self.param.grid_class_style.get("grid-tr"),
)
+
# add all the fields to the row
- for index, column in enumerate(self.param.columns):
- if isinstance(column, (Field, FieldVirtual)):
- field = column
- if field.readable and (field.type != "id" or self.param.show_id):
- tr.append(self._make_field(row, field, index))
- elif isinstance(column, Column):
- classes = self.param.grid_class_style.classes.get(
- column.td_class_style,
- column.td_class_style(row)
- if callable(column.td_class_style)
- else self.param.grid_class_style.classes.get("grid-td"),
- )
- style = self.param.grid_class_style.styles.get(
- column.td_class_style,
- column.td_class_style(row)
- if callable(column.td_class_style)
- else self.param.grid_class_style.styles.get("grid-td"),
- )
- tr.append(
- TD(
- column.render(row, index),
- **clean_sc(_class=classes, _style=style),
- )
- )
+ for col in self.columns:
+ classes = join_classes(
+ [
+ self.param.grid_class_style.get(
+ col.td_class_style,
+ col.td_class_style(row)
+ if callable(col.td_class_style)
+ else self.param.grid_class_style.get("grid-td"),
+ ),
+ f"grid-cell-{col.key}",
+ ]
+ )
+ value = col.represent(row)
+ reformatted_value = self.reformat(value)
+ tr.append(TD(reformatted_value, _class=classes))
tbody.append(tr)
@@ -1256,9 +1007,7 @@ def make_action_buttons(self, row):
button_text=self.T(btn.text),
icon=btn.icon,
additional_classes=btn.additional_classes,
- additional_styles=btn.__dict__.get("additional_styles"),
override_classes=btn.__dict__.get("override_classes"),
- override_styles=btn.__dict__.get("override_styles"),
message=btn.message,
row_id=row_id if btn.append_id else None,
name=btn.__dict__.get("name"),
@@ -1344,9 +1093,7 @@ def make_action_buttons(self, row):
button_text=self.T(btn.text),
icon=btn.icon,
additional_classes=btn.additional_classes,
- additional_styles=btn.__dict__.get("override_styles"),
override_classes=btn.__dict__.get("override_classes"),
- override_styles=btn.__dict__.get("override_styles"),
message=btn.message,
row_id=row_id if btn.append_id else None,
name=btn.__dict__.get("name"),
@@ -1363,7 +1110,7 @@ def make_action_buttons(self, row):
return cat
def _make_table_pager(self):
- pager = DIV(**self.param.grid_class_style.get("grid-pagination"))
+ pager = DIV(_class=self.param.grid_class_style.get("grid-pagination"))
previous_page_number = None
for page_number in self.iter_pages(
self.current_page_number, self.number_of_pages
@@ -1372,7 +1119,7 @@ def _make_table_pager(self):
pager_query_parms["page"] = page_number
# if there is a gat add a spacer
if previous_page_number and page_number - previous_page_number > 1:
- pager.append(SPAN("...", _style="margin:0 10px;"))
+ pager.append(SPAN("..."))
is_current = self.current_page_number == page_number
page_name = (
"grid-pagination-button-current"
@@ -1385,7 +1132,7 @@ def _make_table_pager(self):
pager.append(
A(
page_number,
- **self.param.grid_class_style.get(page_name),
+ _class=self.param.grid_class_style.get(page_name),
_role="button",
**attrs,
)
@@ -1394,8 +1141,8 @@ def _make_table_pager(self):
return pager
def _make_table(self):
- html = DIV(**self.param.grid_class_style.get("grid-wrapper"))
- grid_header = DIV(**self.param.grid_class_style.get("grid-header"))
+ html = DIV(_class=self.param.grid_class_style.get("grid-wrapper"))
+ grid_header = DIV(_class=self.param.grid_class_style.get("grid-header"))
# build the New button if needed
if self.param.create and self.param.create != "":
@@ -1412,12 +1159,7 @@ def _make_table(self):
self.T(self.param.new_action_button_text),
"fa-plus",
icon_size="normal",
- override_classes=self.param.grid_class_style.classes.get(
- "grid-new-button", ""
- ),
- override_styles=self.param.grid_class_style.styles.get(
- "grid-new-button"
- ),
+ override_classes=self.param.grid_class_style.get("grid-new-button"),
)
)
if self.param.header_elements and len(self.param.header_elements) > 0:
@@ -1429,19 +1171,9 @@ def _make_table(self):
else:
override_classes = element.__dict__.get("override_classes", None)
if not override_classes:
- override_classes = (
- self.param.grid_class_style.classes.get(
- "grid-header-element", ""
- )
- + f" {element.additional_classes}"
- )
- override_styles = element.__dict__.get("override_styles", None)
- if not override_styles:
- override_styles = (
- self.param.grid_class_style.styles.get(
- "grid-trailer-element", ""
- )
- + f" {element.__dict__.get('additional_styles')}"
+ override_classes = join_classes(
+ self.param.grid_class_style.get("grid-header-element"),
+ element.additional_classes,
)
grid_header.append(
self._make_action_button(
@@ -1450,9 +1182,7 @@ def _make_table(self):
icon=element.icon,
icon_size="normal",
additional_classes=element.additional_classes,
- additional_styles=element.__dict__.get("additional_styles"),
override_classes=override_classes,
- override_styles=override_styles,
message=element.message,
name=element.__dict__.get("name"),
ignore_attribute_plugin=element.ignore_attribute_plugin,
@@ -1468,7 +1198,7 @@ def _make_table(self):
html.append(grid_header)
- table = TABLE(**self.param.grid_class_style.get("grid-table"))
+ table = TABLE(_class=self.param.grid_class_style.get("grid-table"))
# build the header
table.append(self._make_table_header())
@@ -1477,12 +1207,14 @@ def _make_table(self):
table.append(self._make_table_body())
# add the table to the html
- html.append(DIV(table, **self.param.grid_class_style.get("grid-table-wrapper")))
+ html.append(
+ DIV(table, _class=self.param.grid_class_style.get("grid-table-wrapper"))
+ )
# add the row counter information
- footer = DIV(**self.param.grid_class_style.get("grid-footer"))
+ footer = DIV(_class=self.param.grid_class_style.get("grid-footer"))
- row_count = DIV(**self.param.grid_class_style.get("grid-info"))
+ row_count = DIV(_class=self.param.grid_class_style.get("grid-info"))
(
row_count.append(
str(self.T("Displaying rows %s thru %s of %s"))
@@ -1516,19 +1248,9 @@ def _make_table(self):
else:
override_classes = element.__dict__.get("override_classes", None)
if not override_classes:
- override_classes = (
- self.param.grid_class_style.classes.get(
- "grid-footer-element", ""
- )
- + f" {element.additional_classes}"
- )
- override_styles = element.__dict__.get("override_styles", None)
- if not override_styles:
- override_styles = (
- self.param.grid_class_style.styles.get(
- "grid-footer-element", ""
- )
- + f" {element.__dict__.get('additional_styles')}"
+ override_classes = join_classes(
+ self.param.grid_class_style.get("grid-footer-element"),
+ element.additional_classes,
)
html.append(
self._make_action_button(
@@ -1537,9 +1259,7 @@ def _make_table(self):
icon=element.icon,
icon_size="normal",
additional_classes=element.additional_classes,
- additional_styles=element.__dict__.get("additional_styles"),
override_classes=override_classes,
- override_styles=override_styles,
message=element.message,
name=element.__dict__.get("name"),
ignore_attribute_plugin=element.ignore_attribute_plugin,