├── streamlit_csv_editor ├── utils │ ├── __init__.py │ ├── helpers.py │ └── __pycache__ │ │ ├── __init__.cpython-312.pyc │ │ └── helpers.cpython-312.pyc ├── access_control.xml ├── modules │ ├── __init__.py │ ├── __pycache__ │ │ ├── export.cpython-312.pyc │ │ ├── __init__.cpython-312.pyc │ │ ├── data_ops.cpython-312.pyc │ │ ├── file_loader.cpython-312.pyc │ │ ├── grid_editor.cpython-312.pyc │ │ ├── validators.cpython-312.pyc │ │ ├── mysql_handler.cpython-312.pyc │ │ └── state_manager.cpython-312.pyc │ ├── validators.py │ ├── data_ops.py │ ├── export.py │ ├── state_manager.py │ ├── file_loader.py │ ├── grid_editor.py │ ├── role_manager_db.py │ └── mysql_handler.py ├── requirements.txt ├── assets │ ├── custom.js │ ├── dark_theme.css │ └── custom.css └── app.py ├── .gitattributes ├── .devcontainer └── devcontainer.json ├── ok.py └── README.md /streamlit_csv_editor/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /streamlit_csv_editor/access_control.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /streamlit_csv_editor/requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit 2 | pandas 3 | openpyxl 4 | streamlit-aggrid 5 | xlsxwriter 6 | mysql-connector-python -------------------------------------------------------------------------------- /streamlit_csv_editor/utils/helpers.py: -------------------------------------------------------------------------------- 1 | # utils/helpers.py 2 | 3 | def get_default_row(columns): 4 | return {col: "" for col in columns} 5 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/export.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/export.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/utils/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/utils/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/utils/__pycache__/helpers.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/utils/__pycache__/helpers.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/data_ops.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/data_ops.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/file_loader.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/file_loader.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/grid_editor.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/grid_editor.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/validators.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/validators.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/mysql_handler.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/mysql_handler.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/__pycache__/state_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxprogrammer007/Streamlit_excel_editor/main/streamlit_csv_editor/modules/__pycache__/state_manager.cpython-312.pyc -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/validators.py: -------------------------------------------------------------------------------- 1 | # modules/validators.py 2 | 3 | def enforce_types(df, schema_dict): 4 | for col, col_type in schema_dict.items(): 5 | try: 6 | df[col] = df[col].astype(col_type) 7 | except Exception: 8 | pass # You can also highlight cells with errors if needed 9 | return df 10 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/data_ops.py: -------------------------------------------------------------------------------- 1 | # modules/data_ops.py 2 | import pandas as pd 3 | 4 | def add_blank_row(df): 5 | new_row = {col: "" for col in df.columns} 6 | return df.append(new_row, ignore_index=True) 7 | 8 | def delete_row_by_index(df, index): 9 | return df.drop(index=index).reset_index(drop=True) 10 | 11 | def reset_to_original(original_df): 12 | return original_df.copy() 13 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/export.py: -------------------------------------------------------------------------------- 1 | # modules/export.py 2 | import pandas as pd 3 | 4 | def to_csv(df): 5 | return df.to_csv(index=False).encode("utf-8") 6 | 7 | def to_excel(df): 8 | from io import BytesIO 9 | output = BytesIO() 10 | with pd.ExcelWriter(output, engine='xlsxwriter') as writer: 11 | df.to_excel(writer, index=False, sheet_name='Sheet1') 12 | return output.getvalue() 13 | 14 | def to_json(df): 15 | return df.to_json(orient="records") 16 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/state_manager.py: -------------------------------------------------------------------------------- 1 | # modules/state_manager.py 2 | 3 | def init_history(): 4 | return {"undo": [], "redo": []} 5 | 6 | def push_undo(history, df): 7 | history["undo"].append(df.copy()) 8 | history["redo"].clear() 9 | 10 | def undo(history, current_df): 11 | if history["undo"]: 12 | history["redo"].append(current_df.copy()) 13 | return history["undo"].pop() 14 | return current_df 15 | 16 | def redo(history, current_df): 17 | if history["redo"]: 18 | history["undo"].append(current_df.copy()) 19 | return history["redo"].pop() 20 | return current_df 21 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/file_loader.py: -------------------------------------------------------------------------------- 1 | # modules/file_loader.py 2 | import pandas as pd 3 | import streamlit as st 4 | 5 | def load_file(uploaded_file): 6 | if uploaded_file.name.endswith('.csv'): 7 | df = pd.read_csv(uploaded_file) 8 | return df, None 9 | elif uploaded_file.name.endswith(('.xlsx', '.xls')): 10 | xls = pd.ExcelFile(uploaded_file) 11 | return None, xls 12 | else: 13 | st.error("Unsupported file format.") 14 | return None, None 15 | 16 | def get_sheet(xls, sheet_name=None): 17 | if sheet_name: 18 | return xls.parse(sheet_name) 19 | return xls.parse(xls.sheet_names[0]) # default first sheet 20 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/grid_editor.py: -------------------------------------------------------------------------------- 1 | # modules/grid_editor.py 2 | from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode 3 | 4 | def render_editable_grid(df): 5 | gb = GridOptionsBuilder.from_dataframe(df) 6 | gb.configure_default_column(editable=True, filter=True, resizable=True) 7 | gb.configure_grid_options(enableRowDeletion=True) 8 | grid_options = gb.build() 9 | 10 | grid_response = AgGrid( 11 | df, 12 | gridOptions=grid_options, 13 | update_mode=GridUpdateMode.MODEL_CHANGED, 14 | allow_unsafe_jscode=True, 15 | fit_columns_on_grid_load=True, 16 | theme="balham" # or "alpine" 17 | ) 18 | 19 | 20 | return grid_response['data'] 21 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/role_manager_db.py: -------------------------------------------------------------------------------- 1 | # modules/role_manager_db.py 2 | 3 | import pandas as pd 4 | 5 | def get_user_role(conn, username, password): 6 | query = f"SELECT role FROM users WHERE username = %s AND password = %s" 7 | cursor = conn.cursor() 8 | cursor.execute(query, (username, password)) 9 | result = cursor.fetchone() 10 | return result[0] if result else None 11 | 12 | def get_role_permissions(conn, role, table_name): 13 | query = """ 14 | SELECT permission, column_name 15 | FROM role_permissions 16 | WHERE role = %s AND table_name = %s 17 | """ 18 | df = pd.read_sql(query, conn, params=(role, table_name)) 19 | perms = df["permission"].unique().tolist() 20 | if "all" in df["column_name"].tolist(): 21 | columns = "all" 22 | else: 23 | columns = df["column_name"].unique().tolist() 24 | return { 25 | "permissions": perms, 26 | "columns": columns 27 | } 28 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Python 3", 3 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 4 | "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", 5 | "customizations": { 6 | "codespaces": { 7 | "openFiles": [ 8 | "README.md", 9 | "streamlit_csv_editor/app.py" 10 | ] 11 | }, 12 | "vscode": { 13 | "settings": {}, 14 | "extensions": [ 15 | "ms-python.python", 16 | "ms-python.vscode-pylance" 17 | ] 18 | } 19 | }, 20 | "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y { 4 | console.log("🌟 Modern Custom JS Loaded"); 5 | 6 | const highlightColor = "#2dd4bf"; // Teal glow 7 | const fadeColor = "#1f2937"; // Dark gray fallback 8 | 9 | // Function to clear all highlights 10 | const clearAllHighlights = () => { 11 | document.querySelectorAll(".ag-cell").forEach(cell => { 12 | cell.style.transition = "background-color 0.3s ease"; 13 | cell.style.backgroundColor = "transparent"; 14 | }); 15 | }; 16 | 17 | // Highlight a cell when clicked 18 | document.querySelectorAll(".ag-cell").forEach(cell => { 19 | cell.addEventListener("click", () => { 20 | clearAllHighlights(); 21 | cell.style.backgroundColor = highlightColor; 22 | }); 23 | cell.addEventListener("mouseover", () => { 24 | cell.style.cursor = "pointer"; 25 | }); 26 | }); 27 | 28 | // Animate rows on mount 29 | const rows = document.querySelectorAll(".ag-row"); 30 | rows.forEach((row, idx) => { 31 | row.style.opacity = "0"; 32 | row.style.transform = "translateY(10px)"; 33 | row.style.transition = `opacity 0.5s ${idx * 30}ms ease-out, transform 0.5s ${idx * 30}ms ease-out`; 34 | setTimeout(() => { 35 | row.style.opacity = "1"; 36 | row.style.transform = "translateY(0)"; 37 | }, 100); 38 | }); 39 | 40 | // Tooltip for column headers 41 | document.querySelectorAll(".ag-header-cell-label").forEach(label => { 42 | const text = label.innerText; 43 | if (text) { 44 | label.setAttribute("title", `Column: ${text}`); 45 | } 46 | }); 47 | 48 | // Smooth scroll into view after load 49 | const grid = document.querySelector(".ag-root-wrapper"); 50 | if (grid) { 51 | setTimeout(() => { 52 | grid.scrollIntoView({ behavior: "smooth", block: "center" }); 53 | }, 300); 54 | } 55 | 56 | // Add ripple effect to cells 57 | document.querySelectorAll(".ag-cell").forEach(cell => { 58 | cell.addEventListener("mousedown", (e) => { 59 | const ripple = document.createElement("span"); 60 | ripple.className = "ripple"; 61 | ripple.style.left = `${e.offsetX}px`; 62 | ripple.style.top = `${e.offsetY}px`; 63 | cell.appendChild(ripple); 64 | setTimeout(() => ripple.remove(), 600); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /streamlit_csv_editor/assets/dark_theme.css: -------------------------------------------------------------------------------- 1 | /* assets/dark_theme.css */ 2 | 3 | /* Base */ 4 | body { 5 | background-color: #121212; 6 | color: #e4e4e7; 7 | font-family: "Inter", "Segoe UI", sans-serif; 8 | } 9 | 10 | /* Headings */ 11 | h1, h2, h3, h4, h5, h6 { 12 | color: #f1f3f5; 13 | font-weight: 700; 14 | } 15 | 16 | /* Sidebar */ 17 | section[data-testid="stSidebar"] { 18 | background-color: #1f1f1f; 19 | border-right: 1px solid #2e2e2e; 20 | } 21 | 22 | /* Buttons */ 23 | .stButton > button { 24 | background-color: #0ea5e9; 25 | color: white; 26 | border: none; 27 | border-radius: 8px; 28 | padding: 0.5rem 1.1rem; 29 | font-weight: 600; 30 | transition: all 0.3s ease; 31 | box-shadow: 0 3px 6px rgba(0,0,0,0.15); 32 | } 33 | 34 | .stButton > button:hover { 35 | background-color: #0284c7; 36 | transform: scale(1.02); 37 | } 38 | 39 | /* Download Button */ 40 | .stDownloadButton > button { 41 | background-color: #22c55e; 42 | color: white; 43 | border: none; 44 | border-radius: 8px; 45 | padding: 0.5rem 1rem; 46 | font-weight: 600; 47 | } 48 | 49 | .stDownloadButton > button:hover { 50 | background-color: #16a34a; 51 | } 52 | 53 | /* Expander */ 54 | details > summary { 55 | color: #0ea5e9; 56 | font-weight: 600; 57 | font-size: 1.05rem; 58 | cursor: pointer; 59 | } 60 | 61 | /* Grid Styling */ 62 | .ag-theme-streamlit, 63 | .ag-theme-streamlit-dark { 64 | background-color: #1e1e1e !important; 65 | color: #e5e5e5; 66 | font-family: "Inter", sans-serif; 67 | font-size: 0.9rem; 68 | } 69 | 70 | /* Table rows */ 71 | .ag-row { 72 | background-color: #1e1e1e !important; 73 | border-bottom: 1px solid #2e2e2e !important; 74 | } 75 | 76 | /* Headers */ 77 | .ag-header { 78 | background-color: #2d2d2d !important; 79 | color: #fafafa !important; 80 | } 81 | 82 | /* Cell hover highlight */ 83 | .ag-cell-hover { 84 | background-color: #2a2a2a !important; 85 | } 86 | 87 | /* Scrollbars */ 88 | ::-webkit-scrollbar { 89 | width: 10px; 90 | } 91 | 92 | ::-webkit-scrollbar-track { 93 | background: #1f1f1f; 94 | } 95 | 96 | ::-webkit-scrollbar-thumb { 97 | background-color: #3b3b3b; 98 | border-radius: 6px; 99 | } 100 | 101 | hr { 102 | border-top: 1px solid #2a2a2a; 103 | margin: 1rem 0; 104 | } 105 | 106 | input, select, textarea { 107 | background-color: #1f1f1f !important; 108 | color: #e4e4e7 !important; 109 | border: 1px solid #333 !important; 110 | border-radius: 6px !important; 111 | } 112 | -------------------------------------------------------------------------------- /streamlit_csv_editor/modules/mysql_handler.py: -------------------------------------------------------------------------------- 1 | # modules/mysql_handler.py 2 | 3 | import mysql.connector 4 | import pandas as pd 5 | from mysql.connector import Error 6 | 7 | 8 | class MySQLHandler: 9 | def __init__(self, host, user, password, database=None): 10 | self.host = host 11 | self.user = user 12 | self.password = password 13 | self.database = database 14 | self.conn = None 15 | 16 | def connect(self): 17 | try: 18 | self.conn = mysql.connector.connect( 19 | host=self.host, 20 | user=self.user, 21 | password=self.password, 22 | database=self.database 23 | ) 24 | return self.conn 25 | except Error as e: 26 | raise ConnectionError(f"Failed to connect to MySQL: {e}") 27 | 28 | def close(self): 29 | if self.conn: 30 | self.conn.close() 31 | 32 | def list_databases(self): 33 | self.connect() 34 | cursor = self.conn.cursor() 35 | cursor.execute("SHOW DATABASES") 36 | return [db[0] for db in cursor.fetchall()] 37 | 38 | def list_tables(self, database): 39 | self.conn.database = database 40 | cursor = self.conn.cursor() 41 | cursor.execute("SHOW TABLES") 42 | return [tbl[0] for tbl in cursor.fetchall()] 43 | 44 | def fetch_table(self, table_name): 45 | if self.conn is None or not self.conn.is_connected(): 46 | self.connect() 47 | try: 48 | return pd.read_sql(f"SELECT * FROM `{table_name}`", self.conn) 49 | except Exception as e: 50 | raise RuntimeError(f"Failed to fetch table '{table_name}': {e}") 51 | 52 | def write_dataframe(self, df, table_name, truncate=True): 53 | if self.conn is None or not self.conn.is_connected(): 54 | self.connect() 55 | cursor = self.conn.cursor() 56 | 57 | try: 58 | if truncate: 59 | cursor.execute(f"TRUNCATE TABLE `{table_name}`") 60 | 61 | for _, row in df.iterrows(): 62 | placeholders = ','.join(['%s'] * len(row)) 63 | query = f"INSERT INTO `{table_name}` VALUES ({placeholders})" 64 | cursor.execute(query, tuple(row)) 65 | 66 | self.conn.commit() 67 | except Exception as e: 68 | raise RuntimeError(f"Failed to write data to '{table_name}': {e}") 69 | 70 | def execute_query(self, query): 71 | if self.conn is None or not self.conn.is_connected(): 72 | self.connect() 73 | cursor = self.conn.cursor() 74 | try: 75 | cursor.execute(query) 76 | result = cursor.fetchall() 77 | columns = [desc[0] for desc in cursor.description] 78 | return pd.DataFrame(result, columns=columns) 79 | except Exception as e: 80 | raise RuntimeError(f"Query execution failed: {e}") 81 | -------------------------------------------------------------------------------- /streamlit_csv_editor/assets/custom.css: -------------------------------------------------------------------------------- 1 | /* assets/custom.css - Dark Theme */ 2 | 3 | /* Base Style */ 4 | body { 5 | font-family: "Inter", "Segoe UI", sans-serif; 6 | background-color: #121212 !important; 7 | color: #e5e5e5 !important; 8 | } 9 | 10 | /* Headings */ 11 | h1, h2, h3 { 12 | color: #f1f3f5 !important; 13 | font-weight: 700; 14 | letter-spacing: -0.5px; 15 | } 16 | 17 | h4, h5, h6 { 18 | color: #e5e5e5 !important; 19 | font-weight: 600; 20 | } 21 | 22 | /* Info boxes */ 23 | .stAlert { 24 | border-radius: 10px; 25 | } 26 | 27 | /* Buttons */ 28 | .stButton > button { 29 | background-color: #1e88e5 !important; 30 | color: white !important; 31 | border: none; 32 | border-radius: 8px; 33 | padding: 0.5rem 1.1rem; 34 | font-weight: 600; 35 | transition: all 0.3s ease; 36 | box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.2); 37 | } 38 | 39 | .stButton > button:hover { 40 | background-color: #1565c0 !important; 41 | transform: scale(1.02); 42 | } 43 | 44 | /* Sidebar */ 45 | section[data-testid="stSidebar"] { 46 | background-color: #1f1f1f !important; 47 | color: #e5e5e5 !important; 48 | border-right: 1px solid #2e2e2e; 49 | } 50 | 51 | /* Download buttons */ 52 | .stDownloadButton > button { 53 | background-color: #43a047 !important; 54 | color: #fff !important; 55 | border-radius: 8px; 56 | padding: 0.5rem 1rem; 57 | font-weight: 600; 58 | border: none; 59 | } 60 | 61 | .stDownloadButton > button:hover { 62 | background-color: #2e7d32 !important; 63 | transform: scale(1.02); 64 | } 65 | 66 | /* Expander box */ 67 | details > summary { 68 | font-size: 1.05rem; 69 | font-weight: 600; 70 | color: #4dabf7 !important; 71 | cursor: pointer; 72 | } 73 | 74 | /* AgGrid Enhancements */ 75 | .ag-theme-streamlit, 76 | .ag-theme-streamlit-dark { 77 | font-family: "Inter", sans-serif !important; 78 | font-size: 0.9rem; 79 | border-radius: 6px; 80 | background-color: #1e1e1e !important; 81 | color: #e5e5e5 !important; 82 | } 83 | 84 | .ag-header { 85 | background-color: #2d2d2d !important; 86 | color: #ffffff !important; 87 | } 88 | 89 | .ag-cell { 90 | background-color: #1e1e1e !important; 91 | color: #e5e5e5 !important; 92 | position: relative; 93 | overflow: hidden; 94 | } 95 | 96 | /* Scrollbar */ 97 | ::-webkit-scrollbar { 98 | width: 10px; 99 | } 100 | ::-webkit-scrollbar-track { 101 | background: #1f1f1f; 102 | } 103 | ::-webkit-scrollbar-thumb { 104 | background-color: #3b3b3b; 105 | border-radius: 6px; 106 | } 107 | 108 | /* Ripple effect for cells */ 109 | .ripple { 110 | position: absolute; 111 | width: 12px; 112 | height: 12px; 113 | border-radius: 50%; 114 | background: rgba(255, 255, 255, 0.2); 115 | animation: ripple-effect 0.6s linear; 116 | transform: scale(0); 117 | } 118 | 119 | @keyframes ripple-effect { 120 | to { 121 | transform: scale(8); 122 | opacity: 0; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 📊 Streamlit-Based CSV/Excel/MySQL Data Editor 3 | 4 | An interactive and secure data editor built with **Streamlit**, **AgGrid**, and **MySQL** — supporting: 5 | 6 | - 🔐 User login with role-based permissions 7 | - 📤 CSV/Excel upload and editing 8 | - 🧾 MySQL table editor with cell-level control 9 | - 💾 Export to CSV, Excel, JSON 10 | - 🎨 Modern dark UI with custom JS 11 | 12 | --- 13 | 14 | ## 🚀 Features 15 | 16 | | Feature | Description | 17 | |--------|-------------| 18 | | **User Login** | Users log in with their credentials stored in a `users` table in MySQL | 19 | | **Role-Based Access** | Access controlled by `role_permissions` table: edit/insert/delete at column-level | 20 | | **MySQL Integration** | Load and edit tables from MySQL, with secure commit back | 21 | | **CSV/Excel Upload** | Upload a `.csv` or `.xlsx` file to preview and edit | 22 | | **Editable Grid** | Powered by AgGrid for responsive editing | 23 | | **Column Type Casting** | Optional casting of columns using dropdowns | 24 | | **Export Options** | Export as `.csv`, `.xlsx`, or `.json` | 25 | | **Modern UI** | Custom CSS theme + JS ripple effects for smooth experience | 26 | 27 | --- 28 | 29 | ## 🗃️ Database Schema 30 | 31 | ### `users` Table 32 | 33 | ```sql 34 | CREATE TABLE users ( 35 | username VARCHAR(50) PRIMARY KEY, 36 | password VARCHAR(255), 37 | role VARCHAR(50) 38 | ); 39 | ``` 40 | 41 | ### `role_permissions` Table 42 | 43 | ```sql 44 | CREATE TABLE role_permissions ( 45 | role VARCHAR(50), 46 | table_name VARCHAR(50), 47 | permission ENUM('edit', 'insert', 'delete'), 48 | column_name VARCHAR(50) 49 | ); 50 | ``` 51 | 52 | --- 53 | 54 | ## ⚙️ Setup Instructions 55 | 56 | ### 1. Clone the Repository 57 | 58 | ```bash 59 | git clone https://github.com/yourname/streamlit-data-editor.git 60 | cd streamlit-data-editor 61 | ``` 62 | 63 | ### 2. Install Requirements 64 | 65 | ```bash 66 | pip install -r requirements.txt 67 | ``` 68 | 69 | ### 3. Setup MySQL Tables 70 | 71 | Use the schema above to create `users` and `role_permissions`. Insert users and permissions: 72 | 73 | ```sql 74 | INSERT INTO users VALUES ('admin_user', 'admin123', 'admin'); 75 | INSERT INTO role_permissions VALUES ('admin', 'employees', 'edit', 'all'); 76 | ``` 77 | 78 | ### 4. Launch the App 79 | 80 | ```bash 81 | streamlit run app.py 82 | ``` 83 | 84 | --- 85 | 86 | ## 🔑 Access Control Flow 87 | 88 | 1. User logs in from sidebar using MySQL credentials and app username/password. 89 | 2. App authenticates using `users` table and determines role. 90 | 3. Role permissions are fetched from `role_permissions` table. 91 | 4. Based on permission: 92 | 93 | * Certain columns are editable 94 | * Add/Delete buttons are shown/hidden 95 | * Grid restricts input at column-level 96 | 97 | --- 98 | 99 | ## 🧠 File Structure 100 | 101 | ``` 102 | streamlit_csv_editor/ 103 | ├── app.py 104 | ├── requirements.txt 105 | ├── assets/ 106 | │ ├── custom.css # Dark mode styling 107 | │ ├── custom.js # Ripple effects & interactivity 108 | ├── modules/ 109 | │ ├── mysql_handler.py # MySQL wrapper 110 | │ ├── role_manager_db.py # Role + permission loader from DB 111 | │ └── (other editor modules) 112 | ├── utils/ 113 | │ └── helpers.py 114 | ├── data/ 115 | │ └── sample_test_file.xlsx 116 | ``` 117 | 118 | --- 119 | 120 | ## ✅ Permissions Example 121 | 122 | ### Role: `editor` 123 | 124 | | Table | Permission | Editable Columns | 125 | | --------- | ---------- | ------------------------ | 126 | | employees | `edit` | name, salary, department | 127 | | products | `edit` | name, price | 128 | 129 | ### Role: `viewer` 130 | 131 | * No permissions. Read-only UI. 132 | 133 | --- 134 | 135 | ## 📦 Future Enhancements 136 | 137 | * ✅ Role management via UI 138 | * ✅ Token-based login with hashing 139 | * ✅ Row-level permissions 140 | * ✅ Audit logging of edits 141 | 142 | -------------------------------------------------------------------------------- /streamlit_csv_editor/app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pandas as pd 3 | import os 4 | import mysql.connector 5 | 6 | from modules import file_loader, grid_editor, data_ops, validators, export, state_manager 7 | from modules.mysql_handler import MySQLHandler 8 | from modules.role_manager_db import get_user_role, get_role_permissions 9 | from utils.helpers import get_default_row 10 | 11 | # --- Config --- 12 | st.set_page_config(page_title="Streamlit Data Editor", layout="wide") 13 | 14 | # --- Theme + JS --- 15 | with open("streamlit_csv_editor/assets/custom.css") as f: 16 | st.markdown(f"", unsafe_allow_html=True) 17 | 18 | if os.path.exists("streamlit_csv_editor/assets/custom.js"): 19 | with open("streamlit_csv_editor/assets/custom.js") as f: 20 | st.components.v1.html(f"", height=0) 21 | 22 | # --- Init Session --- 23 | if "df" not in st.session_state: 24 | st.session_state.df = None 25 | st.session_state.original_df = None 26 | st.session_state.history = state_manager.init_history() 27 | if "mysql_handler" not in st.session_state: 28 | st.session_state.mysql_handler = None 29 | if "user_role" not in st.session_state: 30 | st.session_state.user_role = None 31 | if "permissions" not in st.session_state: 32 | st.session_state.permissions = {"permissions": [], "columns": []} 33 | 34 | st.title("📊 CSV / Excel / MySQL Data Editor") 35 | 36 | # --- User Login + MySQL --- 37 | st.sidebar.header("🔐 Login") 38 | with st.sidebar.form("login_form"): 39 | db_host = st.text_input("MySQL Host", "localhost") 40 | db_user = st.text_input("DB User") 41 | db_pass = st.text_input("DB Password", type="password") 42 | app_user = st.text_input("App Username") 43 | app_pass = st.text_input("App Password", type="password") 44 | login_btn = st.form_submit_button("Login") 45 | 46 | if login_btn: 47 | try: 48 | conn = mysql.connector.connect(host=db_host, user=db_user, password=db_pass) 49 | role = get_user_role(conn, app_user, app_pass) 50 | if role: 51 | st.session_state.mysql_handler = MySQLHandler(db_host, db_user, db_pass) 52 | st.session_state.mysql_handler.connect() 53 | st.session_state.user_role = role 54 | st.success(f"✅ Logged in as {app_user} ({role})") 55 | else: 56 | st.error("Invalid credentials or role not assigned.") 57 | except Exception as e: 58 | st.error(f"MySQL connection error: {e}") 59 | 60 | # --- MySQL Database & Table Selection --- 61 | if st.session_state.mysql_handler: 62 | dbs = st.session_state.mysql_handler.list_databases() 63 | db = st.sidebar.selectbox("Database", dbs) 64 | st.session_state.mysql_handler.database = db 65 | tables = st.session_state.mysql_handler.list_tables(db) 66 | tbl = st.sidebar.selectbox("Table", tables) 67 | 68 | if tbl and st.session_state.user_role: 69 | access = get_role_permissions( 70 | st.session_state.mysql_handler.conn, 71 | st.session_state.user_role, 72 | tbl 73 | ) 74 | st.session_state.permissions = access 75 | allowed_cols = access["columns"] 76 | can_edit = "edit" in access["permissions"] 77 | can_insert = "insert" in access["permissions"] 78 | can_delete = "delete" in access["permissions"] 79 | else: 80 | can_edit = can_insert = can_delete = False 81 | allowed_cols = [] 82 | 83 | if st.sidebar.button("📥 Load Table"): 84 | df = st.session_state.mysql_handler.fetch_table(tbl) 85 | st.session_state.df = df.copy() 86 | st.session_state.original_df = df.copy() 87 | st.session_state.history = state_manager.init_history() 88 | 89 | if st.sidebar.button("📤 Save Table") and can_edit: 90 | st.session_state.mysql_handler.write_dataframe(st.session_state.df, tbl) 91 | st.success("✅ Saved to database") 92 | 93 | # --- Upload CSV/Excel (Optional) --- 94 | st.sidebar.markdown("---") 95 | uploaded_file = st.sidebar.file_uploader("📂 Upload CSV / Excel", type=["csv", "xlsx"]) 96 | if uploaded_file: 97 | df, xls = file_loader.load_file(uploaded_file) 98 | if xls: 99 | sheet = st.sidebar.selectbox("Sheet", xls.sheet_names) 100 | df = file_loader.get_sheet(xls, sheet) 101 | st.session_state.df = df.copy() 102 | st.session_state.original_df = df.copy() 103 | st.session_state.history = state_manager.init_history() 104 | can_edit = True 105 | allowed_cols = "all" 106 | 107 | # --- Editor --- 108 | if st.session_state.df is not None: 109 | st.sidebar.markdown("### 🛠 Editor") 110 | 111 | if can_insert: 112 | if st.sidebar.button("➕ Add Row"): 113 | st.session_state.df = data_ops.add_blank_row(st.session_state.df) 114 | state_manager.push_undo(st.session_state.history, st.session_state.df) 115 | 116 | if st.sidebar.button("🔄 Reset"): 117 | st.session_state.df = data_ops.reset_to_original(st.session_state.original_df) 118 | st.session_state.history = state_manager.init_history() 119 | 120 | if st.sidebar.button("↩️ Undo"): 121 | st.session_state.df = state_manager.undo(st.session_state.history, st.session_state.df) 122 | 123 | if st.sidebar.button("↪️ Redo"): 124 | st.session_state.df = state_manager.redo(st.session_state.history, st.session_state.df) 125 | 126 | # --- Grid --- 127 | st.subheader("📋 Editable Grid") 128 | if can_edit: 129 | updated_df = grid_editor.render_editable_grid(st.session_state.df, allowed_cols) 130 | else: 131 | st.warning("Read-only access. You do not have permission to edit.") 132 | updated_df = st.session_state.df 133 | 134 | st.session_state.df = updated_df 135 | 136 | with st.expander("⚙️ Enforce Column Types"): 137 | col_types = {col: st.selectbox(col, ["str", "int", "float", "bool"], key=col) 138 | for col in updated_df.columns} 139 | updated_df = validators.enforce_types(updated_df, col_types) 140 | 141 | # --- Export Options --- 142 | st.sidebar.markdown("### ⬇️ Export") 143 | fmt = st.sidebar.radio("Format", ["CSV", "Excel", "JSON"]) 144 | if fmt == "CSV": 145 | st.sidebar.download_button("Download CSV", export.to_csv(updated_df), "edited.csv") 146 | elif fmt == "Excel": 147 | st.sidebar.download_button("Download Excel", export.to_excel(updated_df), "edited.xlsx") 148 | else: 149 | st.sidebar.download_button("Download JSON", export.to_json(updated_df), "edited.json") 150 | 151 | # --- Final Preview --- 152 | st.markdown("---") 153 | st.subheader("📊 Final Preview") 154 | st.dataframe(updated_df, use_container_width=True) 155 | else: 156 | st.info("📤 Upload a file or connect to MySQL to begin.") 157 | --------------------------------------------------------------------------------