Example #1
0
    def test_01_xml(self):
        db = Database()
        acc1 = Account("Root")
        db += acc1
        acc2 = Account("Foo")
        acc1 += acc2
        acc3 = Account("Bar")
        acc1 += acc3

        t = Transaction(datetime.date.today(), "bar")
        db += t

        i = Item("AAA", -2)
        t += i
        i += acc1
        i = Item("BBB", 2)
        t += i
        i += acc2

        t = Transaction(datetime.date.today() + datetime.timedelta(days=1),
                        "foo")
        db += t
        i = Item("CCC", 2.99)
        t += i
        i += acc1
        i = Item("DDD", -2.99)
        i += acc3
        t += i

        t = Transaction(datetime.date.today() - datetime.timedelta(days=3),
                        "Flix")
        db += t
        i = Item(u"EEE <>/\\!'\"§$%&/(){}", 4)
        t += i
        i += acc1
        i = Item(u"FFF öäüÖÄÜß", -4)
        t += i
        i += acc3

        # remove the date/time depending string
        datePatchRe = re.compile("^\s*<saved\s+datetime=\".+?\"\s*/>\s*$",
                                 re.MULTILINE)

        xml = etree.tostring(db.toXml(),
                             encoding="utf-8",
                             xml_declaration=True,
                             pretty_print=True).decode("utf-8")
        print(xml)
        xml = datePatchRe.sub("", xml)
        print(xml)
        db2 = Database.parseFromXml(
            etree.parse(BytesIO(xml.encode("utf-8"))).getroot())
        xml2 = etree.tostring(db2.toXml(),
                              encoding="utf-8",
                              xml_declaration=True,
                              pretty_print=True).decode("utf-8")
        print(xml2)
        xml2 = datePatchRe.sub("", xml2)
        self.assertEqual(xml, xml2, "xml differs")
Example #2
0
 def menuNewDatabase(self):
     "Create a new and empty database."
     if not self.checkUnsavedChanges():
         return
     LOGGER.info("Creating new database...")
     self._clearViews()
     self._db = Database()
     self._dbPath = "unnamed"
     self._accountTree.setDatabase(self._db)
     self.statusBar().showMessage("Created empty database", 2000)
Example #3
0
    def test_balanced(self):
        db = Database()
        asset = Account("Asset")
        db += asset
        debit = Account("Debit")
        db += asset

        t = Transaction(datetime.date.today())
        db += t

        i = Item("Hello World", 2)
        t += i
        i += asset

        self.assertEqual(Decimal("2.00"), t.getBalance())
        self.assertFalse(t.isBalanced())

        i = Item("Foo Bar", -1)
        t += i
        i += debit

        self.assertEqual(Decimal("1.00"), t.getBalance())
        self.assertFalse(t.isBalanced())

        i.setValue(-2)

        self.assertEqual(Decimal("0"), t.getBalance())
        self.assertTrue(t.isBalanced())
Example #4
0
 def test_01_root(self):
     db = Database()
     acc = Account("Root")
     db += acc
     self.assertIn("Root", db)
     self.assertIsNotNone(db["Root"])
     self.assertNotIn("Foo", db)
     with self.assertRaises(KeyError):
         db["Foo"]
Example #5
0
    def test_transactions(self):
        today = datetime.date.today()
        db = Database()
        acc1 = Account("A")
        db += acc1
        acc2 = Account("B")
        db += acc2

        t0 = Transaction(today, "B")
        db += t0
        i0 = Item("AA", 1)
        t0 += i0
        i0 += acc1
        i1 = Item("BB", -1)
        t0 += i1
        i1 += acc2

        t1 = Transaction(today + datetime.timedelta(days=1), "C")
        db += t1
        i2 = Item("CC", -2)
        t1 += i2
        i2 += acc1
        i3 = Item("DD", -2)
        t1 += i3
        i3 += acc2

        t2 = Transaction(today - datetime.timedelta(days=5), "A")
        db += t2
        i4 = Item("EE", -3)
        t2 += i4
        i4 += acc1
        i5 = Item("FF", -3)
        t2 += i5
        i5 += acc2

        expected_transactions = [t2, t0, t1]
        current_transactions = list(db.filterTransactions())
        self.assertListEqual(expected_transactions, current_transactions)

        t1.setDate(today - datetime.timedelta(days=1))

        expected_transactions = [t2, t1, t0]
        current_transactions = list(db.filterTransactions())
        self.assertListEqual(expected_transactions, current_transactions)
Example #6
0
 def test_02_sub(self):
     db = Database()
     acc = Account("Root")
     db += acc
     sacc = Account("Sub")
     acc += sacc
     self.assertIn("Root/Sub", db)
     self.assertIsNotNone(db["Root/Sub"])
     self.assertNotIn("Root/Foo", db)
     with self.assertRaises(KeyError):
         db["Root/Foo"]
Example #7
0
    def setUp(self):
        self._db = Database()
        accRoot = Account("Root")
        self._db += accRoot
        accFoo = Account("Foo")
        accRoot += accFoo
        accBar = Account("Bar")
        accRoot += accBar
        accTest = Account("Test")
        accRoot += accTest

        t = self._newTransaction(self.dates[0], "one")
        self._newItem("AAA", 2.5, t, accFoo)
        self._newItem("BBB", -2.5, t, accBar)

        t = self._newTransaction(self.dates[1], "two")
        self._newItem("CCC", -2, t, accRoot)
        self._newItem("DDD", 2, t, accTest)

        t = self._newTransaction(self.dates[2], "three")
        self._newItem(u"EEE", 4, t, accFoo)
        self._newItem(u"FFF", -4, t, accBar)

        t = self._newTransaction(self.dates[2], "four")
        self._newItem(u"GGG", 3, t, accFoo)
        self._newItem(u"HHH", -3, t, accBar)

        t = self._newTransaction(self.dates[3], "five")
        self._newItem(u"III", 5, t, accFoo)
        self._newItem(u"JJJ", -5, t, accTest)

        t = self._newTransaction(self.dates[4], "six")
        self._newItem(u"KKK", 5, t, accFoo)
        self._newItem(u"LLL", -5, t, accBar)

        t = self._newTransaction(self.dates[4], "seven")
        self._newItem(u"MMM", 7, t, accFoo)
        self._newItem(u"NNN", -7, t, accBar)

        t = self._newTransaction(self.dates[5], "eight")
        self._newItem(u"OOO", 1, t, accFoo)
        self._newItem(u"PPP", -1, t, accBar)

        t = self._newTransaction(self.dates[6], "nine")
        self._newItem(u"QQQ", 1, t, accTest)
        self._newItem(u"RRR", -1, t, accBar)

        t = self._newTransaction(self.dates[7], "ten")
        self._newItem(u"SSS", 1, t, accFoo)
        self._newItem(u"TTT", -1, t, accBar)
Example #8
0
    def test_item(self):
        db = Database()
        acc = Account("Root")
        db += acc
        t = Transaction(datetime.date.today())
        db += t

        i = Item("Hello World", 2.99)
        t += i
        i += acc

        i = Item("Foo Bar", 1)
        t += i
        i += acc
Example #9
0
    def menuNewRandomDatabase(self):
        "Create a new and random filled database."
        LOGGER.info("Creating new random database...")
        self._clearViews()

        self._db = Database()
        self._dbPath = "unnamed"

        self._db._parsing = True
        for i in range(32):
            acc = Account("Account%d" % i)
            self._db += acc
            for ii in range(2):
                cacc = Account("Account%d" % ii)
                acc += cacc
                for iii in range(2):
                    ccacc = Account("Account%d" % iii)
                    cacc += ccacc
        accs = self._db.getChildAccounts(True)
        dt = datetime.datetime.now() - datetime.timedelta(seconds=60 * 60 *
                                                          500)
        for i in range(3000):
            t = Transaction(dt, "#" * random.randint(1, 32))
            self._db += t
            v = float(random.randint(1, 1000)) / 100
            i = Item("A" * random.randint(1, 32), v)
            t += i
            i += random.choice(accs)
            i = Item("B" * random.randint(1, 32), -v)
            t += i
            i += random.choice(accs)
            dt += datetime.timedelta(seconds=60 * 60)
        self._db._parsing = False

        self._accountTree.setDatabase(self._db)
        self._accountTree.expandAll()
        self.statusBar().showMessage("Created random database", 2000)
Example #10
0
 def menuOpenDatabase(self, filepath=""):
     "Open existing database from file."
     if not self.checkUnsavedChanges():
         return
     if not filepath or not os.path.isfile(filepath):
         filepath, dummyFilter = QtWidgets.QFileDialog.getOpenFileName(
             self, self.tr("Open accounting database..."), "",
             self.tr("Accounting Database Files (*.accdb)"))
     if not filepath or not os.path.isfile(filepath):
         return
     self._clearViews()
     LOGGER.info("Loading database %s..." % filepath)
     self._db = Database.load(filepath)
     self._dbPath = filepath
     self._accountTree.setDatabase(self._db)
     self._accountTree.expandAll()
     self.statusBar().showMessage("Loaded database " + self._dbPath, 2000)
     self.handleModelDirty(False)
     LOGGER.info("Loaded database %s." % filepath)
Example #11
0
    def test_03_items(self):
        db = Database()
        acc1 = Account("A")
        db += acc1
        acc2 = Account("B")
        db += acc2

        t0 = Transaction(datetime.date.today())
        db += t0
        i0 = Item("AA", 1)
        t0 += i0
        i0 += acc1
        i1 = Item("BB", -1)
        t0 += i1
        i1 += acc2

        t1 = Transaction(datetime.date.today() + datetime.timedelta(days=1))
        db += t1
        i2 = Item("CC", -2)
        t1 += i2
        i2 += acc1
        i3 = Item("DD", -2)
        t1 += i3
        i3 += acc2

        expected_acc_items = [i0, i2]
        current_acc_items = list(acc1.filterItems())
        self.assertListEqual(expected_acc_items, current_acc_items)

        expected_acc_items = [i1, i3]
        current_acc_items = list(acc2.filterItems())
        self.assertListEqual(expected_acc_items, current_acc_items)

        t1.setDate(datetime.date.today() - datetime.timedelta(days=1))

        expected_acc_items = [i2, i0]
        current_acc_items = list(acc1.filterItems())
        self.assertListEqual(expected_acc_items, current_acc_items)

        expected_acc_items = [i3, i1]
        current_acc_items = list(acc2.filterItems())
        self.assertListEqual(expected_acc_items, current_acc_items)
Example #12
0
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, dbPath=None):
        "Construct main window and load selected database."
        super().__init__()

        self._initLogger()

        self._queuedActions = []

        self._splash = None
        self._splashEnabled = True  # disable splash for debugging
        LOGGER.info("Starting...")

        self._dbPath = dbPath
        self._db = None
        self._recentReportDir = ""
        self._discardTabChange = False

        self.initUI()
        self._handleQueuedAction(True)

    def _initLogger(self):
        "Initialize logging"
        self._logDlg = LogDialog()

        level = logging.DEBUG
        rootLogger = logging.getLogger()
        rootLogger.setLevel(level)

        self._outLogHdl = logging.StreamHandler(sys.stdout)
        self._outLogHdl.setLevel(level)
        rootLogger.addHandler(self._outLogHdl)

        # f = os.path.expanduser("~/tmp/accounting.log")
        # self._fileLogHdl = logging.StreamHandler( codecs.getwriter(consoleEncoding)(open(f,"wb+"),"replace") )
        # self._fileLogHdl.setLevel( level )
        # rootLogger.addHandler( self._fileLogHdl )

        self._uiLogHdl = UILoggingHandler(self._logDlg.log)
        self._uiLogHdl.setLevel(level)
        rootLogger.addHandler(self._uiLogHdl)

        self._splashLogHdl = UILoggingHandler(self._splashLog)
        self._splashLogHdl.setLevel(level)
        rootLogger.addHandler(self._splashLogHdl)

        rootLogger.setLevel(level)

    def restoreSettings(self):
        "Restore previous application settings"
        screenDim = QtWidgets.QDesktopWidget().screenGeometry()
        defaultSize = QtCore.QSize(screenDim.width() * 0.75,
                                   screenDim.height() * 0.75)
        defaultPos = QtCore.QPoint(
            (screenDim.width() / 2) - (defaultSize.width() / 2),
            (screenDim.height() / 2) - (defaultSize.height() / 2))
        settings = QtCore.QSettings()

        settings.beginGroup("MainWindow")

        self.resize(settings.value("size", defaultSize))
        self.move(settings.value("pos", defaultPos))

        self._recentReportDir = settings.value("mostRecentReportDir", "")

        loadDbPath = self._dbPath
        if not loadDbPath:
            loadDbPath = settings.value("mostRecentDb", "")
        if loadDbPath:
            if os.path.isfile(loadDbPath):
                self._queuedActions += [(self.menuOpenDatabase, (loadDbPath, ))
                                        ]
            else:
                LOGGER.warning(
                    "Skipped loading non existing database at {}".format(
                        loadDbPath))
        self._dbPath = ""

        try:
            currTabName = ""
            for idx in range(settings.beginReadArray("recentTabs")):
                settings.setArrayIndex(idx)
                tab = settings.value("tab", "")
                tabType, tabCurr, tabName = tab.split(":")
                if tabType == "AccTrn":
                    self._queuedActions += [(self._openAccTrnView, (tabName, ))
                                            ]
                    if tabCurr:
                        currTabName = tabCurr
            self._queuedActions += [(self._openAccTrnView, (currTabName, ))]
        except:
            LOGGER.exception("Failed to restore recent tabs")
        finally:
            settings.endArray()

        settings.endGroup()

    def saveSettings(self):
        "Save current application settings"
        settings = QtCore.QSettings()
        settings.beginGroup("MainWindow")

        settings.setValue("size", self.size())
        settings.setValue("pos", self.pos())
        settings.setValue("mostRecentDb", self._dbPath)

        recentTabIdx = 0
        settings.beginWriteArray("recentTabs")
        for tabIdx in range(self._tabs.count()):
            widget = self._tabs.widget(tabIdx)
            currWidget = self._tabs.currentWidget() == widget
            if isinstance(widget, AccountTransactionView):
                settings.setArrayIndex(recentTabIdx)
                recentTabIdx += 1
                settings.setValue(
                    "tab", "AccTrn:%s:%s" %
                    ("*" if currWidget else "", widget.getAccount().fullname))
        settings.endArray()

        settings.endGroup()

    def closeEvent(self, closeEvt):
        "Handle event to close main window"
        if self.checkUnsavedChanges():
            self.saveSettings()
            closeEvt.accept()
        else:
            closeEvt.ignore()

    def _splashMsg(self, msg):
        "Change splash screen message"
        if not self._splash and self._splashEnabled:
            self._splash = QtWidgets.QSplashScreen(
                QtGui.QPixmap(":/images/splash.jpg"),
                flags=QtCore.Qt.WindowStaysOnTopHint)
            self._splash.show()
        elif msg is None and self._splash:
            LOGGER.removeHandler(self._splashLogHdl)
            QtCore.QTimer.singleShot(200, lambda: self._splash.finish(self))

        if self._splash:
            if msg:
                self._splash.showMessage(msg, color=QtGui.QColor("black"))
            QtCore.QCoreApplication.processEvents()

    def _splashLog(self, level, msg):
        "Add given logging message to splash"
        for l in msg.split("\n"):
            self._splashMsg(l)

    def initUI(self):
        "Initialize UI."
        centralWidget = QtWidgets.QWidget()
        self.setCentralWidget(centralWidget)

        vbox = QtWidgets.QVBoxLayout()
        vbox.setContentsMargins(0, 0, 0, 0)

        self._splitter = QtWidgets.QSplitter()
        self._splitter.setChildrenCollapsible(False)

        self._tabs = QtWidgets.QTabWidget()
        self._tabs.setTabsClosable(True)
        self._tabs.currentChanged.connect(self.handleUpdateAccTab)
        self._tabs.tabCloseRequested.connect(self.handleCloseAccTab)

        self._accountTree = AccountTree()
        self._accountTree.showAccount.connect(self._openAccTrnView)
        self._accountTree.createReport.connect(self.handleAccTreeReport)
        self._accountTree.importData.connect(self.handleAccTreeImport)
        self._accountTree.dirty.connect(self.handleModelDirty)
        self._accountTree.editAccount.connect(self.handleEditAccount)
        self._splitter.addWidget(self._accountTree)
        self._splitter.addWidget(self._tabs)
        vbox.addWidget(self._splitter)

        hbox = QtWidgets.QHBoxLayout()
        hbox.setSpacing(4)
        hbox.setContentsMargins(2, 2, 2, 2)
        hbox.addLayout(vbox, 0)

        centralWidget.setLayout(hbox)

        self.initMenuBar()
        self.initStatusBar()
        self.restoreSettings()
        if not self._db:
            self.menuNewDatabase()
        self.show()
        self.raise_()

    def initStatusBar(self):
        self.statusBar().showMessage("Ready.", 4000)

    def initMenuBar(self):
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu(self.tr("File"))
        fileMenu.addAction(self.tr("New database..."), self.menuNewDatabase)
        fileMenu.addAction(self.tr("New random database..."),
                           self.menuNewRandomDatabase)
        fileMenu.addSeparator()
        fileMenu.addAction(self.tr("Open database..."), self.menuOpenDatabase)
        fileMenu.addSeparator()
        self._actionSave = fileMenu.addAction(self.tr("Save database..."),
                                              self.menuSaveDatabase)
        self._actionSave.setDisabled(True)
        self._actionSaveAs = fileMenu.addAction(self.tr("Save database as..."),
                                                self.menuSaveAsDatabase)
        fileMenu.addSeparator()
        fileMenu.addAction(self.tr("Quit"), self.close)

        helpMenu = menuBar.addMenu(self.tr("Help"))
        helpMenu.addAction(self.tr("About..."), self.menuAbout)
        helpMenu.addAction(self.tr("About Qt..."), self.menuAboutQt)
        helpMenu.addSeparator()
        helpMenu.addAction(self.tr("Log..."), self._logDlg.show)

    def _handleQueuedAction(self, triggerTimer=False):
        if not self._queuedActions:
            LOGGER.info("Ready...")
            self._splashMsg(None)
            return
        if triggerTimer:
            QtCore.QTimer.singleShot(100, self._handleQueuedAction)
            return
        func, args = self._queuedActions.pop(0)
        func(*args)
        self._handleQueuedAction(True)

    def checkUnsavedChanges(self):
        """Returns true when unsaved changes have been saved or user wants to discard unsaved changes.
        Returns False if there are unsaved changes and user wants to abort current action"""
        if self.isModelDirty():
            res = QtWidgets.QMessageBox.question(
                self, self.tr("Unsaved changes"),
                self.
                tr("There are unsaved changes in current database. Do you want to save them now ?"
                   ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
                | QtWidgets.QMessageBox.Cancel)
            if res == QtWidgets.QMessageBox.Yes:
                self.menuSaveDatabase()
            elif res == QtWidgets.QMessageBox.Cancel or res == QtWidgets.QMessageBox.Escape:
                return False
        return True

    def _clearViews(self):
        while self._tabs.count():
            self._discardTabChange = True
            self._tabs.removeTab(0)
        self._accountTree.setDatabase(self._db)

    def menuAbout(self):
        "Show about dialog."
        QtWidgets.QMessageBox.about(
            self, self.tr("About Accounting"),
            self.
            tr("A tool to manage transactions of accounts and generate reports of transactions.\n\nVersion "
               + __version__ + "\nWritten by Manuel Koch."))

    def menuAboutQt(self):
        "Show about dialog."
        QtWidgets.QMessageBox.aboutQt(self, self.tr("About Qt"))

    def menuNewDatabase(self):
        "Create a new and empty database."
        if not self.checkUnsavedChanges():
            return
        LOGGER.info("Creating new database...")
        self._clearViews()
        self._db = Database()
        self._dbPath = "unnamed"
        self._accountTree.setDatabase(self._db)
        self.statusBar().showMessage("Created empty database", 2000)

    def menuNewRandomDatabase(self):
        "Create a new and random filled database."
        LOGGER.info("Creating new random database...")
        self._clearViews()

        self._db = Database()
        self._dbPath = "unnamed"

        self._db._parsing = True
        for i in range(32):
            acc = Account("Account%d" % i)
            self._db += acc
            for ii in range(2):
                cacc = Account("Account%d" % ii)
                acc += cacc
                for iii in range(2):
                    ccacc = Account("Account%d" % iii)
                    cacc += ccacc
        accs = self._db.getChildAccounts(True)
        dt = datetime.datetime.now() - datetime.timedelta(seconds=60 * 60 *
                                                          500)
        for i in range(3000):
            t = Transaction(dt, "#" * random.randint(1, 32))
            self._db += t
            v = float(random.randint(1, 1000)) / 100
            i = Item("A" * random.randint(1, 32), v)
            t += i
            i += random.choice(accs)
            i = Item("B" * random.randint(1, 32), -v)
            t += i
            i += random.choice(accs)
            dt += datetime.timedelta(seconds=60 * 60)
        self._db._parsing = False

        self._accountTree.setDatabase(self._db)
        self._accountTree.expandAll()
        self.statusBar().showMessage("Created random database", 2000)

    def menuOpenDatabase(self, filepath=""):
        "Open existing database from file."
        if not self.checkUnsavedChanges():
            return
        if not filepath or not os.path.isfile(filepath):
            filepath, dummyFilter = QtWidgets.QFileDialog.getOpenFileName(
                self, self.tr("Open accounting database..."), "",
                self.tr("Accounting Database Files (*.accdb)"))
        if not filepath or not os.path.isfile(filepath):
            return
        self._clearViews()
        LOGGER.info("Loading database %s..." % filepath)
        self._db = Database.load(filepath)
        self._dbPath = filepath
        self._accountTree.setDatabase(self._db)
        self._accountTree.expandAll()
        self.statusBar().showMessage("Loaded database " + self._dbPath, 2000)
        self.handleModelDirty(False)
        LOGGER.info("Loaded database %s." % filepath)

    def _resetDirty(self):
        "Tell all models to be clean."
        self._accountTree.setDirty(False)
        for i in range(self._tabs.count()):
            widget = self._tabs.widget(i)
            if isinstance(widget, AccountTransactionView):
                widget.setDirty(False)

    def menuSaveDatabase(self):
        "Save current database to file."
        LOGGER.info("Saving database %s..." % self._dbPath)
        self._db.save(self._dbPath)
        self._resetDirty()
        self.statusBar().showMessage("Saved database " + self._dbPath, 2000)
        LOGGER.info("Saved database %s." % self._dbPath)

    def menuSaveAsDatabase(self):
        "Save current database to file."
        fileName, dummyFilter = QtWidgets.QFileDialog.getSaveFileName(
            self, self.tr("Save accounting database..."), "",
            self.tr("Accounting Database Files (*.accdb)"))
        if not fileName:
            return
        LOGGER.info("Saving database as %s..." % fileName)
        self._db.save(fileName)
        self._dbPath = fileName
        self._resetDirty()
        self.statusBar().showMessage("Saved database as " + self._dbPath, 2000)
        LOGGER.info("Saved database as %s..." % self._dbPath)

    def isModelDirty(self):
        "Return whether our model is dirty and needs to be saved."
        if self._accountTree.isDirty():
            return True
        for i in range(self._tabs.count()):
            widget = self._tabs.widget(i)
            if isinstance(widget, AccountTransactionView):
                if widget.isDirty():
                    return True
        return False

    def handleModelDirty(self, isDirty):
        "Handle altered model."
        if not isDirty or os.path.isfile(self._dbPath):
            self._actionSave.setEnabled(isDirty)
        if isDirty:
            self.setWindowTitle("Accounting - " + self._dbPath + " (modified)")
        else:
            self.setWindowTitle("Accounting - " + self._dbPath)

    def handleEditAccount(self, acc):
        "Handle editing account"
        dlg = EditAccountDialog(acc, self)
        if dlg.exec_() == QDialog.Accepted:
            pass

    def handleAccTreeRename(self, acc):
        "Handle renamed account to update tab header"
        for i in range(self._tabs.count()):
            widget = self._tabs.widget(i)
            if isinstance(widget, AccountTransactionView):
                if widget.getAccount() == acc:
                    self._tabs.setTabText(i, acc.fullname)
                    return

    def _openAccTrnView(self, acc):
        "Open or activate tab with transaction table view for given Account instance and return view instance."
        if isinstance(acc, str):
            if not acc in self._db:
                return
            acc = self._db[acc]
        LOGGER.info("Opening account transaction view %s" % acc.fullname)
        for i in range(self._tabs.count()):
            widget = self._tabs.widget(i)
            if isinstance(widget, AccountTransactionView):
                if widget.getAccount() == acc:
                    self.statusBar().showMessage(
                        "Selecting account transactions view for %s..." %
                        acc.fullname, 1000)
                    self._tabs.setCurrentIndex(i)
                    return widget

        self.statusBar().showMessage(
            "Creating account transactions view for %s..." % acc.fullname,
            1000)
        accView = AccountTransactionView()
        accView.dirty.connect(self.handleModelDirty)
        accView.setAccount(acc)

        self._discardTabChange = True
        self._tabs.addTab(accView, acc.fullname)
        self._tabs.setCurrentWidget(accView)
        acc.nameChanged.connect(lambda n: self.handleAccTreeRename(acc))

        return accView

    def handleAccTreeReport(self):
        indexes = self._accountTree.selectedIndexes()
        if not indexes:
            return
        self.statusBar().showMessage(
            "Creating report for %d accounts..." % len(indexes), 1000)
        accounts = [
            self._accountTree.model().data(idx, AccountModel.AccountRole)
            for idx in indexes
        ]
        accReport = AccountReport(accounts)
        self._tabs.addTab(accReport, "Report")
        self._tabs.setCurrentWidget(accReport)

    def handleAccTreeImport(self):
        indexes = self._accountTree.selectedIndexes()
        if not indexes:
            return
        idx = indexes[0]
        if not idx.isValid():
            return

        acc = self._accountTree.model().data(idx, AccountModel.AccountRole)
        accView = self._openAccTrnView(acc)
        accView.startImport()

    def handleUpdateAccTab(self, idx):
        "Handle activation of another tab to force updating it's content."
        if idx >= 0 and not self._discardTabChange:
            # force updating all the table cells from model
            widget = self._tabs.currentWidget()
            if isinstance(widget, AccountTransactionView):
                widget.refreshFromModel()
        self._discardTabChange = False

    def handleCloseAccTab(self, idx):
        "Handle request to closing tab."
        self._tabs.removeTab(idx)
Example #13
0
 def test_transaction(self):
     db = Database()
     acc = Account("Root")
     db += acc
     t = Transaction(datetime.date.today())
     db += t
Example #14
0
    def test_01_save(self):
        db = Database()
        acc1 = Account("Root")
        db += acc1
        acc2 = Account("Foo")
        acc1 += acc2

        self.assertEqual(2, db.nofAccounts())
        account_names = set(
            [a.fullname for a in db.getChildAccounts(recurse=False)])
        self.assertSetEqual({"Root"}, account_names)
        account_names = set(
            [a.fullname for a in db.getChildAccounts(recurse=True)])
        self.assertSetEqual({"Root", "Root/Foo"}, account_names)

        db.save(self.temp_path)

        time.sleep(1)

        acc3 = Account("Bar")
        acc1 += acc3

        self.assertEqual(3, db.nofAccounts())
        account_names = set(
            [a.fullname for a in db.getChildAccounts(recurse=False)])
        self.assertSetEqual({"Root"}, account_names)
        account_names = set(
            [a.fullname for a in db.getChildAccounts(recurse=True)])
        self.assertSetEqual({"Root", "Root/Foo", "Root/Bar"}, account_names)

        db.save(self.temp_path)

        time.sleep(1)

        acc4 = Account("Hello World")
        acc3 += acc4

        self.assertEqual(4, db.nofAccounts())
        account_names = set(
            [a.fullname for a in db.getChildAccounts(recurse=False)])
        self.assertSetEqual({"Root"}, account_names)
        account_names = set(
            [a.fullname for a in db.getChildAccounts(recurse=True)])
        self.assertSetEqual(
            {"Root", "Root/Foo", "Root/Bar", "Root/Bar/Hello World"},
            account_names)

        db.save(self.temp_path)

        db2 = Database.load(self.temp_path)

        self.assertEqual(4, db2.nofAccounts())
        account_names = set(
            [a.fullname for a in db2.getChildAccounts(recurse=False)])
        self.assertSetEqual({"Root"}, account_names)
        account_names = set(
            [a.fullname for a in db2.getChildAccounts(recurse=True)])
        self.assertSetEqual(
            {"Root", "Root/Foo", "Root/Bar", "Root/Bar/Hello World"},
            account_names)