Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Use entity based api in replacement for object and relationship #1964

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
c99b904
Merge object and relationship tree and parameter tables, break things
manuelma Feb 9, 2023
608b973
WIP: refactor entity graph
manuelma Feb 10, 2023
0f5bf56
First stepts to add/update/remove entities
manuelma Feb 13, 2023
0d59608
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Feb 13, 2023
fa96c07
WIP: initial support to add and update entities
manuelma Feb 14, 2023
7d65442
Fix find next entity in tree
manuelma Feb 14, 2023
4b8826e
Fix DBParcel and some stuff in the graph view
manuelma Feb 14, 2023
1480377
Use entity byname
manuelma Feb 15, 2023
d15b93c
Fix adding and updating pvals
manuelma Feb 15, 2023
9a2038b
Removed unused code
manuelma Feb 15, 2023
b78ef2c
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Feb 15, 2023
82fc8fe
Adapting pivot table to entity
manuelma Feb 15, 2023
309f990
Adapt context menu in pivot table
manuelma Feb 15, 2023
e218e11
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Feb 16, 2023
37b9e93
Merge branch 'issue_1632_sort_pivot_table' into spinedb_api_issue_215…
manuelma Feb 16, 2023
bd36944
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Feb 17, 2023
1d897b7
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Feb 17, 2023
b9c126c
Fix removing entities from pivot table
manuelma Feb 17, 2023
2ae325f
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Feb 22, 2023
9c96853
Fix predefined styles in DB editor
manuelma Feb 22, 2023
bdfe341
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Mar 13, 2023
623956b
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Mar 29, 2023
492183c
Fix test crusade
manuelma Mar 30, 2023
978705b
Adapt tests
manuelma Apr 2, 2023
df92cb0
Merge remote-tracking branch 'origin/master' into spinedb_api_issue_2…
manuelma Apr 5, 2023
882490d
Use dry_run argument
manuelma Apr 5, 2023
a74304f
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Apr 6, 2023
f653a9d
Merge remote-tracking branch 'origin/master' into spinedb_api_issue_2…
manuelma Apr 12, 2023
da5c2b5
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Apr 21, 2023
1883ade
Drop 'relationship class level' in entity tree
manuelma Apr 21, 2023
f7d6336
Fix unit tests
manuelma Apr 21, 2023
91dd964
Add option to hide empty classes in entity tree view
manuelma Apr 21, 2023
817c160
Make hide empty classes a persistent setting
manuelma Apr 23, 2023
87387c5
Fix fetch_more
manuelma Apr 23, 2023
429f8c0
Fix tests
manuelma Apr 23, 2023
3d8eeda
Fix import data from json file
manuelma Apr 24, 2023
0a6864e
Let members hang directly from entity like relationships do now
manuelma Apr 24, 2023
81926cd
Prevent merged tree items to be torn down
manuelma Apr 24, 2023
c7e991b
Nicer prefills in Add entities dialog
manuelma Apr 24, 2023
f037ad7
Restore splitter states too
manuelma Apr 26, 2023
4697a6f
WIP: Introduce entity_alternative
manuelma May 2, 2023
2251099
Rationalize the use of the DB cache
manuelma May 3, 2023
68ca45e
Adapt to changes in spinedb_api regarding fetching and commit
manuelma May 5, 2023
109e155
Simplify code in SpineDBCommands and SpineDBManager
manuelma May 9, 2023
5fb30f0
Improve busy effects while fetching
manuelma May 12, 2023
33eb2ac
Use is_committed from CacheItem in sorting parameter tables
manuelma May 12, 2023
3dad9d1
Revert the entity alternative thing for now
manuelma May 12, 2023
3d59084
Use errors from get_data_to_set_scenario_alternatives
manuelma May 12, 2023
8c07222
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma May 12, 2023
c745bac
WIP: use new API to deal with metadata
manuelma May 16, 2023
bc184fb
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma May 16, 2023
554e472
Do the asynch fetching in db worker again
manuelma May 17, 2023
c697dce
Merge remote-tracking branch 'origin/master' into spinedb_api_issue_2…
May 22, 2023
2e975bb
Merge remote-tracking branch 'origin/spinedb_api_issue_215_drop_objec…
manuelma May 22, 2023
07d3a17
Fix some unit tests
manuelma May 22, 2023
4d9da76
Fix Traceback when closing specification editor
soininen May 22, 2023
b07f026
Just fix a lot of tests
manuelma May 23, 2023
2e99414
Merge remote-tracking branch 'origin/spinedb_api_issue_215_drop_objec…
manuelma May 23, 2023
ada77fc
Minor cleanup
manuelma May 23, 2023
e3f2eff
Merge undo commands to help doing stuff as block
manuelma May 24, 2023
4a9e6a2
Fix a couple of bugs, notably use json instead of pickle in scen model
manuelma May 24, 2023
4327fdd
Fix tests
manuelma May 24, 2023
4bbc9c8
Merge remote-tracking branch 'origin/master' into spinedb_api_issue_2…
manuelma Jun 1, 2023
80f7a84
Merge remote-tracking branch 'origin/master' into spinedb_api_issue_2…
manuelma Jun 12, 2023
cdc6b05
Fix funny merge
manuelma Jun 12, 2023
65f2b56
Merge remote-tracking branch 'origin/master' into spinedb_api_issue_2…
manuelma Jun 12, 2023
e922cbc
Fix funny merge
manuelma Jun 12, 2023
524681b
Add widget to show entity alternatives
manuelma Jun 14, 2023
d69bda9
Fix some minor issues
manuelma Jun 14, 2023
33554dd
Add field_map for parameter definition
manuelma Jun 14, 2023
36c8957
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Jul 4, 2023
4a850d5
Fix tests and accommodate spinedb_api changes
manuelma Jul 13, 2023
24398a6
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Jul 13, 2023
9007aac
fix misleading spec tooltips in properties tab
Aug 28, 2023
fa91cb1
Merge branch 'master' into spinedb_api_issue_215_drop_object_and_rela…
manuelma Aug 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions spinetoolbox/fetch_parent.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
The FetchParent and FlexibleFetchParent classes.
"""

from PySide6.QtCore import QTimer, Signal, QObject
from PySide6.QtCore import QTimer, Signal, QObject, Qt
from PySide6.QtGui import QCursor
from .helpers import busy_effect


Expand Down Expand Up @@ -55,7 +56,7 @@ def __init__(self, owner=None, chunk_size=1000):
self._owner.destroyed.connect(lambda obj=None: self.set_obsolete(True))
self.chunk_size = chunk_size

def reset_fetching(self, fetch_token):
def reset(self, fetch_token):
"""Resets fetch parent as if nothing was ever fetched.

Args:
Expand Down Expand Up @@ -109,7 +110,7 @@ def remove_item(self, item, db_map):

@property
def fetch_item_type(self):
"""Returns the type of item to fetch, e.g., "object_class".
"""Returns the DB item type to fetch, e.g., "entity_class".

Returns:
str
Expand Down Expand Up @@ -160,6 +161,8 @@ def set_obsolete(self, obsolete):
Args:
obsolete (bool): whether parent has become obsolete
"""
if obsolete:
self.set_busy(False)
self._obsolete = obsolete

@property
Expand All @@ -172,6 +175,8 @@ def set_fetched(self, fetched):
Args:
fetched (bool): whether parent has been fetched completely
"""
if fetched:
self.set_busy(False)
self._fetched = fetched

@property
Expand All @@ -184,6 +189,10 @@ def set_busy(self, busy):
Args:
busy (bool): whether parent is busy fetching
"""
if busy:
qApp.setOverrideCursor(QCursor(Qt.BusyCursor))
else:
qApp.restoreOverrideCursor()
self._busy = busy

def handle_items_added(self, db_map_data):
Expand Down
47 changes: 11 additions & 36 deletions spinetoolbox/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from PySide6.QtCore import Qt, Slot, QFile, QIODevice, QSize, QRect, QPoint, QUrl, QObject, QEvent
from PySide6.QtCore import __version__ as qt_version
from PySide6.QtCore import __version_info__ as qt_version_info
from PySide6.QtWidgets import QApplication, QMessageBox, QFileIconProvider, QStyle, QFileDialog, QInputDialog
from PySide6.QtWidgets import QApplication, QMessageBox, QFileIconProvider, QStyle, QFileDialog, QInputDialog, QSplitter
from PySide6.QtGui import (
QGuiApplication,
QCursor,
Expand All @@ -54,6 +54,7 @@
)
from spine_engine.utils.serialization import deserialize_path
from spinedb_api.spine_io.gdx_utils import find_gams_directory
from spinedb_api.helpers import group_consecutive
from .config import (
DEFAULT_WORK_DIR,
PLUGINS_PATH,
Expand Down Expand Up @@ -406,23 +407,15 @@ def format_string_list(str_list):


def rows_to_row_count_tuples(rows):
"""Breaks a list of rows into a list of (row, count) tuples to corresponding
chunks of successive rows.
"""Breaks a list of rows into a list of (row, count) tuples corresponding to chunks of successive rows.

Args:
rows (Iterable of int): rows

Returns:
list of tuple: row count tuples
"""
rows = set(rows)
if not rows:
return []
sorted_rows = sorted(rows)
break_points = [k + 1 for k in range(len(sorted_rows) - 1) if sorted_rows[k] + 1 != sorted_rows[k + 1]]
break_points = [0] + break_points + [len(sorted_rows)]
ranges = [(break_points[l], break_points[l + 1]) for l in range(len(break_points) - 1)]
return [(sorted_rows[start], stop - start) for start, stop in ranges]
return [(first, last - first + 1) for first, last in group_consecutive(rows)]


class IconListManager:
Expand Down Expand Up @@ -1393,6 +1386,9 @@ def restore_ui(window, app_settings, settings_group):
window_state = app_settings.value("windowState")
window_maximized = app_settings.value("windowMaximized", defaultValue='false')
n_screens = app_settings.value("n_screens", defaultValue=1)
splitter_states = {
splitter: app_settings.value(splitter.objectName() + "State") for splitter in window.findChildren(QSplitter)
}
app_settings.endGroup()
original_size = window.size()
if window_size:
Expand All @@ -1405,6 +1401,8 @@ def restore_ui(window, app_settings, settings_group):
if len(QGuiApplication.screens()) < int(n_screens):
# There are less screens available now than on previous application startup
window.move(0, 0) # Move this widget to primary screen position (0,0)
for splitter, state in splitter_states.items():
splitter.restoreState(state)
ensure_window_is_on_screen(window, original_size)
if window_maximized == 'true':
window.setWindowState(Qt.WindowMaximized)
Expand All @@ -1424,6 +1422,8 @@ def save_ui(window, app_settings, settings_group):
app_settings.setValue("windowState", window.saveState(version=1))
app_settings.setValue("windowMaximized", window.windowState() == Qt.WindowMaximized)
app_settings.setValue("n_screens", len(QGuiApplication.screens()))
for splitter in window.findChildren(QSplitter):
app_settings.setValue(splitter.objectName() + "State", splitter.saveState())
app_settings.endGroup()


Expand Down Expand Up @@ -1549,31 +1549,6 @@ def _is_metadata_item(item):
return "name" in item and "value" in item


def separate_metadata_and_item_metadata(db_map_data):
"""Separates normal metadata items from item metadata items.

Args:
db_map_data (dict): database records

Returns:
tuple: item metadata records and metadata records
"""
metadata_db_map_data = {}
item_metadata_db_map_data = {}
for db_map, items in db_map_data.items():
metadata_items = []
entity_metadata_items = []
for item in items:
if _is_metadata_item(item):
metadata_items.append(item)
else:
entity_metadata_items.append(item)
if metadata_items:
metadata_db_map_data[db_map] = metadata_items
item_metadata_db_map_data[db_map] = entity_metadata_items
return item_metadata_db_map_data, metadata_db_map_data


class HTMLTagFilter(HTMLParser):
"""HTML tag filter."""

Expand Down
7 changes: 4 additions & 3 deletions spinetoolbox/mvcmodels/compound_table_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,10 @@ def _get_row_for_insertion(self, pos):

def _insert_row_map(self, pos, single_row_map):
if not single_row_map:
# To trigger fetching. The QTimer is to avoid funny situations where the user enters new data
# via the empty row model, and those rows need to be removed at the same time as we fetch the added data.
# Doing it in the same loop cycle causes bugs.
# Emit layoutChanged to trigger fetching.
# The QTimer is to avoid funny situations where the user enters new data via the empty row model,
# and those rows need to be removed at the same time as we fetch the added data.
# Doing it in the same loop cycle was causing bugs.
QTimer.singleShot(0, self.layoutChanged.emit)
return
row = self._get_row_for_insertion(pos)
Expand Down
7 changes: 4 additions & 3 deletions spinetoolbox/mvcmodels/minimal_tree_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def tear_down_recursively(self):
child.tear_down_recursively()
self.tear_down()

def remove_children(self, position, count):
def remove_children(self, position, count, tear_down=True):
"""Removes count children starting from the given position.

Args:
Expand All @@ -182,8 +182,9 @@ def remove_children(self, position, count):
child.parent_item = None
del self.children[first : last + 1]
self.model.endRemoveRows()
for child in children:
child.tear_down_recursively()
if tear_down:
for child in children:
child.tear_down_recursively()
return True

def clear_children(self):
Expand Down
7 changes: 0 additions & 7 deletions spinetoolbox/mvcmodels/project_item_specification_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,6 @@ def data(self, index, role=None):
row = index.row()
if role == Qt.ItemDataRole.DisplayRole:
return self._spec_names[row]
if role == Qt.ItemDataRole.ToolTipRole:
if row >= self.rowCount():
return ""
return (
"<p>Drag-and-drop this onto the Design View "
f"to create a new <b>{self._spec_names[row]}</b> item.</p>"
)
if role == Qt.ItemDataRole.DecorationRole:
spec = self.specification(row)
return self._icons[spec.item_type]
Expand Down
8 changes: 2 additions & 6 deletions spinetoolbox/mvcmodels/resource_filter_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QStandardItemModel, QStandardItem
from spinedb_api.filters.scenario_filter import SCENARIO_FILTER_TYPE
from spinedb_api.filters.tool_filter import TOOL_FILTER_TYPE
from ..project_commands import SetFiltersOnlineCommand


class ResourceFilterModel(QStandardItemModel):
tree_built = Signal()
_SELECT_ALL = "Select all"
_FILTER_TYPES = {"Scenario filter": SCENARIO_FILTER_TYPE, "Tool filter": TOOL_FILTER_TYPE}
_FILTER_TYPES = {"Scenario filter": SCENARIO_FILTER_TYPE}
_FILTER_TYPE_TO_TEXT = dict(zip(_FILTER_TYPES.values(), _FILTER_TYPES.keys()))

def __init__(self, connection, project, undo_stack, logger):
Expand Down Expand Up @@ -92,9 +91,6 @@ def fetch_filters(self):
scenario_names = self._connection.get_scenario_names(url)
if scenario_names:
filters.setdefault(resource.label, {})[SCENARIO_FILTER_TYPE] = scenario_names
tool_names = self._connection.get_tool_names(url)
if tool_names:
filters.setdefault(resource.label, {})[TOOL_FILTER_TYPE] = tool_names
return filters

def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
Expand Down Expand Up @@ -131,7 +127,7 @@ def set_online(self, resource, filter_type, online):

Args:
resource (str): Resource label
filter_type (str): Either SCENARIO_FILTER_TYPE or TOOL_FILTER_TYPE, for now.
filter_type (str): Always SCENARIO_FILTER_TYPE, for now.
online (dict): mapping from scenario/tool id to online flag
"""
self.connection.set_online(resource, filter_type, online)
Expand Down
34 changes: 7 additions & 27 deletions spinetoolbox/project_item/logging_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"""Contains logging connection and jump classes."""

from spinedb_api.filters.scenario_filter import SCENARIO_FILTER_TYPE
from spinedb_api.filters.tool_filter import TOOL_FILTER_TYPE
from spinedb_api import DatabaseMapping, SpineDBAPIError, SpineDBVersionError
from spine_engine.project_item.connection import ResourceConvertingConnection, Jump, ConnectionBase, FilterSettings
from ..log_mixin import LogMixin
Expand Down Expand Up @@ -80,15 +79,8 @@ def _convert_legacy_resource_filter_ids_to_filter_settings(self):
).setdefault(SCENARIO_FILTER_TYPE, {})
for row in db_map.query(db_map.scenario_sq):
specific_filter_settings[row.name]: row.id = row.id in scenario_filter_ids
tool_filter_ids = resource_filter_ids.get(TOOL_FILTER_TYPE)
if tool_filter_ids is not None:
specific_filter_settings = self._filter_settings.known_filters.setdefault(
resource.label, {}
).setdefault(TOOL_FILTER_TYPE, {})
for row in db_map.query(db_map.tool_sq):
specific_filter_settings[row.name] = row.id in tool_filter_ids
finally:
db_map.connection.close()
db_map.close()
self._legacy_resource_filter_ids = None

@staticmethod
Expand Down Expand Up @@ -157,7 +149,7 @@ def graphics_item(self):
return self.link

def has_filters(self):
"""Returns True if connection has scenario or tool filters.
"""Returns True if connection has scenario filters.

Returns:
bool: True if connection has filters, False otherwise
Expand All @@ -177,12 +169,6 @@ def has_filters(self):
return True
if self._filter_settings.auto_online and any(name not in scenario_filters for name in available_scenarios):
return True
available_tools = {x["name"] for x in self._toolbox.db_mngr.get_items(db_map, "tool", only_visible=True)}
tool_filters = self._filter_settings.known_filters.get(resource.label, {}).get(TOOL_FILTER_TYPE, {})
if any(enabled for t, enabled in tool_filters.items() if t in available_tools):
return True
if self._filter_settings.auto_online and any(name not in tool_filters for name in available_tools):
return True
return False

def _get_db_map(self, url, ignore_version_error=False):
Expand Down Expand Up @@ -222,7 +208,7 @@ def _make_fetch_parent(self, db_map, item_type):

def _fetch_more_if_possible(self):
for db_map in self._db_maps.values():
for item_type in ("scenario", "tool"):
for item_type in ("scenario",):
fetch_parent = self._make_fetch_parent(db_map, item_type)
if self._toolbox.db_mngr.can_fetch_more(db_map, fetch_parent):
self._toolbox.db_mngr.fetch_more(db_map, fetch_parent)
Expand All @@ -247,12 +233,6 @@ def get_scenario_names(self, url):
return []
return sorted(x["name"] for x in self._toolbox.db_mngr.get_items(db_map, "scenario", only_visible=True))

def get_tool_names(self, url):
db_map = self._get_db_map(url)
if db_map is None:
return []
return sorted(x["name"] for x in self._toolbox.db_mngr.get_items(db_map, "tool", only_visible=True))

def may_have_filters(self):
"""Returns whether this connection may have filters.

Expand Down Expand Up @@ -317,8 +297,8 @@ def set_online(self, resource, filter_type, online):

Args:
resource (str): Resource label
filter_type (str): Either SCENARIO_FILTER_TYPE or TOOL_FILTER_TYPE, for now.
online (dict): mapping from scenario/tool name to online flag
filter_type (str): Always SCENARIO_FILTER_TYPE, for now.
online (dict): mapping from scenario name to online flag
"""
self._filter_settings.known_filters.setdefault(resource, {}).setdefault(filter_type, {}).update(online)

Expand Down Expand Up @@ -375,7 +355,7 @@ def _check_available_filters(self):
"""
filter_settings = FilterSettings(auto_online=self._filter_settings.auto_online)
for resource in self._resources:
for filter_type in (SCENARIO_FILTER_TYPE, TOOL_FILTER_TYPE):
for filter_type in (SCENARIO_FILTER_TYPE,):
online_filters = self._resource_filters_online(resource, filter_type)
if online_filters is not None:
filter_settings.known_filters.setdefault(resource.label, {})[filter_type] = online_filters
Expand All @@ -388,7 +368,7 @@ def _resource_filters_online(self, resource, filter_type):
db_map = self._get_db_map(url)
if db_map is None:
return None
db_item_type = {SCENARIO_FILTER_TYPE: "scenario", TOOL_FILTER_TYPE: "tool"}[filter_type]
db_item_type = {SCENARIO_FILTER_TYPE: "scenario"}[filter_type]
available_filters = (
x["name"] for x in self._toolbox.db_mngr.get_items(db_map, db_item_type, only_visible=True)
)
Expand Down
5 changes: 2 additions & 3 deletions spinetoolbox/project_item/specification_editor_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,11 @@ def _restore_dock_widgets(self):
"""Restores dockWidgets to some default state. Called in the constructor, before restoring the ui from settings.
Reimplement in subclasses if needed."""

def _make_new_specification(self, spec_name, exiting=None):
def _make_new_specification(self, spec_name):
"""Returns a ProjectItemSpecification from current form settings.

Args:
spec_name (str): Name of the spec
exiting (bool, optional): Set as True if called when trying to exit the editor window

Returns:
ProjectItemSpecification
Expand Down Expand Up @@ -189,7 +188,7 @@ def _save(self, exiting=None):
return self.prompt_exit_without_saving()
self.show_error("Please enter a name for the specification.")
return False
spec = self._make_new_specification(name, exiting)
spec = self._make_new_specification(name)
if spec is None:
return self.prompt_exit_without_saving() if exiting else False
if not self._original_spec_name:
Expand Down
Loading
Loading