예제 #1
0
    def get_display(my):

        widget = Widget()

        div = DivWdg(css="filter_box")

        show_span = SpanWdg(css="med")
        show_span.add("Show All Types: ")
        checkbox = FilterCheckboxWdg("show_all_types")
        checkbox.set_persistence()
        show_span.add(checkbox)
        show_all_types = checkbox.get_value()
        div.add(show_span)

        span = SpanWdg(css="med")
        span.add("Search Type: ")

        select = SelectWdg("filter|search_type")
        select.add_empty_option("-- Select --")
        project = Project.get()
        project_type = project.get_base_type()
        search = Search("sthpw/search_object")
        if show_all_types:
            search.add_where(
                """
            namespace = '%s' or namespace = '%s' or search_type in ('sthpw/task')
            """
                % (project_type, project.get_code())
            )
        else:
            # show only the custom ones
            search.add_filter("namespace", project.get_code())

        search.add_order_by("title")
        sobjects = search.get_sobjects()

        select.set_sobjects_for_options(sobjects, "search_type", "title")
        # select.set_option("query", "sthpw/search_object|search_type|title")
        select.set_persistence()
        select.add_event("onchange", "document.form.submit()")
        search_type = select.get_value()
        span.add(select)
        div.add(span)

        # make sure the current selection exists
        try:
            SearchType.get(search_type)
        except SearchException, e:
            return div
예제 #2
0
    def get_display(my):

        widget = Widget()

        div = DivWdg(css="filter_box")

        show_span = SpanWdg(css='med')
        show_span.add('Show All Types: ')
        checkbox = FilterCheckboxWdg('show_all_types')
        checkbox.set_persistence()
        show_span.add(checkbox)
        show_all_types = checkbox.get_value()
        div.add(show_span)

        span = SpanWdg(css="med")
        span.add("Search Type: ")

        select = SelectWdg("filter|search_type")
        select.add_empty_option("-- Select --")
        project = Project.get()
        project_type = project.get_base_type()
        search = Search("sthpw/search_object")
        if show_all_types:
            search.add_where('''
            namespace = '%s' or namespace = '%s' or search_type in ('sthpw/task')
            ''' % (project_type, project.get_code()))
        else:
            # show only the custom ones
            search.add_filter('namespace', project.get_code())

        search.add_order_by("title")
        sobjects = search.get_sobjects()

        select.set_sobjects_for_options(sobjects, "search_type", "title")
        #select.set_option("query", "sthpw/search_object|search_type|title")
        select.set_persistence()
        select.add_event("onchange", "document.form.submit()")
        search_type = select.get_value()
        span.add(select)
        div.add(span)

        # make sure the current selection exists
        try:
            SearchType.get(search_type)
        except SearchException, e:
            return div
예제 #3
0
 def get_prefs(my):
     my.cal_sub_task = FilterCheckboxWdg('calculate_sub_task', \
         label='include sub tasks', css='small')
     my.cal_sub_task_value = my.cal_sub_task.is_checked()
     return my.cal_sub_task
예제 #4
0
    def get_first_row_wdg(self):

        # read the csv file
        self.file_path = ""

        div = DivWdg()

        div.add(self.get_upload_wdg())

        if not self.search_type:
            return div

        if not self.file_path:
            return div

        if not self.file_path.endswith(".csv"):
            div.add("Uploaded file [%s] is not a csv file" % self.file_path)
            return div

        if not os.path.exists(self.file_path):
            raise Exception("Path '%s' does not exists" % self.file_path)

        div.add(HtmlElement.br(2))

        div.add(
            HtmlElement.
            b("The following is taken from first line in the uploaded csv file.  Select the appropriate column to match."
              ))
        div.add(HtmlElement.br())
        div.add(
            HtmlElement.b(
                "Make sure you have all the required columns** in the csv."))
        option_div = DivWdg()

        option_div.add_style("float: left")
        option_div.add_style("margin-right: 30px")

        option_div.add("<p>3. Parsing Options:</p>")

        self.search_type_obj = SearchType.get(self.search_type)

        # first row and second row
        option_div.add(HtmlElement.br(2))
        option_div.add("Use Title Row: ")
        title_row_checkbox = FilterCheckboxWdg("has_title")
        title_row_checkbox.set_default_checked()
        option_div.add(title_row_checkbox)
        option_div.add(
            HintWdg(
                "Set this to use the first row as a title row to match up columns in the database"
            ))

        option_div.add(HtmlElement.br(2))
        option_div.add("Sample Data Row: ")
        data_row_text = TextWdg("data_row")
        data_row_text.set_attr("size", "3")
        option_div.add(data_row_text)
        option_div.add(
            HintWdg(
                "Set this as a sample data row to match the columns to the database"
            ))

        option_div.add(HtmlElement.br(2))

        div.add(option_div)
        self.has_title = title_row_checkbox.is_checked()

        # parse the first fow
        csv_parser = CsvParser(self.file_path)
        if self.has_title:
            csv_parser.set_has_title_row(True)
        else:
            csv_parser.set_has_title_row(False)
        csv_parser.parse()
        csv_titles = csv_parser.get_titles()
        csv_data = csv_parser.get_data()

        data_row = data_row_text.get_value()
        if not data_row:
            data_row = 0
        else:
            try:
                data_row = int(data_row)
            except ValueError:
                data_row = 0

            if data_row >= len(csv_data):
                data_row = len(csv_data) - 1
        data_row_text.set_value(data_row)

        table = Table()
        table.set_attr("cellpadding", "10")

        table.add_row()
        table.add_header("CSV Column Value")
        table.add_header("TACTIC Column")
        table.add_header("Create New Column")

        columns = self.search_type_obj.get_columns()
        search_type = self.search_type_obj.get_base_search_type()
        sobj = SObjectFactory.create(search_type)
        required_columns = sobj.get_required_columns()

        row = csv_data[data_row]
        labels = []
        for column in columns:
            if column in required_columns:
                label = '%s**' % column
            else:
                label = column
            labels.append(label)

        for j, cell in enumerate(row):
            table.add_row()
            table.add_cell(cell)

            column_select = SelectWdg("column_%s" % j)
            column_select.add_event(
                "onchange",
                "if (this.value!='') {set_display_off('new_column_div_%s')} else {set_display_on('new_column_div_%s')}"
                % (j, j))

            column_select.add_empty_option("-- Select --")
            column_select.set_option("values", columns)
            column_select.set_option("labels", labels)

            # only set the value if it is actually in there
            if csv_titles[j] in columns:
                column_select.set_option("default", csv_titles[j])
            column_select.set_persist_on_submit()
            column_select_value = column_select.get_value()

            display = column_select.get_buffer_display()
            td = table.add_cell(display)

            if csv_titles[j] not in columns:
                td.add(" <b style='color: red'>*</b>")

                # new property
                new_column_div = DivWdg()

                if column_select_value:
                    new_column_div.add_style("display", "none")
                else:
                    new_column_div.add_style("display", "block")

                new_column_div.set_id("new_column_div_%s" % j)

                td = table.add_cell(new_column_div)
                text = TextWdg("new_column_%s" % j)
                text.set_persist_on_submit()

                if self.has_title:
                    text.set_value(csv_titles[j])

                new_column_div.add(" ... or ...")
                new_column_div.add(text)

        self.num_columns = len(row)
        hidden = HiddenWdg("num_columns", self.num_columns)

        # need to somehow specify defaults for columns

        div.add(table)

        div.add("<br/><br/>")

        div.add(self.get_preview_wdg())

        return div
예제 #5
0
    def get_first_row_wdg(my):

        # read the csv file
        my.file_path = ""

        div = DivWdg()

        div.add( my.get_upload_wdg() )

        if not my.search_type:
            return div

        if not my.file_path:
            return div


        if not my.file_path.endswith(".csv"):
            div.add( "Uploaded file [%s] is not a csv file"% my.file_path)
            return div

        if not os.path.exists(my.file_path):
            raise Exception("Path '%s' does not exists" % my.file_path)

        div.add(HtmlElement.br(2))

        div.add( HtmlElement.b("The following is taken from first line in the uploaded csv file.  Select the appropriate column to match.") )
        div.add(HtmlElement.br())
        div.add(  HtmlElement.b("Make sure you have all the required columns** in the csv."))
        option_div = DivWdg()
        
        option_div.add_style("float: left")
        option_div.add_style("margin-right: 30px")

        option_div.add("<p>3. Parsing Options:</p>")

        my.search_type_obj = SearchType.get(my.search_type)


        # first row and second row
        option_div.add( HtmlElement.br(2) )
        option_div.add("Use Title Row: ")
        title_row_checkbox = FilterCheckboxWdg("has_title")
        title_row_checkbox.set_default_checked()
        option_div.add(title_row_checkbox)
        option_div.add( HintWdg("Set this to use the first row as a title row to match up columns in the database") )
        
        option_div.add( HtmlElement.br(2) )
        option_div.add("Sample Data Row: ")
        data_row_text = TextWdg("data_row")
        data_row_text.set_attr("size", "3")
        option_div.add(data_row_text)
        option_div.add( HintWdg("Set this as a sample data row to match the columns to the database") )

        option_div.add( HtmlElement.br(2) )
        
       
        div.add(option_div)
        my.has_title = title_row_checkbox.is_checked()
        
        
        # parse the first fow
        csv_parser = CsvParser(my.file_path)
        if my.has_title:
            csv_parser.set_has_title_row(True)
        else:
            csv_parser.set_has_title_row(False)
        csv_parser.parse()
        csv_titles = csv_parser.get_titles()
        csv_data = csv_parser.get_data()



        data_row = data_row_text.get_value()
        if not data_row:
            data_row = 0
        else:
            try:
                data_row = int(data_row)
            except ValueError:
                data_row = 0

            if data_row >= len(csv_data):
                data_row = len(csv_data)-1
        data_row_text.set_value(data_row)




        table = Table()
        table.set_attr("cellpadding", "10")

        table.add_row()
        table.add_header("CSV Column Value")
        table.add_header("TACTIC Column")
        table.add_header("Create New Column")
        
        columns = my.search_type_obj.get_columns()
        search_type = my.search_type_obj.get_base_search_type()
        sobj = SObjectFactory.create(search_type)
        required_columns = sobj.get_required_columns()
        
        row = csv_data[data_row]
        labels = []
        for column in columns:
            if column in required_columns:
                label = '%s**'%column
            else:
                label = column
            labels.append(label)

        for j, cell in enumerate(row):
            table.add_row()
            table.add_cell(cell)

            column_select = SelectWdg("column_%s" % j)
            column_select.add_event("onchange", "if (this.value!='') {set_display_off('new_column_div_%s')} else {set_display_on('new_column_div_%s')}" % (j,j))

            column_select.add_empty_option("-- Select --")
            column_select.set_option("values", columns)
            column_select.set_option("labels", labels)

            # only set the value if it is actually in there
            if csv_titles[j] in columns:
                column_select.set_option("default", csv_titles[j])
            column_select.set_persist_on_submit()
            column_select_value = column_select.get_value()


            display = column_select.get_buffer_display()
            td = table.add_cell( display )

            if csv_titles[j] not in columns:
                td.add(" <b style='color: red'>*</b>")

                # new property
                new_column_div = DivWdg()

                if column_select_value:
                    new_column_div.add_style("display", "none")
                else:
                    new_column_div.add_style("display", "block")

                new_column_div.set_id("new_column_div_%s" % j)

                td = table.add_cell( new_column_div )
                text = TextWdg("new_column_%s" % j)
                text.set_persist_on_submit()

                if my.has_title:
                    text.set_value(csv_titles[j])


                new_column_div.add( " ... or ..." )
                new_column_div.add( text )


        my.num_columns = len(row)
        hidden = HiddenWdg("num_columns", my.num_columns)


        # need to somehow specify defaults for columns


        div.add(table)

        div.add("<br/><br/>")


        div.add(my.get_preview_wdg())


        return div          
예제 #6
0
    def get_display(self):

        widget = Widget()

        div = DivWdg(css="filter_box")

        show_span = SpanWdg(css='med')
        show_span.add('Show All Types: ')
        checkbox = FilterCheckboxWdg('show_all_types')
        checkbox.set_persistence()
        show_span.add(checkbox)
        show_all_types = checkbox.get_value()
        div.add(show_span)


        span = SpanWdg(css="med")
        span.add("Search Type: ")

        select = SelectWdg("filter|search_type")
        select.add_empty_option("-- Select --")
        project = Project.get()
        project_type = project.get_base_type()
        search = Search("sthpw/search_object")
        if show_all_types:
            search.add_where('''
            namespace = '%s' or namespace = '%s' or search_type in ('sthpw/task')
            ''' % (project_type, project.get_code()) )
        else:
            # show only the custom ones
            search.add_filter('namespace', project.get_code() )

        search.add_order_by("title")
        sobjects = search.get_sobjects()

        select.set_sobjects_for_options(sobjects,"search_type", "title")
        #select.set_option("query", "sthpw/search_object|search_type|title")
        select.set_persistence()
        select.add_event("onchange", "document.form.submit()")
        search_type = select.get_value()
        span.add(select)
        div.add(span)


        # make sure the current selection exists
        try:
            SearchType.get(search_type)
        except SearchException as e:
            return div
        except SqlException as e:
            return div


        # add the view selector
        view_select = SelectWdg("view")
        view_select.add_empty_option("-- View --")
        view_select.add_event("onchange", "document.form.submit()")
        view_select.set_persist_on_submit()
        #view_select.set_persistence()
        span = SpanWdg(css="med")
        span.add("Defined Views: ")
        span.add(view_select)
        div.add(span)

        div.add( self.get_create_view_wdg(search_type))

        div.add( HtmlElement.br(2) )


        div.add( self.get_new_tab_wdg() )


        widget.add(div)

        search = Search("sthpw/widget_config")
        #search.add_user_filter()
        search.add_filter("search_type", search_type)
        search.add_where("view != 'definition' and view != 'custom'")
        #search.add_column("view")
        widget_configs = search.get_sobjects()
        if widget_configs:
            view_select.set_sobjects_for_options(widget_configs,"view","view")
        view = view_select.get_value()
        if not view:
            view = "custom"
            #return widget

        # get the selected widget config
        for widget_config in widget_configs:
            if widget_config.get_value("view") == view:
                break
        else:
            return widget

        # get the handler: a little HACKY.
        config_xml = widget_config.get_xml_value("config")
        handler = config_xml.get_value("config/%s/@handler" % view)

        if not search_type:
            return widget

        widget.add(HtmlElement.br())
        span = SpanWdg()
        custom_view = CustomViewWdg(search_type)
        span.add(custom_view)
        span.add_style("float: right")
        widget.add(span)


        widget.add( HtmlElement.br() )
        widget.add("<h3>Example View [%s]</h3>" % view)

        # add a general filter
        filter_div = DivWdg()
        for i in range(0,1):
            filter = GeneralFilterWdg()
            filter.set_columns_from_search_type(search_type)
            filter_div.add("Filter: ")
            filter_div.add(filter)
            #filter_div.add(IconWdg("Remove Filter", IconWdg.RETIRE))
            filter_div.add( HtmlElement.br(2) )

        widget.add(filter_div)

        search = Search(search_type)
        search.set_limit(5)
        filter.alter_search(search)

        
        if not handler:
            if view in ["edit","insert"]:
                table = EditWdg(search_type, view)
            else:
                table = TableWdg(search_type, view)
        else:
            table = eval("%s(search_type,view)" % handler)

        #table.alter_search(search)
        sobjects = search.get_sobjects()

        if not sobjects and view in ["edit","insert"]:
            sobjects = [SObjectFactory.create(search_type)]
        table.set_sobjects(sobjects)
        widget.add(table)

        # show the custom properties
        widget.add("<h3>Custom Properties [%s]</h3>" % search_type)
        search = Search("prod/custom_property")
        search.add_filter("search_type", search_type)
        # This is actually reading the sthpw/custom_property conf file, weird
        table = TableWdg("prod/custom_property")
        table.set_search_limit(5)
        table.set_sobjects(search.get_sobjects() )
        widget.add(table)

        return widget
예제 #7
0
    def get_display(self):

        widget = Widget()

        div = DivWdg(css="filter_box")

        show_span = SpanWdg(css='med')
        show_span.add('Show All Types: ')
        checkbox = FilterCheckboxWdg('show_all_types')
        checkbox.set_persistence()
        show_span.add(checkbox)
        show_all_types = checkbox.get_value()
        div.add(show_span)

        span = SpanWdg(css="med")
        span.add("Search Type: ")

        select = SelectWdg("filter|search_type")
        select.add_empty_option("-- Select --")
        project = Project.get()
        project_type = project.get_base_type()
        search = Search("sthpw/search_object")
        if show_all_types:
            search.add_where('''
            namespace = '%s' or namespace = '%s' or search_type in ('sthpw/task')
            ''' % (project_type, project.get_code()))
        else:
            # show only the custom ones
            search.add_filter('namespace', project.get_code())

        search.add_order_by("title")
        sobjects = search.get_sobjects()

        select.set_sobjects_for_options(sobjects, "search_type", "title")
        #select.set_option("query", "sthpw/search_object|search_type|title")
        select.set_persistence()
        select.add_event("onchange", "document.form.submit()")
        search_type = select.get_value()
        span.add(select)
        div.add(span)

        # make sure the current selection exists
        try:
            SearchType.get(search_type)
        except SearchException as e:
            return div
        except SqlException as e:
            return div

        # add the view selector
        view_select = SelectWdg("view")
        view_select.add_empty_option("-- View --")
        view_select.add_event("onchange", "document.form.submit()")
        view_select.set_persist_on_submit()
        #view_select.set_persistence()
        span = SpanWdg(css="med")
        span.add("Defined Views: ")
        span.add(view_select)
        div.add(span)

        div.add(self.get_create_view_wdg(search_type))

        div.add(HtmlElement.br(2))

        div.add(self.get_new_tab_wdg())

        widget.add(div)

        search = Search("sthpw/widget_config")
        #search.add_user_filter()
        search.add_filter("search_type", search_type)
        search.add_where("view != 'definition' and view != 'custom'")
        #search.add_column("view")
        widget_configs = search.get_sobjects()
        if widget_configs:
            view_select.set_sobjects_for_options(widget_configs, "view",
                                                 "view")
        view = view_select.get_value()
        if not view:
            view = "custom"
            #return widget

        # get the selected widget config
        for widget_config in widget_configs:
            if widget_config.get_value("view") == view:
                break
        else:
            return widget

        # get the handler: a little HACKY.
        config_xml = widget_config.get_xml_value("config")
        handler = config_xml.get_value("config/%s/@handler" % view)

        if not search_type:
            return widget

        widget.add(HtmlElement.br())
        span = SpanWdg()
        custom_view = CustomViewWdg(search_type)
        span.add(custom_view)
        span.add_style("float: right")
        widget.add(span)

        widget.add(HtmlElement.br())
        widget.add("<h3>Example View [%s]</h3>" % view)

        # add a general filter
        filter_div = DivWdg()
        for i in range(0, 1):
            filter = GeneralFilterWdg()
            filter.set_columns_from_search_type(search_type)
            filter_div.add("Filter: ")
            filter_div.add(filter)
            #filter_div.add(IconWdg("Remove Filter", IconWdg.RETIRE))
            filter_div.add(HtmlElement.br(2))

        widget.add(filter_div)

        search = Search(search_type)
        search.set_limit(5)
        filter.alter_search(search)

        if not handler:
            if view in ["edit", "insert"]:
                table = EditWdg(search_type, view)
            else:
                table = TableWdg(search_type, view)
        else:
            table = eval("%s(search_type,view)" % handler)

        #table.alter_search(search)
        sobjects = search.get_sobjects()

        if not sobjects and view in ["edit", "insert"]:
            sobjects = [SObjectFactory.create(search_type)]
        table.set_sobjects(sobjects)
        widget.add(table)

        # show the custom properties
        widget.add("<h3>Custom Properties [%s]</h3>" % search_type)
        search = Search("prod/custom_property")
        search.add_filter("search_type", search_type)
        # This is actually reading the sthpw/custom_property conf file, weird
        table = TableWdg("prod/custom_property")
        table.set_search_limit(5)
        table.set_sobjects(search.get_sobjects())
        widget.add(table)

        return widget
예제 #8
0
 def get_prefs(self):
     self.cal_sub_task = FilterCheckboxWdg('calculate_sub_task', \
         label='include sub tasks', css='small')
     self.cal_sub_task_value = self.cal_sub_task.is_checked()
     return self.cal_sub_task
예제 #9
0
class TaskCompletionWdg(BaseTableElementWdg):

    ARGS_KEYS = {

        "task_expr": {
            'description': "an expression that retrieves the tasks e.g. @SOBJECT(sthpw/task['context','anim'])",
            'type': 'TextAreaWdg',
            'order': 0,
            'category': 'Display'
        }
    }
    def init(self):
        self.is_preprocessed = False
        self.data = {}
        self.cal_sub_task = None
        self.cal_sub_task_value = False

        # this is meant for handle_td()
        self.row_completion = 0
        self.expression = None


    def get_width(self):
        return 200


    def is_editable(cls):
        '''Determines whether this element is editable'''
        return False
    is_editable = classmethod(is_editable)

    def preprocess(self):
        self.total_completion = 0.0
        self.num_sobjects = 0
        self.expression = self.kwargs.get('task_expr')
        if self.sobjects and  not self.expression:
            if self.expression:
                tasks = Search.eval(self.expression, sobjects=self.sobjects)
            else:
                tasks = Task.get_by_sobjects(self.sobjects)
            # create a data structure
            for task in tasks:
                search_type = task.get_value("search_type")
                search_code = task.get_value("search_code")
                search_key = SearchKey.build_search_key(search_type, search_code, column='code')
                sobject_tasks = self.data.get(search_key)
                if not sobject_tasks:
                    sobject_tasks = []
                    self.data[search_key] = sobject_tasks

                sobject_tasks.append(task)

        self.is_preprocessed = True

    def get_prefs(self):
        self.cal_sub_task = FilterCheckboxWdg('calculate_sub_task', \
            label='include sub tasks', css='small')
        self.cal_sub_task_value = self.cal_sub_task.is_checked()
        return self.cal_sub_task

    def get_width(self):
        '''not used I think'''
        width = self.kwargs.get("width")

        if not width:
            width = 400
        return int(width)

    def get_text_value(self):
        sobject = self.get_current_sobject()
        if sobject.is_insert():
            return ''

        if not self.is_preprocessed:
            self.preprocess()

        sobject = self.get_current_sobject()
        completion = self.get_completion(sobject)
        if not completion:
            completion = 0
        return "%0.1f%%" % completion

    def get_display(self):

        sobject = self.get_current_sobject()
        if sobject.is_insert():
            return ''

        completion = self.get_completion(sobject)
        self.row_completion = completion

        # completion is compared to None, because a 0% completion is valid
        if completion == None:
            div = DivWdg("<i>No tasks</i>")
            div.add_style("color: #aaa")
            return div

        widget = DivWdg()
        width = self.get_width()
        bar_wdg = CompletionBarWdg(completion, width )
        widget.add(bar_wdg)

        # keep a running tab of the total if there is at least one task for this sobject
        self.total_completion += completion
        self.num_sobjects += 1

        return widget


    def get_bottom_wdg(self):
        width = self.get_width()
        
        if self.num_sobjects:
            completion = self.total_completion / self.num_sobjects
            bar_wdg = CompletionBarWdg(completion, width)
        else:
            bar_wdg = "n/a"
        div = DivWdg()
        div.add("Total")
        div.add("<hr>")
        
        div.add(bar_wdg)
        return div


    def get_tasks(self, sobject):
        ''' if the sobject is a task, then just return the sobject, since tasks
         do not have tasks. Account for subtask based on preferences. Also
         filters out tasks belonging to obsolete processes'''
        if isinstance(sobject, Task):
            return [sobject]
        
        if self.expression:
            tasks = Search.eval(self.expression, sobjects=[sobject])
            return tasks

        tasks = self.data.get( SearchKey.get_by_sobject(sobject, use_id=False) )
        if tasks == None:
            tasks = Task.get_by_sobjects([sobject])

        return tasks
        # make sure we only take tasks in the pipeline into account

        #TURN OFF this filtering for now, will add it back as an optional filtering feature.
        """
        parent = sobject

        pipeline = Pipeline.get_by_sobject(parent)
        recurse = False
        if self.cal_sub_task_value:
            recurse = True
        if pipeline:
            processes = pipeline.get_process_names(recurse=recurse)

            filtered_tasks = []
            for task in tasks:
                if task.get_value("process") not in processes:
                    continue
                filtered_tasks.append(task)

            return filtered_tasks

        else:
            return tasks
        """



    def get_completion(self, sobject):
        self.tasks = self.get_tasks(sobject)
        
        percent = 0
        # count the tasks with invalid or obsolete status
        #invalid_count = 0
        for task in self.tasks:
            status_attr = task.get_attr("status")
            task_percent = status_attr.get_percent_completion()
            if task_percent < 0:
                task_percent = 0
                #invalid_count += 1
            percent += task_percent

        if self.tasks:
            # NOT sure if I should subtract total # of tasks by invalid
            # task, leave it for now
            percent = float(percent) / len(self.tasks)
        else:
            return None

        return percent

    def handle_td(self, td):
        td.add_style('vertical-align','middle')
        sobject = self.get_current_sobject()
        if sobject.is_insert():
            return
        td.add_attr("spt_input_value", self.row_completion)