├── .gitignore
├── .streamlit
└── config.toml
├── README.md
├── app.py
├── database.py
├── demo.jpg
├── requirements.txt
├── supermarkt_sales.xlsx
└── upload_to_database.py
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/python
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
4 |
5 | ### Python ###
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | share/python-wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | *.py,cover
55 | .hypothesis/
56 | .pytest_cache/
57 | cover/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 | db.sqlite3-journal
68 |
69 | # Flask stuff:
70 | instance/
71 | .webassets-cache
72 |
73 | # Scrapy stuff:
74 | .scrapy
75 |
76 | # Sphinx documentation
77 | docs/_build/
78 |
79 | # PyBuilder
80 | .pybuilder/
81 | target/
82 |
83 | # Jupyter Notebook
84 | .ipynb_checkpoints
85 |
86 | # IPython
87 | profile_default/
88 | ipython_config.py
89 |
90 | # pyenv
91 | # For a library or package, you might want to ignore these files since the code is
92 | # intended to run in multiple environments; otherwise, check them in:
93 | # .python-version
94 |
95 | # pipenv
96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
99 | # install all needed dependencies.
100 | #Pipfile.lock
101 |
102 | # poetry
103 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
104 | # This is especially recommended for binary packages to ensure reproducibility, and is more
105 | # commonly ignored for libraries.
106 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
107 | #poetry.lock
108 |
109 | # pdm
110 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
111 | #pdm.lock
112 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
113 | # in version control.
114 | # https://pdm.fming.dev/#use-with-ide
115 | .pdm.toml
116 |
117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118 | __pypackages__/
119 |
120 | # Celery stuff
121 | celerybeat-schedule
122 | celerybeat.pid
123 |
124 | # SageMath parsed files
125 | *.sage.py
126 |
127 | # Environments
128 | .env
129 | .venv
130 | env/
131 | venv/
132 | ENV/
133 | env.bak/
134 | venv.bak/
135 |
136 | # Spyder project settings
137 | .spyderproject
138 | .spyproject
139 |
140 | # Rope project settings
141 | .ropeproject
142 |
143 | # mkdocs documentation
144 | /site
145 |
146 | # mypy
147 | .mypy_cache/
148 | .dmypy.json
149 | dmypy.json
150 |
151 | # Pyre type checker
152 | .pyre/
153 |
154 | # pytype static type analyzer
155 | .pytype/
156 |
157 | # Cython debug symbols
158 | cython_debug/
159 |
160 | # PyCharm
161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163 | # and can be added to the global gitignore or merged into this file. For a more nuclear
164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165 | #.idea/
166 |
167 | # End of https://www.toptal.com/developers/gitignore/api/python
--------------------------------------------------------------------------------
/.streamlit/config.toml:
--------------------------------------------------------------------------------
1 | [theme]
2 | # Primary accent color for interactive elements.
3 | primaryColor = "#E694FF"
4 |
5 | # Background color for the main content area.
6 | backgroundColor = "#00172B"
7 |
8 | # Background color used for the sidebar and most interactive widgets.
9 | secondaryBackgroundColor = "#0083B8"
10 |
11 | # Color used for almost all text.
12 | textColor = "#FFF"
13 |
14 | # Font family for all text in the app, except code blocks. One of "sans serif", "serif", or "monospace".
15 | # Default: "sans serif"
16 | font = "sans serif"
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Add a User Authentication Service (Login Form) in Streamlit + Database
3 | This is the second part of adding a user authentication service (login form) in Streamlit.
4 | In this part, we are reading/writing the user credentials from/to a database instead of a pickle file.
5 | In particular, we will be using the free database from deta (https://www.deta.sh/)
6 |
7 | ## Demo Website
8 | ⭐ https://www.dashboarduserauth.pythonandvba.com/
9 |
10 | ## Video Tutorial
11 | [](https://youtu.be/eCbH2nPL9sU)
12 |
13 | ## Environment Variables
14 | To run this project, you will need to add the following environment variable to your .env file
15 | `DETA_KEY`
16 |
17 | ## Watch Part 1 here
18 | [](https://youtu.be/JoFGrSRj4X4)
19 |
20 | ## Screenshot
21 | 
22 |
23 | ## Streamlit-authenticator
24 | ⭐ Check out the library here: https://github.com/mkhorasani/Streamlit-Authenticator
25 |
26 |
27 |
28 | ## 🤓 Check Out My Excel Add-ins
29 | I've developed some handy Excel add-ins that you might find useful:
30 |
31 | - 📊 **[Dashboard Add-in](https://pythonandvba.com/grafly)**: Easily create interactive and visually appealing dashboards.
32 | - 🎨 **[Cartoon Charts Add-In](https://pythonandvba.com/cuteplots)**: Create engaging and fun cartoon-style charts.
33 | - 🤪 **[Emoji Add-in](https://pythonandvba.com/emojify)**: Add a touch of fun to your spreadsheets with emojis.
34 | - 🛠️ **[MyToolBelt Add-in](https://pythonandvba.com/mytoolbelt)**: A versatile toolbelt for Excel, featuring:
35 | - Creation of Pandas DataFrames and Jupyter Notebooks from Excel ranges
36 | - ChatGPT integration for advanced data analysis
37 | - And much more!
38 |
39 |
40 |
41 | ## 🤝 Connect with Me
42 | - 📺 **YouTube:** [CodingIsFun](https://youtube.com/c/CodingIsFun)
43 | - 🌐 **Website:** [PythonAndVBA](https://pythonandvba.com)
44 | - 💬 **Discord:** [Join our Community](https://pythonandvba.com/discord)
45 | - 💼 **LinkedIn:** [Connect with me](https://www.linkedin.com/in/sven-bosau/)
46 | - 📸 **Instagram:** [Follow me](https://www.instagram.com/codingisfun_official/)
47 |
48 | ## Support My Work
49 | Love my content and want to show appreciation? Why not [buy me a coffee](https://pythonandvba.com/coffee-donation) to fuel my creative engine? Your support means the world to me! 😊
50 |
51 | [](https://pythonandvba.com/coffee-donation)
52 |
53 | ## Feedback
54 | Got some thoughts or suggestions? Don't hesitate to reach out to me at contact@pythonandvba.com. I'd love to hear from you! 💡
55 | 
56 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import pandas as pd # pip install pandas openpyxl
2 | import plotly.express as px # pip install plotly-express
3 | import streamlit as st # pip install streamlit
4 | import streamlit_authenticator as stauth # pip install streamlit-authenticator
5 |
6 | import database as db
7 |
8 |
9 | # emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
10 | st.set_page_config(page_title="Sales Dashboard", page_icon=":bar_chart:", layout="wide")
11 |
12 | # --- DEMO PURPOSE ONLY --- #
13 | placeholder = st.empty()
14 | placeholder.info("CREDENTIALS | username:pparker ; password:abc123")
15 | # ------------------------- #
16 |
17 | # --- USER AUTHENTICATION ---
18 | users = db.fetch_all_users()
19 |
20 | usernames = [user["key"] for user in users]
21 | names = [user["name"] for user in users]
22 | hashed_passwords = [user["password"] for user in users]
23 |
24 | authenticator = stauth.Authenticate(names, usernames, hashed_passwords,
25 | "sales_dashboard", "abcdef", cookie_expiry_days=30)
26 |
27 | name, authentication_status, username = authenticator.login("Login", "main")
28 |
29 | if authentication_status == False:
30 | st.error("Username/password is incorrect")
31 |
32 | if authentication_status == None:
33 | st.warning("Please enter your username and password")
34 |
35 | if authentication_status:
36 | placeholder.empty()
37 | # ---- READ EXCEL ----
38 | @st.cache
39 | def get_data_from_excel():
40 | df = pd.read_excel(
41 | io="supermarkt_sales.xlsx",
42 | engine="openpyxl",
43 | sheet_name="Sales",
44 | skiprows=3,
45 | usecols="B:R",
46 | nrows=1000,
47 | )
48 | # Add 'hour' column to dataframe
49 | df["hour"] = pd.to_datetime(df["Time"], format="%H:%M:%S").dt.hour
50 | return df
51 |
52 | df = get_data_from_excel()
53 |
54 | # ---- SIDEBAR ----
55 | authenticator.logout("Logout", "sidebar")
56 | st.sidebar.title(f"Welcome {name}")
57 | st.sidebar.header("Please Filter Here:")
58 | city = st.sidebar.multiselect(
59 | "Select the City:",
60 | options=df["City"].unique(),
61 | default=df["City"].unique()
62 | )
63 |
64 | customer_type = st.sidebar.multiselect(
65 | "Select the Customer Type:",
66 | options=df["Customer_type"].unique(),
67 | default=df["Customer_type"].unique(),
68 | )
69 |
70 | gender = st.sidebar.multiselect(
71 | "Select the Gender:",
72 | options=df["Gender"].unique(),
73 | default=df["Gender"].unique()
74 | )
75 |
76 | df_selection = df.query(
77 | "City == @city & Customer_type ==@customer_type & Gender == @gender"
78 | )
79 |
80 | # ---- MAINPAGE ----
81 | st.title(":bar_chart: Sales Dashboard")
82 | st.markdown("##")
83 |
84 | # TOP KPI's
85 | total_sales = int(df_selection["Total"].sum())
86 | average_rating = round(df_selection["Rating"].mean(), 1)
87 | star_rating = ":star:" * int(round(average_rating, 0))
88 | average_sale_by_transaction = round(df_selection["Total"].mean(), 2)
89 |
90 | left_column, middle_column, right_column = st.columns(3)
91 | with left_column:
92 | st.subheader("Total Sales:")
93 | st.subheader(f"US $ {total_sales:,}")
94 | with middle_column:
95 | st.subheader("Average Rating:")
96 | st.subheader(f"{average_rating} {star_rating}")
97 | with right_column:
98 | st.subheader("Average Sales Per Transaction:")
99 | st.subheader(f"US $ {average_sale_by_transaction}")
100 |
101 | st.markdown("""---""")
102 |
103 | # SALES BY PRODUCT LINE [BAR CHART]
104 | sales_by_product_line = (
105 | df_selection.groupby(by=["Product line"]).sum()[["Total"]].sort_values(by="Total")
106 | )
107 | fig_product_sales = px.bar(
108 | sales_by_product_line,
109 | x="Total",
110 | y=sales_by_product_line.index,
111 | orientation="h",
112 | title="Sales by Product Line",
113 | color_discrete_sequence=["#0083B8"] * len(sales_by_product_line),
114 | template="plotly_white",
115 | )
116 | fig_product_sales.update_layout(
117 | plot_bgcolor="rgba(0,0,0,0)",
118 | xaxis=(dict(showgrid=False))
119 | )
120 |
121 | # SALES BY HOUR [BAR CHART]
122 | sales_by_hour = df_selection.groupby(by=["hour"]).sum()[["Total"]]
123 | fig_hourly_sales = px.bar(
124 | sales_by_hour,
125 | x=sales_by_hour.index,
126 | y="Total",
127 | title="Sales by hour",
128 | color_discrete_sequence=["#0083B8"] * len(sales_by_hour),
129 | template="plotly_white",
130 | )
131 | fig_hourly_sales.update_layout(
132 | xaxis=dict(tickmode="linear"),
133 | plot_bgcolor="rgba(0,0,0,0)",
134 | yaxis=(dict(showgrid=False)),
135 | )
136 |
137 |
138 | left_column, right_column = st.columns(2)
139 | left_column.plotly_chart(fig_hourly_sales, use_container_width=True)
140 | right_column.plotly_chart(fig_product_sales, use_container_width=True)
141 |
142 |
143 | # ---- HIDE STREAMLIT STYLE ----
144 | hide_st_style = """
145 |
150 | """
151 | st.markdown(hide_st_style, unsafe_allow_html=True)
152 |
--------------------------------------------------------------------------------
/database.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from deta import Deta # pip install deta
4 | from dotenv import load_dotenv # pip install python-dotenv
5 |
6 |
7 | # Load the environment variables
8 | load_dotenv(".env")
9 | DETA_KEY = os.getenv("DETA_KEY")
10 |
11 | # Initialize with a project key
12 | deta = Deta(DETA_KEY)
13 |
14 | # This is how to create/connect a database
15 | db = deta.Base("users_db")
16 |
17 |
18 | def insert_user(username, name, password):
19 | """Returns the user on a successful user creation, otherwise raises and error"""
20 | return db.put({"key": username, "name": name, "password": password})
21 |
22 |
23 | def fetch_all_users():
24 | """Returns a dict of all users"""
25 | res = db.fetch()
26 | return res.items
27 |
28 |
29 | def get_user(username):
30 | """If not found, the function will return None"""
31 | return db.get(username)
32 |
33 |
34 | def update_user(username, updates):
35 | """If the item is updated, returns None. Otherwise, an exception is raised"""
36 | return db.update(updates, username)
37 |
38 |
39 | def delete_user(username):
40 | """Always returns None, even if the key does not exist"""
41 | return db.delete(username)
42 |
--------------------------------------------------------------------------------
/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sven-Bo/streamlit-sales-dashboard-with-userauthentication-database/e6cb9579b3785a6ccfa3c8dc7283e568b79daae2/demo.jpg
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | streamlit_authenticator==0.1.5
2 | streamlit==1.12.0
3 | openpyxl==3.0.10
4 | plotly==4.14.3
5 | deta==1.1.0
6 | pandas==1.4.3
7 | python-dotenv==0.21.0
8 |
--------------------------------------------------------------------------------
/supermarkt_sales.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sven-Bo/streamlit-sales-dashboard-with-userauthentication-database/e6cb9579b3785a6ccfa3c8dc7283e568b79daae2/supermarkt_sales.xlsx
--------------------------------------------------------------------------------
/upload_to_database.py:
--------------------------------------------------------------------------------
1 | import streamlit_authenticator as stauth
2 |
3 | import database as db
4 |
5 | usernames = ["pparker", "rmiller"]
6 | names = ["Peter Parker", "Rebecca Miller"]
7 | passwords = ["abc123", "def456"]
8 | hashed_passwords = stauth.Hasher(passwords).generate()
9 |
10 |
11 | for (username, name, hash_password) in zip(usernames, names, hashed_passwords):
12 | db.insert_user(username, name, hash_password)
--------------------------------------------------------------------------------