Expense Stream | Streamlit and MongoDb Atlas App
Atharva Shirdhankar
Posted on December 8, 2022
Background
Since there is rise in inflation and continous happening of recession, which is a stressful thing. But to tackle this stressful scenerio there is one solution keeping track of our budget and manage money in great way. So I thought of creating this Expense tracking web app which will be helpful in tracking expense monthwise.
What I built
I've built this web app using streamlit and mongodb atlas cluster to store data. It takes input like Total income available to spend and yeah even takes our spending and needful expenses as input and then it is stored into Mongodb database monthwise.
And when we want to look for our data we can visualize in nice sankey chart form. Sankey chart shows the flow of earning and spending in great way.
Category Submission:
Think Outside the JS Box
App Link
https://expensestream.azurewebsites.net/
Screenshots
Save Chart as an png image feature :
Description
Helping you to track your earnings and spending.
Link to Source Code
Permissive License
MIT License
How I built it
(How did you utilize MongoDB Atlas? Did you learn something new along the way? Pick up a new skill?)
Development Process :
Setup and Initiating Virtual Environment
- Create empty github repository with readme.md and license file.
- Clone the repository locally
git clone https://github.com/StarTrooper08/ExpenseStream.git
- cd to the project file and initiate virtual env
- gitignore venv folder so that we dont push venv directory to github repo
- Install python libraries Python libraries : streamlit, plotly, pymongo and streamlit_option_menu
pip install streamlit plotly pymongo streamlit_option_menu
Coding Part :
For this application we will create two python files one will be the app.py
where our frontend code and which will interact with database file for adding and fetching data from Mongodb database. And other file name database.py
will be dedicatedly used to create database functions which will help us create document inside our collection and get document from it when called in app.py file.
Now lets create app.py inside our Expense Stream directory and write some code
App.py file
#imports for our app
import streamlit as st
import plotly.graph_objects as go
from streamlit_option_menu import option_menu
import calendar
from datetime import datetime
import database as db
#variables
incomes = ["Salary","Blog","Other Income"]
expenses = ["Rent","Utilities","Groceries","Car","Saving"]
currency = "USD"
page_title = "Expense Stream"
page_icon = ":money_with_wings:"
layout = "centered"
#setting title for our app
st.set_page_config(page_title=page_title, page_icon=page_icon, layout=layout)
st.title(page_title + " " + page_icon)
years = [datetime.today().year, datetime.today().year + 1]
months = list(calendar.month_name[1:])
def get_all_periods():
items = db.fetch_all_periods()
periods = [item["key"] for item in items]
return periods
hide_st_style = """
<style>
#MainMenu {visiblility: hidden;}
footer {visiblility: hidden;}
header {visiblility: hidden;}
</style>
"""
st.markdown(hide_st_style,unsafe_allow_html=True)
selected = option_menu(
menu_title= None,
options=["Data Entry","Data Visualization"],
icons=["pencil-fill","bar-chart-fill"],
orientation= "horizontal",
)
if selected == "Data Entry":
st.header(f"Data Entry in {currency}")
with st.form("Entry_form", clear_on_submit=True):
col1, col2 = st.columns(2)
col1.selectbox("Select Month:", months, key= "month")
col2.selectbox("Select Year:", years, key="year")
with st.expander("Income"):
for income in incomes:
st.number_input(f"{income}:",min_value=0, format="%i", step=10,key=income)
with st.expander("Expenses"):
for expense in expenses:
st.number_input(f"{expense}:", min_value=0,format="%i",step=10,key=expense)
with st.expander("Comment"):
comment = st.text_area("", placeholder="Enter a comment hee ...")
submitted = st.form_submit_button("Save Data")
if submitted:
period = str(st.session_state["year"]) + " " + str(st.session_state["month"])
incomes = {income: st.session_state[income] for income in incomes}
expenses = {expense: st.session_state[expense] for expense in expenses}
db.insert_period(period,incomes,expenses,comment)
st.success("Data Saved!")
if selected == "Data Visualization":
st.header("Data Visualization")
with st.form("Saved periods"):
period = st.selectbox("Select Period:", get_all_periods())
submitted = st.form_submit_button("Plot Period")
if submitted:
period_data = db.get_period(period)
for doc in period_data:
comment = doc["comment"]
expenses = doc["expenses"]
incomes = doc["incomes"]
total_income = sum(incomes.values())
total_expense = sum(expenses.values())
remaining_budget = total_income - total_expense
col1, col2, col3 = st.columns(3)
col1.metric("Total Income",f"{total_income} {currency}")
col2.metric("Total Expense",f"{total_expense} {currency}")
col3.metric("Remaining Budget",f"{remaining_budget} {currency}")
st.text(f"Comment:{comment}")
label = list(incomes.keys()) + ["Total income"] + list(expenses.keys())
source = list(range(len(incomes))) + [len(incomes)] * len(expenses)
target = [len(incomes)] * len(incomes) + [label.index(expense) for expense in expenses.keys()]
value = list(incomes.values()) + list(expenses.values())
link = dict(source=source, target=target,value=value)
node = dict(label=label,pad=20,thickness=25,color="#00684A")
data = go.Sankey(link=link,node=node)
fig = go.Figure(data)
fig.update_layout(margin=dict(l=0,r=0,t=5,b=5))
st.plotly_chart(fig, use_container_width=True)
We can give theme to our app using streamlit config toml file, which will make our app design look great and consistent.
First we will create .streamlit/config.toml directory inside our parent project directory.
.streamlit/config.toml
[theme]
primaryColor = "#00684A"
backgroundColor = "#002b36"
secondaryBackgroundColor = "#586e75"
textColor = "#fafafa"
font ="sans serif"
Before we create database.py file and write code into it first lets setup Mongodb cluster for us to use.
- First click on project dropdown here you will find your existing project but we want to create new project we will click on new project and after that we will be asked to add members to our cluster but we will keep it as it is and move forward.
- After that we will click on Create database.
- Then Select deployment option we will select free tier since this is just a demo app.
- Select cloud provider and region for your cluster. Here I'm selecting GCP and my prefered region which is to close to me. After selecting cloud provider and region,click on create cluster.
- Now we will asked to create user and password and add our local ip address. Its super simple to do just click on add ip address button and it will done automatically.
- Within few minutes our cluster will be created.
- Now we have to connect our application to it. So we will click on connect button on the right side of our database section.
- After that we will get 4 options we will select
connect your application
one. - After clicking on it we will get popup asking for our application language and version. We will select python and version we have installed on our local machine.
- After that we will get our cluster access link. Just one changes we have to make while using the link. Instead of embeded inside the link we have to pass actual password which we given earlier(step 5). Password can be passed using env variable for security purpose. I'm passing it directly for now.
And that's it we are done with setting up our first MongoDB Atlas Cluster.
Now lets use it in our application. To do use it I'm creating database file where I'll connect to database and do data operations.
database.py file
import pymongo
from pymongo import MongoClient
cluster = MongoClient("mongodb+srv://atharvas08:<password>@cluster0.jtpc66a.mongodb.net/?retryWrites=true&w=majority")
db = cluster["monthly_reports"]
collection = db["expense_data"]
#insert period as a document inside our mongodb
def insert_period(period,incomes,expenses,comment):
return collection.insert_one({"key":period,"incomes":incomes,"expenses":expenses,"comment":comment})
def fetch_all_periods():
res = collection.find()
return res
def get_period(period):
if not isinstance(period, dict):
period = {"key": period}
return collection.find(period)
Now lets create requirements.txt file for our app so that we can easily deploy it on internet.
dnspython==2.2.1
plotly==5.11.0
pymongo==4.3.3
python-dateutil==2.8.2
requests==2.28.1
streamlit==1.15.2
streamlit-option-menu==0.3.2
Dockerfile
FROM python:3.9-slim
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
EXPOSE 8501
ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
Additional Resources/Info
Sankey Chart/Diagram:
https://developers.google.com/chart/interactive/docs/gallery/sankey
Mongodb atlas and pymongo doc:
https://www.mongodb.com/languages/python
Streamlit:
https://docs.streamlit.io/
Posted on December 8, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.