Esempio n. 1
0
 def _read_info(self, field):
     super(File, self)._read_info(field)
     if field in ('size', 'ctime', 'mtime'):
         stats = io.stat(self.path)
         self.size = nonone(stats.st_size, 0)
         self.ctime = nonone(stats.st_ctime, 0)
         self.mtime = nonone(stats.st_mtime, 0)
     elif field == 'md5partial':
         try:
             fp = io.open(self.path, 'rb')
             offset = self._md5partial_offset
             size = self._md5partial_size
             fp.seek(offset)
             partialdata = fp.read(size)
             md5 = hashlib.md5(partialdata)
             self.md5partial = md5.digest()
             fp.close()
         except Exception:
             pass
     elif field == 'md5':
         try:
             fp = io.open(self.path, 'rb')
             filedata = fp.read()
             md5 = hashlib.md5(filedata)
             self.md5 = md5.digest()
             fp.close()
         except Exception:
             pass
Esempio n. 2
0
 def _read_info(self, field):
     if field in ('size', 'mtime'):
         stats = self.path.stat()
         self.size = nonone(stats.st_size, 0)
         self.mtime = nonone(stats.st_mtime, 0)
     elif field == 'md5partial':
         try:
             fp = self.path.open('rb')
             offset, size = self._get_md5partial_offset_and_size()
             fp.seek(offset)
             partialdata = fp.read(size)
             md5 = hashlib.md5(partialdata)
             self.md5partial = md5.digest()
             fp.close()
         except Exception:
             pass
     elif field == 'md5':
         try:
             fp = self.path.open('rb')
             md5 = hashlib.md5()
             # The goal here is to not run out of memory on really big files. However, the chunk
             # size has to be large enough so that the python loop isn't too costly in terms of
             # CPU.
             CHUNK_SIZE = 1024 * 1024  # 1 mb
             filedata = fp.read(CHUNK_SIZE)
             while filedata:
                 md5.update(filedata)
                 filedata = fp.read(CHUNK_SIZE)
             self.md5 = md5.digest()
             fp.close()
         except Exception:
             pass
Esempio n. 3
0
 def load_transaction_info(info):
     description = info.description
     payee = info.payee
     checkno = info.checkno
     date = info.date
     transaction = Transaction(date, description, payee, checkno)
     transaction.notes = nonone(info.notes, '')
     for split_info in info.splits:
         account = split_info.account
         amount = split_info.amount
         if split_info.amount_reversed:
             amount = -amount
         memo = nonone(split_info.memo, '')
         split = Split(transaction, account, amount)
         split.memo = memo
         if split_info.reconciliation_date is not None:
             split.reconciliation_date = split_info.reconciliation_date
         elif split_info.reconciled: # legacy
             split.reconciliation_date = transaction.date
         split.reference = split_info.reference
         transaction.splits.append(split)
     while len(transaction.splits) < 2:
         transaction.splits.append(Split(transaction, None, 0))
     transaction.balance()
     transaction.mtime = info.mtime
     if info.reference is not None:
         for split in transaction.splits:
             if split.reference is None:
                 split.reference = info.reference
     return transaction
Esempio n. 4
0
 def _read_info(self, field):
     if field in ('size', 'mtime'):
         stats = self.path.stat()
         self.size = nonone(stats.st_size, 0)
         self.mtime = nonone(stats.st_mtime, 0)
     elif field == 'md5partial':
         try:
             fp = self.path.open('rb')
             offset, size = self._get_md5partial_offset_and_size()
             fp.seek(offset)
             partialdata = fp.read(size)
             md5 = hashlib.md5(partialdata)
             self.md5partial = md5.digest()
             fp.close()
         except Exception:
             pass
     elif field == 'md5':
         try:
             fp = self.path.open('rb')
             md5 = hashlib.md5()
             CHUNK_SIZE = 8192
             filedata = fp.read(CHUNK_SIZE)
             while filedata:
                 md5.update(filedata)
                 filedata = fp.read(CHUNK_SIZE)
             self.md5 = md5.digest()
             fp.close()
         except Exception:
             pass
Esempio n. 5
0
 def _read_info(self, field):
     if field in ('size', 'mtime'):
         stats = self.path.stat()
         self.size = nonone(stats.st_size, 0)
         self.mtime = nonone(stats.st_mtime, 0)
     elif field == 'md5partial':
         try:
             fp = self.path.open('rb')
             offset, size = self._get_md5partial_offset_and_size()
             fp.seek(offset)
             partialdata = fp.read(size)
             md5 = hashlib.md5(partialdata)
             self.md5partial = md5.digest()
             fp.close()
         except Exception:
             pass
     elif field == 'md5':
         try:
             fp = self.path.open('rb')
             md5 = hashlib.md5()
             CHUNK_SIZE = 8192
             filedata = fp.read(CHUNK_SIZE)
             while filedata:
                 md5.update(filedata)
                 filedata = fp.read(CHUNK_SIZE)
             self.md5 = md5.digest()
             fp.close()
         except Exception:
             pass
Esempio n. 6
0
 def _read_info(self, field):
     if field in ('size', 'mtime'):
         stats = self.path.stat()
         self.size = nonone(stats.st_size, 0)
         self.mtime = nonone(stats.st_mtime, 0)
     elif field == 'md5partial':
         try:
             fp = self.path.open('rb')
             offset, size = self._get_md5partial_offset_and_size()
             fp.seek(offset)
             partialdata = fp.read(size)
             md5 = hashlib.md5(partialdata)
             self.md5partial = md5.digest()
             fp.close()
         except Exception:
             pass
     elif field == 'md5':
         try:
             fp = self.path.open('rb')
             md5 = hashlib.md5()
             # The goal here is to not run out of memory on really big files. However, the chunk
             # size has to be large enough so that the python loop isn't too costly in terms of
             # CPU.
             CHUNK_SIZE = 1024 * 1024 # 1 mb
             filedata = fp.read(CHUNK_SIZE)
             while filedata:
                 md5.update(filedata)
                 filedata = fp.read(CHUNK_SIZE)
             self.md5 = md5.digest()
             fp.close()
         except Exception:
             pass
Esempio n. 7
0
 def __init__(self,
              date,
              description=None,
              payee=None,
              checkno=None,
              account=None,
              amount=None):
     #: Date at which the transation occurs.
     self.date = date
     #: Description of the transaction.
     self.description = nonone(description, '')
     #: Person or entity related to the transaction.
     self.payee = nonone(payee, '')
     #: Check number related to the transaction.
     self.checkno = nonone(checkno, '')
     #: Freeform note about the transaction.
     self.notes = ''
     if amount is not None:
         self.splits = [
             Split(self, account, amount),
             Split(self, None, -amount)
         ]
     else:
         #: The list of :class:`Split` affecting this transaction. These splits always balance.
         self.splits = []
     #: Ordering attributes. When two transactions have the same date, we order them with this.
     self.position = 0
     #: Timestamp of the last modification. Used in the UI to let the user sort his transactions.
     #: This is useful for finding a mistake that we know was introduced recently.
     self.mtime = 0
Esempio n. 8
0
 def load_transaction_info(info):
     description = info.description
     payee = info.payee
     checkno = info.checkno
     date = info.date
     transaction = Transaction(date, description, payee, checkno)
     transaction.notes = nonone(info.notes, '')
     for split_info in info.splits:
         account = split_info.account
         amount = split_info.amount
         if split_info.amount_reversed:
             amount = -amount
         memo = nonone(split_info.memo, '')
         split = Split(transaction, account, amount)
         split.memo = memo
         if account is None or not of_currency(amount,
                                               account.currency):
             # fix #442: off-currency transactions shouldn't be reconciled
             split.reconciliation_date = None
         elif split_info.reconciliation_date is not None:
             split.reconciliation_date = split_info.reconciliation_date
         elif split_info.reconciled:  # legacy
             split.reconciliation_date = transaction.date
         split.reference = split_info.reference
         transaction.splits.append(split)
     while len(transaction.splits) < 2:
         transaction.splits.append(Split(transaction, None, 0))
     transaction.balance()
     transaction.mtime = info.mtime
     if info.reference is not None:
         for split in transaction.splits:
             if split.reference is None:
                 split.reference = info.reference
     return transaction
Esempio n. 9
0
 def __init__(self, date, description=None, payee=None, checkno=None, account=None, amount=None):
     self.date = date
     self.description = nonone(description, '')
     self.payee = nonone(payee, '')
     self.checkno = nonone(checkno, '')
     self.notes = ''
     if amount is not None:
         self.splits = [Split(self, account, amount), Split(self, None, -amount)]
     else:
         self.splits = []
     self.position = 0
     self.mtime = 0
Esempio n. 10
0
    def _read_info(self, field):
        # print(f"_read_info({field}) for {self}")
        if field in ("size", "mtime"):
            stats = self.path.stat()
            self.size = nonone(stats.st_size, 0)
            self.mtime = nonone(stats.st_mtime, 0)
        elif field == "md5partial":
            try:
                self.md5partial = filesdb.get(self.path, "md5partial")
                if self.md5partial is None:
                    self.md5partial = self._calc_md5partial()
                    filesdb.put(self.path, "md5partial", self.md5partial)
            except Exception as e:
                logging.warning("Couldn't get md5partial for %s: %s",
                                self.path, e)
        elif field == "md5":
            try:
                self.md5 = filesdb.get(self.path, "md5")
                if self.md5 is None:
                    self.md5 = self._calc_md5()
                    filesdb.put(self.path, "md5", self.md5)
            except Exception as e:
                logging.warning("Couldn't get md5 for %s: %s", self.path, e)
        elif field == "md5samples":
            try:
                with self.path.open("rb") as fp:
                    size = self.size
                    # Might as well hash such small files entirely.
                    if size <= MIN_FILE_SIZE:
                        setattr(self, field, self.md5)
                        return

                    # Chunk at 25% of the file
                    fp.seek(floor(size * 25 / 100), 0)
                    filedata = fp.read(CHUNK_SIZE)
                    md5 = hashlib.md5(filedata)

                    # Chunk at 60% of the file
                    fp.seek(floor(size * 60 / 100), 0)
                    filedata = fp.read(CHUNK_SIZE)
                    md5.update(filedata)

                    # Last chunk of the file
                    fp.seek(-CHUNK_SIZE, 2)
                    filedata = fp.read(CHUNK_SIZE)
                    md5.update(filedata)
                    setattr(self, field, md5.digest())
            except Exception as e:
                logging.error(f"Error computing md5samples: {e}")
Esempio n. 11
0
 def __init__(self):
     LOGGING_LEVEL = logging.DEBUG if proxy.prefValue_('DebugMode') else logging.WARNING
     logging.basicConfig(level=LOGGING_LEVEL, format='%(levelname)s %(message)s')
     install_exception_hook()
     install_cocoa_logger()
     logging.debug('started in debug mode')
     cache_path = op.join(proxy.getCachePath(), 'moneyGuru')
     appdata_path = op.join(proxy.getAppdataPath(), 'moneyGuru')
     plugin_model_path = op.join(proxy.getResourcePath(), 'plugin_examples')
     currency_code = nonone(proxy.systemCurrency(), 'USD')
     logging.info('Currency code: {0}'.format(currency_code))
     try:
         system_currency = Currency(currency_code)
     except ValueError: # currency does not exist
         logging.warning('System currency {0} is not supported. Falling back to USD.'.format(currency_code))
         system_currency = USD
     date_format = proxy.systemShortDateFormat()
     logging.info('System date format: %s' % date_format)
     date_format = clean_format(date_format)
     logging.info('Cleaned date format: %s' % date_format)
     decimal_sep = proxy.systemNumberDecimalSeparator()
     grouping_sep = proxy.systemNumberGroupingSeparator()
     logging.info('System numeric separators: %s and %s' % (grouping_sep, decimal_sep))
     model = Application(self, date_format=date_format, decimal_sep=decimal_sep, 
         grouping_sep=grouping_sep, default_currency=system_currency, cache_path=cache_path,
         appdata_path=appdata_path, plugin_model_path=plugin_model_path)
     PyBaseApp.__init__(self, model)
Esempio n. 12
0
 def __init__(self):
     LOGGING_LEVEL = logging.DEBUG if proxy.prefValue_("DebugMode") else logging.WARNING
     logging.basicConfig(level=LOGGING_LEVEL, format="%(levelname)s %(message)s")
     install_exception_hook("https://github.com/hsoft/moneyguru/issues")
     install_cocoa_logger()
     logging.debug("started in debug mode")
     cache_path = op.join(proxy.getCachePath(), "moneyGuru")
     appdata_path = op.join(proxy.getAppdataPath(), "moneyGuru")
     currency_code = nonone(proxy.systemCurrency(), "USD")
     logging.info("Currency code: {0}".format(currency_code))
     try:
         system_currency = Currency(currency_code)
     except ValueError:  # currency does not exist
         logging.warning("System currency {0} is not supported. Falling back to USD.".format(currency_code))
         system_currency = USD
     date_format = proxy.systemShortDateFormat()
     logging.info("System date format: %s" % date_format)
     date_format = clean_format(date_format)
     logging.info("Cleaned date format: %s" % date_format)
     decimal_sep = proxy.systemNumberDecimalSeparator()
     grouping_sep = proxy.systemNumberGroupingSeparator()
     logging.info("System numeric separators: %s and %s" % (grouping_sep, decimal_sep))
     model = Application(
         self,
         date_format=date_format,
         decimal_sep=decimal_sep,
         grouping_sep=grouping_sep,
         default_currency=system_currency,
         cache_path=cache_path,
         appdata_path=appdata_path,
     )
     PyBaseApp.__init__(self, model)
Esempio n. 13
0
 def __init__(self):
     LOGGING_LEVEL = logging.DEBUG if proxy.prefValue_(
         'DebugMode') else logging.WARNING
     logging.basicConfig(level=LOGGING_LEVEL,
                         format='%(levelname)s %(message)s',
                         handlers=[CocoaHandler()])
     install_exception_hook('https://github.com/hsoft/moneyguru/issues')
     logging.debug('started in debug mode')
     cache_path = op.join(proxy.getCachePath(), 'moneyGuru')
     appdata_path = op.join(proxy.getAppdataPath(), 'moneyGuru')
     currency_code = nonone(proxy.systemCurrency(), 'USD')
     logging.info('Currency code: {0}'.format(currency_code))
     try:
         system_currency = Currency(currency_code)
     except ValueError:  # currency does not exist
         logging.warning(
             'System currency {0} is not supported. Falling back to USD.'.
             format(currency_code))
         system_currency = USD
     date_format = proxy.systemShortDateFormat()
     logging.info('System date format: %s' % date_format)
     date_format = clean_format(date_format)
     logging.info('Cleaned date format: %s' % date_format)
     decimal_sep = proxy.systemNumberDecimalSeparator()
     grouping_sep = proxy.systemNumberGroupingSeparator()
     logging.info('System numeric separators: %s and %s' %
                  (grouping_sep, decimal_sep))
     model = Application(self,
                         date_format=date_format,
                         decimal_sep=decimal_sep,
                         grouping_sep=grouping_sep,
                         default_currency=system_currency,
                         cache_path=cache_path,
                         appdata_path=appdata_path)
     PyBaseApp.__init__(self, model)
Esempio n. 14
0
 def _restore_preferences_after_load(self):
     # some preference need the file loaded before attempting a restore
     logging.debug('restore_preferences_after_load() beginning')
     excluded_account_names = set(nonone(self.get_default(EXCLUDED_ACCOUNTS_PREFERENCE), []))
     self.excluded_accounts = {a for a in self.accounts if a.name in excluded_account_names}
     selected_range = self.app.get_default(SELECTED_DATE_RANGE_PREFERENCE)
     if selected_range == DATE_RANGE_ALL_TRANSACTIONS: # only works after load
         self.select_all_transactions_range()
     self.notify('document_restoring_preferences')
Esempio n. 15
0
 def get_default(self, key, fallback_value=None):
     result = nonone(self.view.get_default(key), fallback_value)
     if fallback_value is not None and not isinstance(result, type(fallback_value)):
         # we don't want to end up with garbage values from the prefs
         try:
             result = type(fallback_value)(result)
         except Exception:
             result = fallback_value
     return result
Esempio n. 16
0
 def get_default(self, key, fallback_value=None):
     result = nonone(self.view.get_default(key), fallback_value)
     if fallback_value is not None and not isinstance(result, type(fallback_value)):
         # we don't want to end up with garbage values from the prefs
         try:
             result = type(fallback_value)(result)
         except Exception:
             result = fallback_value
     return result
Esempio n. 17
0
 def get_stat(self, stat, null_value=0):
     """ Returns the aggregated value of the 'stat' attribute of all self's children.
         
         Returns either int or [str] (sorted by occurence). 
         If the result is None, null_value is returned.
     """
     result = self._get_stat(stat)
     if isinstance(result, dict):
         result = sorted(list(result.keys()), key=result.__getitem__, reverse=True)
     return nonone(result, null_value)
Esempio n. 18
0
 def get_stat(self, stat, null_value=0):
     """ Returns the aggregated value of the 'stat' attribute of all self's children.
         
         Returns either int or [str] (sorted by occurence). 
         If the result is None, null_value is returned.
     """
     result = self._get_stat(stat)
     if isinstance(result, dict):
         result = sorted(list(result.keys()),
                         key=result.__getitem__,
                         reverse=True)
     return nonone(result, null_value)
Esempio n. 19
0
 def layout2preference(layout):
     result = {}
     result['name'] = layout.name
     # trim trailing None values
     columns = list(dropwhile(lambda x: x is None, layout.columns[::-1]))[::-1]
     # None values cannot be put in preferences, change them to an empty string.
     columns = [nonone(col, '') for col in columns]
     result['columns'] = columns
     result['excluded_lines'] = sorted(list(layout.excluded_lines))
     if layout.target_account_name:
         result['target_account'] = layout.target_account_name
     return result
Esempio n. 20
0
 def __init__(self, date, description=None, payee=None, checkno=None, account=None, amount=None):
     #: Date at which the transation occurs.
     self.date = date
     #: Description of the transaction.
     self.description = nonone(description, '')
     #: Person or entity related to the transaction.
     self.payee = nonone(payee, '')
     #: Check number related to the transaction.
     self.checkno = nonone(checkno, '')
     #: Freeform note about the transaction.
     self.notes = ''
     if amount is not None:
         self.splits = [Split(self, account, amount), Split(self, None, -amount)]
     else:
         #: The list of :class:`Split` affecting this transaction. These splits always balance.
         self.splits = []
     #: Ordering attributes. When two transactions have the same date, we order them with this.
     self.position = 0
     #: Timestamp of the last modification. Used in the UI to let the user sort his transactions.
     #: This is useful for finding a mistake that we know was introduced recently.
     self.mtime = 0
Esempio n. 21
0
 def layout2preference(layout):
     result = {}
     result['name'] = layout.name
     # trim trailing None values
     columns = list(dropwhile(lambda x: x is None,
                              layout.columns[::-1]))[::-1]
     # None values cannot be put in preferences, change them to an empty string.
     columns = [nonone(col, '') for col in columns]
     result['columns'] = columns
     result['excluded_lines'] = sorted(list(layout.excluded_lines))
     if layout.target_account_name:
         result['target_account'] = layout.target_account_name
     return result
Esempio n. 22
0
 def __set_groups(self, new_groups):
     self.mark_none()
     self.__groups = new_groups
     self.__group_of_duplicate = {}
     for g in self.__groups:
         for dupe in g:
             self.__group_of_duplicate[dupe] = g
             if not hasattr(dupe, 'is_ref'):
                 dupe.is_ref = False
     self.is_modified = bool(self.__groups)
     old_filters = nonone(self.__filters, [])
     self.apply_filter(None)
     for filter_str in old_filters:
         self.apply_filter(filter_str)
Esempio n. 23
0
 def paint(self, painter, option, index):
     ItemDelegate.paint(self, painter, option, index)
     extraFlags = nonone(self._model.data(index, EXTRA_ROLE), 0)
     if extraFlags & (EXTRA_UNDERLINED | EXTRA_UNDERLINED_DOUBLE):
         p1 = option.rect.bottomLeft()
         p2 = option.rect.bottomRight()
         p1.setX(p1.x()+1)
         p2.setX(p2.x()-1)
         painter.drawLine(p1, p2)
         if extraFlags & EXTRA_UNDERLINED_DOUBLE:
             # Yes, yes, we step over the item's bounds, but we have no choice because under
             # Windows, there's not enough height for double lines. Moreover, the line under
             # a total line is a blank line, so we have plenty of space.
             p1.setY(p1.y()+2)
             p2.setY(p2.y()+2)
             painter.drawLine(p1, p2)
Esempio n. 24
0
 def paint(self, painter, option, index):
     ItemDelegate.paint(self, painter, option, index)
     extraFlags = nonone(self._model.data(index, EXTRA_ROLE), 0)
     if extraFlags & (EXTRA_UNDERLINED | EXTRA_UNDERLINED_DOUBLE):
         p1 = option.rect.bottomLeft()
         p2 = option.rect.bottomRight()
         p1.setX(p1.x() + 1)
         p2.setX(p2.x() - 1)
         painter.drawLine(p1, p2)
         if extraFlags & EXTRA_UNDERLINED_DOUBLE:
             # Yes, yes, we step over the item's bounds, but we have no choice because under
             # Windows, there's not enough height for double lines. Moreover, the line under
             # a total line is a blank line, so we have plenty of space.
             p1.setY(p1.y() + 2)
             p2.setY(p2.y() + 2)
             painter.drawLine(p1, p2)
Esempio n. 25
0
 def render(self, painter):
     # We used to re-use itemDelegate() for cell drawing, but it turned out to me more
     # complex than anything (with margins being too wide and all...)
     columnWidths = self.stats.columnWidths(self.rect.width())
     rowHeight = self.stats.rowHeight
     headerHeight = self.stats.headerHeight
     left = self.rect.left()
     top = self.rect.top()
     painter.save()
     painter.setFont(self.ds.headerFont())
     for colStats, colWidth in zip(self.stats.columns, columnWidths):
         col = colStats.col
         headerRect = QRect(left, top, colWidth, headerHeight)
         headerRect = applyMargin(headerRect, CELL_MARGIN)
         painter.drawText(headerRect, col.alignment, col.display)
         left += colWidth
     top += headerHeight
     painter.restore()
     painter.drawLine(
         self.rect.left(), self.rect.top()+headerHeight,
         self.rect.right(), self.rect.top()+headerHeight
     )
     painter.save()
     painter.setFont(self.ds.rowFont())
     for rs in self.rowStats:
         rowIndex = rs.index
         left = self.rect.left()
         extraRole = nonone(self.ds.data(rowIndex, 0, EXTRA_ROLE), 0)
         rowIsSpanning = extraRole & EXTRA_SPAN_ALL_COLUMNS
         if rowIsSpanning:
             itemRect = QRect(left, top, self.rect.width(), rowHeight)
             self.renderCell(painter, rs, None, itemRect)
         else:
             for colStats, colWidth in zip(self.stats.columns, columnWidths):
                 indentation = self.ds.indentation(rowIndex, colStats.index)
                 itemRect = QRect(left+indentation, top, colWidth, rowHeight)
                 itemRect = applyMargin(itemRect, CELL_MARGIN)
                 self.renderCell(painter, rs, colStats, itemRect)
                 left += colWidth
             if rs.splitCount > 2:
                 splitsRect = QRect(
                     self.rect.left()+SPLIT_XOFFSET, top+rowHeight,
                     self.rect.width(), rs.height-rowHeight
                 )
                 self.renderSplit(painter, rs, splitsRect)
         top += rs.height
     painter.restore()
Esempio n. 26
0
    def get_spawns(self, end):
        """Returns the list of transactions spawned by our recurrence.

        We start at :attr:`start_date` and end at ``end``. We have to specify an end to our spawning
        to avoid getting infinite results.

        .. rubric:: End date adjustment

        If a changed date end up being smaller than the "spawn date", it's possible that a spawn
        that should have been spawned for the date range is not spawned. Therefore, we always
        spawn at least until the date of the last exception. For global changes, it's even more
        complicated. If the global date delta is negative enough, we can end up with a spawn that
        doesn't go far enough, so we must adjust our max date by this delta.

        :param datetime.date end: When to stop spawning.
        :rtype: list of :class:`Spawn`
        """
        if self.date2exception:
            end = max(end, max(self.date2exception.keys()))
        if self.date2globalchange:
            min_date_delta = min(ref.date-date for date, ref in self.date2globalchange.items())
            if min_date_delta < datetime.timedelta(days=0):
                end += -min_date_delta
        end = min(end, nonone(self.stop_date, datetime.date.max))

        date_counter = DateCounter(self.start_date, self.repeat_type, self.repeat_every, end)
        result = []
        global_date_delta = datetime.timedelta(days=0)
        current_ref = self.ref
        for current_date in date_counter:
            if current_date in self.date2globalchange:
                current_ref = self.date2globalchange[current_date]
                global_date_delta = current_ref.date - current_date
            if current_date in self.date2exception:
                exception = self.date2exception[current_date]
                if exception is not None:
                    result.append(exception)
            else:
                if current_date not in self.date2instances:
                    spawn = self._create_spawn(current_ref, current_date)
                    if global_date_delta:
                        # Only muck with spawn.date if we have a delta. otherwise we're breaking
                        # budgets.
                        spawn.date = current_date + global_date_delta
                    self.date2instances[current_date] = spawn
                result.append(self.date2instances[current_date])
        return result
Esempio n. 27
0
 def render(self, painter):
     # We used to re-use itemDelegate() for cell drawing, but it turned out to me more
     # complex than anything (with margins being too wide and all...)
     columnWidths = self.stats.columnWidths(self.rect.width())
     rowHeight = self.stats.rowHeight
     headerHeight = self.stats.headerHeight
     left = self.rect.left()
     top = self.rect.top()
     painter.save()
     painter.setFont(self.ds.headerFont())
     for colStats, colWidth in zip(self.stats.columns, columnWidths):
         col = colStats.col
         headerRect = QRect(left, top, colWidth, headerHeight)
         headerRect = applyMargin(headerRect, CELL_MARGIN)
         painter.drawText(headerRect, col.alignment, col.display)
         left += colWidth
     top += headerHeight
     painter.restore()
     painter.drawLine(
         self.rect.left(), self.rect.top()+headerHeight,
         self.rect.right(), self.rect.top()+headerHeight
     )
     painter.save()
     painter.setFont(self.ds.rowFont())
     for rs in self.rowStats:
         rowIndex = rs.index
         left = self.rect.left()
         extraRole = nonone(self.ds.data(rowIndex, 0, EXTRA_ROLE), 0)
         rowIsSpanning = extraRole & EXTRA_SPAN_ALL_COLUMNS
         if rowIsSpanning:
             itemRect = QRect(left, top, self.rect.width(), rowHeight)
             self.renderCell(painter, rs, None, itemRect)
         else:
             for colStats, colWidth in zip(self.stats.columns, columnWidths):
                 indentation = self.ds.indentation(rowIndex, colStats.index)
                 itemRect = QRect(left+indentation, top, colWidth, rowHeight)
                 itemRect = applyMargin(itemRect, CELL_MARGIN)
                 self.renderCell(painter, rs, colStats, itemRect)
                 left += colWidth
             if rs.splitCount > 2:
                 splitsRect = QRect(
                     self.rect.left()+SPLIT_XOFFSET, top+rowHeight,
                     self.rect.width(), rs.height-rowHeight
                 )
                 self.renderSplit(painter, rs, splitsRect)
         top += rs.height
     painter.restore()
Esempio n. 28
0
    def get_default(self, key, fallback_value=None):
        """Returns moneyGuru user pref for ``key``.

        .. seealso:: :meth:`ApplicationView.get_default`

        :param str key: The key of the prefence to return.
        :param fallback_value: if the pref doesn't exist or isn't of the same type as the
                               fallback value, return the fallback. Therefore, you can use the
                               fallback value as a way to tell "I expect preferences of this type".
        """
        result = nonone(self.view.get_default(key), fallback_value)
        if fallback_value is not None and not isinstance(result, type(fallback_value)):
            # we don't want to end up with garbage values from the prefs
            try:
                result = type(fallback_value)(result)
            except Exception:
                result = fallback_value
        return result
Esempio n. 29
0
    def _read_info(self, field):
        if field in {'size', 'mtime'}:
            size = sum((f.size for f in self._all_items()), 0)
            self.size = size
            stats = self.path.stat()
            self.mtime = nonone(stats.st_mtime, 0)
        elif field in {'md5', 'md5partial'}:
            # What's sensitive here is that we must make sure that subfiles'
            # md5 are always added up in the same order, but we also want a
            # different md5 if a file gets moved in a different subdirectory.
            def get_dir_md5_concat():
                items = self._all_items()
                items.sort(key=lambda f: f.path)
                md5s = [getattr(f, field) for f in items]
                return b''.join(md5s)

            md5 = hashlib.md5(get_dir_md5_concat())
            digest = md5.digest()
            setattr(self, field, digest)
Esempio n. 30
0
    def get_default(self, key, fallback_value=None):
        """Returns moneyGuru user pref for ``key``.

        .. seealso:: :meth:`ApplicationView.get_default`

        :param str key: The key of the prefence to return.
        :param fallback_value: if the pref doesn't exist or isn't of the same type as the
                               fallback value, return the fallback. Therefore, you can use the
                               fallback value as a way to tell "I expect preferences of this type".
        """
        result = nonone(self.view.get_default(key), fallback_value)
        if fallback_value is not None and not isinstance(
                result, type(fallback_value)):
            # we don't want to end up with garbage values from the prefs
            try:
                result = type(fallback_value)(result)
            except Exception:
                result = fallback_value
        return result
Esempio n. 31
0
 def _read_info(self, field):
     if field in {'size', 'mtime'}:
         size = sum((f.size for f in self._all_items()), 0)
         self.size = size
         stats = self.path.stat()
         self.mtime = nonone(stats.st_mtime, 0)
     elif field in {'md5', 'md5partial'}:
         # What's sensitive here is that we must make sure that subfiles'
         # md5 are always added up in the same order, but we also want a
         # different md5 if a file gets moved in a different subdirectory.
         def get_dir_md5_concat():
             items = self._all_items()
             items.sort(key=lambda f:f.path)
             md5s = [getattr(f, field) for f in items]
             return b''.join(md5s)
         
         md5 = hashlib.md5(get_dir_md5_concat())
         digest = md5.digest()
         setattr(self, field, digest)
Esempio n. 32
0
 def __init__(self, ds):
     rowFM = QFontMetrics(ds.rowFont())
     self.splitFM = QFontMetrics(ds.splitFont())
     headerFM = QFontMetrics(ds.headerFont())
     self.rowHeight = rowFM.height() + CELL_MARGIN * 2
     self.splitHeight = self.splitFM.height() + CELL_MARGIN * 2
     self.headerHeight = headerFM.height() + CELL_MARGIN * 2
     spannedRowIndexes = set()
     for rowIndex in range(ds.rowCount()):
         extraRole = nonone(ds.data(rowIndex, 0, EXTRA_ROLE), 0)
         if extraRole & EXTRA_SPAN_ALL_COLUMNS:
             spannedRowIndexes.add(rowIndex)
     self.columns = []
     for colIndex in range(ds.columnCount()):
         col = ds.columnAtIndex(colIndex)
         sumWidth = 0
         maxWidth = 0
         # We need to have *at least* the width of the header.
         minWidth = headerFM.width(col.display) + CELL_MARGIN * 2
         for rowIndex in range(ds.rowCount()):
             if rowIndex in spannedRowIndexes:
                 continue
             data = ds.data(rowIndex, colIndex, Qt.DisplayRole)
             if data:
                 font = ds.data(rowIndex, colIndex, Qt.FontRole)
                 fm = QFontMetrics(font) if font is not None else rowFM
                 width = fm.width(data) + CELL_MARGIN * 2
                 width += ds.indentation(rowIndex, colIndex)
                 sumWidth += width
                 maxWidth = max(maxWidth, width)
             pixmap = ds.data(rowIndex, colIndex, Qt.DecorationRole)
             if pixmap is not None:
                 width = pixmap.width() + CELL_MARGIN * 2
                 maxWidth = max(maxWidth, width)
         avgWidth = sumWidth // ds.rowCount()
         maxWidth = max(maxWidth, minWidth)
         if col.cantTruncate: # if it's a "can't truncate" column, we make no concession
             minWidth = maxWidth
         cs = ColumnStats(colIndex, col, avgWidth, maxWidth, minWidth)
         self.columns.append(cs)
     self.maxWidth = sum(cs.maxWidth for cs in self.columns)
     self.minWidth = sum(cs.minWidth for cs in self.columns)
Esempio n. 33
0
 def __init__(self, ds):
     rowFM = QFontMetrics(ds.rowFont())
     self.splitFM = QFontMetrics(ds.splitFont())
     headerFM = QFontMetrics(ds.headerFont())
     self.rowHeight = rowFM.height() + CELL_MARGIN * 2
     self.splitHeight = self.splitFM.height() + CELL_MARGIN * 2
     self.headerHeight = headerFM.height() + CELL_MARGIN * 2
     spannedRowIndexes = set()
     for rowIndex in range(ds.rowCount()):
         extraRole = nonone(ds.data(rowIndex, 0, EXTRA_ROLE), 0)
         if extraRole & EXTRA_SPAN_ALL_COLUMNS:
             spannedRowIndexes.add(rowIndex)
     self.columns = []
     for colIndex in range(ds.columnCount()):
         col = ds.columnAtIndex(colIndex)
         sumWidth = 0
         maxWidth = 0
         # We need to have *at least* the width of the header.
         minWidth = headerFM.width(col.display) + CELL_MARGIN * 2
         for rowIndex in range(ds.rowCount()):
             if rowIndex in spannedRowIndexes:
                 continue
             data = ds.data(rowIndex, colIndex, Qt.DisplayRole)
             if data:
                 font = ds.data(rowIndex, colIndex, Qt.FontRole)
                 fm = QFontMetrics(font) if font is not None else rowFM
                 width = fm.width(data) + CELL_MARGIN * 2
                 width += ds.indentation(rowIndex, colIndex)
                 sumWidth += width
                 maxWidth = max(maxWidth, width)
             pixmap = ds.data(rowIndex, colIndex, Qt.DecorationRole)
             if pixmap is not None:
                 width = pixmap.width() + CELL_MARGIN * 2
                 maxWidth = max(maxWidth, width)
         avgWidth = sumWidth // ds.rowCount()
         maxWidth = max(maxWidth, minWidth)
         if col.cantTruncate: # if it's a "can't truncate" column, we make no concession
             minWidth = maxWidth
         cs = ColumnStats(colIndex, col, avgWidth, maxWidth, minWidth)
         self.columns.append(cs)
     self.maxWidth = sum(cs.maxWidth for cs in self.columns)
     self.minWidth = sum(cs.minWidth for cs in self.columns)
Esempio n. 34
0
 def renderCell(self, painter, rowStats, colStats, itemRect):
     rowIndex = rowStats.index
     colIndex = colStats.index if colStats is not None else 0
     extraFlags = nonone(self.ds.data(rowIndex, colIndex, EXTRA_ROLE), 0)
     pixmap = self.ds.data(rowIndex, colIndex, Qt.DecorationRole)
     if pixmap:
         painter.drawPixmap(itemRect.topLeft(), pixmap)
     else:
         # we don't support drawing pixmap and text in the same cell (don't need it)
         bgbrush = self.ds.data(rowIndex, colIndex, Qt.BackgroundRole)
         if bgbrush is not None:
             painter.fillRect(itemRect, bgbrush)
         if rowStats.splitCount > 2 and colStats.col.name in {'from', 'to', 'transfer'}:
             text = '--split--'
         else:
             text = self.ds.data(rowIndex, colIndex, Qt.DisplayRole)
         if text:
             alignment = self.ds.data(rowIndex, colIndex, Qt.TextAlignmentRole)
             if not alignment:
                 alignment = Qt.AlignLeft|Qt.AlignVCenter
             font = self.ds.data(rowIndex, colIndex, Qt.FontRole)
             if font is None:
                 font = self.ds.rowFont()
             fm = QFontMetrics(font)
             # elidedText has a tendency to "over-elide" that's why we have "+1"
             text = fm.elidedText(text, Qt.ElideRight, itemRect.width()+1)
             painter.save()
             painter.setFont(font)
             painter.drawText(itemRect, alignment, text)
             painter.restore()
     if extraFlags & (EXTRA_UNDERLINED | EXTRA_UNDERLINED_DOUBLE):
         p1 = itemRect.bottomLeft()
         p2 = itemRect.bottomRight()
         # Things get crowded with double lines and we have to cheat a little bit on
         # item rects.
         p1.setY(p1.y()+2)
         p2.setY(p2.y()+2)
         painter.drawLine(p1, p2)
         if extraFlags & EXTRA_UNDERLINED_DOUBLE:
             p1.setY(p1.y()-3)
             p2.setY(p2.y()-3)
             painter.drawLine(p1, p2)
Esempio n. 35
0
 def renderCell(self, painter, rowStats, colStats, itemRect):
     rowIndex = rowStats.index
     colIndex = colStats.index if colStats is not None else 0
     extraFlags = nonone(self.ds.data(rowIndex, colIndex, EXTRA_ROLE), 0)
     pixmap = self.ds.data(rowIndex, colIndex, Qt.DecorationRole)
     if pixmap:
         painter.drawPixmap(itemRect.topLeft(), pixmap)
     else:
         # we don't support drawing pixmap and text in the same cell (don't need it)
         bgbrush = self.ds.data(rowIndex, colIndex, Qt.BackgroundRole)
         if bgbrush is not None:
             painter.fillRect(itemRect, bgbrush)
         if rowStats.splitCount > 2 and colStats.col.name in {'from', 'to', 'transfer'}:
             text = '--split--'
         else:
             text = self.ds.data(rowIndex, colIndex, Qt.DisplayRole)
         if text:
             alignment = self.ds.data(rowIndex, colIndex, Qt.TextAlignmentRole)
             if not alignment:
                 alignment = Qt.AlignLeft|Qt.AlignVCenter
             font = self.ds.data(rowIndex, colIndex, Qt.FontRole)
             if font is None:
                 font = self.ds.rowFont()
             fm = QFontMetrics(font)
             # elidedText has a tendency to "over-elide" that's why we have "+1"
             text = fm.elidedText(text, Qt.ElideRight, itemRect.width()+1)
             painter.save()
             painter.setFont(font)
             painter.drawText(itemRect, alignment, text)
             painter.restore()
     if extraFlags & (EXTRA_UNDERLINED | EXTRA_UNDERLINED_DOUBLE):
         p1 = itemRect.bottomLeft()
         p2 = itemRect.bottomRight()
         # Things get crowded with double lines and we have to cheat a little bit on 
         # item rects.
         p1.setY(p1.y()+2)
         p2.setY(p2.y()+2)
         painter.drawLine(p1, p2)
         if extraFlags & EXTRA_UNDERLINED_DOUBLE:
             p1.setY(p1.y()-3)
             p2.setY(p2.y()-3)
             painter.drawLine(p1, p2)
Esempio n. 36
0
    def _getDataFromIndex(self, index):
        """Retrieves model amount data and converts it to a DisplayAmount.


        Retrieves the amount string from the model based on the column name.
        Uses a regular expression to parse this string, converting it into
        a DisplayAmount to be returned.

        Args:
            index - QModelIndex in the model

        Returns:
            None if the data is not in the model or the regular expression
            failed to parse the data.

            A DisplayAmount with currency and value information used to
            perform amount painting. For example:

            "MXN 432 321,01" -> DisplayAmount("MXN", "432 321,01")
        """
        if not index.isValid():
            return None
        column = self._model.columns.column_by_index(index.column())
        if column.name != self._attr_name:
            return None
        amount = getattr(self._model[index.row()], column.name)

        amount = CURR_VALUE_RE.match(amount)

        if amount is None:
            logging.warning(
                "Amount for column %s index row %s "
                "with amount '%s' did not match regular "
                "expression for amount painting.", column.name, index.row(),
                amount)
            return None

        amount = amount.groups()

        return DisplayAmount(nonone(amount[0], "").strip(), amount[1])
Esempio n. 37
0
    def _getDataFromIndex(self, index):
        """Retrieves model amount data and converts it to a DisplayAmount.


        Retrieves the amount string from the model based on the column name.
        Uses a regular expression to parse this string, converting it into
        a DisplayAmount to be returned.

        Args:
            index - QModelIndex in the model

        Returns:
            None if the data is not in the model or the regular expression
            failed to parse the data.

            A DisplayAmount with currency and value information used to
            perform amount painting. For example:

            "MXN 432 321,01" -> DisplayAmount("MXN", "432 321,01")
        """
        if not index.isValid():
            return None
        column = self._model.columns.column_by_index(index.column())
        if column.name != self._attr_name:
            return None
        amount = getattr(self._model[index.row()], column.name)

        amount = CURR_VALUE_RE.match(amount)

        if amount is None:
            logging.warning("Amount for column %s index row %s "
                            "with amount '%s' did not match regular "
                            "expression for amount painting.",
                            column.name, index.row(), amount)
            return None

        amount = amount.groups()

        return DisplayAmount(nonone(amount[0], "").strip(), amount[1])
Esempio n. 38
0
 def get_spawns(self, end):
     # END DATE ADJUSTMENT
     # if a changed date end up being smaller than the "spawn date", it's possible that a spawn
     # that should have been spawned for the date range is not spawned. Therefore, we always
     # spawn at least until the date of the last exception. For global changes, it's even more
     # complicated. If the global date delta is negative enough, we can end up with a spawn that
     # doesn't go far enough, so we must adjust our max date by this delta.
     if self.date2exception:
         end = max(end, max(self.date2exception.keys()))
     if self.date2globalchange:
         min_date_delta = min(ref.date-date for date, ref in self.date2globalchange.items())
         if min_date_delta < datetime.timedelta(days=0):
             end += -min_date_delta
     end = min(end, nonone(self.stop_date, datetime.date.max))
     
     date_counter = DateCounter(self.start_date, self.repeat_type, self.repeat_every, end)
     result = []
     global_date_delta = datetime.timedelta(days=0)
     current_ref = self.ref
     for current_date in date_counter:
         if current_date in self.date2globalchange:
             current_ref = self.date2globalchange[current_date]
             global_date_delta = current_ref.date - current_date
         if current_date in self.date2exception:
             exception = self.date2exception[current_date]
             if exception is not None:
                 result.append(exception)
         else:
             if current_date not in self.date2instances:
                 spawn = self._create_spawn(current_ref, current_date)
                 if global_date_delta:
                     # Only muck with spawn.date if we have a delta. otherwise we're breaking
                     # budgets.
                     spawn.date = current_date + global_date_delta
                 self.date2instances[current_date] = spawn
             result.append(self.date2instances[current_date])
     return result
Esempio n. 39
0
 def _set_completion(self, completion):
     completion = nonone(completion, '')
     self._complete_completion = completion
     self.completion = completion[len(self._text):]
     if self.completion:
         self.view.refresh()
Esempio n. 40
0
 def draw_polygon(self, points, pen_id, brush_id):
     points = [list(p) for p in points]
     self.callback.drawPolygonWithPoints_penID_brushID_(
         points, nonone(pen_id, -1), nonone(brush_id, -1))
Esempio n. 41
0
 def draw_rect(self, rect, pen_id, brush_id):
     self.callback.drawRect_penID_brushID_(rect, nonone(pen_id, -1),
                                           nonone(brush_id, -1))
Esempio n. 42
0
 def is_balance_negative(self):
     return nonone(self._the_balance(), 0) < 0
Esempio n. 43
0
 def _set_completion(self, completion):
     completion = nonone(completion, '')
     self._complete_completion = completion
     self.completion = completion[len(self._text):]
     if self.completion:
         self.view.refresh()
Esempio n. 44
0
    def load(self):
        """Loads the parsed info into self.accounts and self.transactions.

        You must have called parse() before calling this.
        """
        def load_transaction_info(info):
            description = info.description
            payee = info.payee
            checkno = info.checkno
            date = info.date
            transaction = Transaction(date, description, payee, checkno)
            transaction.notes = nonone(info.notes, '')
            for split_info in info.splits:
                account = split_info.account
                amount = split_info.amount
                if split_info.amount_reversed:
                    amount = -amount
                memo = nonone(split_info.memo, '')
                split = Split(transaction, account, amount)
                split.memo = memo
                if split_info.reconciliation_date is not None:
                    split.reconciliation_date = split_info.reconciliation_date
                elif split_info.reconciled: # legacy
                    split.reconciliation_date = transaction.date
                split.reference = split_info.reference
                transaction.splits.append(split)
            while len(transaction.splits) < 2:
                transaction.splits.append(Split(transaction, None, 0))
            transaction.balance()
            transaction.mtime = info.mtime
            if info.reference is not None:
                for split in transaction.splits:
                    if split.reference is None:
                        split.reference = info.reference
            return transaction

        self._load()
        self.flush_account() # Implicit
        # Now, we take the info we have and transform it into model instances
        currencies = set()
        start_date = datetime.date.max
        for info in self.group_infos:
            group = Group(info.name, info.type)
            self.groups.append(group)
        for info in self.account_infos:
            account_type = info.type
            if account_type not in AccountType.All:
                account_type = AccountType.Asset
            account_currency = self.default_currency
            try:
                if info.currency:
                    account_currency = Currency(info.currency)
            except ValueError:
                pass # keep account_currency as self.default_currency
            account = Account(info.name, account_currency, account_type)
            if info.group:
                account.group = self.groups.find(info.group, account_type)
            if info.budget:
                self.budget_infos.append(BudgetInfo(info.name, info.budget_target, info.budget))
            account.reference = info.reference
            account.account_number = info.account_number
            account.notes = info.notes
            currencies.add(account.currency)
            self.accounts.add(account)

        # Pre-parse transaction info. We bring all relevant info recorded at the txn level into the split level
        all_txn = self.transaction_infos + [r.transaction_info for r in self.recurrence_infos] +\
            flatten([stripfalse(r.date2exception.values()) for r in self.recurrence_infos]) +\
            flatten([r.date2globalchange.values() for r in self.recurrence_infos])
        for info in all_txn:
            split_accounts = [s.account for s in info.splits]
            if info.account and info.account not in split_accounts:
                info.splits.insert(0, SplitInfo(info.account, info.amount, info.currency, False))
            if info.transfer and info.transfer not in split_accounts:
                info.splits.append(SplitInfo(info.transfer, info.amount, info.currency, True))
            for split_info in info.splits:
                # this amount is just to determine the auto_create_type
                str_amount = split_info.amount
                if split_info.currency:
                    str_amount += split_info.currency
                amount = self.parse_amount(str_amount, self.default_currency)
                auto_create_type = AccountType.Income if amount >= 0 else AccountType.Expense
                split_info.account = (
                    self.accounts.find(split_info.account, auto_create_type)
                    if split_info.account
                    else None
                )
                currency = split_info.account.currency if split_info.account is not None else self.default_currency
                split_info.amount = self.parse_amount(str_amount, currency)
                if split_info.amount:
                    currencies.add(split_info.amount.currency)

        self.transaction_infos.sort(key=attrgetter('date'))
        for date, transaction_infos in groupby(self.transaction_infos, attrgetter('date')):
            start_date = min(start_date, date)
            for position, info in enumerate(transaction_infos, start=1):
                transaction = load_transaction_info(info)
                self.transactions.add(transaction, position=position)

        # Scheduled
        for info in self.recurrence_infos:
            ref = load_transaction_info(info.transaction_info)
            recurrence = Recurrence(ref, info.repeat_type, info.repeat_every)
            recurrence.stop_date = info.stop_date
            for date, transaction_info in info.date2exception.items():
                if transaction_info is not None:
                    exception = load_transaction_info(transaction_info)
                    spawn = Spawn(recurrence, exception, date, exception.date)
                    recurrence.date2exception[date] = spawn
                else:
                    recurrence.delete_at(date)
            for date, transaction_info in info.date2globalchange.items():
                change = load_transaction_info(transaction_info)
                spawn = Spawn(recurrence, change, date, change.date)
                recurrence.date2globalchange[date] = spawn
            self.schedules.append(recurrence)
        # Budgets
        TODAY = datetime.date.today()
        fallback_start_date = datetime.date(TODAY.year, TODAY.month, 1)
        for info in self.budget_infos:
            account = self.accounts.find(info.account)
            if account is None:
                continue
            target = self.accounts.find(info.target) if info.target else None
            amount = self.parse_amount(info.amount, account.currency)
            start_date = nonone(info.start_date, fallback_start_date)
            budget = Budget(account, target, amount, start_date, repeat_type=info.repeat_type)
            budget.notes = nonone(info.notes, '')
            budget.stop_date = info.stop_date
            if info.repeat_every:
                budget.repeat_every = info.repeat_every
            self.budgets.append(budget)
        self._post_load()
        self.oven.cook(datetime.date.min, until_date=None)
        Currency.get_rates_db().ensure_rates(start_date, [x.code for x in currencies])
Esempio n. 45
0
 def original(self):
     if hasattr(self.copyof, 'original'):
         return self.copyof.original
     else:
         return nonone(self.copyof, self)
Esempio n. 46
0
 def original(self):
     if hasattr(self.copyof, 'original'):
         return self.copyof.original
     else:
         return nonone(self.copyof, self)
Esempio n. 47
0
 def draw_rect(self, rect, pen_id, brush_id):
     self.callback.drawRect_penID_brushID_(rect, nonone(pen_id, -1), nonone(brush_id, -1))
Esempio n. 48
0
 def draw_polygon(self, points, pen_id, brush_id):
     points = [list(p) for p in points]
     self.callback.drawPolygonWithPoints_penID_brushID_(points, nonone(pen_id, -1), nonone(brush_id, -1))
Esempio n. 49
0
 def filter_string(self, value):
     value = nonone(value, '').strip()
     if value == self._filter_string:
         return
     self._filter_string = value
     self.notify('filter_applied')
Esempio n. 50
0
    def load(self):
        """Loads the parsed info into self.accounts and self.transactions.

        You must have called parse() before calling this.
        """
        def load_transaction_info(info):
            description = info.description
            payee = info.payee
            checkno = info.checkno
            date = info.date
            transaction = Transaction(date, description, payee, checkno)
            transaction.notes = nonone(info.notes, '')
            for split_info in info.splits:
                account = split_info.account
                amount = split_info.amount
                if split_info.amount_reversed:
                    amount = -amount
                memo = nonone(split_info.memo, '')
                split = Split(transaction, account, amount)
                split.memo = memo
                if account is None or not of_currency(amount,
                                                      account.currency):
                    # fix #442: off-currency transactions shouldn't be reconciled
                    split.reconciliation_date = None
                elif split_info.reconciliation_date is not None:
                    split.reconciliation_date = split_info.reconciliation_date
                elif split_info.reconciled:  # legacy
                    split.reconciliation_date = transaction.date
                split.reference = split_info.reference
                transaction.splits.append(split)
            while len(transaction.splits) < 2:
                transaction.splits.append(Split(transaction, None, 0))
            transaction.balance()
            transaction.mtime = info.mtime
            if info.reference is not None:
                for split in transaction.splits:
                    if split.reference is None:
                        split.reference = info.reference
            return transaction

        self._load()
        self.flush_account()  # Implicit
        # Now, we take the info we have and transform it into model instances
        currencies = set()
        start_date = datetime.date.max
        for info in self.group_infos:
            group = Group(info.name, info.type)
            self.groups.append(group)
        for info in self.account_infos:
            account_type = info.type
            if account_type not in AccountType.All:
                account_type = AccountType.Asset
            account_currency = self.default_currency
            try:
                if info.currency:
                    account_currency = Currency(info.currency)
            except ValueError:
                pass  # keep account_currency as self.default_currency
            account = Account(info.name, account_currency, account_type)
            if info.group:
                account.group = self.groups.find(info.group, account_type)
            if info.budget:
                self.budget_infos.append(
                    BudgetInfo(info.name, info.budget_target, info.budget))
            account.reference = info.reference
            account.account_number = info.account_number
            account.inactive = info.inactive
            account.notes = info.notes
            currencies.add(account.currency)
            self.accounts.add(account)

        # Pre-parse transaction info. We bring all relevant info recorded at the txn level into the split level
        all_txn = self.transaction_infos + [r.transaction_info for r in self.recurrence_infos] +\
            flatten([stripfalse(r.date2exception.values()) for r in self.recurrence_infos]) +\
            flatten([r.date2globalchange.values() for r in self.recurrence_infos])
        for info in all_txn:
            split_accounts = [s.account for s in info.splits]
            if info.account and info.account not in split_accounts:
                info.splits.insert(
                    0,
                    SplitInfo(info.account, info.amount, info.currency, False))
            if info.transfer and info.transfer not in split_accounts:
                info.splits.append(
                    SplitInfo(info.transfer, info.amount, info.currency, True))
            for split_info in info.splits:
                # this amount is just to determine the auto_create_type
                str_amount = split_info.amount
                if split_info.currency:
                    str_amount += split_info.currency
                amount = self.parse_amount(str_amount, self.default_currency)
                auto_create_type = AccountType.Income if amount >= 0 else AccountType.Expense
                split_info.account = (self.accounts.find(
                    split_info.account, auto_create_type)
                                      if split_info.account else None)
                currency = split_info.account.currency if split_info.account is not None else self.default_currency
                split_info.amount = self.parse_amount(str_amount, currency)
                if split_info.amount:
                    currencies.add(split_info.amount.currency)

        self.transaction_infos.sort(key=attrgetter('date'))
        for date, transaction_infos in groupby(self.transaction_infos,
                                               attrgetter('date')):
            start_date = min(start_date, date)
            for position, info in enumerate(transaction_infos, start=1):
                transaction = load_transaction_info(info)
                self.transactions.add(transaction, position=position)

        # Scheduled
        for info in self.recurrence_infos:
            ref = load_transaction_info(info.transaction_info)
            recurrence = Recurrence(ref, info.repeat_type, info.repeat_every)
            recurrence.stop_date = info.stop_date
            for date, transaction_info in info.date2exception.items():
                if transaction_info is not None:
                    exception = load_transaction_info(transaction_info)
                    spawn = Spawn(recurrence, exception, date, exception.date)
                    recurrence.date2exception[date] = spawn
                else:
                    recurrence.delete_at(date)
            for date, transaction_info in info.date2globalchange.items():
                change = load_transaction_info(transaction_info)
                spawn = Spawn(recurrence, change, date, change.date)
                recurrence.date2globalchange[date] = spawn
            self.schedules.append(recurrence)
        # Budgets
        TODAY = datetime.date.today()
        fallback_start_date = datetime.date(TODAY.year, TODAY.month, 1)
        for info in self.budget_infos:
            account = self.accounts.find(info.account)
            if account is None:
                continue
            target = self.accounts.find(info.target) if info.target else None
            amount = self.parse_amount(info.amount, account.currency)
            start_date = nonone(info.start_date, fallback_start_date)
            budget = Budget(account,
                            target,
                            amount,
                            start_date,
                            repeat_type=info.repeat_type)
            budget.notes = nonone(info.notes, '')
            budget.stop_date = info.stop_date
            if info.repeat_every:
                budget.repeat_every = info.repeat_every
            self.budgets.append(budget)
        self._post_load()
        self.oven.cook(datetime.date.min, until_date=None)
        Currency.get_rates_db().ensure_rates(start_date,
                                             [x.code for x in currencies])