From 28e5024345d6e85faa38ff308195b1de895ddd61 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 18 Oct 2024 21:36:07 +0200 Subject: [PATCH 1/9] Datasets: Add domain field --- Orange/widgets/data/owdatasets.py | 40 ++++++++++++++++++++ Orange/widgets/data/tests/test_owdatasets.py | 21 ++++++++++ i18n/si/msgs.jaml | 7 ++++ 3 files changed, 68 insertions(+) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index 7c4d15f59ba..5f9dcc04faa 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -122,6 +122,7 @@ def __init__(self, **kwargs): self.seealso = [] self.tags = [] self.language = "English" + self.domain = "core" super(Namespace, self).__init__(**kwargs) @@ -144,6 +145,7 @@ class SortFilterProxyWithLanguage(QSortFilterProxyModel): def __init__(self): super().__init__() self.__language = None + self.__domain = None def setLanguage(self, language): self.__language = language @@ -152,11 +154,21 @@ def setLanguage(self, language): def language(self): return self.__language + def setDomain(self, domain): + self.__domain = domain + self.invalidateFilter() + + def domain(self): + return self.__domain + def filterAcceptsRow(self, row, parent): source = self.sourceModel() return super().filterAcceptsRow(row, parent) and ( self.__language is None or source.index(row, 0).data(Qt.UserRole).language == self.__language + ) and ( + self.__domain is None + or source.index(row, 0).data(Qt.UserRole).domain == self.__domain ) @@ -177,6 +189,7 @@ class OWDataSets(OWWidget): DATASET_DIR = "datasets" DEFAULT_LANG = "English" ALL_LANGUAGES = "All Languages" + ALL_DOMAINS = "(general)" # override HEADER_SCHEMA to define new columns # if schema is changed override methods: self.assign_delegates and @@ -235,6 +248,7 @@ def __init__(self): textChanged=self.filter, placeholderText="Search for data set ..." ) layout.addWidget(self.filterLineEdit) + layout.addSpacing(20) layout.addWidget(QLabel("Show data sets in ")) lang_combo = self.language_combo = QComboBox() @@ -245,6 +259,14 @@ def __init__(self): lang_combo.setCurrentText(self.language) lang_combo.activated.connect(self._on_language_changed) layout.addWidget(lang_combo) + + layout.addSpacing(20) + layout.addWidget(QLabel("Domain: ")) + domain_combo = self.domain_combo = QComboBox() + domain_combo.addItem(self.ALL_DOMAINS) + domain_combo.activated.connect(self._on_domain_changed) + layout.addWidget(domain_combo) + self.mainArea.layout().addLayout(layout) self.splitter = QSplitter(orientation=Qt.Vertical) @@ -353,6 +375,7 @@ def _parse_info(self, file_path): def create_model(self): self.update_language_combo() + self.update_domain_combo() return self.update_model() def update_language_combo(self): @@ -374,6 +397,18 @@ def update_language_combo(self): else: combo.setCurrentText(self.ALL_LANGUAGES) + def update_domain_combo(self): + combo = self.domain_combo + current_domain = combo.currentText() + allkeys = set(self.allinfo_local) | set(self.allinfo_remote) + domains = {self._parse_info(key).domain for key in allkeys} + domains.discard("core") + domains = sorted(domains) + combo.clear() + combo.addItem(self.ALL_DOMAINS) + combo.addItems(domains) + combo.setCurrentText(current_domain) + def update_model(self): allkeys = set(self.allinfo_local) | set(self.allinfo_remote) allkeys = sorted(allkeys) @@ -423,6 +458,11 @@ def _on_language_changed(self): self.language = combo.currentText() self.view.model().setLanguage(self.language) + def _on_domain_changed(self): + combo = self.domain_combo + domain = "core" if combo.currentIndex() == 0 else combo.currentText() + self.view.model().setDomain(domain) + @Slot(object) def __set_index(self, f): # type: (Future) -> None diff --git a/Orange/widgets/data/tests/test_owdatasets.py b/Orange/widgets/data/tests/test_owdatasets.py index fb4fab76352..9dd887158a8 100644 --- a/Orange/widgets/data/tests/test_owdatasets.py +++ b/Orange/widgets/data/tests/test_owdatasets.py @@ -62,6 +62,27 @@ def test_filtering(self): model.setLanguage(None) self.assertEqual(model.rowCount(), 2) + @patch("Orange.widgets.data.owdatasets.list_remote", + Mock(side_effect=requests.exceptions.ConnectionError)) + @patch("Orange.widgets.data.owdatasets.list_local", + Mock(return_value={('core', 'foo.tab'): {"domain": "core"}, + ('core', 'bar.tab'): {"domain": "edu"}})) + @patch("Orange.widgets.data.owdatasets.log", Mock()) + def test_filtering_by_domain(self): + w = self.create_widget(OWDataSets) # type: OWDataSets + model = w.view.model() + model.setDomain(None) + self.wait_until_stop_blocking(w) + self.assertEqual(model.rowCount(), 2) + + model.setDomain("edu") + self.assertEqual(model.rowCount(), 1) + self.assertEqual(model.index(0, 0).data(Qt.UserRole).title, "bar.tab") + + model.setDomain("core") + self.assertEqual(model.rowCount(), 1) + self.assertEqual(model.index(0, 0).data(Qt.UserRole).title, "foo.tab") + @patch("Orange.widgets.data.owdatasets.list_remote", Mock(return_value={('core', 'foo.tab'): {"language": "English"}, ('core', 'bar.tab'): {"language": "Slovenščina"}})) diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index b76186a7a96..24b6c1303c4 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -5237,6 +5237,7 @@ widgets/data/owdatasets.py: class `Namespace`: def `__init__`: English: false + core: false class `OWDataSets`: Datasets: Zbirke podatkov Load a dataset from an online repository: Naloži podatke s spletnega skladišča. @@ -5247,6 +5248,7 @@ widgets/data/owdatasets.py: datasets: false English: Slovenščina All Languages: (Vsi jeziki) + (general): (splošno) islocal: false label: false title: false @@ -5273,17 +5275,22 @@ widgets/data/owdatasets.py: _header_index: false Search for data set ...: Poišči podatke ... 'Show data sets in ': 'Prikaži zbirke podatkov v jeziku ' + 'Domain: ': 'Področje: ' Press Return or double-click to send: Pritisni Enter ali dvoklikni za izbor Description: Opis splitter_state: false Initializing: Zaganjam def `_parse_info`: version: false + def `update_domain_combo`: + core: false def `update_model`: ' ': false ' ': false ', ': false /: false + def `_on_domain_changed`: + core: false def `__set_index`: Error while fetching updated index: false def `set_model`: From a9d3a28f6be4509e778f0d9323bbc072ec4a504b Mon Sep 17 00:00:00 2001 From: janezd Date: Sun, 20 Oct 2024 16:22:27 +0200 Subject: [PATCH 2/9] Datasets: Hide unpublished data unless requested in filter --- Orange/widgets/data/owdatasets.py | 23 +++++++++---- Orange/widgets/data/tests/test_owdatasets.py | 36 +++++++++++++++++++- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index 5f9dcc04faa..d694e412ea2 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -102,6 +102,8 @@ class UniformHeightIndicatorDelegate( class Namespace(SimpleNamespace): + PUBLISHED, UNLISTED = range(2) + def __init__(self, **kwargs): self.file_path = None self.prefix = None @@ -123,6 +125,7 @@ def __init__(self, **kwargs): self.tags = [] self.language = "English" self.domain = "core" + self.publication_status = self.PUBLISHED super(Namespace, self).__init__(**kwargs) @@ -146,6 +149,11 @@ def __init__(self): super().__init__() self.__language = None self.__domain = None + self.__filter = None + + def setFilterFixedString(self, pattern): + self.__filter = pattern and pattern.casefold() + super().setFilterFixedString(pattern) def setLanguage(self, language): self.__language = language @@ -163,12 +171,15 @@ def domain(self): def filterAcceptsRow(self, row, parent): source = self.sourceModel() - return super().filterAcceptsRow(row, parent) and ( - self.__language is None - or source.index(row, 0).data(Qt.UserRole).language == self.__language - ) and ( - self.__domain is None - or source.index(row, 0).data(Qt.UserRole).domain == self.__domain + data = source.index(row, 0).data(Qt.UserRole) + return (super().filterAcceptsRow(row, parent) + and (self.__language is None or data.language == self.__language) + and (self.__domain is None or data.domain == self.__domain) + and (data.publication_status == Namespace.PUBLISHED or ( + self.__filter is not None + and len(self.__filter) >= 5 + and data.title.casefold().startswith(self.__filter) + )) ) diff --git a/Orange/widgets/data/tests/test_owdatasets.py b/Orange/widgets/data/tests/test_owdatasets.py index 9dd887158a8..0d5c5b82d6b 100644 --- a/Orange/widgets/data/tests/test_owdatasets.py +++ b/Orange/widgets/data/tests/test_owdatasets.py @@ -5,7 +5,7 @@ from AnyQt.QtCore import QItemSelectionModel, Qt -from Orange.widgets.data.owdatasets import OWDataSets +from Orange.widgets.data.owdatasets import OWDataSets, Namespace as DSNamespace from Orange.widgets.tests.base import WidgetTest @@ -83,6 +83,40 @@ def test_filtering_by_domain(self): self.assertEqual(model.rowCount(), 1) self.assertEqual(model.index(0, 0).data(Qt.UserRole).title, "foo.tab") + @patch("Orange.widgets.data.owdatasets.list_remote", + Mock(side_effect=requests.exceptions.ConnectionError)) + @patch("Orange.widgets.data.owdatasets.list_local", + Mock(return_value={ + ('core', 'foo.tab'): {"title": "an unlisted data set", + "publication_status": DSNamespace.UNLISTED}, + ('core', 'bar.tab'): {"title": "a published data set", + "publication_status": DSNamespace.PUBLISHED}, + ('core', 'baz.tab'): {"title": "an unp unp", + "publication_status": DSNamespace.PUBLISHED} + })) + @patch("Orange.widgets.data.owdatasets.log", Mock()) + def test_filtering_unlisted(self): + def titles(): + return { + model.index(row, 0).data(Qt.UserRole).title + for row in range(model.rowCount()) } + + w = self.create_widget(OWDataSets) # type: OWDataSets + model = w.view.model() + self.assertEqual(titles(), {"a published data set", "an unp unp"}) + + model.setFilterFixedString("an u") + self.assertEqual(titles(), {"an unp unp"}) + + model.setFilterFixedString("an Un") + self.assertEqual(titles(), {"an unlisted data set", "an unp unp"}) + + model.setFilterFixedString("") + self.assertEqual(titles(), {"a published data set", "an unp unp"}) + + model.setFilterFixedString(None) + self.assertEqual(titles(), {"a published data set", "an unp unp"}) + @patch("Orange.widgets.data.owdatasets.list_remote", Mock(return_value={('core', 'foo.tab'): {"language": "English"}, ('core', 'bar.tab'): {"language": "Slovenščina"}})) From 8993404f028e562de8b59a9c21cc00f97da36521 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 25 Oct 2024 10:45:24 +0200 Subject: [PATCH 3/9] Datasets: Move files to root; make translatable --- Orange/widgets/data/owdatasets.py | 65 +++++++++++----- Orange/widgets/data/tests/test_owdatasets.py | 80 +++++++++++++------- i18n/si/msgs.jaml | 13 +--- 3 files changed, 101 insertions(+), 57 deletions(-) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index d694e412ea2..cb2828a9dab 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -35,6 +35,12 @@ log = logging.getLogger(__name__) +# These two constants are used in settings (and in the proxy filter model). +# The corresponding options in the combo box are translatable, therefore +# the settings must be stored in language-independent form. +GENERAL_DOMAIN = None +ALL_DOMAINS = "" # The setting is Optional[str], so don't use other types here + def ensure_local(index_url, file_path, local_cache_path, force=False, progress_advance=None): @@ -124,7 +130,7 @@ def __init__(self, **kwargs): self.seealso = [] self.tags = [] self.language = "English" - self.domain = "core" + self.domain = None self.publication_status = self.PUBLISHED super(Namespace, self).__init__(**kwargs) @@ -174,7 +180,7 @@ def filterAcceptsRow(self, row, parent): data = source.index(row, 0).data(Qt.UserRole) return (super().filterAcceptsRow(row, parent) and (self.__language is None or data.language == self.__language) - and (self.__domain is None or data.domain == self.__domain) + and (self.__domain == ALL_DOMAINS or data.domain == self.__domain) and (data.publication_status == Namespace.PUBLISHED or ( self.__filter is not None and len(self.__filter) >= 5 @@ -200,7 +206,10 @@ class OWDataSets(OWWidget): DATASET_DIR = "datasets" DEFAULT_LANG = "English" ALL_LANGUAGES = "All Languages" - ALL_DOMAINS = "(general)" + + # These two combo options are translatable; others (domain names) are not + GENERAL_DOMAIN_LABEL = "(General)" + ALL_DOMAINS_LABEL = "(Show all)" # override HEADER_SCHEMA to define new columns # if schema is changed override methods: self.assign_delegates and @@ -230,6 +239,8 @@ class Outputs: #: Selected dataset id selected_id = Setting(None) # type: Optional[str] language = Setting(DEFAULT_LANG) + domain = Setting(GENERAL_DOMAIN) + settings_version = 2 #: main area splitter state splitter_state = Setting(b'') # type: bytes @@ -272,9 +283,9 @@ def __init__(self): layout.addWidget(lang_combo) layout.addSpacing(20) - layout.addWidget(QLabel("Domain: ")) + layout.addWidget(QLabel("Domain:")) domain_combo = self.domain_combo = QComboBox() - domain_combo.addItem(self.ALL_DOMAINS) + domain_combo.addItem(self.GENERAL_DOMAIN_LABEL) domain_combo.activated.connect(self._on_domain_changed) layout.addWidget(domain_combo) @@ -410,15 +421,12 @@ def update_language_combo(self): def update_domain_combo(self): combo = self.domain_combo - current_domain = combo.currentText() allkeys = set(self.allinfo_local) | set(self.allinfo_remote) domains = {self._parse_info(key).domain for key in allkeys} - domains.discard("core") - domains = sorted(domains) - combo.clear() - combo.addItem(self.ALL_DOMAINS) - combo.addItems(domains) - combo.setCurrentText(current_domain) + domains -= {None, "sc"} + if domains: + combo.addItems(sorted(domains)) + combo.addItem(self.ALL_DOMAINS_LABEL) def update_model(self): allkeys = set(self.allinfo_local) | set(self.allinfo_remote) @@ -456,8 +464,17 @@ def update_model(self): model.appendRow(row) # for settings do not use os.path.join (Windows separator is different) - if "/".join(file_path) == self.selected_id: + if file_path[-1] == self.selected_id: current_index = i + self.domain = datainfo.domain + combo = self.domain_combo + if self.domain == GENERAL_DOMAIN: + combo.setCurrentIndex(0) + elif self.domain == ALL_DOMAINS: + combo.setCurrentIndex(combo.count() - 1) + else: + combo.setCurrentText(self.domain) + self._on_domain_changed() return model, current_index @@ -471,8 +488,13 @@ def _on_language_changed(self): def _on_domain_changed(self): combo = self.domain_combo - domain = "core" if combo.currentIndex() == 0 else combo.currentText() - self.view.model().setDomain(domain) + if combo.currentIndex() == combo.count() - 1: + self.domain = ALL_DOMAINS + elif combo.currentIndex() == 0: + self.domain = GENERAL_DOMAIN + else: + self.domain = combo.currentText() + self.view.model().setDomain(self.domain) @Slot(object) def __set_index(self, f): @@ -578,7 +600,7 @@ def __on_selection(self): text = description_html(di) self.descriptionlabel.setText(text) # for settings do not use os.path.join (Windows separator is different) - self.selected_id = "/".join(di.file_path) + self.selected_id = di.file_path[-1] else: self.descriptionlabel.setText("") self.selected_id = None @@ -695,10 +717,13 @@ def load_data(path): return Orange.data.Table(path) @classmethod - def migrate_settings(cls, settings, _): - # until including 3.36.0 selected dataset was saved with \ on Windows - if "selected_id" in settings and isinstance(settings["selected_id"], str): - settings["selected_id"] = settings["selected_id"].replace("\\", "/") + def migrate_settings(cls, settings, version: Optional[int] = None): + selected_id = settings.get("selected_id") + if isinstance(selected_id, str): + # until including 3.36.0 selected dataset was saved with \ on Windows + selected_id = selected_id.replace("\\", "/") + if version is None or version < 2: + settings["selected_id"] = selected_id.split("/")[-1] class FutureWatcher(QObject): diff --git a/Orange/widgets/data/tests/test_owdatasets.py b/Orange/widgets/data/tests/test_owdatasets.py index 0d5c5b82d6b..9923245db8d 100644 --- a/Orange/widgets/data/tests/test_owdatasets.py +++ b/Orange/widgets/data/tests/test_owdatasets.py @@ -1,3 +1,4 @@ +import time import unittest from unittest.mock import patch, Mock @@ -5,7 +6,8 @@ from AnyQt.QtCore import QItemSelectionModel, Qt -from Orange.widgets.data.owdatasets import OWDataSets, Namespace as DSNamespace +from Orange.widgets.data.owdatasets import OWDataSets, Namespace as DSNamespace, \ + GENERAL_DOMAIN, ALL_DOMAINS from Orange.widgets.tests.base import WidgetTest @@ -65,13 +67,17 @@ def test_filtering(self): @patch("Orange.widgets.data.owdatasets.list_remote", Mock(side_effect=requests.exceptions.ConnectionError)) @patch("Orange.widgets.data.owdatasets.list_local", - Mock(return_value={('core', 'foo.tab'): {"domain": "core"}, - ('core', 'bar.tab'): {"domain": "edu"}})) + Mock(return_value={('core', 'foo.tab'): {"domain": None}, + ('edu', 'bar.tab'): {"domain": "edu"}})) @patch("Orange.widgets.data.owdatasets.log", Mock()) def test_filtering_by_domain(self): w = self.create_widget(OWDataSets) # type: OWDataSets model = w.view.model() - model.setDomain(None) + model.setDomain(GENERAL_DOMAIN) + self.wait_until_stop_blocking(w) + self.assertEqual(model.rowCount(), 1) + + model.setDomain(ALL_DOMAINS) self.wait_until_stop_blocking(w) self.assertEqual(model.rowCount(), 2) @@ -79,9 +85,41 @@ def test_filtering_by_domain(self): self.assertEqual(model.rowCount(), 1) self.assertEqual(model.index(0, 0).data(Qt.UserRole).title, "bar.tab") - model.setDomain("core") - self.assertEqual(model.rowCount(), 1) - self.assertEqual(model.index(0, 0).data(Qt.UserRole).title, "foo.tab") + model.setDomain("baz") + self.assertEqual(model.rowCount(), 0) + + @patch("Orange.widgets.data.owdatasets.list_local", + Mock(return_value={('core', 'foo.tab'): {"domain": None}, + ('core', 'bar.tab'): {"domain": "edu"}})) + @patch("Orange.widgets.data.owdatasets.log", Mock()) + @patch("Orange.widgets.data.owdatasets.OWDataSets.commit", Mock()) + def test_change_domain(self): + def wait_and_return(_): + time.sleep(0.2) + return {('core', 'foo.tab'): {"domain": "edu"}, + ('core', 'bar.tab'): {"domain": "edu"}} + with patch("Orange.widgets.data.owdatasets.list_remote", + new=wait_and_return): + self.widget = w = self.create_widget(OWDataSets, + stored_settings={"selected_id": "bar.tab", + "domain": "edu"}) + self.wait_until_stop_blocking() + self.assertEqual(w.selected_id, "bar.tab") + self.assertEqual(w.domain_combo.currentText(), "edu") + + self.widget = w = self.create_widget(OWDataSets, + stored_settings={"selected_id": "foo.tab", + "domain": "(core)"}) + self.wait_until_stop_blocking() + self.assertEqual(w.selected_id, "foo.tab") + self.assertEqual(w.domain_combo.currentText(), "edu") + + self.widget = w = self.create_widget(OWDataSets, + stored_settings={"selected_id": "bar.tab", + "domain": "(core)"}) + self.wait_until_stop_blocking() + self.assertEqual(w.selected_id, "bar.tab") + self.assertEqual(w.domain_combo.currentText(), "edu") @patch("Orange.widgets.data.owdatasets.list_remote", Mock(side_effect=requests.exceptions.ConnectionError)) @@ -151,25 +189,7 @@ def test_download_iris(self): # select the only dataset sel_type = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows w.view.selectionModel().select(w.view.model().index(0, 0), sel_type) - self.assertEqual(w.selected_id, "core/iris.tab") - w.commit() - iris = self.get_output(w.Outputs.data, w) - self.assertEqual(len(iris), 150) - - @patch("Orange.widgets.data.owdatasets.list_remote", - Mock(return_value={('dir1', 'dir2', 'foo.tab'): {}})) - @patch("Orange.widgets.data.owdatasets.list_local", - Mock(return_value={})) - @patch("Orange.widgets.data.owdatasets.ensure_local", - Mock(return_value="iris.tab")) - @WidgetTest.skipNonEnglish - def test_download_multidir(self): - w = self.create_widget(OWDataSets) # type: OWDataSets - self.wait_until_stop_blocking(w) - # select the only dataset - sel_type = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows - w.view.selectionModel().select(w.view.model().index(0, 0), sel_type) - self.assertEqual(w.selected_id, "dir1/dir2/foo.tab") + self.assertEqual(w.selected_id, "iris.tab") w.commit() iris = self.get_output(w.Outputs.data, w) self.assertEqual(len(iris), 150) @@ -197,11 +217,15 @@ def test_migrate_selected_id(self): settings = {"selected_id": "dir1\\bar"} OWDataSets.migrate_settings(settings, 0) - self.assertEqual(settings["selected_id"], "dir1/bar") + self.assertEqual(settings["selected_id"], "bar") settings = {"selected_id": "dir1/bar"} OWDataSets.migrate_settings(settings, 0) - self.assertEqual(settings["selected_id"], "dir1/bar") + self.assertEqual(settings["selected_id"], "bar") + + settings = {"selected_id": "bar"} + OWDataSets.migrate_settings(settings, 0) + self.assertEqual(settings["selected_id"], "bar") if __name__ == "__main__": diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index 24b6c1303c4..0b1ee2252ed 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -5237,7 +5237,6 @@ widgets/data/owdatasets.py: class `Namespace`: def `__init__`: English: false - core: false class `OWDataSets`: Datasets: Zbirke podatkov Load a dataset from an online repository: Naloži podatke s spletnega skladišča. @@ -5248,7 +5247,8 @@ widgets/data/owdatasets.py: datasets: false English: Slovenščina All Languages: (Vsi jeziki) - (general): (splošno) + (General): (Splošno) + (Show all): (Prikaži vse) islocal: false label: false title: false @@ -5275,7 +5275,7 @@ widgets/data/owdatasets.py: _header_index: false Search for data set ...: Poišči podatke ... 'Show data sets in ': 'Prikaži zbirke podatkov v jeziku ' - 'Domain: ': 'Področje: ' + Domain:: Področje: Press Return or double-click to send: Pritisni Enter ali dvoklikni za izbor Description: Opis splitter_state: false @@ -5283,14 +5283,11 @@ widgets/data/owdatasets.py: def `_parse_info`: version: false def `update_domain_combo`: - core: false + sc: false def `update_model`: ' ': false ' ': false ', ': false - /: false - def `_on_domain_changed`: - core: false def `__set_index`: Error while fetching updated index: false def `set_model`: @@ -5302,8 +5299,6 @@ widgets/data/owdatasets.py: def `__update_cached_state`: ' ': false ' ': false - def `__on_selection`: - /: false def `commit`: Fetching...: Pobiram... def `__commit_complete`: From 39ef490ac7a5a75ed70f3d156b24dd05e4102f71 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 8 Nov 2024 10:40:26 +0100 Subject: [PATCH 4/9] Datasets: Lint (general) --- Orange/widgets/data/owdatasets.py | 24 +++++++++----------- Orange/widgets/data/tests/test_owdatasets.py | 7 ++++++ i18n/si/msgs.jaml | 14 ++++++------ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index cb2828a9dab..e5b223ba3ed 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -133,7 +133,7 @@ def __init__(self, **kwargs): self.domain = None self.publication_status = self.PUBLISHED - super(Namespace, self).__init__(**kwargs) + super().__init__(**kwargs) # if title missing, use filename if not self.title and self.filename: @@ -180,7 +180,7 @@ def filterAcceptsRow(self, row, parent): data = source.index(row, 0).data(Qt.UserRole) return (super().filterAcceptsRow(row, parent) and (self.__language is None or data.language == self.__language) - and (self.__domain == ALL_DOMAINS or data.domain == self.__domain) + and self.__domain in (ALL_DOMAINS, data.domain) and (data.publication_status == Namespace.PUBLISHED or ( self.__filter is not None and len(self.__filter) >= 5 @@ -631,8 +631,7 @@ def commit(self): self.__awaiting_state = None if not di.islocal: - pr = progress() - callback = lambda pr=pr: pr.advance.emit() + pr = Progress() pr.advance.connect(self.__progress_advance, Qt.QueuedConnection) self.progressBarInit() @@ -642,7 +641,7 @@ def commit(self): f = self._executor.submit( ensure_local, self.INDEX_URL, di.file_path, self.local_cache_path, force=di.outdated, - progress_advance=callback) + progress_advance=pr.advance.emit) w = FutureWatcher(f, parent=self) w.done.connect(self.__commit_complete) self.__awaiting_state = _FetchState(f, w, pr) @@ -693,8 +692,7 @@ def onDeleteWidget(self): self.__awaiting_state.pb.advance.disconnect(self.__progress_advance) self.__awaiting_state = None - @staticmethod - def sizeHint(): + def sizeHint(self): return QSize(1100, 500) def closeEvent(self, event): @@ -742,7 +740,7 @@ def __on_done(self, f): self.done.emit(self.__future) -class progress(QObject): +class Progress(QObject): advance = Signal() @@ -768,7 +766,7 @@ def make_html_list(items): style = '"margin: 5px; text-indent: -40px; margin-left: 40px;"' def format_item(i): - return '

{}

'.format(style, i) + return f'

{i}

' return '\n'.join([format_item(i) for i in items]) @@ -779,11 +777,11 @@ def description_html(datainfo): Summarize a data info as a html fragment. """ html = [] - year = " ({})".format(str(datainfo.year)) if datainfo.year else "" - source = ", from {}".format(datainfo.source) if datainfo.source else "" + year = f" ({datainfo.year})" if datainfo.year else "" + source = f", from {datainfo.source}" if datainfo.source else "" - html.append("{}{}{}".format(escape(datainfo.title), year, source)) - html.append("

{}

".format(datainfo.description)) + html.append(f"{escape(datainfo.title)}{year}{source}") + html.append(f"

{datainfo.description}

") seealso = make_html_list(datainfo.seealso) if seealso: html.append("See Also\n" + seealso + "") diff --git a/Orange/widgets/data/tests/test_owdatasets.py b/Orange/widgets/data/tests/test_owdatasets.py index 9923245db8d..240b50a2d46 100644 --- a/Orange/widgets/data/tests/test_owdatasets.py +++ b/Orange/widgets/data/tests/test_owdatasets.py @@ -12,6 +12,13 @@ class TestOWDataSets(WidgetTest): + def setUp(self): + # Most tests check the iniitialization of widget under different + # conditions, therefore mocks are needed prior to calling createWidget. + # Inherited methods will set self.widget; here we set it to None to + # avoid lint errors. + self.widget = None + @patch("Orange.widgets.data.owdatasets.list_remote", Mock(side_effect=requests.exceptions.ConnectionError)) @patch("Orange.widgets.data.owdatasets.list_local", diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index 0b1ee2252ed..13b3b790952 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -203,8 +203,8 @@ util.py: unsafe: false version.py: 3.38.0: false - 3.38.0.dev0+c767157: false - c7671579ac399d27a1b5fb21c31f870448db9ed8: false + 3.38.0.dev0+b3dd2eb: false + b3dd2eba6a3cfa73ba06460226d6e16a8e25a23a: false .dev: false canvas/__main__.py: ORANGE_STATISTICS_API_URL: false @@ -5314,13 +5314,13 @@ widgets/data/owdatasets.py: def `make_html_list`: '"margin: 5px; text-indent: -40px; margin-left: 40px;"': false def `format_item`: -

{}

: false +

{i}

: false \n: false def `description_html`: - ' ({})': true - , from {}: , z {} - {}{}{}: true -

{}

: true + ' ({datainfo.year})': true + , from {datainfo.source}: , z {datainfo.source} + {escape(datainfo.title)}{year}{source}: true +

{datainfo.description}

: true See Also\n: Glej tudi\n : true References\n: Viri\n From c2e03083abb852bb5a3815bcd82e0526688a06ef Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 8 Nov 2024 13:39:16 +0100 Subject: [PATCH 5/9] Datasets: Skip domain-related code in derived widgets (e.i. SC Data sets) --- Orange/widgets/data/owdatasets.py | 37 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index e5b223ba3ed..649467268f6 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -287,7 +287,8 @@ def __init__(self): domain_combo = self.domain_combo = QComboBox() domain_combo.addItem(self.GENERAL_DOMAIN_LABEL) domain_combo.activated.connect(self._on_domain_changed) - layout.addWidget(domain_combo) + if self.core_widget: + layout.addWidget(domain_combo) self.mainArea.layout().addLayout(layout) @@ -334,6 +335,9 @@ def __init__(self): proxy.setFilterKeyColumn(-1) proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self.view.setModel(proxy) + if not self.core_widget: + self.domain = ALL_DOMAINS + self.view.model().setDomain(self.domain) if self.splitter_state: self.splitter.restoreState(self.splitter_state) @@ -350,6 +354,18 @@ def __init__(self): self._on_language_changed() + # Single cell add-on has a data set widget that derives from this one + # although this class isn't defined as open. Adding the domain broke + # single-cell. A proper solution would be to split this widget into an + # (open) base class and a closed widget that adds the domain functionality. + # Yet, simply excluding three chunks of code makes this code simpler + # - which is better, if we assume that extending this widget is an anomaly. + @property + def core_widget(self): + # Compare by names; unit tests wrap widget classes in to detect + # missing onDeleteWidget calls + return type(self).__name__ == OWDataSets.__name__ + def assign_delegates(self): # NOTE: All columns must have size hinting delegates. # QTreeView queries only the columns displayed in the viewport so @@ -466,15 +482,16 @@ def update_model(self): # for settings do not use os.path.join (Windows separator is different) if file_path[-1] == self.selected_id: current_index = i - self.domain = datainfo.domain - combo = self.domain_combo - if self.domain == GENERAL_DOMAIN: - combo.setCurrentIndex(0) - elif self.domain == ALL_DOMAINS: - combo.setCurrentIndex(combo.count() - 1) - else: - combo.setCurrentText(self.domain) - self._on_domain_changed() + if self.core_widget: + self.domain = datainfo.domain + combo = self.domain_combo + if self.domain == GENERAL_DOMAIN: + combo.setCurrentIndex(0) + elif self.domain == ALL_DOMAINS: + combo.setCurrentIndex(combo.count() - 1) + else: + combo.setCurrentText(self.domain) + self._on_domain_changed() return model, current_index From 6766860bbe3941f573bad0e5b319ab97882f63b3 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 8 Nov 2024 16:06:10 +0100 Subject: [PATCH 6/9] Datasets: fix domain changing if there is only 1 domain --- Orange/widgets/data/owdatasets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index 649467268f6..526c7828b79 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -505,10 +505,10 @@ def _on_language_changed(self): def _on_domain_changed(self): combo = self.domain_combo - if combo.currentIndex() == combo.count() - 1: - self.domain = ALL_DOMAINS - elif combo.currentIndex() == 0: + if combo.currentIndex() == 0: self.domain = GENERAL_DOMAIN + elif combo.currentIndex() == combo.count() - 1: + self.domain = ALL_DOMAINS else: self.domain = combo.currentText() self.view.model().setDomain(self.domain) From 3e9fbeb71724a75cc7603e36db318b47717fe89a Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 8 Nov 2024 16:06:49 +0100 Subject: [PATCH 7/9] Datasets; save/load selection from the ignored sc domain --- Orange/widgets/data/owdatasets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index 526c7828b79..e0d4548eed7 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -484,6 +484,8 @@ def update_model(self): current_index = i if self.core_widget: self.domain = datainfo.domain + if self.domain == "sc": # domain from the list of ignored domain + self.domain = ALL_DOMAINS combo = self.domain_combo if self.domain == GENERAL_DOMAIN: combo.setCurrentIndex(0) From 16bbfdfeecff0bb6fc6de562014d1faca1e8905d Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 8 Nov 2024 16:25:30 +0100 Subject: [PATCH 8/9] Datasets: load workflows with unlisted datasets correctly Also, if an unlisted data set was selected, keep showing it. --- Orange/widgets/data/owdatasets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index e0d4548eed7..84a2675e1a9 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -482,6 +482,8 @@ def update_model(self): # for settings do not use os.path.join (Windows separator is different) if file_path[-1] == self.selected_id: current_index = i + # for selected_id, set publication status so that unlisted data load correctly + datainfo.publication_status = Namespace.PUBLISHED if self.core_widget: self.domain = datainfo.domain if self.domain == "sc": # domain from the list of ignored domain @@ -620,6 +622,8 @@ def __on_selection(self): self.descriptionlabel.setText(text) # for settings do not use os.path.join (Windows separator is different) self.selected_id = di.file_path[-1] + # do not clear a dataset once you select it if it was unlisted + di.publication_status = Namespace.PUBLISHED else: self.descriptionlabel.setText("") self.selected_id = None From 300a7539a7fc1557ca99e19fb6e30430effd318a Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 8 Nov 2024 17:05:40 +0100 Subject: [PATCH 9/9] trubar: ignore new string --- i18n/si/msgs.jaml | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index 13b3b790952..a7c5bd09a01 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -5288,6 +5288,7 @@ widgets/data/owdatasets.py: ' ': false ' ': false ', ': false + sc: false def `__set_index`: Error while fetching updated index: false def `set_model`: