Skip to content

Commit

Permalink
Make sorting more logical in db editor
Browse files Browse the repository at this point in the history
- Entity tree is now sorted by dimensionality, then by names.
- values in SearchBarEditor are now sorted logically so that 11 comes
  after 10.

Re #2869, #2319
  • Loading branch information
Henrik Koski committed Jul 4, 2024
1 parent c59fca2 commit 4f5db40
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 26 deletions.
2 changes: 1 addition & 1 deletion spinetoolbox/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1833,7 +1833,7 @@ def order_key(name):
If given a string that starts with a digit, a 'big' string (in comparisons) will be added to the start
of the order key that makes sure that every key starts with a str and alternates with int after that.
"""
key_list = [int(text) if text.isdigit() else text for text in re.split(r"(\d+)", name) if text]
key_list = [int(text) if text.isdigit() else text for text in re.split(r"(\d+|__)", name) if text]
if len(key_list) and isinstance(key_list[0], int):
key_list.insert(0, "\U0010FFFF")
return key_list
4 changes: 2 additions & 2 deletions spinetoolbox/spine_db_editor/mvcmodels/entity_tree_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""Classes to represent entities in a tree."""
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QBrush, QIcon
from spinetoolbox.helpers import DB_ITEM_SEPARATOR, plain_to_tool_tip
from spinetoolbox.helpers import DB_ITEM_SEPARATOR, plain_to_tool_tip, order_key
from spinetoolbox.fetch_parent import FlexibleFetchParent, FetchIndex
from .multi_db_tree_item import MultiDBTreeItem

Expand Down Expand Up @@ -112,7 +112,7 @@ def is_hidden(self):
@property
def _children_sort_key(self):
"""Reimplemented so groups are above non-groups."""
return lambda item: (not item.is_group, item.display_id)
return lambda item: (not item.is_group, order_key("__".join(item.display_id[1]).casefold()))

def default_parameter_data(self):
"""Return data to put as default in a parameter table when this item is selected."""
Expand Down
5 changes: 2 additions & 3 deletions spinetoolbox/spine_db_editor/mvcmodels/multi_db_tree_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
######################################################################################################################

"""Base classes to represent items from multiple databases in a tree."""
from operator import attrgetter
from PySide6.QtCore import Qt
from ...helpers import rows_to_row_count_tuples, bisect_chunks
from ...helpers import rows_to_row_count_tuples, bisect_chunks, order_key
from ...fetch_parent import FlexibleFetchParent
from ...mvcmodels.minimal_tree_model import TreeItem

Expand Down Expand Up @@ -289,7 +288,7 @@ def _insert_children_sorted(self, new_children):

@property
def _children_sort_key(self):
return attrgetter("display_id")
return lambda item: (len(item.display_id[1]), order_key(item.display_id[0].casefold()), item.display_id[1:])

@property
def fetch_item_type(self):
Expand Down
4 changes: 2 additions & 2 deletions spinetoolbox/spine_db_editor/mvcmodels/single_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ def item_type(self):

def _sort_key(self, element):
item = self.db_item_from_id(element)
byname = order_key("_".join(item.get("entity_byname", ())))
byname = order_key("__".join(item.get("entity_byname", ())))
parameter_name = order_key(item.get("parameter_name", ""))
alt_name = order_key(item.get("alternative_name", ""))
return byname, parameter_name, alt_name
Expand All @@ -413,7 +413,7 @@ def item_type(self):

def _sort_key(self, element):
item = self.db_item_from_id(element)
byname = order_key("_".join(item.get("entity_byname", ())))
byname = order_key("__".join(item.get("entity_byname", ())))
alt_name = order_key(item.get("alternative_name", ""))
return byname, alt_name

Expand Down
4 changes: 2 additions & 2 deletions spinetoolbox/spine_db_editor/widgets/custom_editors.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
QComboBox,
)
from PySide6.QtGui import QPalette, QStandardItemModel, QStandardItem, QColor
from spinetoolbox.helpers import IconListManager, interpret_icon_id, make_icon_id, try_number_from_string
from spinetoolbox.helpers import IconListManager, interpret_icon_id, make_icon_id, try_number_from_string, order_key
from spinetoolbox.spine_db_editor.helpers import FALSE_STRING, TRUE_STRING


Expand Down Expand Up @@ -200,7 +200,7 @@ def set_data(self, current, items):
items (Sequence of str): items to show in the list
"""
item_list = [QStandardItem(current)]
for item in sorted(items, key=lambda x: x.casefold()):
for item in sorted(items, key=lambda x: order_key(x.casefold())):
qitem = QStandardItem(item)
item_list.append(qitem)
qitem.setFlags(~Qt.ItemIsEditable)
Expand Down
12 changes: 6 additions & 6 deletions tests/spine_db_editor/widgets/test_SpineDBEditorAdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ def test_add_relationships_to_object_tree_model(self):
fish_item = next(x for x in root_item.children if x.display_data == "fish")
nemo_item = fish_item.child(0)
pluto_item, scooby_item = dog_item.children
pluto_nemo_item1 = pluto_item.child(0)
pluto_nemo_item2 = nemo_item.child(0)
nemo_pluto_item1 = pluto_item.child(1)
nemo_pluto_item2 = nemo_item.child(1)
nemo_scooby_item1 = scooby_item.child(0)
nemo_scooby_item2 = nemo_item.child(2)
pluto_nemo_item1 = next(x for x in pluto_item.children if x.byname == ('pluto', 'nemo'))
pluto_nemo_item2 = next(x for x in nemo_item.children if x.byname == ('pluto', 'nemo'))
nemo_pluto_item1 = next(x for x in pluto_item.children if x.byname == ('nemo', 'pluto'))
nemo_pluto_item2 = next(x for x in nemo_item.children if x.byname == ('nemo', 'pluto'))
nemo_scooby_item1 = next(x for x in scooby_item.children if x.byname == ('nemo', 'scooby'))
nemo_scooby_item2 = next(x for x in nemo_item.children if x.byname == ('nemo', 'scooby'))
self.assertEqual(nemo_item.child_count(), 3)
self.assertEqual(pluto_item.child_count(), 2)
self.assertEqual(scooby_item.child_count(), 1)
Expand Down
14 changes: 7 additions & 7 deletions tests/spine_db_editor/widgets/test_SpineDBEditorFilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_filter_parameter_tables_per_zero_dimensional_entity_class(self):
model.fetchMore(None)
self.put_mock_dataset_in_db_mngr()
root_item = self.spine_db_editor.entity_tree_model.root_item
fish_item = root_item.child(2)
fish_item = next(x for x in root_item.children if x.display_data == "fish")
fish_index = self.spine_db_editor.entity_tree_model.index_from_item(fish_item)
selection_model = self.spine_db_editor.ui.treeView_entity.selectionModel()
selection_model.setCurrentIndex(fish_index, QItemSelectionModel.NoUpdate)
Expand All @@ -80,7 +80,7 @@ def test_filter_parameter_tables_per_nonzero_dimensional_entity_class(self):
model.fetchMore(None)
self.put_mock_dataset_in_db_mngr()
root_item = self.spine_db_editor.entity_tree_model.root_item
fish_dog_item = root_item.child(3)
fish_dog_item = next(x for x in root_item.children if x.display_data == "fish__dog")
fish_dog_index = self.spine_db_editor.entity_tree_model.index_from_item(fish_dog_item)
selection_model = self.spine_db_editor.ui.treeView_entity.selectionModel()
selection_model.setCurrentIndex(fish_dog_index, QItemSelectionModel.NoUpdate)
Expand All @@ -102,11 +102,11 @@ def test_filter_parameter_tables_per_entity_class_and_entity_cross_selection(sel
model.fetchMore(None)
self.put_mock_dataset_in_db_mngr()
root_item = self.spine_db_editor.entity_tree_model.root_item
fish_item = root_item.child(2)
fish_item = next(x for x in root_item.children if x.display_data == "fish")
fish_index = self.spine_db_editor.entity_tree_model.index_from_item(fish_item)
selection_model = self.spine_db_editor.ui.treeView_entity.selectionModel()
dog_item = root_item.child(0)
scooby_item = dog_item.child(1)
dog_item = next(x for x in root_item.children if x.display_data == "dog")
scooby_item = next(x for x in dog_item.children if x.display_data == "scooby")
scooby_index = self.spine_db_editor.entity_tree_model.index_from_item(scooby_item)
selection_model.setCurrentIndex(fish_index, QItemSelectionModel.NoUpdate)
selection_model.select(fish_index, QItemSelectionModel.Select)
Expand All @@ -129,8 +129,8 @@ def test_filter_parameter_tables_per_entity(self):
model.fetchMore(None)
self.put_mock_dataset_in_db_mngr()
root_item = self.spine_db_editor.entity_tree_model.root_item
dog_item = root_item.child(0)
pluto_item = dog_item.child(0)
dog_item = next(x for x in root_item.children if x.display_data == "dog")
pluto_item = next(x for x in dog_item.children if x.display_data == "pluto")
pluto_index = self.spine_db_editor.entity_tree_model.index_from_item(pluto_item)
selection_model = self.spine_db_editor.ui.treeView_entity.selectionModel()
selection_model.setCurrentIndex(pluto_index, QItemSelectionModel.NoUpdate)
Expand Down
2 changes: 1 addition & 1 deletion tests/spine_db_editor/widgets/test_SpineDBEditorRemove.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def test_remove_relationships_from_object_tree_model(self):
self.fetch_entity_tree_model()
root_item = self.spine_db_editor.entity_tree_model.root_item
fish_item = next(iter(item for item in root_item.children if item.display_data == "fish"))
nemo_item = fish_item.child(0)
nemo_item = next(iter(item for item in fish_item.children if item.display_data == "nemo"))
relationships = [x.display_id for x in nemo_item.children]
self.assertEqual(nemo_item.child_count(), 3)
self.assertTrue("dog__fish" in relationships[0] and "pluto" in relationships[0][1])
Expand Down
52 changes: 50 additions & 2 deletions tests/spine_db_editor/widgets/test_custom_qtreeview.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def test_add_class_with_single_dimension(self):
class_index = model.index(0, 0, root_index)
view._context_item = model.item_from_index(class_index)
self._add_multidimensional_class("a_relationship_class", ["an_entity_class"])
class_index = model.index(0, 0, root_index)
class_index = model.index(1, 0, root_index)
self.assertEqual(model.rowCount(class_index), 0)
self.assertEqual(class_index.data(), "a_relationship_class")
class_database_index = model.index(0, 1, root_index)
Expand Down Expand Up @@ -281,7 +281,7 @@ def test_add_entity_with_single_dimension(self):
view._context_item = model.item_from_index(class_index)
self._add_multidimensional_class("a_relationship_class", ["an_entity_class"])
self.assertEqual(model.rowCount(root_index), 2)
class_index = model.index(0, 0, root_index) # Classes are sorted alphabetically.
class_index = model.index(1, 0, root_index) # N-D classes come after 0-D classes
self.assertEqual(class_index.data(), "a_relationship_class")
view._context_item = model.item_from_index(class_index)
self._add_multidimensional_entity("a_relationship", ["an_entity"])
Expand Down Expand Up @@ -755,6 +755,54 @@ def _remove_entity(self):
_remove_entity_tree_item(view, "Remove...", RemoveEntitiesDialog)


class TestEntityTreeViewSorting(TestBase):
"""Tests that the entity tree is sorted correctly"""
def setUp(self):
self._temp_dir = TemporaryDirectory()
url = "sqlite:///" + os.path.join(self._temp_dir.name, "test_database.sqlite")
db_map = DatabaseMapping(url, create=True)
import_entity_classes(db_map, (("entity_class",), ("P_entity_class",), ("P_entity_class (1)",)))
import_entities(
db_map,
(
("entity_class", "entity_11"),
("entity_class", "entity_12"),
("P_entity_class", "entity_1"),
("P_entity_class", "entity_2"),
("P_entity_class (1)", "entity_1 (1)"),
("P_entity_class (1)", "entity_2 (1)"),
),
)
import_entity_classes(db_map, (("entity_class_ND", ("entity_class", "P_entity_class")),))
import_entities(
db_map,
(("entity_class_ND", ("entity_11", "entity_2")), ("entity_class_ND", ("entity_12", "entity_1"))),
)
db_map.commit_session("Add entities.")
db_map.close()
self._common_setup(url, create=False)
model = self._db_editor.ui.treeView_entity.model()
self.root_item = model.root_item
for item in model.visit_all():
while item.can_fetch_more():
item.fetch_more()
qApp.processEvents()

def tearDown(self):
self._common_tear_down()
self._temp_dir.cleanup()

def test_tree_item_sorting(self):
result = [tuple((i.name, [x.name for x in i.children])) for i in self.root_item.children]
expected = [
("entity_class", ['entity_11', 'entity_12']),
("P_entity_class", ['entity_1', 'entity_2']),
("P_entity_class (1)", ['entity_1 (1)', 'entity_2 (1)']),
("entity_class_ND", ['entity_11__entity_2', 'entity_12__entity_1']),
]
self.assertEqual(expected, result)


class TestParameterValueListTreeViewWithInitiallyEmptyDatabase(TestBase):
def setUp(self):
self._common_setup("sqlite://", create=True)
Expand Down

0 comments on commit 4f5db40

Please sign in to comment.