Implement show/hide warnings depending on columns
This commit is contained in:
parent
14e830ead0
commit
8a0d7f748f
@ -8,9 +8,7 @@ import argparse
|
|||||||
def categorize_func(args):
|
def categorize_func(args):
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
df = None
|
df = pd.read_csv(args.i, delimiter=args.d)
|
||||||
if args.i is not None:
|
|
||||||
df = pd.read_csv(args.i, delimiter=';')
|
|
||||||
|
|
||||||
import signal
|
import signal
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
@ -35,11 +33,14 @@ def main():
|
|||||||
|
|
||||||
categorize_parser = subparsers.add_parser("categorize")
|
categorize_parser = subparsers.add_parser("categorize")
|
||||||
categorize_parser.set_defaults(func=categorize_func)
|
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")
|
help="Bank statement CSV")
|
||||||
categorize_parser.add_argument('-f', required=False,
|
categorize_parser.add_argument('-f', required=False,
|
||||||
help="JSON file containing regexes to"
|
help="JSON file containing regexes to"
|
||||||
" pre-categorize statement entries")
|
" 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 = subparsers.add_parser("report")
|
||||||
report_parser.set_defaults(func=report_func)
|
report_parser.set_defaults(func=report_func)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from PyQt6.QtCore import Qt, QSortFilterProxyModel
|
|||||||
from PyQt6.QtGui import QPixmap, QAction
|
from PyQt6.QtGui import QPixmap, QAction
|
||||||
from PyQt6.QtWidgets import QMainWindow, QPushButton, QHBoxLayout, QLabel, \
|
from PyQt6.QtWidgets import QMainWindow, QPushButton, QHBoxLayout, QLabel, \
|
||||||
QVBoxLayout, QMenu, QApplication, QTableView, QListView, QInputDialog, \
|
QVBoxLayout, QMenu, QApplication, QTableView, QListView, QInputDialog, \
|
||||||
QMessageBox
|
QMessageBox, QFileDialog
|
||||||
|
|
||||||
|
|
||||||
class PandasModel(QtCore.QAbstractTableModel):
|
class PandasModel(QtCore.QAbstractTableModel):
|
||||||
@ -74,31 +74,34 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self._warning_layout \
|
self._warning_layout \
|
||||||
= self.findChild(QVBoxLayout, "warningLayout")
|
= self.findChild(QVBoxLayout, "warningLayout")
|
||||||
|
|
||||||
self._create_button \
|
self._create_button \
|
||||||
= self.findChild(QPushButton, "createCategoryButton")
|
= self.findChild(QPushButton, "createCategoryButton")
|
||||||
self._delete_button \
|
self._delete_button \
|
||||||
= self.findChild(QPushButton, "deleteCategoryButton")
|
= self.findChild(QPushButton, "deleteCategoryButton")
|
||||||
self._apply_button \
|
self._apply_button \
|
||||||
= self.findChild(QPushButton, "applyCategoryButton")
|
= self.findChild(QPushButton, "applyCategoryButton")
|
||||||
|
|
||||||
self._list_view \
|
self._list_view \
|
||||||
= self.findChild(QListView, "categoryListView")
|
= self.findChild(QListView, "categoryListView")
|
||||||
|
|
||||||
self._table_view \
|
self._table_view \
|
||||||
= self.findChild(QTableView, "transactionTableView")
|
= self.findChild(QTableView, "transactionTableView")
|
||||||
|
self._action_save \
|
||||||
|
= self.findChild(QAction, "actionSave")
|
||||||
|
|
||||||
|
self._warnings = []
|
||||||
|
|
||||||
self._category_model = QtGui.QStandardItemModel()
|
self._category_model = QtGui.QStandardItemModel()
|
||||||
self._list_view.setModel(self._category_model)
|
self._list_view.setModel(self._category_model)
|
||||||
|
|
||||||
self._create_button.clicked.connect(
|
self._create_button.clicked.connect(self._handle_create_click)
|
||||||
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 = self._table_view.horizontalHeader()
|
||||||
header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
header.customContextMenuRequested.connect(self._header_right_clicked)
|
header.customContextMenuRequested.connect(self._header_right_clicked)
|
||||||
|
|
||||||
def add_warning_text(self, text: str):
|
def _add_warning_text(self, text: str):
|
||||||
layout = QHBoxLayout()
|
layout = QHBoxLayout()
|
||||||
|
|
||||||
warningIcon = QLabel()
|
warningIcon = QLabel()
|
||||||
@ -115,6 +118,7 @@ class MainWindow(QMainWindow):
|
|||||||
layout.setStretch(1, 1)
|
layout.setStretch(1, 1)
|
||||||
|
|
||||||
self._warning_layout.addLayout(layout)
|
self._warning_layout.addLayout(layout)
|
||||||
|
self._warnings.append((layout, warningIcon, label))
|
||||||
|
|
||||||
def set_statement_data(self, df: pd.DataFrame):
|
def set_statement_data(self, df: pd.DataFrame):
|
||||||
model = PandasModel(df)
|
model = PandasModel(df)
|
||||||
@ -122,14 +126,42 @@ class MainWindow(QMainWindow):
|
|||||||
proxyModel.setSourceModel(model)
|
proxyModel.setSourceModel(model)
|
||||||
self._table_view.setModel(proxyModel)
|
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()
|
self._table_view.resizeColumnsToContents()
|
||||||
else:
|
else:
|
||||||
|
# Quickly approximate sizes
|
||||||
for i, col in enumerate(df.columns):
|
for i, col in enumerate(df.columns):
|
||||||
max_char = max(max([len(str(x)) for x in df[col].values]),
|
max_char = max(max([len(str(x)) for x in df[col].values]),
|
||||||
len(col))
|
len(col))
|
||||||
self._table_view.setColumnWidth(i, max_char * 10)
|
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:
|
def get_statement_data(self) -> pd.DataFrame:
|
||||||
return self._table_view.model().sourceModel().get_dataframe()
|
return self._table_view.model().sourceModel().get_dataframe()
|
||||||
|
|
||||||
@ -143,15 +175,19 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
rename_action = QAction("Rename", self)
|
rename_action = QAction("Rename", self)
|
||||||
delete_action = QAction("Delete", self)
|
delete_action = QAction("Delete", self)
|
||||||
|
switch_action = QAction("Switch position", self)
|
||||||
|
|
||||||
column = self._table_view.horizontalHeader().logicalIndexAt(pos)
|
column = self._table_view.horizontalHeader().logicalIndexAt(pos)
|
||||||
rename_action.triggered.connect(
|
rename_action.triggered.connect(
|
||||||
partial(self._header_rename_handler, column))
|
partial(self._header_rename_handler, column))
|
||||||
delete_action.triggered.connect(
|
delete_action.triggered.connect(
|
||||||
partial(self._header_delete_handler, column))
|
partial(self._header_delete_handler, column))
|
||||||
|
switch_action.triggered.connect(
|
||||||
|
partial(self._header_switch_handler, column))
|
||||||
|
|
||||||
context.addAction(rename_action)
|
context.addAction(rename_action)
|
||||||
context.addAction(delete_action)
|
context.addAction(delete_action)
|
||||||
|
context.addAction(switch_action)
|
||||||
|
|
||||||
context.exec(self.sender().mapToGlobal(pos))
|
context.exec(self.sender().mapToGlobal(pos))
|
||||||
|
|
||||||
@ -181,6 +217,26 @@ class MainWindow(QMainWindow):
|
|||||||
in enumerate(df.columns) if j != column]]
|
in enumerate(df.columns) if j != column]]
|
||||||
self.set_statement_data(df)
|
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):
|
def _handle_create_click(self):
|
||||||
self.add_categories(['asdf'])
|
self.add_categories(['asdf'])
|
||||||
|
|
||||||
@ -190,6 +246,15 @@ class MainWindow(QMainWindow):
|
|||||||
def _handle_apply_click(self):
|
def _handle_apply_click(self):
|
||||||
pass
|
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,
|
def show_main_window(categories: typing.Sequence[str] = None,
|
||||||
df: pd.DataFrame = None):
|
df: pd.DataFrame = None):
|
||||||
@ -202,29 +267,18 @@ def show_main_window(categories: typing.Sequence[str] = None,
|
|||||||
if df is not None:
|
if df is not None:
|
||||||
window.set_statement_data(df)
|
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()
|
window.show()
|
||||||
app.exec()
|
app.exec()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
from banking_breakdown.statement_parser import get_stripped_statement
|
|
||||||
import os
|
import os
|
||||||
os.chdir("../")
|
os.chdir("../")
|
||||||
|
|
||||||
categories = ["Food", "Rent & Utilities", "Hobbies", "Education",
|
categories = ["Food", "Rent & Utilities", "Hobbies", "Education",
|
||||||
"Transportation", "Social Life", "Other"]
|
"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)
|
show_main_window(categories, df)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user