Allow column or row selection in the query tool. Fixes #2216

This commit is contained in:
Matthew Kleiman 2017-04-18 13:28:45 +01:00 committed by Dave Page
parent c35dbc7211
commit d663d553c5
21 changed files with 1427 additions and 59 deletions

BIN
docs/en_US/images/query_output_data.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,76 @@
import pyperclip
import time
from selenium.webdriver import ActionChains
from regression.python_test_utils import test_utils
from regression.feature_utils.base_feature_test import BaseFeatureTest
class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
"""
Tests various ways to copy data from the query results grid.
"""
scenarios = [
("Test Copying Query Results", dict())
]
def before(self):
connection = test_utils.get_db_connection(self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'])
test_utils.drop_database(connection, "acceptance_test_db")
test_utils.create_database(self.server, "acceptance_test_db")
test_utils.create_table(self.server, "acceptance_test_db", "test_table")
self.page.add_server(self.server)
def runTest(self):
self.page.toggle_open_tree_item(self.server['name'])
self.page.toggle_open_tree_item('Databases')
self.page.toggle_open_tree_item('acceptance_test_db')
time.sleep(5)
self.page.find_by_partial_link_text("Tools").click()
self.page.find_by_partial_link_text("Query Tool").click()
self.page.click_tab('Query-1')
time.sleep(5)
ActionChains(self.page.driver).send_keys("SELECT * FROM test_table").perform()
self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe"))
self.page.find_by_id("btn-flash").click()
self._copies_rows()
self._copies_columns()
def _copies_rows(self):
pyperclip.copy("old clipboard contents")
time.sleep(5)
self.page.find_by_xpath("//*[contains(@class, 'sr')]/*[1]/input[@type='checkbox']").click()
self.page.find_by_xpath("//*[@id='btn-copy-row']").click()
self.assertEqual("'Some-Name','6'",
pyperclip.paste())
def _copies_columns(self):
pyperclip.copy("old clipboard contents")
self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]/input").click()
self.page.find_by_xpath("//*[@id='btn-copy-row']").click()
self.assertEqual(
"""'Some-Name'
'Some-Other-Name'""",
pyperclip.paste())
def after(self):
self.page.close_query_tool()
self.page.remove_server(self.server)
connection = test_utils.get_db_connection(self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'])
test_utils.drop_database(connection, "acceptance_test_db")

View File

@ -0,0 +1,92 @@
define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) {
var ColumnSelector = function () {
var init = function (grid) {
grid.onHeaderClick.subscribe(function (event, eventArgument) {
var column = eventArgument.column;
if (column.selectable !== false) {
if (!clickedCheckbox(event)) {
var $checkbox = $("[data-id='checkbox-" + column.id + "']");
toggleCheckbox($checkbox);
}
updateRanges(grid, column.id);
}
}
);
grid.getSelectionModel().onSelectedRangesChanged
.subscribe(handleSelectedRangesChanged.bind(null, grid));
};
var handleSelectedRangesChanged = function (grid, event, ranges) {
$('[data-cell-type="column-header-row"] input:checked')
.each(function (index, checkbox) {
var $checkbox = $(checkbox);
var columnIndex = grid.getColumnIndex($checkbox.data('column-id'));
var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, rangeSelectionHelper.rangeForColumn(grid, columnIndex));
if (!isStillSelected) {
toggleCheckbox($checkbox);
}
});
};
var updateRanges = function (grid, columnId) {
var selectionModel = grid.getSelectionModel();
var ranges = selectionModel.getSelectedRanges();
var columnIndex = grid.getColumnIndex(columnId);
var columnRange = rangeSelectionHelper.rangeForColumn(grid, columnIndex);
var newRanges;
if (rangeSelectionHelper.isRangeSelected(ranges, columnRange)) {
newRanges = rangeSelectionHelper.removeRange(ranges, columnRange);
} else {
if (rangeSelectionHelper.areAllRangesColumns(ranges, grid)) {
newRanges = rangeSelectionHelper.addRange(ranges, columnRange);
} else {
newRanges = [columnRange];
}
}
selectionModel.setSelectedRanges(newRanges);
};
var clickedCheckbox = function (e) {
return e.target.type == "checkbox"
};
var toggleCheckbox = function (checkbox) {
if (checkbox.prop("checked")) {
checkbox.prop("checked", false)
} else {
checkbox.prop("checked", true)
}
};
var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
return _.map(columnDefinitions, function (columnDefinition) {
if (columnDefinition.selectable !== false) {
var name =
"<span data-cell-type='column-header-row' " +
" data-test='output-column-header'>" +
" <input data-id='checkbox-" + columnDefinition.id + "' " +
" data-column-id='" + columnDefinition.id + "' " +
" type='checkbox'/>" +
" <span class='column-description'>" + columnDefinition.name + "</span>" +
"</span>";
return _.extend(columnDefinition, {
name: name
});
} else {
return columnDefinition;
}
});
};
$.extend(this, {
"init": init,
"getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
});
};
return ColumnSelector;
});

View File

@ -0,0 +1,52 @@
define([
'jquery',
'underscore',
'sources/selection/clipboard',
'sources/selection/range_selection_helper',
'sources/selection/range_boundary_navigator'],
function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
var copyData = function () {
var self = this;
var grid = self.slickgrid;
var columnDefinitions = grid.getColumns();
var selectedRanges = grid.getSelectionModel().getSelectedRanges();
var data = grid.getData();
var rows = grid.getSelectedRows();
if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) {
self.copied_rows = rows.map(function (rowIndex) {
return data[rowIndex];
});
setPasteRowButtonEnablement(self.can_edit, true);
} else {
self.copied_rows = [];
setPasteRowButtonEnablement(self.can_edit, false);
}
var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges);
if (csvText) {
clipboard.copyTextToClipboard(csvText);
}
};
var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) {
if (canEditFlag) {
$("#btn-paste-row").prop('disabled', !isEnabled);
}
};
var allTheRangesAreFullRows = function (ranges, columnDefinitions) {
var colRangeBounds = ranges.map(function (range) {
return [range.fromCell, range.toCell];
});
if(RangeSelectionHelper.isFirstColumnData(columnDefinitions)) {
return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]);
}
return _.isEqual(_.union.apply(null, colRangeBounds), [1, columnDefinitions.length - 1]);
};
return copyData;
});

View File

@ -0,0 +1,79 @@
define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_selector'],
function ($, ColumnSelector, RowSelector) {
var Slick = window.Slick;
var GridSelector = function (columnDefinitions) {
var rowSelector = new RowSelector(columnDefinitions);
var columnSelector = new ColumnSelector(columnDefinitions);
var init = function (grid) {
this.grid = grid;
grid.onHeaderClick.subscribe(function (event, eventArguments) {
if (eventArguments.column.selectAllOnClick) {
toggleSelectAll(grid);
}
});
grid.getSelectionModel().onSelectedRangesChanged
.subscribe(handleSelectedRangesChanged.bind(null, grid));
grid.registerPlugin(rowSelector);
grid.registerPlugin(columnSelector);
};
var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
columnDefinitions = columnSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
columnDefinitions[0].selectAllOnClick = true;
columnDefinitions[0].name = '<input type="checkbox" data-id="checkbox-select-all" ' +
'title="Select/Deselect All"/>' + columnDefinitions[0].name;
return columnDefinitions;
};
function handleSelectedRangesChanged(grid) {
$("[data-id='checkbox-select-all']").prop("checked", isEntireGridSelected(grid));
}
function isEntireGridSelected(grid) {
var selectionModel = grid.getSelectionModel();
var selectedRanges = selectionModel.getSelectedRanges();
return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid));
}
function toggleSelectAll(grid) {
if (isEntireGridSelected(grid)) {
deselect(grid);
} else {
selectAll(grid)
}
}
var isSameRange = function (range, otherRange) {
return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell &&
range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow;
};
function getRangeOfWholeGrid(grid) {
return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1);
}
function deselect(grid) {
var selectionModel = grid.getSelectionModel();
selectionModel.setSelectedRanges([]);
}
function selectAll(grid) {
var range = getRangeOfWholeGrid(grid);
var selectionModel = grid.getSelectionModel();
selectionModel.setSelectedRanges([range]);
}
$.extend(this, {
"init": init,
"getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
});
};
return GridSelector;
});

View File

@ -0,0 +1,111 @@
define(['sources/selection/range_selection_helper'], function (RangeSelectionHelper) {
return {
getUnion: function (allRanges) {
if (_.isEmpty(allRanges)) {
return [];
}
allRanges.sort(firstElementNumberComparator);
var unionedRanges = [allRanges[0]];
allRanges.forEach(function (range) {
var maxBeginningOfRange = _.last(unionedRanges);
if (isStartInsideRange(range, maxBeginningOfRange)) {
if (!isEndInsideRange(range, maxBeginningOfRange)) {
maxBeginningOfRange[1] = range[1];
}
} else {
unionedRanges.push(range);
}
});
return unionedRanges;
function firstElementNumberComparator(a, b) {
return a[0] - b[0];
}
function isStartInsideRange(range, surroundingRange) {
return range[0] <= surroundingRange[1] + 1;
}
function isEndInsideRange(range, surroundingRange) {
return range[1] <= surroundingRange[1];
}
},
mapDimensionBoundaryUnion: function (unionedDimensionBoundaries, iteratee) {
var mapResult = [];
unionedDimensionBoundaries.forEach(function (subrange) {
for (var index = subrange[0]; index <= subrange[1]; index += 1) {
mapResult.push(iteratee(index));
}
});
return mapResult;
},
mapOver2DArray: function (rowRangeBounds, colRangeBounds, processCell, rowCollector) {
var unionedRowRanges = this.getUnion(rowRangeBounds);
var unionedColRanges = this.getUnion(colRangeBounds);
return this.mapDimensionBoundaryUnion(unionedRowRanges, function (rowId) {
var rowData = this.mapDimensionBoundaryUnion(unionedColRanges, function (colId) {
return processCell(rowId, colId);
});
return rowCollector(rowData);
}.bind(this));
},
rangesToCsv: function (data, columnDefinitions, selectedRanges) {
var rowRangeBounds = selectedRanges.map(function (range) {
return [range.fromRow, range.toRow];
});
var colRangeBounds = selectedRanges.map(function (range) {
return [range.fromCell, range.toCell];
});
if (!RangeSelectionHelper.isFirstColumnData(columnDefinitions)) {
colRangeBounds = this.removeFirstColumn(colRangeBounds);
}
var csvRows = this.mapOver2DArray(rowRangeBounds, colRangeBounds, this.csvCell.bind(this, data, columnDefinitions), function (rowData) {
return rowData.join(',');
});
return csvRows.join('\n');
},
removeFirstColumn: function (colRangeBounds) {
var unionedColRanges = this.getUnion(colRangeBounds);
var firstSubrangeStartsAt0 = function () {
return unionedColRanges[0][0] == 0;
};
function firstSubrangeIsJustFirstColumn() {
return unionedColRanges[0][1] == 0;
}
if (firstSubrangeStartsAt0()) {
if (firstSubrangeIsJustFirstColumn()) {
unionedColRanges.shift();
} else {
unionedColRanges[0][0] = 1;
}
}
return unionedColRanges;
},
csvCell: function (data, columnDefinitions, rowId, colId) {
var val = data[rowId][columnDefinitions[colId].pos];
if (val && _.isObject(val)) {
val = "'" + JSON.stringify(val) + "'";
} else if (val && typeof val != "number" && typeof val != "boolean") {
val = "'" + val.toString() + "'";
} else if (_.isNull(val) || _.isUndefined(val)) {
val = '';
}
return val;
}
};
});

View File

@ -0,0 +1,78 @@
define(['slickgrid'], function () {
var Slick = window.Slick;
var isSameRange = function (range, otherRange) {
return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell &&
range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow;
};
var isRangeSelected = function (selectedRanges, range) {
return _.any(selectedRanges, function (selectedRange) {
return isSameRange(selectedRange, range)
})
};
var removeRange = function (selectedRanges, range) {
return _.filter(selectedRanges, function (selectedRange) {
return !(isSameRange(selectedRange, range))
})
};
var addRange = function (ranges, range) {
ranges.push(range);
return ranges;
};
var areAllRangesRows = function (ranges, grid) {
return _.every(ranges, function (range) {
return range.fromRow == range.toRow &&
range.fromCell == 1 && range.toCell == grid.getColumns().length - 1
})
};
var areAllRangesColumns = function (ranges, grid) {
return _.every(ranges, function (range) {
return range.fromCell == range.toCell &&
range.fromRow == 0 && range.toRow == grid.getDataLength() - 1
})
};
var rangeForRow = function (grid, rowId) {
var columnDefinitions = grid.getColumns();
if(isFirstColumnData(columnDefinitions)) {
return new Slick.Range(rowId, 0, rowId, grid.getColumns().length - 1);
}
return new Slick.Range(rowId, 1, rowId, grid.getColumns().length - 1);
};
function rangeForColumn(grid, columnIndex) {
return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex)
};
var getRangeOfWholeGrid = function (grid) {
return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1);
};
var isEntireGridSelected = function (grid) {
var selectionModel = grid.getSelectionModel();
var selectedRanges = selectionModel.getSelectedRanges();
return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid));
};
var isFirstColumnData = function (columnDefinitions) {
return !_.isUndefined(columnDefinitions[0].pos);
};
return {
addRange: addRange,
removeRange: removeRange,
isRangeSelected: isRangeSelected,
areAllRangesRows: areAllRangesRows,
areAllRangesColumns: areAllRangesColumns,
rangeForRow: rangeForRow,
rangeForColumn: rangeForColumn,
isEntireGridSelected: isEntireGridSelected,
getRangeOfWholeGrid: getRangeOfWholeGrid,
isFirstColumnData: isFirstColumnData
}
});

View File

@ -0,0 +1,85 @@
define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) {
var RowSelector = function () {
var Slick = window.Slick;
var gridEventBus = new Slick.EventHandler();
var init = function (grid) {
grid.getSelectionModel()
.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged.bind(null, grid));
gridEventBus
.subscribe(grid.onClick, handleClick.bind(null, grid))
};
var handleClick = function (grid, event, args) {
if (grid.getColumns()[args.cell].id === 'row-header-column') {
if (event.target.type != "checkbox") {
var checkbox = $(event.target).find('input[type="checkbox"]');
toggleCheckbox($(checkbox));
}
updateRanges(grid, args.row);
}
}
var handleSelectedRangesChanged = function (grid, event, ranges) {
$('[data-cell-type="row-header-checkbox"]:checked')
.each(function (index, checkbox) {
var $checkbox = $(checkbox);
var row = parseInt($checkbox.data('row'));
var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges,
rangeSelectionHelper.rangeForRow(grid, row));
if (!isStillSelected) {
toggleCheckbox($checkbox);
}
});
}
var updateRanges = function (grid, rowId) {
var selectionModel = grid.getSelectionModel();
var ranges = selectionModel.getSelectedRanges();
var rowRange = rangeSelectionHelper.rangeForRow(grid, rowId);
var newRanges;
if (rangeSelectionHelper.isRangeSelected(ranges, rowRange)) {
newRanges = rangeSelectionHelper.removeRange(ranges, rowRange);
} else {
if (rangeSelectionHelper.areAllRangesRows(ranges, grid)) {
newRanges = rangeSelectionHelper.addRange(ranges, rowRange);
} else {
newRanges = [rowRange];
}
}
selectionModel.setSelectedRanges(newRanges);
}
var toggleCheckbox = function (checkbox) {
if (checkbox.prop("checked")) {
checkbox.prop("checked", false)
} else {
checkbox.prop("checked", true)
}
};
var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
columnDefinitions.unshift({
id: 'row-header-column',
name: '',
selectable: false,
focusable: false,
formatter: function (rowIndex) {
return '<input type="checkbox" ' +
'data-row="' + rowIndex + '" ' +
'data-cell-type="row-header-checkbox"/>'
}
});
return columnDefinitions;
};
$.extend(this, {
"init": init,
"getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
});
};
return RowSelector;
});

View File

@ -96,7 +96,7 @@
</ul>
</div>
<div class="btn-group" role="group" aria-label="">
<button id="btn-copy-row" type="button" class="btn btn-default" title="{{ _('Copy Row') }}" disabled>
<button id="btn-copy-row" type="button" class="btn btn-default" title="{{ _('Copy') }}" disabled>
<i class="fa fa-files-o" aria-hidden="true"></i>
</button>
<button id="btn-paste-row" type="button" class="btn btn-default" title="{{ _('Paste Row') }}" disabled>

View File

@ -336,12 +336,6 @@ li {
background-color: transparent; /* show default selected row background */
}
.sc-cb {
background: #f0f0f0;
border-right-color: silver;
border-right-style: solid;
}
#datagrid .slick-header .ui-state-default,
#datagrid .slick-header .ui-widget-content.ui-state-default,
#datagrid .slick-header .ui-widget-header .ui-state-default {
@ -358,6 +352,10 @@ li {
padding: 4px 0 4px 6px;
}
.column-description {
display: table-cell;
}
.long_text_editor {
margin-left: 5px;
font-size: 12px !important;
@ -419,6 +417,11 @@ input.editor-checkbox:focus {
background: #e46b6b;
}
/* color the first column */
.sr .sc:first-child {
background-color: #2c76b4;
}
#datagrid div.slick-header.ui-state-default {
background: #2c76b4;
}

View File

@ -2,7 +2,8 @@ define(
[
'jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin',
'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
'sources/selection/clipboard',
'sources/selection/grid_selector', 'sources/selection/clipboard',
'sources/selection/copy_data',
'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
@ -21,13 +22,12 @@ define(
'slickgrid/plugins/slick.cellrangedecorator',
'slickgrid/plugins/slick.cellrangeselector',
'slickgrid/plugins/slick.cellselectionmodel',
'slickgrid/plugins/slick.checkboxselectcolumn',
'slickgrid/plugins/slick.cellcopymanager',
'slickgrid/plugins/slick.rowselectionmodel',
'slickgrid/slick.grid'
],
function(
$, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, clipboard
$, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, clipboard, copyData
) {
/* Return back, this has been called more than once */
if (pgAdmin.SqlEditor)
@ -549,14 +549,7 @@ define(
collection = [];
}
var grid_columns = new Array(),
checkboxSelector;
checkboxSelector = new Slick.CheckboxSelectColumn({
cssClass: "sc-cb"
});
grid_columns.push(checkboxSelector.getColumnDefinition());
var grid_columns = [];
var grid_width = $($('#editor-panel').find('.wcFrame')[1]).width()
_.each(columns, function(c) {
@ -592,6 +585,9 @@ define(
grid_columns.push(options)
});
var gridSelector = new GridSelector();
grid_columns = gridSelector.getColumnDefinitionsWithCheckboxes(grid_columns);
var grid_options = {
editable: true,
enableAddRow: is_editable,
@ -635,7 +631,7 @@ define(
var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options);
grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) );
grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
grid.registerPlugin(checkboxSelector);
grid.registerPlugin(gridSelector);
var editor_data = {
keys: self.handler.primary_keys,
@ -2947,46 +2943,7 @@ define(
},
// This function will copy the selected row.
_copy_row: function() {
var self = this, grid, data, rows, copied_text = '';
self.copied_rows = [];
// Disable copy button
$("#btn-copy-row").prop('disabled', true);
// Enable paste button
if(self.can_edit) {
$("#btn-paste-row").prop('disabled', false);
}
grid = self.slickgrid;
data = grid.getData();
rows = grid.getSelectedRows();
// Iterate over all the selected rows & fetch data
for (var i = 0; i < rows.length; i += 1) {
var idx = rows[i],
_rowData = data[idx],
_values = [];
self.copied_rows.push(_rowData);
// Convert it as CSV for clipboard
for (var j = 0; j < self.columns.length; j += 1) {
var val = _rowData[self.columns[j].pos];
if(val && _.isObject(val))
val = "'" + JSON.stringify(val) + "'";
else if(val && typeof val != "number" && typeof true != "boolean")
val = "'" + val.toString() + "'";
else if (_.isNull(val) || _.isUndefined(val))
val = '';
_values.push(val);
}
// Append to main text string
if(_values.length > 0)
copied_text += _values.toString() + "\n";
}
// If there is something to set into clipboard
if(copied_text)
clipboard.copyTextToClipboard(copied_text);
},
_copy_row: copyData,
// This function will paste the selected row.
_paste_row: function() {

View File

@ -22,6 +22,13 @@ installed with:
(pgadmin4) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt
While running in Linux environments install:
(pgadmin4) $ sudo apt-get install xsel
Otherwise the following error happens:
"Pyperclip could not find a copy/paste mechanism for your system"
General Information
-------------------

View File

@ -28,6 +28,7 @@ class BaseFeatureTest(BaseTestGenerator):
self.page = PgadminPage(self.driver, app_config)
try:
self.page.driver.switch_to.default_content()
self.page.wait_for_app()
self.page.wait_for_spinner_to_disappear()
self.page.reset_layout()

View File

@ -58,7 +58,18 @@ class PgadminPage:
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "']")
def close_query_tool(self):
self.driver.switch_to.default_content()
tab = self.find_by_xpath("//*[contains(@class,'wcPanelTab') and contains(.,'" + "Query" + "')]")
ActionChains(self.driver).context_click(tab).perform()
self.find_by_xpath("//li[contains(@class, 'context-menu-item')]/span[contains(text(), 'Remove Panel')]").click()
self.driver.switch_to.frame(self.driver.find_elements_by_tag_name("iframe")[0])
time.sleep(.5)
self.click_element(self.find_by_xpath('//button[contains(@class, "ajs-button") and contains(.,"Yes")]'))
self.driver.switch_to.default_content()
def remove_server(self, server_config):
self.driver.switch_to.default_content()
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']").click()
self.find_by_partial_link_text("Object").click()
self.find_by_partial_link_text("Delete/Drop").click()

View File

@ -0,0 +1,235 @@
define(
["jquery",
"underscore",
"slickgrid/slick.grid",
"sources/selection/column_selector",
"slickgrid/slick.rowselectionmodel",
"slickgrid"
],
function ($, _, SlickGrid, ColumnSelector, RowSelectionModel, Slick) {
describe("ColumnSelector", function () {
var container, data, columns, options;
beforeEach(function () {
container = $("<div></div>");
container.height(9999);
data = [{'some-column-name': 'first value', 'second column': 'second value'}];
columns = [
{
id: '1',
name: 'some-column-name',
},
{
id: '2',
name: 'second column',
},
{
name: 'some-non-selectable-column',
selectable: false
}
]
});
describe("when a column is not selectable", function () {
it("does not create a checkbox for selecting the column", function () {
var checkboxColumn = {
name: 'some-column-name-4',
selectable: false
};
columns.push(checkboxColumn);
setupGrid(columns);
expect(container.find('.slick-header-columns input').length).toBe(2)
});
});
it("renders a checkbox in the column header", function () {
setupGrid(columns);
expect(container.find('.slick-header-columns input').length).toBe(2)
});
it("displays the name of the column", function () {
setupGrid(columns);
expect($(container.find('.slick-header-columns .slick-column-name')[0]).text())
.toContain('some-column-name');
expect($(container.find('.slick-header-columns .slick-column-name')[1]).text())
.toContain('second column');
});
it("preserves the other attributes of column definitions", function () {
var columnSelector = new ColumnSelector();
var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
expect(selectableColumns[0].id).toBe('1');
});
describe("selecting columns", function () {
var grid, rowSelectionModel;
beforeEach(function () {
var columnSelector = new ColumnSelector();
columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
data = [];
for (var i = 0; i < 10; i++) {
data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
}
grid = new SlickGrid(container, data, columns, options);
rowSelectionModel = new RowSelectionModel();
grid.setSelectionModel(rowSelectionModel);
grid.registerPlugin(columnSelector);
grid.invalidate();
$("body").append(container);
});
afterEach(function () {
$("body").find(container).remove();
});
describe("when the user clicks a column header", function () {
it("selects the column", function () {
container.find('.slick-header-column')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
});
});
describe("when the user clicks additional column headers", function () {
beforeEach(function () {
container.find('.slick-header-column')[1].click();
});
it("selects additional columns", function () {
container.find('.slick-header-column')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
var column1 = selectedRanges[0];
expect(selectedRanges.length).toEqual(2);
expect(column1.fromCell).toBe(1);
expect(column1.toCell).toBe(1);
var column2 = selectedRanges[1];
expect(column2.fromCell).toBe(0);
expect(column2.toCell).toBe(0);
});
});
describe("when the user clicks a column header checkbox", function () {
it("selects the column", function () {
container.find('.slick-header-columns input')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
});
it("checks the checkbox", function () {
container.find('.slick-header-column')[1].click();
expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeTruthy();
});
});
describe("when a row is selected", function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
rowSelectionModel.setSelectedRanges(selectedRanges);
});
it("deselects the row", function () {
container.find('.slick-header-column')[1].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toBe(1);
var column = selectedRanges[0];
expect(column.fromCell).toBe(1);
expect(column.toCell).toBe(1);
expect(column.fromRow).toBe(0);
expect(column.toRow).toBe(9);
})
});
describe("clicking a second time", function () {
beforeEach(function () {
container.find('.slick-header-column')[1].click();
});
it("unchecks checkbox", function () {
container.find('.slick-header-column')[1].click();
expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy();
});
it("deselects the column", function () {
container.find('.slick-header-column')[1].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
})
});
describe("when the column is not selectable", function () {
it("does not select the column", function () {
$(container.find('.slick-header-column:contains(some-non-selectable-column)')).click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
});
});
describe("when the column is deselected through setSelectedRanges", function () {
beforeEach(function () {
container.find('.slick-header-column')[1].click();
});
it("unchecks the checkbox", function () {
rowSelectionModel.setSelectedRanges([]);
expect($(container.find('.slick-header-columns input')[1])
.is(':checked')).toBeFalsy();
});
});
describe("when a non-column range was already selected", function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 0, 1, 0)];
rowSelectionModel.setSelectedRanges(selectedRanges);
});
it("deselects the non-column range", function () {
container.find('.slick-header-column')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
})
});
});
var setupGrid = function (columns) {
var columnSelector = new ColumnSelector();
columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
var grid = new SlickGrid(container, data, columns, options);
var rowSelectionModel = new RowSelectionModel();
grid.setSelectionModel(rowSelectionModel);
grid.registerPlugin(columnSelector);
grid.invalidate();
};
function expectOnlyTheFirstColumnToBeSelected(selectedRanges) {
var row = selectedRanges[0];
expect(selectedRanges.length).toEqual(1);
expect(row.fromCell).toBe(0);
expect(row.toCell).toBe(0);
expect(row.fromRow).toBe(0);
expect(row.toRow).toBe(9);
}
});
});

View File

@ -0,0 +1,119 @@
define(
["jquery",
"slickgrid/slick.grid",
"slickgrid/slick.rowselectionmodel",
"sources/selection/copy_data",
"sources/selection/clipboard",
"sources/selection/range_selection_helper"
],
function ($, SlickGrid, RowSelectionModel, copyData, clipboard, RangeSelectionHelper) {
describe('copyData', function () {
var grid, sqlEditor;
beforeEach(function () {
var data = [[1, "leopord", "12"],
[2, "lion", "13"],
[3, "puma", "9"]];
var columns = [{
name: "id",
pos: 0,
label: "id<br> numeric",
cell: "number",
can_edit: false,
type: "numeric"
}, {
name: "brand",
pos: 1,
label: "flavor<br> character varying",
cell: "string",
can_edit: false,
type: "character varying"
}, {
name: "size",
pos: 2,
label: "size<br> numeric",
cell: "number",
can_edit: false,
type: "numeric"
}
]
;
var gridContainer = $("<div id='grid'></div>");
$("body").append(gridContainer);
$("body").append("<button id='btn-paste-row' disabled></button>");
grid = new Slick.Grid("#grid", data, columns, {});
grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
sqlEditor = {slickgrid: grid};
});
afterEach(function() {
$("body").remove('#grid');
$("body").remove('#btn-paste-row');
});
describe("when rows are selected", function () {
beforeEach(function () {
grid.getSelectionModel().setSelectedRanges([
RangeSelectionHelper.rangeForRow(grid, 0),
RangeSelectionHelper.rangeForRow(grid, 2)]
);
});
it("copies them", function () {
spyOn(clipboard, 'copyTextToClipboard');
copyData.apply(sqlEditor);
expect(sqlEditor.copied_rows.length).toBe(2);
expect(clipboard.copyTextToClipboard).toHaveBeenCalled();
expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain("1,'leopord','12'");
expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain("3,'puma','9'");
});
describe("when the user can edit the grid", function () {
it("enables the paste row button", function () {
copyData.apply(_.extend({can_edit: true}, sqlEditor));
expect($("#btn-paste-row").prop('disabled')).toBe(false);
});
});
});
describe("when a column is selected", function () {
beforeEach(function () {
var firstColumn = new Slick.Range(0, 0, 2, 0);
grid.getSelectionModel().setSelectedRanges([firstColumn])
});
it("copies text to the clipboard", function () {
spyOn(clipboard, 'copyTextToClipboard');
copyData.apply(sqlEditor);
expect(clipboard.copyTextToClipboard).toHaveBeenCalled();
var copyArg = clipboard.copyTextToClipboard.calls.mostRecent().args[0];
var rowStrings = copyArg.split('\n');
expect(rowStrings[0]).toBe("1");
expect(rowStrings[1]).toBe("2");
expect(rowStrings[2]).toBe("3");
});
it("sets copied_rows to empty", function () {
copyData.apply(sqlEditor);
expect(sqlEditor.copied_rows.length).toBe(0);
});
describe("when the user can edit the grid", function () {
it("disables the paste row button", function () {
copyData.apply(_.extend({can_edit: true}, sqlEditor));
expect($("#btn-paste-row").prop('disabled')).toBe(true);
});
});
});
});
});

View File

@ -0,0 +1,126 @@
define(["jquery",
"underscore",
"slickgrid/slick.grid",
"slickgrid/slick.rowselectionmodel",
"sources/selection/grid_selector"
],
function ($, _, SlickGrid, RowSelectionModel, GridSelector) {
describe("GridSelector", function () {
var container, data, columns, gridSelector, rowSelectionModel;
beforeEach(function () {
container = $("<div></div>");
container.height(9999);
columns = [{
id: '1',
name: 'some-column-name',
}, {
id: '2',
name: 'second column',
}];
gridSelector = new GridSelector();
columns = gridSelector.getColumnDefinitionsWithCheckboxes(columns);
data = [];
for (var i = 0; i < 10; i++) {
data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
}
var grid = new SlickGrid(container, data, columns);
rowSelectionModel = new RowSelectionModel();
grid.setSelectionModel(rowSelectionModel);
grid.registerPlugin(gridSelector);
grid.invalidate();
$("body").append(container);
});
afterEach(function () {
$("body").find(container).remove();
});
it("renders an additional column on the left for selecting rows", function () {
expect(columns.length).toBe(3);
var leftmostColumn = columns[0];
expect(leftmostColumn.id).toBe('row-header-column');
});
it("renders checkboxes for selecting columns", function () {
expect(container.find('[data-test="output-column-header"] input').length).toBe(2)
});
it("renders a checkbox for selecting all the cells", function () {
expect(container.find("[title='Select/Deselect All']").length).toBe(1);
});
describe("when the cell for the select/deselect all is clicked", function () {
it("selects the whole grid", function () {
container.find("[title='Select/Deselect All']").parent().click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toBe(1);
var selectedRange = selectedRanges[0];
expect(selectedRange.fromCell).toBe(1);
expect(selectedRange.toCell).toBe(2);
expect(selectedRange.fromRow).toBe(0);
expect(selectedRange.toRow).toBe(9);
});
it("checks the checkbox", function () {
container.find("[title='Select/Deselect All']").parent().click();
expect($(container.find("[data-id='checkbox-select-all']")).is(':checked')).toBeTruthy();
})
});
describe("when the main checkbox in the corner gets selected", function () {
it("unchecks all the columns", function () {
container.find("[title='Select/Deselect All']").click();
expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy();
expect($(container.find('.slick-header-columns input')[2]).is(':checked')).toBeFalsy();
});
it("selects all the cells", function () {
container.find("[title='Select/Deselect All']").click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toBe(1);
var selectedRange = selectedRanges[0];
expect(selectedRange.fromCell).toBe(1);
expect(selectedRange.toCell).toBe(2);
expect(selectedRange.fromRow).toBe(0);
expect(selectedRange.toRow).toBe(9);
});
describe("when the main checkbox in the corner gets deselected", function () {
beforeEach(function () {
container.find("[title='Select/Deselect All']").click();
});
it("deselects all the cells", function () {
container.find("[title='Select/Deselect All']").click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toBe(0);
});
});
describe("and then the underlying selection changes", function () {
beforeEach(function () {
container.find("[title='Select/Deselect All']").click();
});
it("unchecks the main checkbox", function () {
var ranges = [new Slick.Range(0, 0, 0, 1)];
rowSelectionModel.setSelectedRanges(ranges);
expect($(container.find("[title='Select/Deselect All']")).is(':checked')).toBeFalsy();
});
});
});
});
});

View File

@ -0,0 +1,158 @@
define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNavigator) {
describe("#getUnion", function () {
describe("when the ranges completely overlap", function () {
it("returns a list with that range", function () {
var ranges = [[1, 4], [1, 4], [1, 4]];
var union = rangeBoundaryNavigator.getUnion(ranges);
expect(union).toEqual([[1, 4]]);
});
});
describe("when the ranges all overlap partially or touch", function () {
it("returns one long range", function () {
var rangeBounds = [[3, 6], [1, 4], [7, 14]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[1, 14]]);
});
it("returns them in order from lowest to highest", function () {
var rangeBounds = [[3, 6], [2, 3], [10, 12]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[2, 6], [10, 12]]);
});
describe("when one range completely overlaps another", function() {
it("returns them in order from lowest to highest", function () {
var rangeBounds = [[9, 14], [2, 3], [11, 13]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[2, 3], [9, 14]]);
});
});
describe("when one range is a subset of another", function () {
it("returns the larger range", function () {
var rangeBounds = [[2, 6], [1, 14], [8, 10]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[1, 14]]);
})
})
});
describe("when the ranges do not touch", function () {
it("returns them in order from lowest to highest", function () {
var rangeBounds = [[3, 6], [1, 1], [8, 10]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[1, 1], [3, 6], [8, 10]]);
});
});
});
describe("#mapDimensionBoundaryUnion", function () {
it("returns a list of the results of the callback", function () {
var rangeBounds = [[0, 1], [3, 3]];
var callback = function () {
return 'hello';
};
var result = rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback);
expect(result).toEqual(['hello', 'hello', 'hello']);
});
it("calls the callback with each index in the dimension", function () {
var rangeBounds = [[0, 1], [3, 3]];
var callback = jasmine.createSpy('callbackSpy');
rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback);
expect(callback.calls.allArgs()).toEqual([[0], [1], [3]]);
});
});
describe("#mapOver2DArray", function () {
var data, rowCollector, processCell;
beforeEach(function () {
data = [[0, 1, 2, 3], [2, 2, 2, 2], [4, 5, 6, 7]];
processCell = function (rowIndex, columnIndex) {
return data[rowIndex][columnIndex];
};
rowCollector = function (rowData) {
return JSON.stringify(rowData);
};
});
it("calls the callback for each item in the ranges", function () {
var rowRanges = [[0, 0], [2, 2]];
var colRanges = [[0, 3]];
var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector);
expect(selectionResult).toEqual(["[0,1,2,3]", "[4,5,6,7]"]);
});
describe("when the ranges are out of order/duplicated", function () {
var rowRanges, colRanges;
beforeEach(function () {
rowRanges = [[2, 2], [2, 2], [0, 0]];
colRanges = [[0, 3]];
});
it("uses the union of the ranges", function () {
spyOn(rangeBoundaryNavigator, "getUnion").and.callThrough();
var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector);
expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(rowRanges);
expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(colRanges);
expect(selectionResult).toEqual(["[0,1,2,3]", "[4,5,6,7]"]);
});
});
});
describe("#rangesToCsv", function () {
var data, columnDefinitions, ranges;
beforeEach(function () {
data = [[1, "leopard", "12"],
[2, "lion", "13"],
[3, "cougar", "9"],
[4, "tiger", "10"]];
columnDefinitions = [{name: 'id', pos: 0}, {name: 'animal', pos: 1}, {name: 'size', pos: 2}];
ranges = [new Slick.Range(0, 0, 0, 2), new Slick.Range(3, 0, 3, 2)];
});
it("returns csv for the provided ranges", function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges);
expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'");
});
describe("when there is an extra column with checkboxes", function () {
beforeEach(function () {
columnDefinitions = [{name: 'not-a-data-column'}, {name: 'id', pos: 0}, {name: 'animal', pos: 1}, {
name: 'size',
pos: 2
}];
ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)];
});
it("returns csv for the columns with data", function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges);
expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'");
});
});
});
});

View File

@ -0,0 +1,174 @@
define(
["jquery",
"underscore",
"slickgrid/slick.grid",
"sources/selection/row_selector",
"slickgrid/slick.rowselectionmodel",
"slickgrid",
],
function ($, _, SlickGrid, RowSelector, RowSelectionModel, Slick) {
describe("RowSelector", function () {
var container, data, columnDefinitions, grid, rowSelectionModel;
beforeEach(function () {
container = $("<div></div>");
container.height(9999);
columnDefinitions = [{
id: '1',
name: 'some-column-name',
selectable: true
}, {
id: '2',
name: 'second column',
selectable: true
}];
var rowSelector = new RowSelector();
data = [];
for (var i = 0; i < 10; i++) {
data.push(['some-value-' + i, 'second value ' + i]);
}
columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
grid = new SlickGrid(container, data, columnDefinitions);
rowSelectionModel = new RowSelectionModel();
grid.setSelectionModel(rowSelectionModel);
grid.registerPlugin(rowSelector);
grid.invalidate();
$("body").append(container);
});
afterEach(function () {
$("body").find(container).remove();
});
it("renders an additional column on the left", function () {
expect(columnDefinitions.length).toBe(3);
var leftmostColumn = columnDefinitions[0];
expect(leftmostColumn.id).toBe('row-header-column');
expect(leftmostColumn.name).toBe('');
expect(leftmostColumn.selectable).toBe(false);
});
it("renders a checkbox the leftmost column", function () {
expect(container.find('.sr').length).toBe(11);
expect(container.find('.sr .sc:first-child input[type="checkbox"]').length).toBe(10);
});
it("preserves the other attributes of column definitions", function () {
expect(columnDefinitions[1].id).toBe('1');
expect(columnDefinitions[1].selectable).toBe(true);
});
describe("selecting rows", function () {
describe("when the user clicks a row header checkbox", function () {
it("selects the row", function () {
container.find('.sr .sc:first-child input[type="checkbox"]')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expectOnlyTheFirstRowToBeSelected(selectedRanges);
});
it("checks the checkbox", function () {
container.find('.sr .sc:first-child input[type="checkbox"]')[5].click();
expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[5])
.is(':checked')).toBeTruthy();
});
});
describe("when the user clicks a row header", function () {
it("selects the row", function () {
container.find('.sr .sc:first-child')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expectOnlyTheFirstRowToBeSelected(selectedRanges);
});
it("checks the checkbox", function () {
container.find('.sr .sc:first-child')[7].click();
expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[7])
.is(':checked')).toBeTruthy();
});
});
describe("when the user clicks multiple row headers", function () {
it("selects another row", function () {
container.find('.sr .sc:first-child')[4].click();
container.find('.sr .sc:first-child')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(2);
var row1 = selectedRanges[0];
expect(row1.fromRow).toBe(4);
expect(row1.toRow).toBe(4);
var row2 = selectedRanges[1];
expect(row2.fromRow).toBe(0);
expect(row2.toRow).toBe(0);
});
});
describe("when a column was already selected", function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
rowSelectionModel.setSelectedRanges(selectedRanges);
});
it("deselects the column", function () {
container.find('.sr .sc:first-child')[0].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expectOnlyTheFirstRowToBeSelected(selectedRanges);
})
});
describe("when the row is deselected through setSelectedRanges", function () {
beforeEach(function () {
container.find('.sr .sc:first-child')[4].click();
});
it("should uncheck the checkbox", function () {
rowSelectionModel.setSelectedRanges([]);
expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[4])
.is(':checked')).toBeFalsy();
});
});
describe("click a second time", function () {
beforeEach(function () {
container.find('.sr .sc:first-child')[1].click();
});
it("unchecks checkbox", function () {
container.find('.sr .sc:first-child')[1].click();
expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[1])
.is(':checked')).toBeFalsy();
});
it("unselects the row", function () {
container.find('.sr .sc:first-child')[1].click();
var selectedRanges = rowSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
})
});
});
});
function expectOnlyTheFirstRowToBeSelected(selectedRanges) {
var row = selectedRanges[0];
expect(selectedRanges.length).toEqual(1);
expect(row.fromCell).toBe(1);
expect(row.toCell).toBe(2);
expect(row.fromRow).toBe(0);
expect(row.toRow).toBe(0);
}
});

View File

@ -163,6 +163,9 @@ def create_table(server, db_name, table_name):
table_name)
pg_cursor.execute(
'''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % table_name)
pg_cursor.execute(
'''INSERT INTO "%s" VALUES ('Some-Other-Name', 22)''' % table_name)
connection.set_isolation_level(old_isolation_level)
connection.commit()

View File

@ -1,4 +1,5 @@
chromedriver_installer==0.0.6
pyperclip~=1.5.27
selenium==3.3.1
testscenarios==0.5.0
testtools==2.0.0