def compute_trending_table(submissions_list, table_type, user_id=None): """ Create trending table from the rows @params submission_list (Rows): Submissions to be considered @params table_type (String): friends / global @params user_id (Long): ID of signed in user else None """ T = current.T if table_type == "friends": table_header = T("Trending among friends") column_name = T("Friends") else: table_header = T("Trending Globally") column_name = T("Users") if len(submissions_list) == 0: table = TABLE(_class="bordered centered") thead = THEAD( TR(TH(T("Problem")), TH(T("Recent Submissions")), TH(column_name))) table.append(thead) table.append(TBODY(TR(TD("Not enough data to show", _colspan=3)))) return table # Sort the rows according to the number of users # who solved the problem in last PAST_DAYS custom_compare = lambda x: (len(x[1]["users"]) + \ len(x[1]["custom_users"]), x[1]["total_submissions"]) problems_dict = {} for submission in submissions_list: plink = submission.problem_link pname = submission.problem_name uid = submission.user_id cid = submission.custom_user_id if plink not in problems_dict: problems_dict[plink] = { "name": pname, "total_submissions": 0, "users": set([]), "custom_users": set([]) } pdict = problems_dict[plink] pdict["total_submissions"] += 1 if uid: pdict["users"].add(uid) else: pdict["custom_users"].add(cid) trending_problems = sorted(problems_dict.items(), key=custom_compare, reverse=True) return render_trending_table(table_header, trending_problems[:current.PROBLEMS_PER_PAGE], column_name, user_id)
def render_trending_table(caption, problems, column_name, user_id): """ Create trending table from the rows """ T = current.T table = TABLE(_class="bordered centered") thead = THEAD( TR(TH(T("Problem")), TH(T("Recent Submissions")), TH(column_name))) table.append(thead) tbody = TBODY() for problem in problems: tr = TR() link_class = get_link_class(problem[0], user_id) link_title = (" ".join(link_class.split("-"))).capitalize() tr.append( TD( problem_widget(problem[1]["name"], problem[0], link_class, link_title))) tr.append(TD(problem[1]["total_submissions"])) tr.append(TD(len(problem[1]["users"]) + \ len(problem[1]["custom_users"]))) tbody.append(tr) table.append(tbody) table = DIV(H5(caption, _class="center"), HR(), table) return table
def render_table(submissions, duplicates=[]): """ Create the HTML table from submissions """ status_dict = { "AC": "Accepted", "WA": "Wrong Answer", "TLE": "Time Limit Exceeded", "MLE": "Memory Limit Exceeded", "RE": "Runtime Error", "CE": "Compile Error", "SK": "Skipped", "HCK": "Hacked", "OTH": "Others" } table = TABLE(_class="striped centered") table.append( THEAD( TR(TH("User Name"), TH("Site"), TH("Site Handle"), TH("Time of submission"), TH("Problem"), TH("Language"), TH("Status"), TH("Points"), TH("View Code")))) tbody = TBODY() for submission in submissions: tr = TR() append = tr.append span = SPAN() if submission.user_id: person_id = submission.user_id else: person_id = submission.custom_user_id # Check if the given custom_user is a duplicate # We need to do this because there might be a case # when a duplicate custom_user is created and then # his name or institute is changed for f in duplicates: if f[1] == person_id and f[0] != None: person_id = current.db.custom_friend(f[0]) break span = SPAN(_class="orange tooltipped", data={"position": "right", "delay": "50", "tooltip": "Custom User"}, _style="cursor: pointer; " + \ "float:right; " + \ "height:10px; " + \ "width:10px; " + \ "border-radius: 50%;") append( TD( DIV( span, A(person_id.first_name + " " + person_id.last_name, _href=URL("user", "profile", args=[person_id.stopstalk_handle], extension=False), _target="_blank")))) append(TD(submission.site)) append( TD( A(submission.site_handle, _href=get_link(submission.site, submission.site_handle), _target="_blank"))) append(TD(submission.time_stamp)) append( TD( A(submission.problem_name, _href=URL("problems", "index", vars={ "pname": submission.problem_name, "plink": submission.problem_link }, extension=False), _target="_blank"))) append(TD(submission.lang)) append( TD( IMG(_src=URL("static", "images/" + submission.status + ".jpg", extension=False), _title=status_dict[submission.status], _alt=status_dict[submission.status], _style="height: 25px; width: 25px;"))) append(TD(submission.points)) if submission.view_link: append( TD( A("View", _href=submission.view_link, _class="btn waves-light waves-effect", _style="background-color: #FF5722", _target="_blank"))) else: append(TD()) tbody.append(tr) table.append(tbody) return table
def _render(cls, resource, data, meta_data, format=None): """ Method to pre-render the contents for the message template @param resource: the S3Resource @param data: the data returned from S3Resource.select @param meta_data: the meta data for the notification @param format: the contents format ("text" or "html") """ created_on_selector = resource.prefix_selector("created_on") created_on_colname = None notify_on = meta_data["notify_on"] last_check_time = meta_data["last_check_time"] rows = data["rows"] rfields = data["rfields"] output = {} new, upd = [], [] if format == "html": # Pre-formatted HTML colnames = [] new_headers = TR() mod_headers = TR() for rfield in rfields: if rfield.selector == created_on_selector: created_on_colname = rfield.colname elif rfield.ftype != "id": colnames.append(rfield.colname) label = rfield.label new_headers.append(TH(label)) mod_headers.append(TH(label)) for row in rows: append_record = upd.append if created_on_colname: try: created_on = row["_row"][created_on_colname] except (KeyError, AttributeError): pass else: if s3_utc(created_on) >= last_check_time: append_record = new.append tr = TR([TD(XML(row[colname])) for colname in colnames]) append_record(tr) if "new" in notify_on and len(new): output["new"] = len(new) output["new_records"] = TABLE(THEAD(new_headers), TBODY(new)) else: output["new"] = None if "upd" in notify_on and len(upd): output["upd"] = len(upd) output["upd_records"] = TABLE(THEAD(new_headers), TBODY(upd)) else: output["upd"] = None else: # Standard text format labels = [] append = labels.append for rfield in rfields: if rfield.selector == created_on_selector: created_on_colname = rfield.colname elif rfield.ftype != "id": append((rfield.colname, rfield.label)) for row in rows: append_record = upd.append if created_on_colname: try: created_on = row["_row"][created_on_colname] except (KeyError, AttributeError): pass else: if s3_utc(created_on) >= last_check_time: append_record = new.append record = [] append_column = record.append for colname, label in labels: append_column((label, row[colname])) append_record(record) if "new" in notify_on and len(new): output["new"] = len(new) output["new_records"] = new else: output["new"] = None if "upd" in notify_on and len(upd): output["upd"] = len(upd) output["upd_records"] = upd else: output["upd"] = None output.update(meta_data) return output
def render_table(submissions, duplicates=[], user_id=None): """ Create the HTML table from submissions @param submissions (Dict): Dictionary of submissions to display @param duplicates (List): List of duplicate user ids @return (TABLE): HTML TABLE containing all the submissions """ T = current.T status_dict = { "AC": "Accepted", "WA": "Wrong Answer", "TLE": "Time Limit Exceeded", "MLE": "Memory Limit Exceeded", "RE": "Runtime Error", "CE": "Compile Error", "SK": "Skipped", "HCK": "Hacked", "PS": "Partially Solved", "OTH": "Others" } table = TABLE(_class="bordered centered submissions-table") table.append( THEAD( TR(TH(T("Name")), TH(T("Site Profile")), TH(T("Time of submission")), TH(T("Problem")), TH(T("Language")), TH(T("Status")), TH(T("Points")), TH(T("View/Download Code"))))) tbody = TBODY() # Dictionary to optimize lookup for solved and unsolved problems # Multiple lookups in the main set is bad plink_to_class = {} for submission in submissions: span = SPAN() if submission.user_id: person_id = submission.user_id else: person_id = submission.custom_user_id # Check if the given custom_user is a duplicate # We need to do this because there might be a case # when a duplicate custom_user is created and then # his name or institute is changed for duplicate in duplicates: if duplicate[1] == person_id and duplicate[0]: person_id = current.db.custom_friend(duplicate[0]) break span = SPAN(_class="orange tooltipped", data={"position": "right", "delay": "50", "tooltip": T("Custom User")}, _style="cursor: pointer; " + \ "float:right; " + \ "height:10px; " + \ "width:10px; " + \ "border-radius: 50%;") tr = TR() append = tr.append append( TD( DIV( span, A(person_id.first_name + " " + person_id.last_name, _href=URL("user", "profile", args=person_id.stopstalk_handle, extension=False), _class="submission-user-name", _target="_blank")))) append(TD(A(IMG(_src=current.get_static_url("images/" + \ submission.site.lower() + \ "_small.png"), _style="height: 30px; width: 30px;"), _class="submission-site-profile", _href=current.get_profile_url(submission.site, submission.site_handle), _target="_blank"))) append(TD(submission.time_stamp, _class="stopstalk-timestamp")) link_class = "" plink = submission.problem_link if plink_to_class.has_key(plink): link_class = plink_to_class[plink] else: link_class = get_link_class(plink, user_id) plink_to_class[plink] = link_class link_title = (" ".join(link_class.split("-"))).capitalize() append( TD( problem_widget(submission.problem_name, submission.problem_link, link_class, link_title))) append(TD(submission.lang)) append( TD( IMG(_src=current.get_static_url("images/" + submission.status + ".jpg"), _title=status_dict[submission.status], _alt=status_dict[submission.status], _class="status-icon"))) append(TD(submission.points)) if submission.view_link: submission_data = { "view-link": submission.view_link, "site": submission.site } button_class = "btn waves-light waves-effect" if current.auth.is_logged_in(): if submission.site != "HackerEarth": td = TD(BUTTON(T("View"), _class="view-submission-button " + button_class, _style="background-color: #FF5722", data=submission_data), " ", BUTTON(T("Download"), _class="download-submission-button " + \ button_class, _style="background-color: #2196F3", data=submission_data)) else: td = TD( A(T("View"), _href=submission.view_link, _class="btn waves-light waves-effect", _style="background-color: #FF5722", _target="_blank")) append(td) else: append( TD( BUTTON(T("View"), _class="btn tooltipped disabled", _style="background-color: #FF5722", data={ "position": "bottom", "delay": "50", "tooltip": T("Login to View") }), " ", BUTTON(T("Download"), _class="btn tooltipped disabled", _style="background-color: #2196F3", data={ "position": "bottom", "delay": "50", "tooltip": T("Login to Download") }))) else: append(TD()) tbody.append(tr) table.append(tbody) return table
def merge(self, r, **attr): """ Merge form for two records @param r: the S3Request @param **attr: the controller attributes for the request @note: this method can always only be POSTed, and requires both "selected" and "mode" in post_vars, as well as the duplicate bookmarks list in session.s3 """ T = current.T session = current.session response = current.response output = {} tablename = self.tablename # Get the duplicate bookmarks s3 = session.s3 DEDUPLICATE = self.DEDUPLICATE if DEDUPLICATE in s3: bookmarks = s3[DEDUPLICATE] if tablename in bookmarks: record_ids = bookmarks[tablename] # Process the post variables post_vars = r.post_vars mode = post_vars.get("mode") selected = post_vars.get("selected", "") selected = selected.split(",") if mode == "Inclusive": ids = selected elif mode == "Exclusive": ids = [i for i in record_ids if i not in selected] else: # Error ids = [] if len(ids) != 2: r.error(501, T("Please select exactly two records"), next=r.url(id=0, vars={})) # Get the selected records table = self.table query = (table._id == ids[0]) | (table._id == ids[1]) orderby = table.created_on if "created_on" in table else None rows = current.db(query).select(orderby=orderby, limitby=(0, 2)) if len(rows) != 2: r.error(404, current.ERROR.BAD_RECORD, next=r.url(id=0, vars={})) original = rows[0] duplicate = rows[1] # Prepare form construction formfields = [f for f in table if f.readable or f.writable] ORIGINAL, DUPLICATE, KEEP = self.ORIGINAL, self.DUPLICATE, self.KEEP keep_o = KEEP.o in post_vars and post_vars[KEEP.o] keep_d = KEEP.d in post_vars and post_vars[KEEP.d] trs = [] init_requires = self.init_requires index = 1 num_fields = len(formfields) for f in formfields: # Render the widgets oid = "%s_%s" % (ORIGINAL, f.name) did = "%s_%s" % (DUPLICATE, f.name) sid = "swap_%s" % f.name init_requires(f, original[f], duplicate[f]) if keep_o or not any((keep_o, keep_d)): owidget = self.widget(f, original[f], _name=oid, _id=oid, _tabindex=index) else: try: owidget = s3_represent_value(f, value=original[f]) except: owidget = s3_str(original[f]) if keep_d or not any((keep_o, keep_d)): dwidget = self.widget(f, duplicate[f], _name=did, _id=did) else: try: dwidget = s3_represent_value(f, value=duplicate[f]) except: dwidget = s3_str(duplicate[f]) # Swap button if not any((keep_o, keep_d)): swap = INPUT( _value="<-->", _class="swap-button", _id=sid, _type="button", _tabindex=index + num_fields, ) else: swap = DIV(_class="swap-button") if owidget is None or dwidget is None: continue # Render label row label = f.label trs.append( TR(TD(label, _class="w2p_fl"), TD(), TD(label, _class="w2p_fl"))) # Append widget row trs.append( TR(TD(owidget, _class="mwidget"), TD(swap), TD(dwidget, _class="mwidget"))) index = index + 1 # Show created_on/created_by for each record if "created_on" in table: original_date = original.created_on duplicate_date = duplicate.created_on if "created_by" in table: represent = table.created_by.represent original_author = represent(original.created_by) duplicate_author = represent(duplicate.created_by) created = T("Created on %s by %s") original_created = created % (original_date, original_author) duplicate_created = created % (duplicate_date, duplicate_author) else: created = T("Created on %s") original_created = created % original_date duplicate_created = created % duplicate_date else: original_created = "" duplicate_created = "" # Page title and subtitle output["title"] = T("Merge records") #output["subtitle"] = self.crud_string(tablename, "title_list") # Submit buttons if keep_o or not any((keep_o, keep_d)): submit_original = INPUT( _value=T("Keep Original"), _type="submit", _name=KEEP.o, _id=KEEP.o, ) else: submit_original = "" if keep_d or not any((keep_o, keep_d)): submit_duplicate = INPUT( _value=T("Keep Duplicate"), _type="submit", _name=KEEP.d, _id=KEEP.d, ) else: submit_duplicate = "" # Build the form form = FORM( TABLE( THEAD( TR( TH(H3(T("Original"))), TH(), TH(H3(T("Duplicate"))), ), TR( TD(original_created), TD(), TD(duplicate_created), _class="authorinfo", ), ), TBODY(trs), TFOOT(TR( TD(submit_original), TD(), TD(submit_duplicate), ), ), ), # Append mode and selected - required to get back here! hidden={ "mode": "Inclusive", "selected": ",".join(ids), }) output["form"] = form # Add RESET and CANCEL options output["reset"] = FORM( INPUT( _value=T("Reset"), _type="submit", _name="reset", _id="form-reset", ), A(T("Cancel"), _href=r.url(id=0, vars={}), _class="action-lnk"), hidden={ "mode": mode, "selected": ",".join(ids), }, ) # Process the merge form formname = "merge_%s_%s_%s" % (tablename, original[table._id], duplicate[table._id]) if form.accepts( post_vars, session, formname=formname, onvalidation=lambda form: self.onvalidation(tablename, form), keepvalues=False, hideerror=False): s3db = current.s3db if form.vars[KEEP.d]: prefix = "%s_" % DUPLICATE original, duplicate = duplicate, original else: prefix = "%s_" % ORIGINAL data = Storage() for key in form.vars: if key.startswith(prefix): fname = key.split("_", 1)[1] data[fname] = form.vars[key] search = False resource = s3db.resource(tablename) try: resource.merge(original[table._id], duplicate[table._id], update=data) except current.auth.permission.error: r.unauthorised() except KeyError: r.error(404, current.ERROR.BAD_RECORD) except Exception: import sys r.error(424, T("Could not merge records. (Internal Error: %s)") % sys.exc_info()[1], next=r.url()) else: # Cleanup bookmark list if mode == "Inclusive": bookmarks[tablename] = [ i for i in record_ids if i not in ids ] if not bookmarks[tablename]: del bookmarks[tablename] search = True elif mode == "Exclusive": bookmarks[tablename].extend(ids) if not selected: search = True # Confirmation message # @todo: Having the link to the merged record in the confirmation # message would be nice, but it's currently not clickable there :/ #result = A(T("Open the merged record"), #_href=r.url(method="read", #id=original[table._id], #vars={})) response.confirmation = T("Records merged successfully.") # Go back to bookmark list if search: self.next = r.url(method="", id=0, vars={}) else: self.next = r.url(id=0, vars={}) # View response.view = self._view(r, "merge.html") return output
def render_user_editorials_table(user_editorials, user_id=None, logged_in_user_id=None, read_editorial_class=""): """ Render User editorials table @param user_editorials (Rows): Rows object of the editorials @param user_id (Number): For which user is the listing happening @param logged_in_user_id (Number): Which use is logged in @param read_editorial_class (String): HTML class for GA tracking @return (HTML): HTML table representing the user editorials """ db = current.db atable = db.auth_user ptable = db.problem T = current.T user_ids = set([x.user_id for x in user_editorials]) users = db(atable.id.belongs(user_ids)).select() user_mappings = {} for user in users: user_mappings[user.id] = user query = (ptable.id.belongs([x.problem_id for x in user_editorials])) problem_records = db(query).select(ptable.id, ptable.name, ptable.link) precords = {} for precord in problem_records: precords[precord.id] = {"name": precord.name, "link": precord.link} table = TABLE(_class="centered user-editorials-table") thead = THEAD( TR(TH(T("Problem")), TH(T("Editorial By")), TH(T("Added on")), TH(T("Votes")), TH())) tbody = TBODY() color_mapping = {"accepted": "green", "rejected": "red", "pending": "blue"} for editorial in user_editorials: if logged_in_user_id != 1 and user_id != editorial.user_id and editorial.verification != "accepted": continue user = user_mappings[editorial.user_id] record = precords[editorial.problem_id] number_of_votes = len( editorial.votes.split(",")) if editorial.votes else 0 link_class = get_link_class(record["link"], logged_in_user_id) link_title = (" ".join(link_class.split("-"))).capitalize() tr = TR( TD( problem_widget(record["name"], record["link"], link_class, link_title))) if logged_in_user_id is not None and \ (editorial.user_id == logged_in_user_id or logged_in_user_id == 1): tr.append(TD(A(user.first_name + " " + user.last_name, _href=URL("user", "profile", args=user.stopstalk_handle)), " ", DIV(editorial.verification.capitalize(), _class="verification-badge " + \ color_mapping[editorial.verification]))) else: tr.append( TD( A(user.first_name + " " + user.last_name, _href=URL("user", "profile", args=user.stopstalk_handle)))) tr.append(TD(editorial.added_on)) vote_class = "" if logged_in_user_id is not None and \ str(logged_in_user_id) in set(editorial.votes.split(",")): vote_class = "red-text" tr.append( TD( DIV(SPAN(I(_class="fa fa-heart " + vote_class), _class="love-editorial", data={"id": editorial.id}), " ", DIV(number_of_votes, _class="love-count", _style="margin-left: 5px;"), _style="display: inline-flex;"))) actions_td = TD( A(I(_class="fa fa-eye fa-2x"), _href=URL("problems", "read_editorial", args=editorial.id, extension=False), _class="btn btn-primary tooltipped " + read_editorial_class, _style="background-color: #13AA5F;", data={ "position": "bottom", "delay": 40, "tooltip": T("Read Editorial") })) if logged_in_user_id is not None and \ (user.id == logged_in_user_id or logged_in_user_id == 1) and \ editorial.verification != "accepted": actions_td.append( BUTTON( I(_class="fa fa-trash fa-2x"), _style="margin-left: 2%;", _class="btn btn-primary red tooltipped delete-editorial", data={ "position": "bottom", "delay": 40, "tooltip": T("Delete Editorial"), "id": editorial.id })) tr.append(actions_td) tbody.append(tr) table.append(thead) table.append(tbody) return table
def render_table(submissions): """ Create the HTML table from submissions """ status_dict = { "AC": "Accepted", "WA": "Wrong Answer", "TLE": "Time Limit Exceeded", "MLE": "Memory Limit Exceeded", "RE": "Runtime Error", "CE": "Compile Error", "SK": "Skipped", "HCK": "Hacked", "OTH": "Others" } table = TABLE(_class="striped centered") table.append( THEAD( TR(TH("User Name"), TH("Site"), TH("Site Handle"), TH("Time of submission"), TH("Problem"), TH("Language"), TH("Status"), TH("Points"), TH("View Code")))) tbody = TBODY() for submission in submissions: tr = TR() append = tr.append person_id = submission.custom_user_id if submission.user_id: person_id = submission.user_id append( TD( A(person_id.first_name + " " + person_id.last_name, _href=URL("user", "profile", args=[submission.stopstalk_handle]), _target="_blank"))) append(TD(submission.site)) append( TD( A(submission.site_handle, _href=get_link(submission.site, submission.site_handle), _target="_blank"))) append(TD(submission.time_stamp)) append( TD( A(submission.problem_name, _href=URL("problems", "index", vars={ "pname": submission.problem_name, "plink": submission.problem_link }), _target="_blank"))) append(TD(submission.lang)) append( TD( IMG(_src=URL("static", "images/" + submission.status + ".jpg"), _title=status_dict[submission.status], _style="height: 25px; width: 25px;"))) append(TD(submission.points)) if submission.view_link: append( TD( A("View", _href=submission.view_link, _class="btn waves-light waves-effect", _style="background-color: #FF5722", _target="_blank"))) else: append(TD()) tbody.append(tr) table.append(tbody) return table
def render_table(submissions, duplicates=[]): """ Create the HTML table from submissions @param submissions (Dict): Dictionary of submissions to display @param duplicates (List): List of duplicate user ids @return (TABLE): HTML TABLE containing all the submissions """ status_dict = {"AC": "Accepted", "WA": "Wrong Answer", "TLE": "Time Limit Exceeded", "MLE": "Memory Limit Exceeded", "RE": "Runtime Error", "CE": "Compile Error", "SK": "Skipped", "HCK": "Hacked", "OTH": "Others"} table = TABLE(_class="striped centered") table.append(THEAD(TR(TH("User Name"), TH("Site"), TH("Site Handle"), TH("Time of submission"), TH("Problem"), TH("Language"), TH("Status"), TH("Points"), TH("View/Download Code")))) tbody = TBODY() for submission in submissions: span = SPAN() if submission.user_id: person_id = submission.user_id else: person_id = submission.custom_user_id # Check if the given custom_user is a duplicate # We need to do this because there might be a case # when a duplicate custom_user is created and then # his name or institute is changed for duplicate in duplicates: if duplicate[1] == person_id and duplicate[0]: person_id = current.db.custom_friend(duplicate[0]) break span = SPAN(_class="orange tooltipped", data={"position": "right", "delay": "50", "tooltip": "Custom User"}, _style="cursor: pointer; " + \ "float:right; " + \ "height:10px; " + \ "width:10px; " + \ "border-radius: 50%;") tr = TR() append = tr.append append(TD(DIV(span, A(person_id.first_name + " " + person_id.last_name, _href=URL("user", "profile", args=person_id.stopstalk_handle, extension=False), _target="_blank")))) append(TD(submission.site)) append(TD(A(submission.site_handle, _href=get_link(submission.site, submission.site_handle), _target="_blank"))) append(TD(submission.time_stamp)) append(TD(A(submission.problem_name, _href=URL("problems", "index", vars={"pname": submission.problem_name, "plink": submission.problem_link}, extension=False), _target="_blank"))) append(TD(submission.lang)) append(TD(IMG(_src=URL("static", "images/" + submission.status + ".jpg", extension=False), _title=status_dict[submission.status], _alt=status_dict[submission.status], _style="height: 25px; width: 25px;"))) append(TD(submission.points)) if submission.view_link: if current.auth.is_logged_in(): td = TD(A("View", _href=submission.view_link, _class="btn waves-light waves-effect", _style="background-color: #FF5722", _target="_blank"), " ") if submission.site != "HackerEarth": td.append(A("Download", _class="download-submission-button btn waves-light waves-effect", _style="background-color: #2196F3", _target="_blank", data={"view-link": submission.view_link, "site": submission.site})) append(td) else: append(TD(A("View", _class="btn tooltipped disabled", _style="background-color: #FF5722", _target="_blank", data={"position": "bottom", "delay": "50", "tooltip": "Login to View"}), " ", A("Download", _class="btn tooltipped disabled", _style="background-color: #2196F3", _target="_blank", data={"position": "bottom", "delay": "50", "tooltip": "Login to Download"}))) else: append(TD()) tbody.append(tr) table.append(tbody) return table
def create_balance_table(self): table_keys = THEAD(TR(*[TD(B(k)) for k in self.balance_table_columns])) table_rows = [TR(*[TD(v) for k, v in row.items()]) for row in self.balance_per_category] balance_table = TABLE(table_keys, table_rows, _class='web2py_grid') return balance_table