From 8a0d7f748fece2e242c7e7a296e802db19c2ca4a Mon Sep 17 00:00:00 2001 From: Andreas Tsouchlos Date: Thu, 4 Jan 2024 02:27:27 +0100 Subject: [PATCH] Implement show/hide warnings depending on columns --- banking_breakdown/__main__.py | 9 ++-- banking_breakdown/ui.py | 94 +++++++++++++++++++++++++++-------- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/banking_breakdown/__main__.py b/banking_breakdown/__main__.py index e22e353..7d8b5bb 100644 --- a/banking_breakdown/__main__.py +++ b/banking_breakdown/__main__.py @@ -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) diff --git a/banking_breakdown/ui.py b/banking_breakdown/ui.py index 6e815ef..2072793 100644 --- a/banking_breakdown/ui.py +++ b/banking_breakdown/ui.py @@ -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)