def render_search_form(self): _sf = DIV(_class='is-pulled-right', _style='padding-bottom: 1rem;') _sf.append(self.search_form.custom['begin']) _tr = TR() for field in self.search_form.table: _td = TD(_style='padding-right: .5rem;') if field.type == 'boolean': _td.append(self.search_form.custom['widgets'][field.name]) _td.append(field.label) else: _td.append(self.search_form.custom['widgets'][field.name]) if field.name in self.search_form.custom['errors'] and self.search_form.custom['errors'][ field.name]: _td.append(DIV(self.search_form.custom['errors'][field.name], _style="color:#ff0000")) _tr.append(_td) if self.search_button: _tr.append(TD(INPUT(_class='button', _type='submit', _value=self.search_button))) else: _tr.append(TD(self.search_form.custom['submit'])) _sf.append(TABLE(_tr)) for hidden_widget in self.search_form.custom['hidden_widgets'].keys(): _sf.append(self.search_form.custom['hidden_widgets'][hidden_widget]) _sf.append(self.search_form.custom['end']) return _sf
def render_default_form(self): seach_type = safe_int(request.query.get("seach_type", 0), default=0) seach_string = request.query.get("seach_string") options = [ OPTION(items[0], _value=k, _selected=(k == seach_type)) for k, items in enumerate(self.param.search_queries) ] hidden_fields = [ INPUT(_name=key, _value=request.query.get(key), _type="hidden") for key in request.query if not key in ("seach_type", "seach_string") ] form = FORM(*hidden_fields, **dict(_method="GET", _action=self.endpoint)) select = SELECT(*options, **dict(_name="seach_type",)) input = INPUT(_type="text", _name="seach_string", _value=seach_string,) submit = INPUT(_type="submit", _value="Search") clear_script = "document.querySelector('[name=seach_string]').value='';" clear = INPUT(_type="submit", _value="Clear", _onclick=clear_script) div = DIV(_id="grid-search", **self.param.grid_class_style.get("grid-search")) # we do not need classes for these elements tr = TR() if len(options) > 1: tr.append(TD(select)) tr.append(TD(input)) tr.append(TD(submit, clear)) form.append(TABLE(tr)) div.append(form) return div
def dict_renderer(key, value, rec): if isinstance(value, dict): return TABLE( *[ TR(TH(k, ":"), TD(rec(key + "." + k, v))) for k, v in value.items() ] )
def make(self): """makes the grid, must be called inside an action""" T, db, table, query = self.T, self.db, self.table, self.query # bypass rest API if it is a form id = safeint(request.query.get("id") or request.forms.get("id")) if id is not None: if (id == 0 and not self.create) or (id > 0 and not self.editable): raise HTTP(404) record = db(query)(table.id == id).select().first() if id else None form = Form(table, record, deletable=self.deletable, **self.form_attributes) form.action = (request.url.split("?")[0] + "?" + urllib.parse.urlencode(request.query)) del request.query["id"] if form.deleted: message = T("Record deleted") elif form.accepted: message = T("Record created") if not id else T("Record Saved") else: return DIV(self.header(id), form, _class="py4web-grid") else: message = "" if self.denormalize: lookup = [] for k, fs in self.denormalize.items(): lookup.append("%s!:%s[%s]" % (k, k, ",".join(fs))) request.query["@lookup"] = ",".join(lookup) offset = safeint(request.query.get("@offset", 0)) request.query["@count"] = "true" request.query["@limit"] = offset + self.limit id = None data = self.restapi("GET", self.table._tablename, None, request.query) items = data.get("items", []) count = data.get("count", 0) table = TABLE(_class="table") fields = (items[0].keys() if items else [f.rsplit(".", 1)[0] for f in request.query if f[:1] != "@"]) table.append(TR(*[TH(self.sortlink(key)) for key in fields])) table.append(TR(*[TH(self.filterlink(key)) for key in fields])) for item in items: table.append( TR(*[ TD(self.render(key, value)) for key, value in item.items() ])) header = self.header(id, message) footer = self.footer(count, offset, len(items)) return DIV(header, table, footer, _class="py4web-grid")
def table(self): _html = DIV() if self.create_url and self.create_url != '': _a = A('', _href=self.create_url, _class='button', _style='margin-bottom: 1rem;') _span = SPAN(_class='icon is-small') _span.append(I(_class='fas fa-plus')) _a.append(_span) _a.append(SPAN('New')) _html.append(_a) _table = TABLE(_id='datatables_table', _class='compact stripe hover cell-border order-column', _style='padding-top: 1rem;') _thead = THEAD() _tr = TR() for field in self.fields: _tr.append(TH(field.label, _class='datatables-header')) _tr.append( TH('ACTIONS', _class='datatables-header has-text-centered', _style='color: black; width: 1px; white-space: nowrap;')) _thead.append(_tr) _table.append(_thead) _table.append(TBODY()) _html.append(_table) return str(_html)
def render_search_form(self): # TODO: Do we need this? div = DIV(_id="grid-search", **self.param.grid_class_style.get("grid-search")) div.append(self.param.search_form.custom["begin"]) tr = TR(**self.param.grid_class_style.get("search_form_tr")) for field in self.param.search_form.table: td = TD(**self.param.grid_class_style.get("search_form_td")) if field.type == "boolean": sb = DIV(**self.param.grid_class_style.get("search_boolean")) sb.append(self.param.search_form.custom["widgets"][field.name]) sb.append(field.label) td.append(sb) else: td.append(self.param.search_form.custom["widgets"][field.name]) td.append(self.param.search_form.custom["wrapped_widgets"][field.name]) if ( 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", ) ) tr.append(td) if self.param.search_button_text: tr.append( TD( INPUT( _class="button", _type="submit", _value=self.param.search_button_text, ), **self.param.grid_class_style.get("search_form_td"), ) ) else: tr.append( TD( self.param.search_form.custom["submit"], **self.param.grid_class_style.get("search_form_td"), ) ) div.append(TABLE(tr, **self.param.grid_class_style.get("search_form_table"))) for hidden_widget in self.param.search_form.custom["hidden_widgets"].keys(): div.append(self.param.search_form.custom["hidden_widgets"][hidden_widget]) div.append(self.param.search_form.custom["end"]) return div
def render_table(self): _html = DIV(_class='field') _top_div = DIV(_style='padding-bottom: 1rem;') # build the New button if needed if self.create and self.create != '': if isinstance(self.create, str): create_url = self.create else: create_url = create_url = URL(self.endpoint) + '/new/%s/0' % self.tablename _top_div.append(self.render_action_button(create_url, 'New', 'fa-plus', size='normal')) # build the search form if provided if self.search_form: _top_div.append(self.render_search_form()) _html.append(_top_div) _table = TABLE(_class='table is-bordered is-striped is-hoverable is-fullwidth') # build the header _table.append(self.render_table_header()) # include moment.js to present dates in the proper locale _html.append(XML('<script src="https://momentjs.com/downloads/moment.js"></script>')) # build the rows _table.append(self.render_table_body()) # add the table to the html _html.append(_table) # add the row counter information _row_count = DIV(_class='is-pulled-left') _row_count.append( P('Displaying rows %s thru %s of %s' % (self.page_start + 1 if self.number_of_pages > 1 else 1, self.page_end if self.page_end < self.total_number_of_rows else self.total_number_of_rows, self.total_number_of_rows))) _html.append(_row_count) # build the pager if self.number_of_pages > 1: _html.append(self.render_table_pager()) if self.deletable: _html.append((XML(""" <script type="text/javascript"> $('.confirmation').on('click', function () { return confirm($(this).attr('message') +' - Are you sure?'); }); </script> """))) return XML(_html)
def render_table(self): html = DIV(**self.param.grid_class_style.get("grid-wrapper")) grid_header = DIV(**self.param.grid_class_style.get("grid-header")) # build the New button if needed if self.param.create and self.param.create != "": if isinstance(self.param.create, str): create_url = self.param.create else: create_url = self.endpoint + "/new" create_url += "?%s" % self.referrer grid_header.append( self.render_action_button( create_url, 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.get( "new_button"), )) # build the search form if provided if self.param.search_form: grid_header.append(self.render_search_form()) elif self.param.search_queries and len(self.param.search_queries) > 0: grid_header.append(self.render_default_form()) html.append(grid_header) table = TABLE(**self.param.grid_class_style.get("grid-table")) # build the header table.append(self.render_table_header()) # build the rows table.append(self.render_table_body()) # add the table to the html html.append( DIV(table, **self.param.grid_class_style.get("grid-table-wrapper"))) # add the row counter information footer = DIV(**self.param.grid_class_style.get("grid-footer")) row_count = DIV(**self.param.grid_class_style.get("grid-info")) row_count.append("Displaying rows %s thru %s of %s" % ( self.page_start + 1 if self.number_of_pages > 1 else 1, self.page_end if self.page_end < self.total_number_of_rows else self.total_number_of_rows, self.total_number_of_rows, )) if self.number_of_pages > 0 else row_count.append( "No rows to display") footer.append(row_count) # build the pager if self.number_of_pages > 1: footer.append(self.render_table_pager()) html.append(footer) return XML(html)
def FormStyleDefault(table, vars, errors, readonly, deletable): form = FORM(TABLE(), _method='POST', _action='#', _enctype='multipart/form-data') for field in table: input_id = '%s_%s' % (field.tablename, field.name) value = field.formatter(vars.get(field.name)) error = errors.get(field.name) field_class = field.type.split()[0].replace(':', '-') if field.type == 'blob': # never display blobs (mistake?) continue elif readonly or field.type == 'id': if not field.readable: continue else: control = field.represent and field.represent( value) or value or '' elif not field.writable: continue elif field.widget: control = field.widget(table, value) elif field.type == 'text': control = TEXTAREA(value or '', _id=input_id, _name=field.name) elif field.type == 'boolean': control = INPUT(_type='checkbox', _id=input_id, _name=field.name, _value='ON', _checked=value) elif field.type == 'upload': control = DIV(INPUT(_type='file', _id=input_id, _name=field.name)) if value: control.append(A('download', _href=donwload_url(value))) control.append( INPUT(_type='checkbox', _value='ON', _name='_delete_' + field.name)) control.append('(check to remove)') elif hasattr(field.requires, 'options'): multiple = field.type.startswith('list:') value = value if isinstance(value, list) else [value] options = [ OPTION(v, _value=k, _selected=(k in value)) for k, v in field.requires.options() ] control = SELECT(*options, _id=input_id, _name=field.name, _multiple=multiple) else: field_type = 'password' if field.type == 'password' else 'text' control = INPUT(_type=field_type, _id=input_id, _name=field.name, _value=value, _class=field_class) form[0].append( TR(TD(LABEL(field.label, _for=input_id)), TD(control, DIV(error, _class='error') if error else ''), TD(field.comment or ''))) td = TD(INPUT(_type='submit', _value='Submit')) if deletable: td.append(INPUT(_type='checkbox', _value='ON', _name='_delete')) td.append('(check to delete)') form[0].append(TR(TD(), td, TD())) return form
def sql2table( tbl, db, tbl_query=None, order_by=None, rows_on_page=13, caller="index", csv=False, pagi=False, links=[], hlinks=[], fld_links={}, fld_skip=[ 0, ], fld_length=15, page_d={}, show_thead=True, ): def stop_button(): return A('!', _title='stop', _role='button', _style="background-color:lightgray;color:black;") if not tbl in db.tables: return f"unknown tbl: {tbl}" if tbl_query is None: tbl_query = db[tbl].id > 0 if tbl_query and not isinstance(tbl_query, pydal.objects.Query): return f"bad tbl_query! tbl: {tbl}" if order_by is None: order_by = ~db[tbl].id try: pg = int(page_d.get("page", 1)) except (ValueError, TypeError): pg = 1 table_items = len(db(tbl_query).select()) if rows_on_page > table_items: rows_on_page = table_items if table_items == 0: show_thead = False max_pages, rem = divmod(table_items, rows_on_page) if table_items else (0, 0) if rem: max_pages += 1 limitby = ((pg - 1) * rows_on_page, pg * rows_on_page) if not pagi: rows_on_page = table_items limitby = (0, table_items) rows = db(tbl_query).select(orderby=order_by, limitby=limitby) ij_start = -len(links) ff = [f for f in db[tbl].fields] hh = [db[tbl][f].label for f in ff] def h_func(x, jj): if jj < 0: if len(hlinks) >= -jj: return hlinks[len(hlinks) + jj] return "act" if jj in fld_skip: return '' if not x is None and isinstance(x, str) and len(x) > fld_length: x = x[:fld_length] + '...' return f"{x}" def r_func(x, ii, r, t, f_nm): if ii < 0: if len(links) >= -ii: return links[len(links) + ii](t, r.id) return "act" if ii in fld_skip: return '' if ii in fld_links: return fld_links[ii](t, x, r.id) if f_nm in fld_links: return fld_links[f_nm](t, x, r.id) if not x is None and isinstance(x, str) and len(x) > fld_length: x = x[:fld_length] + '~' return f"{x}" return DIV( SPAN("table_name: ", ), SPAN(f"{tbl}", _style="color:red"), SPAN(f"; {table_items} rows, {rows_on_page} rows_on_page, {pg} page "), DIV( # <div> SPAN( A( "prev", _role="button", _href=URL( caller, vars=dict(page=pg - 1 if pg > 1 else pg), ), ) if pg > 1 else stop_button(), A( "next", _role="button", _href=URL( caller, vars=dict(page=pg + 1 if pg < max_pages else pg), ), ) if pg < max_pages else stop_button(), ) if pagi else "", SPAN( A( "csv", _role="button", _title="table to csv file", _href=URL( "some_func", vars=dict(t_=tbl, c="a"), ), ), A( "xls", _role="button", _title="table to xls file", _href=URL( "some_func", vars=dict(t_=tbl, c="b"), ), ), ) if csv else "", ), # </div> TABLE( THEAD( TR(*[ TD(H6(h_func(hh[j], j))) for j in range(ij_start, len(hh)) ])) if show_thead else "", TBODY(*[ TR(*[ TD(r_func(row[ff[i]], i, row, tbl, ff[i])) for i in range(ij_start, len(ff)) ]) for row in rows ]), ), )