Implement show/hide warnings depending on columns

This commit is contained in:
Andreas Tsouchlos 2024-01-04 02:27:27 +01:00
parent 14e830ead0
commit 8a0d7f748f
2 changed files with 79 additions and 24 deletions

View File

@ -8,9 +8,7 @@ import argparse
def categorize_func(args):
import pandas as pd
df = None
if args.i is not None:
df = pd.read_csv(args.i, delimiter=';')
df = pd.read_csv(args.i, delimiter=args.d)
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
@ -35,11 +33,14 @@ def main():
categorize_parser = subparsers.add_parser("categorize")
categorize_parser.set_defaults(func=categorize_func)
categorize_parser.add_argument('-i', required=False,
categorize_parser.add_argument('-i', required=True,
help="Bank statement CSV")
categorize_parser.add_argument('-f', required=False,
help="JSON file containing regexes to"
" pre-categorize statement entries")
categorize_parser.add_argument('-d', required=False,
help="Delimiter to use when reading the"
" bank statement", default=',')
report_parser = subparsers.add_parser("report")
report_parser.set_defaults(func=report_func)

View File

@ -8,7 +8,7 @@ from PyQt6.QtCore import Qt, QSortFilterProxyModel
from PyQt6.QtGui import QPixmap, QAction
from PyQt6.QtWidgets import QMainWindow, QPushButton, QHBoxLayout, QLabel, \
QVBoxLayout, QMenu, QApplication, QTableView, QListView, QInputDialog, \
QMessageBox
QMessageBox, QFileDialog
class PandasModel(QtCore.QAbstractTableModel):
@ -74,31 +74,34 @@ class MainWindow(QMainWindow):
self._warning_layout \
= self.findChild(QVBoxLayout, "warningLayout")
self._create_button \
= self.findChild(QPushButton, "createCategoryButton")
self._delete_button \
= self.findChild(QPushButton, "deleteCategoryButton")
self._apply_button \
= self.findChild(QPushButton, "applyCategoryButton")
self._list_view \
= self.findChild(QListView, "categoryListView")
self._table_view \
= self.findChild(QTableView, "transactionTableView")
self._action_save \
= self.findChild(QAction, "actionSave")
self._warnings = []
self._category_model = QtGui.QStandardItemModel()
self._list_view.setModel(self._category_model)
self._create_button.clicked.connect(
self._handle_create_click)
self._create_button.clicked.connect(self._handle_create_click)
self._delete_button.clicked.connect(self._handle_create_click)
self._apply_button.clicked.connect(self._handle_create_click)
self._action_save.triggered.connect(self._handle_save)
header = self._table_view.horizontalHeader()
header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
header.customContextMenuRequested.connect(self._header_right_clicked)
def add_warning_text(self, text: str):
def _add_warning_text(self, text: str):
layout = QHBoxLayout()
warningIcon = QLabel()
@ -115,6 +118,7 @@ class MainWindow(QMainWindow):
layout.setStretch(1, 1)
self._warning_layout.addLayout(layout)
self._warnings.append((layout, warningIcon, label))
def set_statement_data(self, df: pd.DataFrame):
model = PandasModel(df)
@ -122,14 +126,42 @@ class MainWindow(QMainWindow):
proxyModel.setSourceModel(model)
self._table_view.setModel(proxyModel)
if len(df.columns) < 7:
if len(df.columns) < 10: # Experimentally determined threshold
# Properly resize columns (takes longer)
self._table_view.resizeColumnsToContents()
else:
# Quickly approximate sizes
for i, col in enumerate(df.columns):
max_char = max(max([len(str(x)) for x in df[col].values]),
len(col))
self._table_view.setColumnWidth(i, max_char * 10)
self._show_warnings(df)
def _show_warnings(self, df: pd.DataFrame):
for (layout, icon, text) in self._warnings:
icon.hide()
text.hide()
self._warning_layout.removeItem(layout)
if 't' not in df.columns:
self._add_warning_text(
"The column 't' does not exist. Please rename the"
" column containing the dates of the transactions"
" to 't'.")
if 'value' not in df.columns:
self._add_warning_text(
"The column 'value' does not exist. Please rename"
" the column containing the values of the"
" transactions to 'value'.")
if 'balance' not in df.columns:
self._add_warning_text(
"The column 'balance' does not exist. Please"
" rename the column containing the balance"
" after each transaction to 'balance'")
def get_statement_data(self) -> pd.DataFrame:
return self._table_view.model().sourceModel().get_dataframe()
@ -143,15 +175,19 @@ class MainWindow(QMainWindow):
rename_action = QAction("Rename", self)
delete_action = QAction("Delete", self)
switch_action = QAction("Switch position", self)
column = self._table_view.horizontalHeader().logicalIndexAt(pos)
rename_action.triggered.connect(
partial(self._header_rename_handler, column))
delete_action.triggered.connect(
partial(self._header_delete_handler, column))
switch_action.triggered.connect(
partial(self._header_switch_handler, column))
context.addAction(rename_action)
context.addAction(delete_action)
context.addAction(switch_action)
context.exec(self.sender().mapToGlobal(pos))
@ -181,6 +217,26 @@ class MainWindow(QMainWindow):
in enumerate(df.columns) if j != column]]
self.set_statement_data(df)
def _header_switch_handler(self, column):
model = self._table_view.horizontalHeader().model()
column_text = model.headerData(column, Qt.Orientation.Horizontal)
df = self.get_statement_data()
other_name, _ = QInputDialog.getItem(self, "Switch column position",
f"Switch position of colum"
f" '{column_text}' with:",
df.columns, editable=False)
column_titles = list(df.columns)
index1, index2 = column_titles.index(column_text), column_titles.index(
other_name)
column_titles[index1], column_titles[index2] \
= column_titles[index2], column_titles[index1]
df = df.reindex(columns=column_titles)
self.set_statement_data(df)
def _handle_create_click(self):
self.add_categories(['asdf'])
@ -190,6 +246,15 @@ class MainWindow(QMainWindow):
def _handle_apply_click(self):
pass
def _handle_save(self):
filename, _ = QFileDialog.getSaveFileName(self, 'Save File')
if filename == '':
return
df = self.get_statement_data()
df.to_csv(filename, index=False)
def show_main_window(categories: typing.Sequence[str] = None,
df: pd.DataFrame = None):
@ -202,29 +267,18 @@ def show_main_window(categories: typing.Sequence[str] = None,
if df is not None:
window.set_statement_data(df)
window.add_warning_text("The column 't' does not exist. Please rename the"
" column containing the dates of the transactions"
" to 't'.")
window.add_warning_text("The column 'balance' does not exist. Please"
" rename the column containing the balance after"
" each transaction to 'balance'")
window.add_warning_text("The column 'value' does not exist. Please rename"
" the column containing the values of the"
" transactions to 'value'.")
window.show()
app.exec()
def main():
from banking_breakdown.statement_parser import get_stripped_statement
import os
os.chdir("../")
categories = ["Food", "Rent & Utilities", "Hobbies", "Education",
"Transportation", "Social Life", "Other"]
df = pd.read_csv("res/bank_statement_2023.csv", delimiter=';')
df = pd.read_csv("res/bank_statement_2023_categorized_renamed.csv")
show_main_window(categories, df)