0

I must conditionally format rows of an html table that is generated with Python. I previously had to sort table columns and did so using javascript. Rather than conditionally format background-color on html rows how can I modify Python code to do this? where 'results' row is 'success' then row is green, where row is 'fail' then row is red? the code snippet shows how I tried to do this with js only. Code is 2 py files, the js code is at end of code function resultFormating.

import os
import sys
import random

#
# update PYTHONPATH
#
sys.path.append(os.getcwd())

from test_output import Test_Output, Test_record 


desc_choices = ('Cat', 'Dog', 'Pig', 'Horse', 'Mule')

info_choices = ('Red', 'Blue', 'Purple', 'Brown', 'Maroon')

facil_choices = ('Kitchen', 'Shower', 'Room', 'Den', 'Patio')

test_report = Test_Output()

test_report.init_report('Test_Report')

for i in range(10):
   test_report.add_report_record(
              Test_record(
                          Facility = random.choice(facil_choices),
                          Test_group = int(random.random() * 10**3),
                          Test_number = i,
                          Description = random.choice(desc_choices),
                          Result = random.choice((0,8)),
                          Execution_time = int(random.random() * 10**3),
                          Information = random.choice(info_choices),
                          Output = ''
              )
   )


test_report.write_report(display_report = True)
`
import os, sys
import webbrowser
import platform
from tempfile import gettempdir
from datetime import datetime
from collections import namedtuple
from timeit import default_timer as timer


DEFAULT_SCREEN_STACK_SIZE = 20

FILE_LINK = "file:///"

HTML_HEADER = """\
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
""".splitlines()


HTML_TRAILER = """\
</body>
</html>
""".splitlines()


field_names = [
               'Facility', 'Test_group', 'Test_number',
              'Description', 'Result', 'Execution_time',
              'Information', 'Output'
]

Test_record = namedtuple('Test_record', field_names )


def _write_HTML_header(fp):
   for line in HTML_HEADER: fp.write(line)


def _write_HTML_trailer(fp):
   for line in HTML_TRAILER: fp.write(line)


def return_seconds_as_h_m_s(seconds):
    '''
    return tuple h, m, s representing hours, minutes, seconds
    '''
    m, s = divmod(seconds, 60)
    h, m = divmod(m, 60)
    return h, m, s


class Test_Output:
    '''
    Manage and generate test output data
    '''
    def __init__(self):
        self.test_output_dir = None
        self.test_output = None
        self.screen_trace_stack = []
        self.screen_trace_stack_size = DEFAULT_SCREEN_STACK_SIZE
        self.output_records = []
        self.report_start = 0
        self.init_output()


    def init_output(self):
        '''
        Initialized test output area
        '''
        self.test_output = []
        self.screen_trace_stack = []


    def _format_text_html(self, text, size = None):
        '''
        format text to html
        '''
        #
        # TODO add HTML text formatting: color, font, size
        #

        if isinstance(text,str):
            text = text.splitlines()

        #
        # add html new line tag
        #
        if size is None:
            text_size = 30

        return ['<p style="font-size:{0}px">'.format(text_size)] + \
                            [ line + '<br>' for line in text] + \
                            ['</p>']


    def add_text(self, text, size = None):
        '''
        Add text to test output
        '''
        self.test_output += self._format_text_html(text, size = size)


    def add_screen_trace_stack(self, screen):
        ''' Add screen print to screen stack
        '''
        self.screen_trace_stack.append(screen)
        if (
            len(self.screen_trace_stack)
            ==
            self.screen_trace_stack_size*3
        ):
           self.screen_trace_stack = self.screen_trace_stack[
                                          -self.screen_trace_stack_size:
           ]


    def _write_screen_trace_stack(self, fp):
       for screen in self.screen_trace_stack[
                                          -self.screen_trace_stack_size:
       ]:
          for line in screen:
             fp.write(line.encode('ascii', 'ignore').decode() + '\n')


    def add_screen(self, screen):
        '''
        Add screen print to test output. screen is a list of data
        no html header should be included in screen
        '''

        #
        # slice out html header and trailer
        #
        self.test_output += screen


    def write_file(self, filename):
        '''
        Write test output created. '.htm' is appended to filename
        '''

        #
        # Add html trailer
        #

        if self.test_output_dir is None:
            self.set_dir('Test_Output')

        os.makedirs(self.test_output_dir, exist_ok = True)

        full_filename = self.test_output_dir + os.sep + filename + '.htm'

        with open(full_filename, 'w') as fp:
           _write_HTML_header(fp)
           for line in self.test_output:
               fp.write(line.encode('ascii', 'ignore').decode() + '\n')

           fp.write(
                    ''.join(
                            self._format_text_html(
                                   'Screen trace stack. Size = {}'
                                   .format(self.screen_trace_stack_size)
                            )
                    )
           )
           self._write_screen_trace_stack(fp)
           _write_HTML_trailer(fp)

        print('Test output written to: ' + full_filename)

        return full_filename


    def set_dir(self, prefix_dir = None):
        '''
        Set output direcory
        '''
        self.test_output_dir = (
                                gettempdir()
                                + os.sep
                                + (
                                   '' if prefix_dir is None
                                   else prefix_dir
                                )
                                + os.sep
                                + 'D'
                                + datetime
                                .strftime(datetime.now(), '%Y%m%d')
                                + os.sep
                                + 'T'
                                + datetime
                                .strftime(datetime.now(), '%H%M%S')
        )


    def init_report(self, prefix_dir = None):
        '''
        initialize data for report
        '''
        self.output_records = []

        # set output directory
        self.set_dir(prefix_dir)

        self.report_start = timer()


    def add_report_record(self, *args, **kwargs):
       '''
       Add report record information. All parameters from this list
       must be specified:
       '''

       # Accept Test_record as one parameter
       if len(args) == 1 and isinstance(args[0], Test_record):
          self.output_records.append(args[0])

       # other wise accept field from tuple as parm
       else:
          tuple_parms = ""
          for fn in field_names:
              tuple_parms += fn + " = kwargs['" + fn + "'], "

          self.output_records.append(eval("Test_record(" + tuple_parms +
                                                     ")"
                                         )
                                    )


    def write_report(self, display_report = True):
        '''
        Write report, calculate total count, failed, and total report time
        '''

        report_end = timer()
        test_count = fail_count = skip_count = 0

        html_output = """ \
                      <!DOCTYPE html>
                      <html>
                      <head>
                          <style>
                      td {
                          width: 200px;
                          height: 60px;
                      }
                      th {
                      cursor: pointer;
                      }
                          </style>
                          </head>
                      <body>
                          <table border="1" id="myTable">
                              <thead>
                                  <tr>
<th onclick="sortTable(0)">Facility</th>
<th onclick="sortTable(1)">Test_group</th>
<th onclick="sortTable(2)">Test_number</th>
<th onclick="sortTable(3)">Description</th>
<th onclick="sortTable(4)">Result</th>
<th onclick="sortTable(5)">Execution_time</th>
<th onclick="sortTable(6)">Information</th>
<th onclick="sortTable(7)">Output</th>
                      """.splitlines()


        #
        # add column headers
        #
        #for fn in field_names:
            #html_output.append("<th>" + fn + "</th>")

        html_output += """ \
                                   </tr>
                               </thead>
                               <tbody>
                       """.splitlines()
        #
        # Create table with test information records
        #
        for tr in self.output_records:
            test_count += 1
            new_row = '<tr>'
            for fn in field_names:
                if fn == 'Result':
                    if tr.Result > 4:
                        fail_count += 1
                        output_value = 'Fail'
                    elif tr.Result == 4:
                        skip_count += 1
                        output_value = 'Skipped'
                    else:
                        output_value = 'Success'
                elif fn == 'Output':
                    output_value = ''
                    if tr.Output != '':
                        output_value = '<a target="_blank" href=' + \
                                       FILE_LINK + tr.Output + \
                                       ' style="display:block;">Output</a>'
                elif fn == 'Execution_time':
                    output_value = ('%d:%02d:%02d' %
                                    return_seconds_as_h_m_s(tr.Execution_time)
                                   )
                else:
                    output_value = eval('str(tr.' + fn + ')')

                new_row += '<td>' + output_value + '</td>'

            new_row += '</tr>'
            html_output.append(new_row)


        html_output += self._format_text_html(
                 "Total tests: %d. Failed tests: %d. Skipped tests: %d."
                 % (test_count, fail_count, skip_count)
        )

        html_output += self._format_text_html(
                                       'Report test time %d:%02d:%02d' %
                                    return_seconds_as_h_m_s(report_end -
                                                     self.report_start))
        html_output += """ \
                               </tbody>
                           </table>
<script>
function sortTable(n) {
  var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
  table = document.getElementById("myTable");
  switching = true;
  dir = "asc";
  while (switching) {
    switching = false;
    rows = table.getElementsByTagName("TR");
    for (i = 1; i < (rows.length -1); i++) {
      shouldSwitch = false;
      x = rows[i].getElementsByTagName("TD")[n];
      y = rows[i+1].getElementsByTagName("TD")[n];
      if (dir == "asc") {
        if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
          shouldSwitch = true;
          break;
        }
      } else if (dir == "desc") {
        if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
          shouldSwitch = true;
          break;
        }
      }
    }
if (shouldSwitch) {
  rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
  switching = true;
  switchcount ++;
} else {
  if (switchcount == 0 && dir == "asc") {
    dir = "desc";
    switching = true;
  }
}
}
}
</script>

<script>
function resultFormatting() {
  var rows = document.getElementById("myTable").getElementsByTagName('tr');
  for(var i = 0; rows[0].children[i].innerHTML === "Result" || i < rows[0].children.length; i++);
  for(var j = 1; j < rows.length; j++) {
    rows[k].classList.add(rows[j].children[i].innerHTML === "Success" ? 'selected' : 'bad');
  }
});
.selected{
  background-color: #008000;
}
.bad{
 background-color: #FF0000;
}

</script>
                       </body>
                       </html>
                       """.splitlines()


        #
        # create and write report file
        #
        os.makedirs(self.test_output_dir, exist_ok = True)
        full_filename = self.test_output_dir + os.sep + 'test_report.htm'

        with open(full_filename, 'w') as fp:
           for line in html_output: fp.write(line + '\n')


        if display_report:
            #
            # Check if mac os X
            #
            webbrowser.open(FILE_LINK + full_filename)

        #
        # Return full filename of report
        #
        return full_filename
Sunny Patel
  • 7,830
  • 2
  • 31
  • 46
fjm
  • 23
  • 2
  • 8

1 Answers1

0

So you'll need to add additional logic when you add your table rows:

#
# Create table with test information records
#
for tr in self.output_records:
    test_count += 1
    new_row, row_result = '', None                                 #Modified
    for fn in field_names:
        if fn == 'Result':
            if tr.Result > 4:
                fail_count += 1
                output_value, row_result = 'Fail', False           #Modified
            elif tr.Result == 4:
                skip_count += 1
                output_value = 'Skipped'
            else:
                output_value, row_result = 'Success', True         #Modified
        elif fn == 'Output':
            output_value = ''
            if tr.Output != '':
                output_value = '<a target="_blank" href=' + \
                                FILE_LINK + tr.Output + \
                                ' style="display:block;">Output</a>'
        elif fn == 'Execution_time':
            output_value = ('%d:%02d:%02d' %
                            return_seconds_as_h_m_s(tr.Execution_time)
                            )
        else:
            output_value = str(getattr(tr, fn))                    #Modified

        new_row += '<td>' + output_value + '</td>'

    #Added new line
    result_class = '' if row_result is None else ' class="{0}"'.format('selected' if row_result else 'bad')
    new_row = '<tr{0}>{1}</tr>'.format(result_class, new_row)      #Modified

    html_output.append(new_row)

I introduced another variable row_result that will keep track of rows that pass or fail. Once that has been calculated, you can add that value to the row class (<tr class="") to stylize that row's output.

For the record, string building in loops is best accomplised by using the join method. Also, for a cleaner approach you can use .format to build out each line. You can see lots of evidence online.

Finally, don't use eval if you can avoid it. It can easily introduce vulnerabilities. For your case, you can use getattr to get the variable parameter name from tr.

Sunny Patel
  • 7,830
  • 2
  • 31
  • 46
  • With all this, you don't need the Javascript function `resultFormatting()` at all. – Sunny Patel Jul 30 '18 at 19:12
  • added, thank you, at **new_row += '...** have TypeError must be str not tuple, will continue to research, (will also try to get the js version to work) – fjm Jul 30 '18 at 19:32
  • Did you define `row_result`? `new_row, row_result = '', None` – Sunny Patel Jul 30 '18 at 19:56
  • @fjm I added comments to places where I modified your code. – Sunny Patel Jul 30 '18 at 19:58
  • I did, I did a line by line review from top to bottom on code you provided and made modifications, I just double-checked based on your comment – fjm Jul 30 '18 at 20:01
  • @fjm Does this mean you still have issues such as the `TypeError`? – Sunny Patel Jul 30 '18 at 20:04
  • no, upon double-check found what I missed, now I need to connect '.selected' and '.bad' to format the row color, currently it's text at the bottom of the web page – fjm Jul 30 '18 at 20:05
  • sorry, not good enough rep yet to chat...but I see I must connect new row_result to css background-color... – fjm Jul 30 '18 at 20:08
  • As stated in a comment from the other question, you need to move those css declarations (`.selected` and `.bad`) to your previously declared `` tag. – Sunny Patel Jul 30 '18 at 20:09
  • Sunny - many many thanks for the guidance, code and patience, spot on. I will try the other option and mark answered once I get it to work. – fjm Jul 30 '18 at 20:13