Expense Stream | Streamlit and MongoDb Atlas App

Atharva Shirdhankar - Dec 8 '22 - - Dev Community

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

Image description

Image description

Image description

Image description

Image description

Image description

Image description

Save Chart as an png image feature :

Image description

Description

Helping you to track your earnings and spending.

Link to Source Code

GitHub logo StarTrooper08 / ExpenseStream

Helping you to track your earnings and spending.

ExpenseStream




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

  1. Create empty github repository with readme.md and license file.
  2. Clone the repository locally git clone https://github.com/StarTrooper08/ExpenseStream.git
  3. cd to the project file and initiate virtual env
  4. gitignore venv folder so that we dont push venv directory to github repo
  5. Install python libraries Python libraries : streamlit, plotly, pymongo and streamlit_option_menu
pip install streamlit plotly pymongo streamlit_option_menu
Enter fullscreen mode Exit fullscreen mode

Image description
Image description
Image description

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)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Before we create database.py file and write code into it first lets setup Mongodb cluster for us to use.

  1. 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. Image description Image description
  2. After that we will click on Create database. Image description
  3. Then Select deployment option we will select free tier since this is just a demo app. Image description
  4. 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. Image description
  5. 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. Image description Image description
  6. Within few minutes our cluster will be created.
  7. Now we have to connect our application to it. So we will click on connect button on the right side of our database section. Image description
  8. After that we will get 4 options we will select connect your application one. Image description
  9. 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. Image description
  10. 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). Image description 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)

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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/

. . . . . . . . . . . . . . . . . . . . . . . . .