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
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
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
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
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
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
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
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}")
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)
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)
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)
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')
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
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)
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
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
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)
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)
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)
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()
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
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
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)
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
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)
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)
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)
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])
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])
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
def _set_completion(self, completion): completion = nonone(completion, '') self._complete_completion = completion self.completion = completion[len(self._text):] if self.completion: self.view.refresh()
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))
def draw_rect(self, rect, pen_id, brush_id): self.callback.drawRect_penID_brushID_(rect, nonone(pen_id, -1), nonone(brush_id, -1))
def is_balance_negative(self): return nonone(self._the_balance(), 0) < 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])
def original(self): if hasattr(self.copyof, 'original'): return self.copyof.original else: return nonone(self.copyof, self)
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))
def filter_string(self, value): value = nonone(value, '').strip() if value == self._filter_string: return self._filter_string = value self.notify('filter_applied')
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])