Skip to content

Commit

Permalink
Merge pull request #5991 from CAM-Gerlach/tuples-uneditable
Browse files Browse the repository at this point in the history
PR: Properly disable editing in Variable Explorer for values in immutable collections (e.g. tuples)
  • Loading branch information
ccordoba12 authored Dec 26, 2017
2 parents 1ca9d1a + 17b61b2 commit b5af395
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 24 deletions.
47 changes: 25 additions & 22 deletions spyder/widgets/variableexplorer/collectionseditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,38 +474,41 @@ def createEditor(self, parent, option, index):
self.create_dialog(editor, dict(model=index.model(), editor=editor,
key=key, readonly=readonly))
return None
#---editor = QDateTimeEdit
elif isinstance(value, datetime.datetime):
editor = QDateTimeEdit(value, parent)
editor.setCalendarPopup(True)
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
return editor
#---editor = QDateEdit
#---editor = QDateEdit or QDateTimeEdit
elif isinstance(value, datetime.date):
editor = QDateEdit(value, parent)
editor.setCalendarPopup(True)
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
return editor
if readonly:
return None
else:
if isinstance(value, datetime.datetime):
editor = QDateTimeEdit(value, parent)
else:
editor = QDateEdit(value, parent)
editor.setCalendarPopup(True)
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
return editor
#---editor = TextEditor
elif is_text_string(value) and len(value) > 40:
te = TextEditor(None)
if te.setup_and_check(value):
editor = TextEditor(value, key)
editor = TextEditor(value, key, readonly=readonly)
self.create_dialog(editor, dict(model=index.model(),
editor=editor, key=key,
readonly=readonly))
return None
#---editor = QLineEdit
elif is_editable_type(value):
editor = QLineEdit(parent)
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
editor.setAlignment(Qt.AlignLeft)
# This is making Spyder crash because the QLineEdit that it's
# been modified is removed and a new one is created after
# evaluation. So the object on which this method is trying to
# act doesn't exist anymore.
# editor.returnPressed.connect(self.commitAndCloseEditor)
return editor
if readonly:
return None
else:
editor = QLineEdit(parent)
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
editor.setAlignment(Qt.AlignLeft)
# This is making Spyder crash because the QLineEdit that it's
# been modified is removed and a new one is created after
# evaluation. So the object on which this method is trying to
# act doesn't exist anymore.
# editor.returnPressed.connect(self.commitAndCloseEditor)
return editor
#---editor = CollectionsEditor for an arbitrary object
else:
editor = CollectionsEditor()
Expand Down Expand Up @@ -670,7 +673,7 @@ def setup_menu(self, minmax):
triggered=self.paste)
self.copy_action = create_action(self, _("Copy"),
icon=ima.icon('editcopy'),
triggered=self.copy)
triggered=self.copy)
self.edit_action = create_action(self, _("Edit"),
icon=ima.icon('edit'),
triggered=self.edit_item)
Expand Down
108 changes: 106 additions & 2 deletions spyder/widgets/variableexplorer/tests/test_collectioneditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

# Standard library imports
import copy
import datetime
try:
from unittest.mock import Mock
from unittest.mock import Mock, ANY
except ImportError:
from mock import Mock # Python 2
from mock import Mock, ANY # Python 2

# Third party imports
import pandas
Expand Down Expand Up @@ -161,5 +162,108 @@ def test_rename_and_duplicate_item_in_collection_editor():
assert editor.model.get_data() == coll_copy + [coll_copy[0]]


def test_edit_mutable_and_immutable_types(qtbot, monkeypatch):
# To ensure mutable types (lists, dicts) and individual values are editable
# But not immutable ones (tuples) or anything inside of them, per #5991
MockQLineEdit = Mock()
attr_to_patch_qlineedit = ('spyder.widgets.variableexplorer.' +
'collectionseditor.QLineEdit')
monkeypatch.setattr(attr_to_patch_qlineedit, MockQLineEdit)

MockTextEditor = Mock()
attr_to_patch_textedit = ('spyder.widgets.variableexplorer.' +
'collectionseditor.TextEditor')
monkeypatch.setattr(attr_to_patch_textedit, MockTextEditor)

MockQDateTimeEdit = Mock()
attr_to_patch_qdatetimeedit = ('spyder.widgets.variableexplorer.' +
'collectionseditor.QDateTimeEdit')
monkeypatch.setattr(attr_to_patch_qdatetimeedit, MockQDateTimeEdit)

MockCollectionsEditor = Mock()
mockCollectionsEditor_instance = MockCollectionsEditor()
attr_to_patch_coledit = ('spyder.widgets.variableexplorer.' +
'collectionseditor.CollectionsEditor')
monkeypatch.setattr(attr_to_patch_coledit, MockCollectionsEditor)

list_test = [1, "012345678901234567901234567890123456789012",
datetime.datetime(2017, 12, 24, 7, 9), [1, 2, 3], (2, "eggs")]
tup_test = tuple(list_test)

# Tests for mutable type (list) #
editor_list = CollectionsEditorTableView(None, list_test)

# Directly editable values inside list
editor_list_value = editor_list.delegate.createEditor(
None, None, editor_list.model.createIndex(0, 3))
assert editor_list_value is not None
assert MockQLineEdit.call_count == 1

# Text Editor for long text inside list
editor_list.delegate.createEditor(None, None,
editor_list.model.createIndex(1, 3))
assert MockTextEditor.call_count == 2
MockTextEditor.assert_called_with(ANY, ANY, readonly=False)

# Datetime inside list
editor_list_datetime = editor_list.delegate.createEditor(
None, None, editor_list.model.createIndex(2, 3))
assert editor_list_datetime is not None
assert MockQDateTimeEdit.call_count == 1

# List inside list
editor_list.delegate.createEditor(None, None,
editor_list.model.createIndex(3, 3))
assert mockCollectionsEditor_instance.show.call_count == 1
mockCollectionsEditor_instance.setup.assert_called_with(ANY, ANY,
icon=ANY,
readonly=False)

# Tuple inside list
editor_list.delegate.createEditor(None, None,
editor_list.model.createIndex(4, 3))
assert mockCollectionsEditor_instance.show.call_count == 2
mockCollectionsEditor_instance.setup.assert_called_with(ANY, ANY,
icon=ANY,
readonly=True)

# Tests for immutable type (tuple) #
editor_tup = CollectionsEditorTableView(None, tup_test)

# Directly editable values inside tuple
editor_tup_value = editor_tup.delegate.createEditor(
None, None, editor_tup.model.createIndex(0, 3))
assert editor_tup_value is None
assert MockQLineEdit.call_count == 1

# Text Editor for long text inside tuple
editor_tup.delegate.createEditor(None, None,
editor_tup.model.createIndex(1, 3))
assert MockTextEditor.call_count == 4
MockTextEditor.assert_called_with(ANY, ANY, readonly=True)

# Datetime inside tuple
editor_tup_datetime = editor_tup.delegate.createEditor(
None, None, editor_tup.model.createIndex(2, 3))
assert editor_tup_datetime is None
assert MockQDateTimeEdit.call_count == 1

# List inside tuple
editor_tup.delegate.createEditor(None, None,
editor_tup.model.createIndex(3, 3))
assert mockCollectionsEditor_instance.show.call_count == 3
mockCollectionsEditor_instance.setup.assert_called_with(ANY, ANY,
icon=ANY,
readonly=True)

# Tuple inside tuple
editor_tup.delegate.createEditor(None, None,
editor_tup.model.createIndex(4, 3))
assert mockCollectionsEditor_instance.show.call_count == 4
mockCollectionsEditor_instance.setup.assert_called_with(ANY, ANY,
icon=ANY,
readonly=True)


if __name__ == "__main__":
pytest.main()

0 comments on commit b5af395

Please sign in to comment.