diff --git a/src/widgetastic/exceptions.py b/src/widgetastic/exceptions.py index 1e3885a1..0625ac1c 100644 --- a/src/widgetastic/exceptions.py +++ b/src/widgetastic/exceptions.py @@ -24,3 +24,8 @@ class DoNotReadThisWidget(WidgetasticException): class RowNotFound(IndexError, WidgetasticException): pass + + +class DynamicTableAddError(Exception): + """Raised when an attempt to add or save a row to a `widgetastic_manageiq.DynamicTable` fails""" + pass diff --git a/src/widgetastic/widget.py b/src/widgetastic/widget.py index 3c2a2831..8d670ff0 100644 --- a/src/widgetastic/widget.py +++ b/src/widgetastic/widget.py @@ -19,7 +19,7 @@ from .browser import Browser, BrowserParentWrapper from .exceptions import ( NoSuchElementException, LocatorNotImplemented, WidgetOperationFailed, DoNotReadThisWidget, - RowNotFound) + RowNotFound, DynamicTableAddError) from .log import ( PrependParentsAdapter, create_widget_logger, logged, call_sig, create_child_logger, create_item_logger) @@ -1937,6 +1937,62 @@ def row_save(self, row=None): self.logger.debug('Row saving not used.') +class DynamicTableMixin(object): + """Extend the widget.Table class to implement row_add for dynamic tables with an 'Actions' + column. + In these tables, the top or bottom row can be clicked to add a new row, and when it is + clicked the row is replaced (top or bottom) with a row containing fillable widgets. + + When the row is saved, it is moved to the bottom of the table. This behavior is specifc to + some MIQ dynamic tables. + + Args: + action_row: index of the action row, generally 0 or -1, defaults to 0 + + See Widgetastic.widget.Table for more arguments + """ + + def __init__(self, *args, **kwargs): + self.action_row = kwargs.pop('action_row', 0) # pull this off and pass the rest up + + def row_add(self): + """Use the action-cell column widget to add a row + + Clicks on the row directly, not the action button + + Returns: + int positive row index of the action row where the new widgets should be displayed + """ + # convert action_row into a positive index + if self.action_row >= 0: + pos_action_index = self.action_row + else: + pos_action_index = self._process_negative_index(nindex=self.action_row) + + try: + self[pos_action_index].click() + except IndexError: # self.action_row must have been None + raise DynamicTableAddError('DynamicTable action_row index "{}" not found in table' + .format(self.action_row)) + return pos_action_index + + def row_save(self, row=None): + """Save the row, assuming attributized columns includes 'actions' + + Implements behavior of AnalysisProfile type tables, where the row is moved to the bottom + on save + + Returns: + int row index of the last row in the table + """ + try: + self[row or self.action_row].actions.click() + except IndexError: # self.action_row must have been None + raise DynamicTableAddError('DynamicTable action_row index "{}" not found in table' + .format(self.action_row)) + return self._process_negative_index(nindex=-1) # use process_negative_index to get last row + + class Select(Widget): """Representation of the bogo-standard ``