def test_two_mem_files_sharing_same_logs(self): filename = os.path.join(os.getcwd(), "tests", "files", "basedir_2_mem", "deck1.mem") self.mem_importer().do_import(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 1 filename = os.path.join(os.getcwd(), "tests", "files", "basedir_2_mem", "deck2.mem") self.mem_importer().do_import(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 3 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 2 card = self.database().card("4c8fff73", is_id_internal=False) assert self.database().average_thinking_time(card) == 1.5 assert self.database().total_thinking_time(card) == 3.0 assert self.database().card_count_for_grade(0, active_only=True) == 2 tag = self.database().get_or_create_tag_with_name("666") assert self.database().card_count_for_grade_and_tag( 0, tag, active_only=True) == 0 from mnemosyne.libmnemosyne.statistics_pages.grades import Grades page = Grades(component_manager=self.mnemosyne.component_manager) from mnemosyne.libmnemosyne.tag_tree import TagTree self.tag_tree = TagTree(self.mnemosyne.component_manager, count_cards=False) self.nodes = self.tag_tree.nodes() for index, node in enumerate(self.nodes): if node == "666": page.prepare_statistics(index) assert page.y == [0, 0, 0, 0, 0, 0, 0] page.prepare_statistics(-1) assert page.y == [0, 2, 0, 0, 0, 0, 0]
class Grades(PlotStatisticsPage): name = _("Grades") ALL_CARDS = -2 ACTIVE_CARDS = -1 def __init__(self, **kwds): super().__init__(**kwds) self.tag_tree = TagTree(self.component_manager, count_cards=False) self.nodes = self.tag_tree.nodes() self.variants = [(self.ALL_CARDS, _("All cards")), (self.ACTIVE_CARDS, _("Active cards only"))] for index, node in enumerate(self.nodes): if node == "__UNTAGGED__": node = _("Untagged") self.variants.append((index, node)) def prepare_statistics(self, variant): self.x = list(range(-1, 6)) if variant == self.ALL_CARDS: self.y = [self.database().card_count_for_grade \ (grade, active_only=False) for grade in self.x] elif variant == self.ACTIVE_CARDS: self.y = [self.database().card_count_for_grade \ (grade, active_only=True) for grade in self.x] else: self.y = [] for grade in self.x: self.y.append(0) for tag in self.tag_tree.tags_in_subtree(self.nodes[variant]): self.y[-1] += self.database().card_count_for_grade_and_tag \ (grade, tag, active_only=False)
class Easiness(PlotStatisticsPage): name = _("Easiness") ALL_CARDS = -2 ACTIVE_CARDS = -1 def __init__(self, **kwds): super().__init__(**kwds) self.tag_tree = TagTree(self.component_manager, count_cards=False) self.nodes = self.tag_tree.nodes() self.variants = [(self.ALL_CARDS, _("All cards")), (self.ACTIVE_CARDS, _("Active cards only"))] for index, node in enumerate(self.nodes): if node == "__UNTAGGED__": node = _("Untagged") self.variants.append((index, node)) def prepare_statistics(self, variant): if variant == self.ALL_CARDS: self.data = self.database().easinesses(active_only=False) elif variant == self.ACTIVE_CARDS: self.data = self.database().easinesses(active_only=True) else: self.data = [] for tag in self.tag_tree.tags_in_subtree(self.nodes[variant]): self.data.extend(self.database().easinesses_for_tag\ (tag, active_only=False))
def rebuild(self): """To be called when external events invalidate the tag tree, e.g. due to edits in the card browser widget. """ self.save_criterion() self.store_tree_state() self.tag_tree = TagTree(self.component_manager) self.restore_criterion()
def __init__(self, **kwds): super().__init__(**kwds) self.tag_tree = TagTree(self.component_manager, count_cards=False) self.nodes = self.tag_tree.nodes() self.variants = [(self.ALL_CARDS, _("All cards")), (self.ACTIVE_CARDS, _("Active cards only"))] for index, node in enumerate(self.nodes): if node == "__UNTAGGED__": node = _("Untagged") self.variants.append((index, node))
def test_delete_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.delete_subtree("b::c") card = self.database().card(card._id, is_id_internal=True) assert card.tag_string() == "" self.database().con.execute("select tags from cards where _id=?", (card._id, )).fetchone()[0] == "b" assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert "b" not in self.tree.card_count_for_node assert "__UNTAGGED__" in self.tree.card_count_for_node assert "b::c" not in self.tree.card_count_for_node assert "b::c::d" not in self.tree.card_count_for_node
def display(self, criterion=None): # Create criterion if needed. if criterion is None: criterion = DefaultCriterion(self.component_manager) for tag in self.database().tags(): criterion._tag_ids_active.add(tag._id) # Create tree. self.tag_tree = TagTree(self.component_manager) self.tree_wdgt.clear() self.node_items = [] self.tag_for_node_item = [] self.nodes_which_can_be_deleted = [] self.nodes_which_can_be_renamed = [] node = "__ALL__" node_name = "%s (%d)" % (self.tag_tree.display_name_for_node[node], self.tag_tree.card_count_for_node[node]) root = self.tag_tree[node] root_item = QtWidgets.QTreeWidgetItem(\ self.tree_wdgt, [node_name, node], 0) root_item.setFlags(root_item.flags() | \ QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate) root_item.setCheckState(0, QtCore.Qt.Checked) self.create_tree(self.tag_tree[node], qt_parent=root_item) # Set forbidden tags. if len(criterion._tag_ids_forbidden): for i in range(len(self.node_items)): node_item = self.node_items[i] tag = self.tag_for_node_item[i] if tag._id in criterion._tag_ids_forbidden: node_item.setCheckState(0, QtCore.Qt.Checked) else: node_item.setCheckState(0, QtCore.Qt.Unchecked) # Set active tags. else: # We first set all the tags inactive. We cannot do this in the # second branch of the upcoming 'if' statement, as then an # inactive parent tag coming later in the list will deactivate # active child tags coming earlier in the list. for node_item in self.node_items: node_item.setCheckState(0, QtCore.Qt.Unchecked) for i in range(len(self.node_items)): node_item = self.node_items[i] tag = self.tag_for_node_item[i] if tag._id in criterion._tag_ids_active: node_item.setCheckState(0, QtCore.Qt.Checked) # Restore state of the tree. self.tree_wdgt.expandAll() collapsed = self.config()["tag_tree_wdgt_state"] if collapsed is None: collapsed = [] iterator = QtWidgets.QTreeWidgetItemIterator(self.tree_wdgt) while iterator.value(): if iterator.value().text(1) in collapsed: iterator.value().setExpanded(False) iterator += 1
def test_1(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=[])[0] self.controller().save_file() from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(list(self.tree.keys())) == 2 assert self.tree['__ALL__'] == ['__UNTAGGED__']
def test_rename_to_empty_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "") assert self.tree.card_count_for_node["tag1"] == 1 assert len(self.database().tags()) == 2
def test_4(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.rename_node("Z", "Z::Z") self.tree.rename_node("b::c", "b::cc") assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["Z::Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::cc"] == 1 assert self.tree.card_count_for_node["b::cc::d"] == 1
def test_rename_to_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "__UNTAGGED__") assert "__UNTAGGED__" in self.tree.card_count_for_node
def test_3(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1
def test_count_1(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 2
def test_rename_to_existing_tag_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards( fact_data, card_type, grade=-1, tag_names=["Xx::vb::test", "Xx::aa::vb::test"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.database().tags()) == 3 self.tree.rename_node("Xx::aa::vb::test", "Xx::vb::test") assert self.tree.card_count_for_node["Xx::vb::test"] == 1 assert "Xx::aa::vb::test" not in self.tree.card_count_for_node assert "," not in self.database().con.execute(\ "select tags from cards where _id=?", (card._id,)).fetchone()[0] assert self.database().con.execute(\ "select count() from tags_for_card").fetchone()[0] == 1 assert len(self.database().tags()) == 2
def test_2(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag_1"])[0] self.controller().save_file() from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.tree.keys()) == 3 assert self.tree['__ALL__'] == [u'tag_1', u'__UNTAGGED__']
def test_delete_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["forbidden"])[0] assert self.database().active_count() == 1 c = DefaultCriterion(self.mnemosyne.component_manager) c.deactivated_card_type_fact_view_ids = set() c._tag_ids_active = set( [self.database().get_or_create_tag_with_name("active")._id, 1]) c._tag_ids_forbidden = set( [self.database().get_or_create_tag_with_name("forbidden")._id]) self.database().set_current_criterion(c) assert self.database().active_count() == 0 from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.delete_subtree("forbidden") assert self.database().active_count() == 1
def display(self, criterion=None): # Create criterion if needed. if criterion is None: criterion = DefaultCriterion(self.component_manager) for tag in self.database().tags(): criterion._tag_ids_active.add(tag._id) # Create tree. self.tag_tree = TagTree(self.component_manager) self.tree_wdgt.clear() self.tag_for_node_item = {} self.nodes_which_can_be_deleted = [] self.nodes_which_can_be_renamed = [] node = "__ALL__" node_name = "%s (%d)" % (self.tag_tree.display_name_for_node[node], self.tag_tree.card_count_for_node[node]) root = self.tag_tree[node] root_item = QtGui.QTreeWidgetItem(\ self.tree_wdgt, [node_name, node], 0) root_item.setFlags(root_item.flags() | \ QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate) root_item.setCheckState(0, QtCore.Qt.Checked) self.create_tree(self.tag_tree[node], qt_parent=root_item) # Set forbidden tags. if len(criterion._tag_ids_forbidden): for node_item, tag in self.tag_for_node_item.iteritems(): if tag._id in criterion._tag_ids_forbidden: node_item.setCheckState(0, QtCore.Qt.Checked) else: node_item.setCheckState(0, QtCore.Qt.Unchecked) # Set active tags. else: # We first set all the tags inactive. We cannot do this in the # second branch of the upcoming 'if' statement, as then an # inactive parent tag coming later in the list will deactivate # active child tags coming earlier in the list. for node_item in self.tag_for_node_item: node_item.setCheckState(0, QtCore.Qt.Unchecked) for node_item, tag in self.tag_for_node_item.iteritems(): if tag._id in criterion._tag_ids_active: node_item.setCheckState(0, QtCore.Qt.Checked) # Restore state of the tree. self.tree_wdgt.expandAll() collapsed = self.config()["tag_tree_wdgt_state"] if collapsed is None: collapsed = [] iterator = QtGui.QTreeWidgetItemIterator(self.tree_wdgt) while iterator.value(): if unicode(iterator.value().text(1)) in collapsed: iterator.value().setExpanded(False) iterator += 1
def test_rename_to_existing_tag_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Xx::vb::test", "Xx::aa::vb::test"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.database().tags()) == 3 self.tree.rename_node("Xx::aa::vb::test", "Xx::vb::test") assert self.tree.card_count_for_node["Xx::vb::test"] == 1 assert "Xx::aa::vb::test" not in self.tree.card_count_for_node assert "," not in self.database().con.execute(\ "select tags from cards where _id=?", (card._id,)).fetchone()[0] assert self.database().con.execute(\ "select count() from tags_for_card").fetchone()[0] == 1 assert len(self.database().tags()) == 2
def test_delete_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["forbidden"])[0] assert self.database().active_count() == 1 c = DefaultCriterion(self.mnemosyne.component_manager) c.deactivated_card_type_fact_view_ids = set() c._tag_ids_active = set([self.database().get_or_create_tag_with_name("active")._id, 1]) c._tag_ids_forbidden = set([self.database().get_or_create_tag_with_name("forbidden")._id]) self.database().set_current_criterion(c) assert self.database().active_count() == 0 from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.delete_subtree("forbidden") assert self.database().active_count() == 1
def test_delete_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.delete_subtree("b::c") card = self.database().card(card._id, is_id_internal=True) assert card.tag_string() == "b" self.database().con.execute("select tags from cards where _id=?", (card._id, )).fetchone()[0] == "b" assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert "__UNTAGGED__" in self.tree.card_count_for_node assert "b::c" not in self.tree.card_count_for_node assert "b::c::d" not in self.tree.card_count_for_node
class TagsTreeWdgt(QtGui.QWidget, Component): """Displays all the tags in a tree together with check boxes. """ tags_changed_signal = QtCore.pyqtSignal() def __init__(self, component_manager, parent, acquire_database=None): Component.__init__(self, component_manager) QtGui.QWidget.__init__(self, parent) self.layout = QtGui.QVBoxLayout(self) self.tree_wdgt = QtGui.QTreeWidget(self) self.tree_wdgt.setColumnCount(2) self.tree_wdgt.setColumnHidden(1, True) self.tree_wdgt.setColumnHidden(NODE, True) self.tree_wdgt.setHeaderHidden(True) self.tree_wdgt.setSelectionMode(\ QtGui.QAbstractItemView.ExtendedSelection) self.delegate = TagDelegate(component_manager, self) self.tree_wdgt.setItemDelegate(self.delegate) self.delegate.rename_node.connect(self.rename_node) self.delegate.redraw_node.connect(self.redraw_node) self.layout.addWidget(self.tree_wdgt) self.tree_wdgt.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tree_wdgt.customContextMenuRequested.connect(\ self.context_menu) self.acquire_database = acquire_database def selected_nodes_which_can_be_renamed(self): nodes = [] for index in self.tree_wdgt.selectedIndexes(): node_index = \ index.model().index(index.row(), NODE, index.parent()) node = index.model().data(node_index).toString() if node in self.nodes_which_can_be_renamed: nodes.append(node) return nodes def selected_nodes_which_can_be_deleted(self): nodes = [] for index in self.tree_wdgt.selectedIndexes(): node_index = \ index.model().index(index.row(), NODE, index.parent()) node = index.model().data(node_index).toString() if node in self.nodes_which_can_be_deleted: nodes.append(node) return nodes def context_menu(self, point): menu = QtGui.QMenu(self) to_rename = self.selected_nodes_which_can_be_renamed() if len(to_rename) >= 1: rename_action = QtGui.QAction(_("&Rename"), menu) rename_action.triggered.connect(self.menu_rename) rename_action.setShortcut(QtCore.Qt.Key_Enter) menu.addAction(rename_action) if len(to_rename) > 1: rename_action.setEnabled(False) to_delete = self.selected_nodes_which_can_be_deleted() if len(to_delete) >= 1: delete_action = QtGui.QAction(_("&Delete"), menu) delete_action.triggered.connect(self.menu_delete) delete_action.setShortcut(QtGui.QKeySequence.Delete) menu.addAction(delete_action) if len(to_delete) + len(to_rename) >= 1: menu.exec_(self.tree_wdgt.mapToGlobal(point)) def keyPressEvent(self, event): if event.key() in [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]: self.menu_rename() elif event.key() in [QtCore.Qt.Key_Delete, QtCore.Qt.Key_Backspace]: self.menu_delete() else: QtGui.QWidget.keyPressEvent(self, event) def menu_rename(self): nodes = self.selected_nodes_which_can_be_renamed() # If there are tags selected, this means that we could only have got # after pressing return on an actual edit, due to our custom # 'keyPressEvent'. We should not continue in that case. if len(nodes) == 0: return # We display the full node (i.e. all levels including ::), so that # the hierarchy can be changed upon editing. from mnemosyne.pyqt_ui.ui_rename_tag_dlg import Ui_RenameTagDlg class RenameDlg(QtGui.QDialog, Ui_RenameTagDlg): def __init__(self, old_tag_name): QtGui.QDialog.__init__(self) self.setupUi(self) self.tag_name.setText(\ old_tag_name.replace("::" + _("Untagged"), "" )) old_tag_name = nodes[0] dlg = RenameDlg(old_tag_name) if dlg.exec_() == QtGui.QDialog.Accepted: self.rename_node(nodes[0], unicode(dlg.tag_name.text())) def menu_delete(self): nodes = self.selected_nodes_which_can_be_deleted() if len(nodes) == 0: return if len(nodes) > 1: question = _("Delete these tags? Cards with these tags will not be deleted.") else: question = _("Delete this tag? Cards with this tag will not be deleted.") answer = self.main_widget().show_question\ (question, _("&OK"), _("&Cancel"), "") if answer == 1: # Cancel. return self.delete_nodes(nodes) def create_tree(self, tree, qt_parent): for node in tree: node_name = "%s (%d)" % \ (self.tag_tree.display_name_for_node[node], self.tag_tree.card_count_for_node[node]) node_item = QtGui.QTreeWidgetItem(qt_parent, [node_name, node], 0) node_item.setFlags(node_item.flags() | \ QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate) if node not in ["__ALL__", "__UNTAGGED__"] and \ not "::" + _("Untagged") in node: node_item.setFlags(node_item.flags() | \ QtCore.Qt.ItemIsEditable) self.nodes_which_can_be_renamed.append(node) self.nodes_which_can_be_deleted.append(node) if node in self.tag_tree.tag_for_node: self.tag_for_node_item[node_item] = \ self.tag_tree.tag_for_node[node] node_item.setData(NODE, QtCore.Qt.DisplayRole, QtCore.QVariant(QtCore.QString(node))) self.create_tree(tree=self.tag_tree[node], qt_parent=node_item) def display(self, criterion=None): # Create criterion if needed. if criterion is None: criterion = DefaultCriterion(self.component_manager) for tag in self.database().tags(): criterion._tag_ids_active.add(tag._id) # Create tree. self.tag_tree = TagTree(self.component_manager) self.tree_wdgt.clear() self.tag_for_node_item = {} self.nodes_which_can_be_deleted = [] self.nodes_which_can_be_renamed = [] node = "__ALL__" node_name = "%s (%d)" % (self.tag_tree.display_name_for_node[node], self.tag_tree.card_count_for_node[node]) root = self.tag_tree[node] root_item = QtGui.QTreeWidgetItem(\ self.tree_wdgt, [node_name, node], 0) root_item.setFlags(root_item.flags() | \ QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate) root_item.setCheckState(0, QtCore.Qt.Checked) self.create_tree(self.tag_tree[node], qt_parent=root_item) # Set forbidden tags. if len(criterion._tag_ids_forbidden): for node_item, tag in self.tag_for_node_item.iteritems(): if tag._id in criterion._tag_ids_forbidden: node_item.setCheckState(0, QtCore.Qt.Checked) else: node_item.setCheckState(0, QtCore.Qt.Unchecked) # Set active tags. else: # We first set all the tags inactive. We cannot do this in the # second branch of the upcoming 'if' statement, as then an # inactive parent tag coming later in the list will deactivate # active child tags coming earlier in the list. for node_item in self.tag_for_node_item: node_item.setCheckState(0, QtCore.Qt.Unchecked) for node_item, tag in self.tag_for_node_item.iteritems(): if tag._id in criterion._tag_ids_active: node_item.setCheckState(0, QtCore.Qt.Checked) # Restore state of the tree. self.tree_wdgt.expandAll() collapsed = self.config()["tag_tree_wdgt_state"] if collapsed is None: collapsed = [] iterator = QtGui.QTreeWidgetItemIterator(self.tree_wdgt) while iterator.value(): if unicode(iterator.value().text(1)) in collapsed: iterator.value().setExpanded(False) iterator += 1 def checked_to_active_tags_in_criterion(self, criterion): for item, tag in self.tag_for_node_item.iteritems(): if item.checkState(0) == QtCore.Qt.Checked: criterion._tag_ids_active.add(tag._id) criterion._tag_ids_forbidden = set() return criterion def checked_to_forbidden_tags_in_criterion(self, criterion): for item, tag in self.tag_for_node_item.iteritems(): if item.checkState(0) == QtCore.Qt.Checked: criterion._tag_ids_forbidden.add(tag._id) criterion._tag_ids_active = \ set([tag._id for tag in self.tag_for_node_item.values()]) return criterion def unchecked_to_forbidden_tags_in_criterion(self, criterion): for item, tag in self.tag_for_node_item.iteritems(): if item.checkState(0) == QtCore.Qt.Unchecked: criterion._tag_ids_forbidden.add(tag._id) return criterion def save_criterion(self): self.saved_criterion = DefaultCriterion(self.component_manager) self.checked_to_active_tags_in_criterion(self.saved_criterion) # We also save the unchecked tags as this will allow us to identify # any new tags created afterwards. self.unchecked_to_forbidden_tags_in_criterion(self.saved_criterion) # Now we've saved the checked state of the tree. # Saving and restoring the selected state is less trivial, because # in the case of trees, the model indexes have parents which become # invalid when creating the widget. # The solution would be to save tags and reselect those in the new # widget. def restore_criterion(self): new_criterion = DefaultCriterion(self.component_manager) for tag in self.database().tags(): if tag._id in self.saved_criterion._tag_ids_active or \ tag._id not in self.saved_criterion._tag_ids_forbidden: # Second case deals with recently added tag. new_criterion._tag_ids_active.add(tag._id) self.display(new_criterion) def store_tree_state(self): """Store which nodes are collapsed. """ collapsed = [] iterator = QtGui.QTreeWidgetItemIterator(self.tree_wdgt) while iterator.value(): if not iterator.value().isExpanded(): collapsed.append(unicode(iterator.value().text(1))) iterator += 1 self.config()["tag_tree_wdgt_state"] = collapsed def rename_node(self, node, new_name): if self.acquire_database: self.acquire_database() self.save_criterion() self.store_tree_state() self.tag_tree.rename_node(unicode(node), unicode(new_name)) self.restore_criterion() self.tags_changed_signal.emit() def delete_nodes(self, nodes): if self.acquire_database: self.acquire_database() self.save_criterion() self.store_tree_state() for node in nodes: self.tag_tree.delete_subtree(unicode(node)) self.restore_criterion() self.tags_changed_signal.emit() def redraw_node(self, node): """When renaming a tag to the same name, we need to redraw the node to show the card count again. """ # We do the redrawing in a rather hackish way now, simply by # recreating the widget. Could be sped up, but at the expense of more # complicated code. self.save_criterion() self.restore_criterion() def rebuild(self): """To be called when external events invalidate the tag tree, e.g. due to edits in the card browser widget. """ self.save_criterion() self.store_tree_state() self.tag_tree = TagTree(self.component_manager) self.restore_criterion() def closeEvent(self, event): self.store_tree_state()
class TestMemImport(MnemosyneTest): def setup(self): self.initialise_data_dir() self.mnemosyne = Mnemosyne(upload_science_logs=False, interested_in_old_reps=True, asynchronous_database=True) self.mnemosyne.components.insert( 0, ("mnemosyne.libmnemosyne.gui_translators.gettext_gui_translator", "GetTextGuiTranslator")) self.mnemosyne.gui_for_component["ScheduledForgottenNew"] = \ [("mnemosyne_test", "TestReviewWidget")] self.mnemosyne.components.append(\ ("test_mem_import", "Widget")) self.mnemosyne.components.append(\ ("test_mem_import", "MyImportDialog")) self.mnemosyne.initialise(os.path.abspath("dot_test"), automatic_upgrades=False) self.review_controller().reset() def mem_importer(self): for format in self.mnemosyne.component_manager.all("file_format"): if format.__class__.__name__ == "Mnemosyne1Mem": return format @mock.patch( "mnemosyne.libmnemosyne.file_formats.mnemosyne1_mem.open", mock.Mock(side_effect=[FileNotFoundError, IndexError("Mocked Error")])) def test_exceptions(self): filename = os.path.join(os.getcwd(), "tests", "files", "nothere.mem") self.mem_importer().do_import(filename) assert last_error.startswith("Unable to open") self.mem_importer().do_import("name_does_not_matter") assert last_error.strip().endswith("IndexError: Mocked Error") @MnemosyneTest.set_timezone_utc def test_card_type_1(self): filename = os.path.join(os.getcwd(), "tests", "files", "1sided.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 1 card = self.review_controller().card assert card.grade == 2 assert card.easiness == 2.5 assert card.acq_reps == 1 assert card.ret_reps == 0 assert card.lapses == 0 assert card.acq_reps_since_lapse == 1 assert card.ret_reps_since_lapse == 0 assert [tag.name for tag in card.tags] == ["__UNTAGGED__"] assert card.last_rep == 1247529600 assert card.next_rep == 1247616000 assert card.id == "9cff728f" def test_card_type_1_unseen(self): filename = os.path.join(os.getcwd(), "tests", "files", "1sided_unseen.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 1 card = self.review_controller().card assert card.grade == -1 assert card.easiness == 2.5 assert card.acq_reps == 0 assert card.ret_reps == 0 assert card.lapses == 0 assert card.acq_reps_since_lapse == 0 assert card.ret_reps_since_lapse == 0 assert card.last_rep == -1 assert card.next_rep == -1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 def test_card_type_1_edited(self): filename = os.path.join(os.getcwd(), "tests", "files", "1sided.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 1 card = self.review_controller().card assert card.id == "9cff728f" assert "question" in card.question() filename = os.path.join(os.getcwd(), "tests", "files", "1sided.mem") self.mem_importer().do_import(filename) assert last_error.startswith( "These cards seem to have been imported before") def test_card_type_2(self): filename = os.path.join(os.getcwd(), "tests", "files", "2sided.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 2 card_1 = self.review_controller().card assert "question" in card_1.question() assert "answer" in card_1.answer() cards = self.database().cards_from_fact(card_1.fact) if cards[0] == card_1: card_2 = cards[1] else: card_2 = cards[0] assert "question" in card_2.answer() assert "answer" in card_2.question() assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 2 def test_card_type_3(self): filename = os.path.join(os.getcwd(), "tests", "files", "3sided.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 2 card_1 = self.review_controller().card assert card_1.fact.data == {"f": "f", "p_1": "p", "m_1": "t"} assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 2 def test_card_type_3_corrupt(self): filename = os.path.join(os.getcwd(), "tests", "files", "3sided_corrupt.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 2 card_1 = self.review_controller().card assert card_1.fact.data == {"f": "f", "m_1": "t"} assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 2 def test_card_type_3_missing(self): filename = os.path.join(os.getcwd(), "tests", "files", "3sided_missing.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 1 card_1 = self.review_controller().card assert card_1.fact.data == {"f": "t", "b": "f\np"} assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 def test_media(self): os.mkdir(os.path.join(os.getcwd(), "tests", "files", "figs")) os.mkdir(os.path.join(os.getcwd(), "tests", "files", "figs", "figs")) figures = [\ os.path.join(os.getcwd(), "tests", "files", "a.png"), os.path.join(os.getcwd(), "tests", "files", "figs", "a.png"), os.path.join(os.getcwd(), "tests", "files", "figs", "figs", "a.png")] for filename in figures: open(filename, "w") filename = os.path.join(os.getcwd(), "tests", "files", "media.mem") self.mem_importer().do_import(filename) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "a.png")) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "figs", "a.png")) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "figs", "a.png")) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_MEDIA_FILE, )).fetchone()[0] == 3 def test_media_missing(self): os.mkdir(os.path.join(os.getcwd(), "tests", "files", "figs")) os.mkdir(os.path.join(os.getcwd(), "tests", "files", "figs", "figs")) figures = [\ os.path.join(os.getcwd(), "tests", "files", "a.png"), os.path.join(os.getcwd(), "tests", "files", "figs", "a.png")] for filename in figures: open(filename, "w") filename = os.path.join(os.getcwd(), "tests", "files", "media.mem") self.mem_importer().do_import(filename) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "a.png")) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "figs", "a.png")) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_MEDIA_FILE, )).fetchone()[0] == 2 def test_media_missing_2(self): filename = os.path.join(os.getcwd(), "tests", "files", "media.mem") self.mem_importer().do_import(filename) assert not os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "a.png")) assert not os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "figs", "a.png")) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_MEDIA_FILE, )).fetchone()[0] == 0 def test_media_slashes(self): os.mkdir(os.path.join(os.getcwd(), "tests", "files", "figs")) os.mkdir(os.path.join(os.getcwd(), "tests", "files", "figs", "figs")) figures = [\ os.path.join(os.getcwd(), "tests", "files", "a.png"), os.path.join(os.getcwd(), "tests", "files", "figs", "a.png"), os.path.join(os.getcwd(), "tests", "files", "figs", "figs", "a.png")] for filename in figures: open(filename, "w") filename = os.path.join(os.getcwd(), "tests", "files", "media_slashes.mem") self.mem_importer().do_import(filename) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "a.png")) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "figs", "a.png")) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "figs", "a.png")) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_MEDIA_FILE, )).fetchone()[0] == 3 def test_media_quotes(self): filename = os.path.join(os.getcwd(), "tests", "files", "basedir_media", "default.mem") self.mem_importer().do_import(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_MEDIA_FILE, )).fetchone()[0] == 1 def test_sound(self): os.mkdir(os.path.join(\ os.getcwd(), "tests", "files", "soundfiles")) soundname = os.path.join(os.path.join(\ os.getcwd(), "tests", "files", "soundfiles", "a.ogg")) open(soundname, "w") filename = os.path.join(os.getcwd(), "tests", "files", "sound.mem") self.mem_importer().do_import(filename) assert os.path.exists(os.path.join(\ os.path.abspath("dot_test"), "default.db_media", "soundfiles", "a.ogg")) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_MEDIA_FILE, )).fetchone()[0] == 1 self.review_controller().reset() card = self.review_controller().card assert card.fact["f"] == """<audio src="soundfiles/a.ogg">""" def test_map(self): filename = os.path.join(os.getcwd(), "tests", "files", "map.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.database().card_count() == 2 card = self.review_controller().card assert card.fact["loc"] == "<b>Drenthe</b>" assert card.fact["marked"] == \ """<img src_missing="maps/Netherlands-Provinces/Drenthe.png">""" assert card.fact["blank"] == \ """<img src_missing="maps/Netherlands-Provinces/Netherlands-Provinces.png">""" def test_dups(self): filename = os.path.join(os.getcwd(), "tests", "files", "dups.mem") self.mem_importer().do_import(filename) self.review_controller().reset() assert self.review_controller().card.fact["loc"] == \ """<b>Freistaat Th\xfcringen (Free State of Thuringia)</b>""" assert self.review_controller().card.tag_string( ) == "Germany: States, MISSING_MEDIA" def test_logs_new_1(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "new_1.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 10 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='9525224f'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='9525224f'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select scheduled_interval from log where event_type=? and object_id='9525224f' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == (6)*60*60*24 assert self.database().con.execute(\ """select actual_interval from log where event_type=? and object_id='9525224f' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 0 # This is an artificial log. timestamp = self.database().con.execute(\ """select timestamp from log where event_type=? and object_id='9525224f' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] next_rep = self.database().con.execute(\ """select next_rep from log where event_type=? and object_id='9525224f' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] assert next_rep - timestamp == (14 - 3) * 60 * 60 * 24 assert self.database().con.execute(\ "select count() from log").fetchone()[0] == 25 assert self.database().con.execute(\ "select acq_reps from log where event_type=? order by _id desc limit 1", (EventTypes.LOADED_DATABASE, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select ret_reps from log where event_type=? order by _id desc limit 1", (EventTypes.LOADED_DATABASE, )).fetchone()[0] == 7 assert self.database().con.execute(\ "select lapses from log where event_type=? order by _id desc limit 1", (EventTypes.LOADED_DATABASE, )).fetchone()[0] == 336 assert self.database().con.execute(\ "select acq_reps from log where event_type=? order by _id desc limit 1", (EventTypes.SAVED_DATABASE, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select ret_reps from log where event_type=? order by _id desc limit 1", (EventTypes.SAVED_DATABASE, )).fetchone()[0] == 12 assert self.database().con.execute(\ "select lapses from log where event_type=? order by _id desc limit 1", (EventTypes.SAVED_DATABASE, )).fetchone()[0] == 341 def test_logs_new_2(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "new_2.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='8da62cfb'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='8da62cfb'", (EventTypes.REPETITION, )).fetchone()[0] == 1 def test_logs_new_3(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "new_3.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 4 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='5106b621'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='5106b621'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select acq_reps from log where event_type=? and object_id='5106b621' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select acq_reps_since_lapse from log where event_type=? and object_id='5106b621' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 def test_logs_new_4(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "new_4.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 2 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='b7601e0c'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select ret_reps from log where event_type=? and object_id='b7601e0c'", (EventTypes.REPETITION, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='b7601e0c'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select acq_reps from log where event_type=? and object_id='b7601e0c' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select ret_reps from log where event_type=? and object_id='b7601e0c' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select acq_reps_since_lapse from log where event_type=? and object_id='b7601e0c' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 def test_logs_new_5(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "new_5.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 2 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='9c8ce28e-1a4b-4148-8287-b8a7790d86d0.1.1'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select ret_reps from log where event_type=? and object_id='9c8ce28e-1a4b-4148-8287-b8a7790d86d0.1.1'", (EventTypes.REPETITION, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='9c8ce28e-1a4b-4148-8287-b8a7790d86d0.1.1'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select acq_reps from log where event_type=? and object_id='9c8ce28e-1a4b-4148-8287-b8a7790d86d0.1.1' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 2 assert self.database().con.execute(\ """select ret_reps from log where event_type=? and object_id='9c8ce28e-1a4b-4148-8287-b8a7790d86d0.1.1' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 0 assert self.database().con.execute(\ """select acq_reps_since_lapse from log where event_type=? and object_id='9c8ce28e-1a4b-4148-8287-b8a7790d86d0.1.1' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 2 assert self.database().con.execute(\ """select object_id from log where event_type=?""", (EventTypes.STARTED_SCHEDULER, )).fetchone()[0] == "SM2 Mnemosyne" def test_logs_new_6(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "new_6.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 2 sql_res = self.database().con.execute(\ "select * from log where event_type=? and object_id='4c53e29a-f9e9-498b-8beb-d3a494f61bca.1.1'", (EventTypes.REPETITION, )).fetchone() assert sql_res[4] == 5 assert sql_res[5] == 2.5 assert sql_res[6] == 1 assert sql_res[7] == 0 assert sql_res[8] == 0 assert sql_res[9] == 1 assert sql_res[10] == 0 assert sql_res[11] == 0 assert sql_res[12] == 0 assert sql_res[14] - sql_res[2] == 345600 assert sql_res[13] == 0 sql_res = self.database().con.execute(\ """select * from log where event_type=? and object_id='4c53e29a-f9e9-498b-8beb-d3a494f61bca.1.1' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone() assert sql_res[4] == 2 assert sql_res[5] == 2.5 assert sql_res[6] == 1 assert sql_res[7] == 1 assert sql_res[8] == 0 assert sql_res[9] == 1 assert sql_res[10] == 1 assert sql_res[11] == 302986 assert sql_res[12] == 10 assert sql_res[14] - sql_res[2] == 475774 assert sql_res[13] == 1 def test_logs_imported_1(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "imported_1.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 3 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='f5d9bbe7'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select ret_reps from log where event_type=? and object_id='f5d9bbe7'", (EventTypes.REPETITION, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='f5d9bbe7'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select acq_reps from log where event_type=? and object_id='f5d9bbe7' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ """select ret_reps from log where event_type=? and object_id='f5d9bbe7' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 2 assert self.database().con.execute(\ """select acq_reps_since_lapse from log where event_type=? and object_id='f5d9bbe7' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 1 def test_logs_imported_2(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "imported_2.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select acq_reps from log where event_type=? and object_id='14670f10'", (EventTypes.REPETITION, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select ret_reps from log where event_type=? and object_id='14670f10'", (EventTypes.REPETITION, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select acq_reps_since_lapse from log where event_type=? and object_id='14670f10'", (EventTypes.REPETITION, )).fetchone()[0] == 1 def test_logs_imported_3(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "imported_3.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 def test_restored_1(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "restored_1.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 1 sql_res = self.database().con.execute(\ "select * from log where event_type=?", (EventTypes.REPETITION, )).fetchone() assert sql_res[4] == 1 assert sql_res[5] == 2.36 assert sql_res[6] == 23 assert sql_res[7] == 8 assert sql_res[8] == 2 assert sql_res[9] == 0 assert sql_res[10] == 0 assert sql_res[11] == 89 * 24 * 60 * 60 assert sql_res[12] == 0 # No last rep data. assert sql_res[14] - sql_res[2] == 0 assert sql_res[13] == 5 def test_restored_2(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "restored_2.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 def test_logs_act_interval(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "actinterval_1.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ """select actual_interval from log where event_type=? and object_id='f1300e5a' order by _id desc limit 1""", (EventTypes.REPETITION, )).fetchone()[0] == 5 def test_logs_deleted(self): self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "delete_1.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.DELETED_CARD, )).fetchone()[0] == 1 def test_logs_corrupt_1(self): # Wrong data, missing creation event. self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "corrupt_1.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where object_id=?", ("4b59b830", )).fetchone()[0] == 3 def test_logs_corrupt_2(self): # Wrong data, isolated deletion event. self.database().update_card_after_log_import = (lambda x, y, z: 0) self.database().before_1x_log_import() filename = os.path.join(os.getcwd(), "tests", "files", "corrupt_2.txt") ScienceLogParser(self.database()).parse(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select count() from log where object_id=?", ("4b59b830", )).fetchone()[0] == 0 def test_two_mem_files_sharing_same_logs(self): filename = os.path.join(os.getcwd(), "tests", "files", "basedir_2_mem", "deck1.mem") self.mem_importer().do_import(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 1 filename = os.path.join(os.getcwd(), "tests", "files", "basedir_2_mem", "deck2.mem") self.mem_importer().do_import(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 3 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 2 card = self.database().card("4c8fff73", is_id_internal=False) assert self.database().average_thinking_time(card) == 1.5 assert self.database().total_thinking_time(card) == 3.0 assert self.database().card_count_for_grade(0, active_only=True) == 2 tag = self.database().get_or_create_tag_with_name("666") assert self.database().card_count_for_grade_and_tag( 0, tag, active_only=True) == 0 from mnemosyne.libmnemosyne.statistics_pages.grades import Grades page = Grades(component_manager=self.mnemosyne.component_manager) from mnemosyne.libmnemosyne.tag_tree import TagTree self.tag_tree = TagTree(self.mnemosyne.component_manager, count_cards=False) self.nodes = self.tag_tree.nodes() for index, node in enumerate(self.nodes): if node == "666": page.prepare_statistics(index) assert page.y == [0, 0, 0, 0, 0, 0, 0] page.prepare_statistics(-1) assert page.y == [0, 2, 0, 0, 0, 0, 0] def test_bz2(self): filename = os.path.join(os.getcwd(), "tests", "files", "basedir_bz2", "default.mem") self.mem_importer().do_import(filename) assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.REPETITION, )).fetchone()[0] == 0 assert self.database().con.execute(\ "select count() from log where event_type=?", (EventTypes.ADDED_CARD, )).fetchone()[0] == 1 assert self.database().con.execute(\ "select count() from log where object_id=?", ("82f2ed0d", )).fetchone()[0] == 0 def test_sch(self): self.controller().show_import_file_dialog() assert self.database().card_count_scheduled_n_days_ago(0) == 1 def test_upgrade(self): old_data_dir = os.path.join(os.getcwd(), "tests", "files", "basedir_bz2") from mnemosyne.libmnemosyne.upgrades.upgrade1 import Upgrade1 Upgrade1(self.mnemosyne.component_manager).upgrade_from_old_data_dir( old_data_dir) assert self.config()["dvipng"].rstrip() == \ "dvipng -D 300 -T tight tmp.dvi\necho" assert "14pt" in self.config()["latex_preamble"] assert self.config()["user_id"] == "f3fb13c7" assert self.log().log_index_of_last_upload() == 2 assert os.path.exists( os.path.join(old_data_dir, "DIRECTORY_NO_LONGER_USED_BY_MNEMOSYNE2")) assert os.path.exists( os.path.join(self.mnemosyne.config().data_dir, "history", "a_2.bz2")) log = open(os.path.join(self.mnemosyne.config().data_dir, "log.txt")) assert log.readline().strip() == \ "2005-11-01 09:29:08 : Imported item 82f2ed0d 0 0 0 0 0" def teardown(self): filename = os.path.join(os.getcwd(), "tests", "files", "basedir_bz2", "DIRECTORY_NO_LONGER_USED_BY_MNEMOSYNE2") if os.path.exists(filename): os.remove(filename) filename = os.path.join(os.getcwd(), "tests", "files", "a.png") if os.path.exists(filename): os.remove(filename) filename = os.path.join(os.getcwd(), "tests", "files", "a.ogg") if os.path.exists(filename): os.remove(filename) dirname = os.path.join(os.getcwd(), "tests", "files", "figs") if os.path.exists(dirname): shutil.rmtree(dirname) dirname = os.path.join(os.getcwd(), "tests", "files", "soundfiles") if os.path.exists(dirname): shutil.rmtree(dirname) MnemosyneTest.teardown(self)
class TagsTreeWdgt(Component, QtWidgets.QWidget): """Displays all the tags in a tree together with check boxes. """ tags_changed_signal = QtCore.pyqtSignal() def __init__(self, acquire_database=None, **kwds): super().__init__(**kwds) self.layout = QtWidgets.QVBoxLayout(self) self.tree_wdgt = QtWidgets.QTreeWidget(self) self.tree_wdgt.setColumnCount(2) self.tree_wdgt.setColumnHidden(1, True) self.tree_wdgt.setColumnHidden(NODE, True) self.tree_wdgt.setHeaderHidden(True) self.tree_wdgt.setSelectionMode(\ QtWidgets.QAbstractItemView.ExtendedSelection) self.delegate = TagDelegate(\ component_manager=kwds["component_manager"], parent=self) self.tree_wdgt.setItemDelegate(self.delegate) self.delegate.rename_node.connect(self.rename_node) self.delegate.redraw_node.connect(self.redraw_node) self.layout.addWidget(self.tree_wdgt) self.tree_wdgt.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tree_wdgt.customContextMenuRequested.connect(\ self.context_menu) self.acquire_database = acquire_database def selected_nodes_which_can_be_renamed(self): nodes = [] for index in self.tree_wdgt.selectedIndexes(): node_index = \ index.model().index(index.row(), NODE, index.parent()) node = index.model().data(node_index) if node in self.nodes_which_can_be_renamed: nodes.append(node) return nodes def selected_nodes_which_can_be_deleted(self): nodes = [] for index in self.tree_wdgt.selectedIndexes(): node_index = \ index.model().index(index.row(), NODE, index.parent()) node = index.model().data(node_index) if node in self.nodes_which_can_be_deleted: nodes.append(node) return nodes def context_menu(self, point): menu = QtWidgets.QMenu(self) to_rename = self.selected_nodes_which_can_be_renamed() if len(to_rename) >= 1: rename_action = QtWidgets.QAction(_("&Rename"), menu) rename_action.triggered.connect(self.menu_rename) rename_action.setShortcut(QtCore.Qt.Key_Enter) menu.addAction(rename_action) if len(to_rename) > 1: rename_action.setEnabled(False) to_delete = self.selected_nodes_which_can_be_deleted() if len(to_delete) >= 1: delete_action = QtWidgets.QAction(_("&Delete"), menu) delete_action.triggered.connect(self.menu_delete) delete_action.setShortcut(QtGui.QKeySequence.Delete) menu.addAction(delete_action) if len(to_delete) + len(to_rename) >= 1: menu.exec_(self.tree_wdgt.mapToGlobal(point)) def keyPressEvent(self, event): if event.key() in [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]: self.menu_rename() elif event.key() in [QtCore.Qt.Key_Delete, QtCore.Qt.Key_Backspace]: self.menu_delete() else: QtWidgets.QWidget.keyPressEvent(self, event) def menu_rename(self): nodes = self.selected_nodes_which_can_be_renamed() # If there are tags selected, this means that we could only have got # after pressing return on an actual edit, due to our custom # 'keyPressEvent'. We should not continue in that case. if len(nodes) == 0: return # We display the full node (i.e. all levels including ::), so that # the hierarchy can be changed upon editing. from mnemosyne.pyqt_ui.ui_rename_tag_dlg import Ui_RenameTagDlg class RenameDlg(QtWidgets.QDialog, Ui_RenameTagDlg): def __init__(self, old_tag_name): super().__init__() self.setupUi(self) self.tag_name.setText(\ old_tag_name.replace("::" + _("Untagged"), "" )) old_tag_name = nodes[0] dlg = RenameDlg(old_tag_name) if dlg.exec_() == QtWidgets.QDialog.Accepted: self.rename_node(nodes[0], dlg.tag_name.text()) def menu_delete(self): nodes = self.selected_nodes_which_can_be_deleted() if len(nodes) == 0: return if len(nodes) > 1: question = _("Delete these tags? Cards with these tags will not be deleted.") else: question = _("Delete this tag? Cards with this tag will not be deleted.") answer = self.main_widget().show_question\ (question, _("&OK"), _("&Cancel"), "") if answer == 1: # Cancel. return self.delete_nodes(nodes) def create_tree(self, tree, qt_parent): for node in tree: node_name = "%s (%d)" % \ (self.tag_tree.display_name_for_node[node], self.tag_tree.card_count_for_node[node]) node_item = QtWidgets.QTreeWidgetItem(qt_parent, [node_name, node], 0) node_item.setFlags(node_item.flags() | \ QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate) if node not in ["__ALL__", "__UNTAGGED__"] and \ not "::" + _("Untagged") in node: node_item.setFlags(node_item.flags() | \ QtCore.Qt.ItemIsEditable) self.nodes_which_can_be_renamed.append(node) self.nodes_which_can_be_deleted.append(node) if node in self.tag_tree.tag_for_node: # Since node_item seems mutable, we cannot use a dict. self.node_items.append(node_item) self.tag_for_node_item.append(self.tag_tree.tag_for_node[node]) node_item.setData(NODE, QtCore.Qt.DisplayRole, QtCore.QVariant(node)) self.create_tree(tree=self.tag_tree[node], qt_parent=node_item) def display(self, criterion=None): # Create criterion if needed. if criterion is None: criterion = DefaultCriterion(self.component_manager) for tag in self.database().tags(): criterion._tag_ids_active.add(tag._id) # Create tree. self.tag_tree = TagTree(self.component_manager) self.tree_wdgt.clear() self.node_items = [] self.tag_for_node_item = [] self.nodes_which_can_be_deleted = [] self.nodes_which_can_be_renamed = [] node = "__ALL__" node_name = "%s (%d)" % (self.tag_tree.display_name_for_node[node], self.tag_tree.card_count_for_node[node]) root = self.tag_tree[node] root_item = QtWidgets.QTreeWidgetItem(\ self.tree_wdgt, [node_name, node], 0) root_item.setFlags(root_item.flags() | \ QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate) root_item.setCheckState(0, QtCore.Qt.Checked) self.create_tree(self.tag_tree[node], qt_parent=root_item) # Set forbidden tags. if len(criterion._tag_ids_forbidden): for i in range(len(self.node_items)): node_item = self.node_items[i] tag = self.tag_for_node_item[i] if tag._id in criterion._tag_ids_forbidden: node_item.setCheckState(0, QtCore.Qt.Checked) else: node_item.setCheckState(0, QtCore.Qt.Unchecked) # Set active tags. else: # We first set all the tags inactive. We cannot do this in the # second branch of the upcoming 'if' statement, as then an # inactive parent tag coming later in the list will deactivate # active child tags coming earlier in the list. for node_item in self.node_items: node_item.setCheckState(0, QtCore.Qt.Unchecked) for i in range(len(self.node_items)): node_item = self.node_items[i] tag = self.tag_for_node_item[i] if tag._id in criterion._tag_ids_active: node_item.setCheckState(0, QtCore.Qt.Checked) # Restore state of the tree. self.tree_wdgt.expandAll() collapsed = self.config()["tag_tree_wdgt_state"] if collapsed is None: collapsed = [] iterator = QtWidgets.QTreeWidgetItemIterator(self.tree_wdgt) while iterator.value(): if iterator.value().text(1) in collapsed: iterator.value().setExpanded(False) iterator += 1 def checked_to_active_tags_in_criterion(self, criterion): for i in range(len(self.node_items)): tag = self.tag_for_node_item[i] if self.node_items[i].checkState(0) == QtCore.Qt.Checked: criterion._tag_ids_active.add(tag._id) criterion._tag_ids_forbidden = set() return criterion def checked_to_forbidden_tags_in_criterion(self, criterion): for i in range(len(self.node_items)): tag = self.tag_for_node_item[i] if self.node_items[i].checkState(0) == QtCore.Qt.Checked: criterion._tag_ids_forbidden.add(tag._id) criterion._tag_ids_active = \ set([tag._id for tag in self.tag_for_node_item]) return criterion def unchecked_to_forbidden_tags_in_criterion(self, criterion): for i in range(len(self.node_items)): tag = self.tag_for_node_item[i] if self.node_items[i].checkState(0) == QtCore.Qt.Unchecked: criterion._tag_ids_forbidden.add(tag._id) return criterion def save_criterion(self): self.saved_criterion = DefaultCriterion(self.component_manager) self.checked_to_active_tags_in_criterion(self.saved_criterion) # We also save the unchecked tags as this will allow us to identify # any new tags created afterwards. self.unchecked_to_forbidden_tags_in_criterion(self.saved_criterion) # Now we've saved the checked state of the tree. # Saving and restoring the selected state is less trivial, because # in the case of trees, the model indexes have parents which become # invalid when creating the widget. # The solution would be to save tags and reselect those in the new # widget. def restore_criterion(self): new_criterion = DefaultCriterion(self.component_manager) for tag in self.database().tags(): if tag._id in self.saved_criterion._tag_ids_active or \ tag._id not in self.saved_criterion._tag_ids_forbidden: # Second case deals with recently added tag. new_criterion._tag_ids_active.add(tag._id) self.display(new_criterion) def store_tree_state(self): """Store which nodes are collapsed. """ collapsed = [] iterator = QtWidgets.QTreeWidgetItemIterator(self.tree_wdgt) while iterator.value(): if not iterator.value().isExpanded(): collapsed.append(iterator.value().text(1)) iterator += 1 self.config()["tag_tree_wdgt_state"] = collapsed def rename_node(self, node, new_name): if self.acquire_database: self.acquire_database() self.save_criterion() self.store_tree_state() self.tag_tree.rename_node(node, new_name) self.restore_criterion() self.tags_changed_signal.emit() def delete_nodes(self, nodes): if self.acquire_database: self.acquire_database() self.save_criterion() self.store_tree_state() for node in nodes: self.tag_tree.delete_subtree(node) self.restore_criterion() self.tags_changed_signal.emit() def redraw_node(self, node): """When renaming a tag to the same name, we need to redraw the node to show the card count again. """ # We do the redrawing in a rather hackish way now, simply by # recreating the widget. Could be sped up, but at the expense of more # complicated code. self.save_criterion() self.restore_criterion() def rebuild(self): """To be called when external events invalidate the tag tree, e.g. due to edits in the card browser widget. """ self.save_criterion() self.store_tree_state() self.tag_tree = TagTree(self.component_manager) self.restore_criterion() def closeEvent(self, event): self.store_tree_state()
class TestTagTree(MnemosyneTest): def test_1(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=[])[0] self.controller().save_file() from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(list(self.tree.keys())) == 2 assert self.tree['__ALL__'] == ['__UNTAGGED__'] def test_2(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag_1"])[0] self.controller().save_file() from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(list(self.tree.keys())) == 3 assert self.tree['__ALL__'] == ['tag_1', '__UNTAGGED__'] def test_3(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 assert self.tree.nodes() == \ ["a", "a::Untagged", "a::b", "a::c", "b", "b::c", "b::c::d", "Z", "__UNTAGGED__"] def test_4(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.rename_node("Z", "Z::Z") self.tree.rename_node("b::c", "b::cc") assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["Z::Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::cc"] == 1 assert self.tree.card_count_for_node["b::cc::d"] == 1 def test_5(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.rename_node("b::c", "b") assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::d"] == 1 def test_rename_to_existing_tag(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag2"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.database().tags()) == 3 self.tree.rename_node("tag1", "tag2") assert self.tree.card_count_for_node["tag2"] == 2 assert len(self.database().tags()) == 2 def test_rename_to_existing_tag_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards( fact_data, card_type, grade=-1, tag_names=["Xx::vb::test", "Xx::aa::vb::test"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.database().tags()) == 3 self.tree.rename_node("Xx::aa::vb::test", "Xx::vb::test") assert self.tree.card_count_for_node["Xx::vb::test"] == 1 assert "Xx::aa::vb::test" not in self.tree.card_count_for_node assert "," not in self.database().con.execute(\ "select tags from cards where _id=?", (card._id,)).fetchone()[0] assert self.database().con.execute(\ "select count() from tags_for_card").fetchone()[0] == 1 assert len(self.database().tags()) == 2 def test_rename_to_empty(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag2"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("tag1", "") assert self.tree.card_count_for_node["__UNTAGGED__"] == 1 assert len(self.database().tags()) == 2 def test_rename_to_empty_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "") assert self.tree.card_count_for_node["tag1"] == 1 assert len(self.database().tags()) == 2 def test_rename_to_empty_3(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "") assert self.tree.card_count_for_node["__UNTAGGED__"] == 1 assert self.tree.card_count_for_node["tag1"] == 1 assert len(self.database().tags()) == 2 def test_rename_to_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "__UNTAGGED__") assert "__UNTAGGED__" in self.tree.card_count_for_node def test_delete(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.delete_subtree("b::c") card = self.database().card(card._id, is_id_internal=True) assert card.tag_string() == "" self.database().con.execute("select tags from cards where _id=?", (card._id, )).fetchone()[0] == "" assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["__UNTAGGED__"] == 1 assert "b" not in self.tree.card_count_for_node assert "b::c" not in self.tree.card_count_for_node assert "b::c::d" not in self.tree.card_count_for_node def test_delete_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.delete_subtree("b::c") card = self.database().card(card._id, is_id_internal=True) assert card.tag_string() == "" self.database().con.execute("select tags from cards where _id=?", (card._id, )).fetchone()[0] == "b" assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert "b" not in self.tree.card_count_for_node assert "__UNTAGGED__" in self.tree.card_count_for_node assert "b::c" not in self.tree.card_count_for_node assert "b::c::d" not in self.tree.card_count_for_node def test_count_1(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 2 def test_count_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["X::a"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["X::a", "X::b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 2 assert self.tree.card_count_for_node["X"] == 2 def test_delete_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["forbidden"])[0] assert self.database().active_count() == 1 c = DefaultCriterion(self.mnemosyne.component_manager) c.deactivated_card_type_fact_view_ids = set() c._tag_ids_active = set( [self.database().get_or_create_tag_with_name("active")._id, 1]) c._tag_ids_forbidden = set( [self.database().get_or_create_tag_with_name("forbidden")._id]) self.database().set_current_criterion(c) assert self.database().active_count() == 0 from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.delete_subtree("forbidden") assert self.database().active_count() == 1
class TestTagTree(MnemosyneTest): def test_1(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=[])[0] self.controller().save_file() from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.tree.keys()) == 2 assert self.tree['__ALL__'] == [u'__UNTAGGED__'] def test_2(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag_1"])[0] self.controller().save_file() from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.tree.keys()) == 3 assert self.tree['__ALL__'] == [u'tag_1', u'__UNTAGGED__'] def test_3(self): fact_data = {"f": "question", "b": "answer"} card_type = self.card_type_with_id("1") card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 def test_4(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.rename_node("Z", "Z::Z") self.tree.rename_node("b::c", "b::cc") assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["Z::Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::cc"] == 1 assert self.tree.card_count_for_node["b::cc::d"] == 1 def test_5(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.rename_node("b::c", "b") assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::d"] == 1 def test_rename_to_existing_tag(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag2"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.database().tags()) == 3 self.tree.rename_node("tag1", "tag2") assert self.tree.card_count_for_node["tag2"] == 2 assert len(self.database().tags()) == 2 def test_rename_to_existing_tag_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Xx::vb::test", "Xx::aa::vb::test"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert len(self.database().tags()) == 3 self.tree.rename_node("Xx::aa::vb::test", "Xx::vb::test") assert self.tree.card_count_for_node["Xx::vb::test"] == 1 assert "Xx::aa::vb::test" not in self.tree.card_count_for_node assert "," not in self.database().con.execute(\ "select tags from cards where _id=?", (card._id,)).fetchone()[0] assert self.database().con.execute(\ "select count() from tags_for_card").fetchone()[0] == 1 assert len(self.database().tags()) == 2 def test_rename_to_empty(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["tag2"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("tag1", "") assert self.tree.card_count_for_node["__UNTAGGED__"] == 1 assert len(self.database().tags()) == 2 def test_rename_to_empty_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "") assert self.tree.card_count_for_node["tag1"] == 1 assert len(self.database().tags()) == 2 def test_rename_to_empty_3(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "") assert self.tree.card_count_for_node["__UNTAGGED__"] == 1 assert self.tree.card_count_for_node["tag1"] == 1 assert len(self.database().tags()) == 2 def test_rename_to_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A::tag1"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["A"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.rename_node("A", "__UNTAGGED__") assert "__UNTAGGED__" in self.tree.card_count_for_node def test_delete(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.delete_subtree("b::c") card = self.database().card(card._id, is_id_internal=True) assert card.tag_string() == "" self.database().con.execute("select tags from cards where _id=?", (card._id, )).fetchone()[0] == "" assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["__UNTAGGED__"] == 1 assert "b" not in self.tree.card_count_for_node assert "b::c" not in self.tree.card_count_for_node assert "b::c::d" not in self.tree.card_count_for_node def test_delete_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a"])[0] fact_data = {"f": "question2", "b": "answer2"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["Z"])[0] fact_data = {"f": "question3", "b": "answer3"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["a::c"])[0] fact_data = {"f": "question5", "b": "answer5"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b::c::d", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert self.tree.card_count_for_node["b::c"] == 1 assert self.tree.card_count_for_node["b::c::d"] == 1 self.tree.delete_subtree("b::c") card = self.database().card(card._id, is_id_internal=True) assert card.tag_string() == "b" self.database().con.execute("select tags from cards where _id=?", (card._id, )).fetchone()[0] == "b" assert self.tree.card_count_for_node["__ALL__"] == 5 assert self.tree.card_count_for_node["a"] == 3 assert self.tree.card_count_for_node["Z"] == 1 assert self.tree.card_count_for_node["a::b"] == 1 assert self.tree.card_count_for_node["a::c"] == 1 assert self.tree.card_count_for_node["b"] == 1 assert "__UNTAGGED__" in self.tree.card_count_for_node assert "b::c" not in self.tree.card_count_for_node assert "b::c::d" not in self.tree.card_count_for_node def test_count_1(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["b", "b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 2 def test_count_2(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question4", "b": "answer4"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["X::a"])[0] fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["X::a", "X::b"])[0] from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) assert self.tree.card_count_for_node["__ALL__"] == 2 assert self.tree.card_count_for_node["X"] == 2 def test_delete_forbidden(self): card_type = self.card_type_with_id("1") fact_data = {"f": "question", "b": "answer"} card = self.controller().create_new_cards(fact_data, card_type, grade=-1, tag_names=["forbidden"])[0] assert self.database().active_count() == 1 c = DefaultCriterion(self.mnemosyne.component_manager) c.deactivated_card_type_fact_view_ids = set() c._tag_ids_active = set([self.database().get_or_create_tag_with_name("active")._id, 1]) c._tag_ids_forbidden = set([self.database().get_or_create_tag_with_name("forbidden")._id]) self.database().set_current_criterion(c) assert self.database().active_count() == 0 from mnemosyne.libmnemosyne.tag_tree import TagTree self.tree = TagTree(self.mnemosyne.component_manager) self.tree.delete_subtree("forbidden") assert self.database().active_count() == 1