class CardModel(QAbstractItemModel): """Model to be used for list and tree view.""" class InvalidIndexError(Exception): pass class ModelNotActiveError(Exception): pass def __init__(self, parent=None): QAbstractListModel.__init__(self, parent) self.cardDb = CardDb() def _checkIndex(self, index): if index is None or not index.isValid() or index == QModelIndex(): raise CardModel.InvalidIndexError, "Invalid index given" def _checkActive(self): if not self.isActive(): raise CardModel.ModelNotActiveError, "Model is not active. Use open first." def open(self, dbpath): self.cardDb.open(str(dbpath)) # FIXME why these do not work?? self.reset() # ^ self.emit(SIGNAL('modelReset()')) def close(self): self.emit(SIGNAL('modelAboutToBeReset()')) self.cardDb.close() self.reset() def filepath(self): """Returns path to currently open database""" if self.cardDb.is_open(): return self.cardDb.db_path else: return None def isActive(self): return self.cardDb.is_open() def parent(self, index): return QModelIndex() def rowCount(self, parent=QModelIndex()): if parent.isValid(): return 0 else: if self.cardDb.is_open(): return self.cardDb.get_cards_count() else: return 0 def columnCount(self, parent=QModelIndex()): if parent.isValid(): return 0 else: if self.cardDb.is_open(): return 5 else: return 0 def index(self, row, column, parent=QModelIndex()): if row < 0 or column < 0 or not self.cardDb.is_open(): return QModelIndex() else: # returns index with given card id header = self.cardDb.get_card_headers('', row, row + 1) if len(header) == 1: return self.createIndex(row, column, int(header[0][0])) else: return QModelIndex() # for display role only id+question in following columns will be # for specific data , in the following columns def data(self, index, role=Qt.DisplayRole): self._checkIndex(index) if role not in (Qt.DisplayRole, Qt.UserRole): return QVariant() card = self.cardDb.get_card(index.internalId()) if role == Qt.UserRole: return card else: if index.column() == 0: return QVariant('#%d %s' % (card.id, str(card.question).strip())) elif index.column() == 1: return QVariant('%s' % str(card.answer).strip()) elif index.column() == 2: return QVariant('%s' % str(card.question_hint).strip()) elif index.column() == 3: return QVariant('%s' % str(card.answer_hint).strip()) elif index.column() == 4: return QVariant('%s' % str(card.score)) else: return QVariant() def flags(self, index): return QAbstractListModel.flags(self, index) | Qt.ItemIsEnabled | Qt.ItemIsSelectable def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: if section == 0: return QVariant("Question") elif section == 1: return QVariant("Answer") elif section == 2: return QVariant(tr("Question hint")) elif section == 3: return QVariant(tr("Answer hint")) elif section == 4: return QVariant(tr("Score")) else: return QVariant() else: return QVariant(str(section)) return QVariant() def getPreviousIndex(self, index): """Returns previous index before given or given if it's first.""" self._checkIndex(index) if index.row() == 0: return index else: return self.index(index.row() - 1, 0) # pointer , get row before def getNextIndex(self, index): """Returns next index after given or given if it's last.""" self._checkIndex(index) if index.row() == self.rowCount() - 1: return index else: return self.index(index.row() + 1, 0) # get row after ? # TODO # what about inserting rows # and moving rows up and down ?? # must have parameter position or display position ?? # TODO # add special handlers like rowsAboutToBeInserted etc . # right now only model to be reset def addNewCard(self): """Adds a new empty card.""" self.emit(SIGNAL('modelAboutToBeReset()')) rowid = self.cardDb.add_card(Card()) # TODO is it ok to return it here? result = self.createIndex(self.cardDb.get_cards_count(), 0, rowid) # cards.addCard(Card()) # TODO # why these do not work ? self.reset() # self.emit(SIGNAL('modelReset()')) # return result def deleteCard(self, index): self._checkIndex(index) self.emit(SIGNAL('modelAboutToBeReset()')) self.cardDb.delete_card(index.internalId()) # why these do not work?? self.reset() # self.emit(SIGNAL('modelReset()')) # cards - delete_card card_id # TODO question # how to update card if peg is somewhere else ? # maybe keep blob as well ? # the items are then splitted def updateCard(self, index, question, answer): self._checkIndex(index) card = Card(index.internalId(), question, answer) self.cardDb.update_card(card) # update data in the model self.emit(SIGNAL('dataChanged(QModelIndex)'), index) # TODO model should not have any algorithms - it should be just as a proxy # between database and any more advanced algorithm # e.g. database importer # btw. they should use the same classes with the probe program # TODO progress bar for importing and possibility to cancel if is a long # operatoin def importQAFile(self, file, clean=True): """Import cards from given question&answer file. @param file can be file name or file like object """ self.emit(SIGNAL('modelAboutToBeReset()')) self._checkActive() if isstring(file): file = open(file, 'rt') if clean: self.cardDb.delete_all_cards() prefix = '' last_prefix = '' card = Card() for line in file.readlines(): if line.upper().startswith('Q:') or line.upper().startswith('A:'): last_prefix = prefix prefix = line[:2].upper() line = line[3:] # if new card then recreate if prefix == 'Q:' and prefix != last_prefix: if not card.is_empty(): self.cardDb.add_card(card, False) card = Card() if line.strip() != '': if prefix == 'Q:': card.question += line else: # prefix == a card.answer += line # add last card if not card.is_empty(): self.cardDb.add_card(card) # TODO do it in a real transaction way # in case of error do a rollback self.cardDb.commit() self.reset()
class TestCards(unittest.TestCase): def setUp(self): self.cards = CardDb() self.cards.open(':memory:') def tearDown(self): self.cards.close() def test_get_card(self): card1 = Card(1, 'question', 'answer', 'qhint', 'ahint', 13) card2 = Card(2, 'frage', 'antwort', 'qhint', 'ahint2', 20) id1 = self.cards.add_card(card1) id2 = self.cards.add_card(card2) card1a = self.cards.get_card(id1) card2a = self.cards.get_card(id2) self.assertEqual(card1, card1a) self.assertEqual(card2, card2a) def test_get_card_count(self): self.cards.add_card(Card(None, 'co', 'tutaj')) self.cards.add_card(Card(None, 'ale', 'fajnie')) self.assertEqual(self.cards.get_cards_count(), 2) def test_delete_card(self): id1 = self.cards.add_card(Card(None, 'one', 'eins')) id2 = self.cards.add_card(Card(None, 'two', 'zwei')) id3 = self.cards.add_card(Card(None, 'three', 'drei')) # delete card 1, check if other exist self.cards.delete_card(id1) self.assertFalse(self.cards.exists_card(id1)) self.assertTrue(self.cards.exists_card(id2)) self.assertTrue(self.cards.exists_card(id3)) # add and delete next card id4 = self.cards.add_card(Card(4, 'four', 'vier')) self.cards.delete_card(id4) self.assertFalse(self.cards.exists_card(id4)) self.assertTrue(self.cards.exists_card(id2)) self.assertTrue(self.cards.exists_card(id3)) def test_update_card(self): id1 = self.cards.add_card(Card(None, 'one', 'eins')) id2 = self.cards.add_card(Card(None, 'two', 'zwei')) id3 = self.cards.add_card(Card(None, 'three', 'drei')) # update second card and check if update is successful # and the other cards are intact self.cards.update_card(Card(id2, 'two!', 'zwei!')) self.assertEqual(self.cards.get_card(id2), Card(id2, 'two!', 'zwei!')) self.assertEqual(self.cards.get_card(id1), Card(id1, 'one', 'eins')) self.assertEqual(self.cards.get_card(id3), Card(id3, 'three', 'drei')) def test_get_card_headers(self): id1 = self.cards.add_card(Card(None, 'one', 'eins')) id2 = self.cards.add_card(Card(None, 'two', 'zwei')) id3 = self.cards.add_card(Card(None, 'three', 'drei')) id4 = self.cards.add_card(Card(None, 'four', 'vier')) id5 = self.cards.add_card(Card(None, 'five', 'fuenf')) id6 = self.cards.add_card(Card(None, 'six', 'sechs')) id7 = self.cards.add_card(Card(None, 'seven', 'sieben')) id8 = self.cards.add_card(Card(None, 'eight', 'acht')) id9 = self.cards.add_card(Card(None, 'nine', 'neun')) # retrieve card from rownum 0 to 0 cards = self.cards.get_card_headers('', 0, 1) self.assertEqual(len(cards), 1) self.assertEqual(cards[0], (1, 'one')) # retrieve cards from rownum 1 to 1 cards = self.cards.get_card_headers('', 1, 2) self.assertEqual(len(cards), 1) self.assertEqual(cards[0], (2, 'two')) # retrieve cards from rownum 2 to 4 cards = self.cards.get_card_headers('', 2, 6) self.assertEqual(len(cards), 4) self.assertEqual(cards[0], (3, 'three')) self.assertEqual(cards[1], (4, 'four')) self.assertEqual(cards[2], (5, 'five')) self.assertEqual(cards[3], (6, 'six')) # delete some and check retrieve again # retrieve cards < 7 self.cards.delete_card(id3) self.cards.delete_card(id4) cards = self.cards.get_card_headers('ID < 7', 2, 4) self.assertEqual(len(cards), 2) self.assertEqual(cards[0], (5, 'five')) self.assertEqual(cards[1], (6, 'six')) # add some cards and check retrieve again # retrieve last 3 cards id10 = self.cards.add_card(Card(None, 'ten', 'zehn')) id11 = self.cards.add_card(Card(None, 'eleven', 'einzehn')) id12 = self.cards.add_card(Card(None, 'twelve', 'zwoelf')) cards = self.cards.get_card_headers('', 7, 13) self.assertEqual(len(cards), 3) self.assertEqual(cards[0], (id10, 'ten')) self.assertEqual(cards[1], (id11, 'eleven')) self.assertEqual(cards[2], (id12, 'twelve')) # check for query when no range is added cards = self.cards.get_card_headers() self.assertEqual(len(cards), 10) # check for assertion error when invalid minrow, maxrow self.assertRaises(AssertionError, self.cards.get_card_headers, '', 4, 1)