A. Introduction
Income and expense app or application is used for tracking assets, liabilities and net worth. It stores the deals or transactions as records that happened. This record can be used to generate statistics, etc. about the financial status.In this post, I will create an app that stores deals in sqlite database through the use of SQLModel [2] library. ReactPy [1] together with bootstrap 5 [3] creates components or user interfaces to build this app. I also use the Pandas [4] library to convert the sqlite table to html and then finally display the html with the use of reactpy utility html_to_vdom. The app reloads data from database when app is reset.
B. Interface
We have the header as cards shown at the top to display the asset, liability and net worth. At the bottom left we have the form to take input from users. The bottom right displays the deals history. The app is responsive which also looks great in small screens.1. Summary component
This component shows financial summaries at the top of the page.
@component def Summary(): """Generates three cards for asset, liability and net worth. These cards are displayed in a row at the top of the page. The asset and liablity values are taken from the sqlite database using SQLModel library. All positive amounts are assets whereas all negative amounts are liabilities. """ db_results = select_deals() records = get_records(db_results) asset_value = sum([rec[2] for rec in records if rec[2] >= 0]) liability_value = sum([rec[2] for rec in records if rec[2] < 0]) net_value = asset_value + liability_value return html.div( html.div( {'class': 'row'}, Card('Assets', asset_value), Card('Liabilities', liability_value), Card('Net Worth', net_value), ), )
The select_deals()
returns all the recorded deals from sqlite database. The assets, liabilities and net worth are calculated based from retrieved records. Three card components are built along the row styled by bootstrap 5. using the row class.
2. Card component
@component def Card(label, amount): """Shows asset, liability and net worth info.""" text_color = CARD_TEXT_COLOR[label] if label == 'Net Worth' and amount < 0: text_color = 'text-warning' return html.div( {'class': 'col-sm-4'}, html.div( {'class': 'card mb-3 border-primary'}, html.div( {'class': 'card-body'}, html.div( {'class': f'card-title {text_color}'}, html.h5( {'style': {'font-family': 'Chalkduster, fantasy'}}, label ), ), html.div( {'class': 'card-text'}, html.span({'class': f'{text_color} fw-bold'}, amount), ), ), ), )
A card is just a box with elements inside. This card is built using bootstrap card. All the classes are from bootstrap. The Card has 2 parameters label and amount.
3. User input form component
html.div( {'class': 'col-lg-6'}, html.form( {'on_submit': save_record}, FormDateInput(date, set_date), FormAmountInput(amount, set_amount), FormSelect(category, set_category), FormCommentInput(comment, set_comment), FormSaveButton(), ), ),
This is the form where users will input date, amount, etc. It is composed of other components. The FormDateInput(), and others.
4. The history and database form
html.div( {'class': 'col-lg-6'}, DealHistory(), html.form( {'on_submit': delete_id}, FormDeleteInput(bad_id, set_bad_id), FormDeleteButton(), ), ),
This is the interface in the second column for the history and a form for the user to delete an entry by id.
C. SQLModel
This app has a dedicated module to manage the records.
sqlmodeldb.pyfrom typing import Optional from pathlib import Path from sqlmodel import Field, Session, SQLModel, create_engine, select import sqlalchemy class Deal(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) date: str value: float category: str comment: str sqlite_file_name = "networth.db" sqlite_url = f"sqlite:///data/{sqlite_file_name}" engine = create_engine(sqlite_url, echo=False) def create_db_and_tables(): folder_path = Path.cwd() / 'data' folder_path.mkdir(exist_ok=True) SQLModel.metadata.create_all(engine) def add_deal(date: str, value: float, category: str, comment: str): """Adds an entry to the table.""" deal = Deal( date=date, value=value, category=category, comment=comment ) with Session(engine) as session: session.add(deal) session.commit() def select_deals(): """Gets all reacords from the table.""" with Session(engine) as session: statement = select(Deal) results = session.exec(statement) return results.all() def delete_deal(id_: int): """Deletes an entry from a given id.""" with Session(engine) as session: statement = select(Deal).where(Deal.id == id_) results = session.exec(statement) try: deal = results.one() except sqlalchemy.exc.NoResultFound: pass except Exception as err: print(repr(err)) else: session.delete(deal) session.commit()
Functions in this module are used in networth.py. The income and expense app is under the file networth.py.
The line
sqlite_url = f"sqlite:///data/{sqlite_file_name}"
saves the records under the data folder with filename sqlite_file_name which is networth.db
.
This is the imports in networth.py
from reactpy import component, html, hooks, event, utils from reactpy.backend.fastapi import configure, Options from fastapi import FastAPI from sqlmodeldb import (create_db_and_tables, add_deal, select_deals, delete_deal) import pandas as pd
D. Full code
The source code of this app is in my github ReactPy-NetWorth repository [5].
E. Summary
Income and expense app is built with the use of Reactpy [1] and Bootstrap [3] for components, SQLModel [2] and Pandas [4] for database management. Deals or transactions are saved in sqlite database. Statistics on assets, liabilities and net worth are shown in the user interface. The app can be reset without lossing session data.
F. References
[1]. ReactPy for building user interfaces in Python
[2]. SQLModel - a library for interacting with SQL databases from Python code
[3]. Bootstrap 5 - an HTML, CSS, and JavaScript framework for building responsive web sites
[4]. Pandas - a library for data analysis and manipulation
[5]. Github ReactPy-NetWorth repository