Initial commit: Kivy database interface application with search, add/update, delete functionality and Windows build support
This commit is contained in:
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
*.spec.bak
|
||||
*.manifest
|
||||
*.exe
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.log
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
163
BUILD_WINDOWS_GUIDE.md
Normal file
163
BUILD_WINDOWS_GUIDE.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Building Windows Executable with PyInstaller
|
||||
|
||||
## Prerequisites
|
||||
You need to build the executable **on a Windows machine** (PyInstaller creates platform-specific executables).
|
||||
|
||||
## Step 1: Install PyInstaller
|
||||
|
||||
On Windows:
|
||||
```cmd
|
||||
# Activate virtual environment
|
||||
venv\Scripts\activate
|
||||
|
||||
# Install PyInstaller
|
||||
pip install pyinstaller
|
||||
```
|
||||
|
||||
## Step 2: Build the Executable
|
||||
|
||||
### Option A: Using the build script (Recommended)
|
||||
```cmd
|
||||
python build_windows.py
|
||||
```
|
||||
|
||||
### Option B: Manual PyInstaller command
|
||||
```cmd
|
||||
pyinstaller --name=DatabaseApp --onefile --windowed --hidden-import=mysql.connector --collect-all=kivy main.py
|
||||
```
|
||||
|
||||
## Step 3: Find Your Executable
|
||||
After building, the executable will be in:
|
||||
```
|
||||
dist/DatabaseApp.exe
|
||||
```
|
||||
|
||||
## Step 4: Distribution
|
||||
Copy the following to your target Windows machine:
|
||||
- `DatabaseApp.exe` (from the `dist` folder)
|
||||
|
||||
That's it! The executable is fully standalone and doesn't require Python installation.
|
||||
|
||||
## Advanced Build Options
|
||||
|
||||
### Creating a Spec File for Customization
|
||||
```cmd
|
||||
pyi-makespec --onefile --windowed main.py
|
||||
```
|
||||
|
||||
Then edit `main.spec` and build:
|
||||
```cmd
|
||||
pyinstaller main.spec
|
||||
```
|
||||
|
||||
### Adding an Application Icon
|
||||
1. Get an `.ico` file (you can convert PNG to ICO online)
|
||||
2. Save it as `app_icon.ico` in the project folder
|
||||
3. Build with icon:
|
||||
```cmd
|
||||
pyinstaller --name=DatabaseApp --onefile --windowed --icon=app_icon.ico --collect-all=kivy main.py
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Failed to execute script"
|
||||
- Make sure all dependencies are included
|
||||
- Try building with `--onedir` instead of `--onefile` for debugging:
|
||||
```cmd
|
||||
pyinstaller --name=DatabaseApp --onedir --windowed --collect-all=kivy main.py
|
||||
```
|
||||
|
||||
### Missing modules
|
||||
Add hidden imports:
|
||||
```cmd
|
||||
pyinstaller --name=DatabaseApp --onefile --windowed --hidden-import=module_name --collect-all=kivy main.py
|
||||
```
|
||||
|
||||
### Large executable size
|
||||
This is normal for Kivy apps. Typical size: 50-100 MB
|
||||
To reduce size:
|
||||
- Use `--onedir` instead of `--onefile`
|
||||
- Use UPX compression: `pip install pyinstaller[encryption]`
|
||||
|
||||
## Build Spec File (Advanced)
|
||||
|
||||
Create `DatabaseApp.spec` for more control:
|
||||
|
||||
```python
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('database_manager.py', '.')],
|
||||
hiddenimports=['mysql.connector', 'kivy'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
# Collect all Kivy dependencies
|
||||
a.datas += Tree('venv/Lib/site-packages/kivy', prefix='kivy')
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='DatabaseApp',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # No console window
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon='app_icon.ico' # Optional
|
||||
)
|
||||
```
|
||||
|
||||
Then build:
|
||||
```cmd
|
||||
pyinstaller DatabaseApp.spec
|
||||
```
|
||||
|
||||
## Testing the Executable
|
||||
|
||||
1. Copy `DatabaseApp.exe` to a test Windows machine without Python
|
||||
2. Make sure MariaDB/MySQL is accessible (local or remote)
|
||||
3. Run the executable
|
||||
4. Use the Settings button to configure the database server IP if needed
|
||||
|
||||
## Notes
|
||||
|
||||
- The executable size will be ~50-100 MB due to Kivy framework
|
||||
- Build time: 2-5 minutes depending on your system
|
||||
- The executable is completely standalone - no Python needed
|
||||
- Database server must still be accessible (local or network)
|
||||
- Settings are not persistent between runs (saved in memory only)
|
||||
|
||||
## Creating an Installer (Optional)
|
||||
|
||||
Use Inno Setup to create a Windows installer:
|
||||
1. Download Inno Setup: https://jrsoftware.org/isinfo.php
|
||||
2. Create an installer script
|
||||
3. Package `DatabaseApp.exe` with installer
|
||||
|
||||
This makes distribution even more professional.
|
||||
38
DatabaseApp.spec
Normal file
38
DatabaseApp.spec
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=['mysql.connector', 'kivy.core.window.window_sdl2', 'win32timezone'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=['_tkinter', 'matplotlib', 'numpy'],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='DatabaseApp',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
100
IMPORTANT_BUILD_NOTE.md
Normal file
100
IMPORTANT_BUILD_NOTE.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Important Note About Building Windows Executables
|
||||
|
||||
## ⚠️ Cross-Platform Building Limitation
|
||||
|
||||
**PyInstaller creates platform-specific executables.** This means:
|
||||
|
||||
- ❌ You **CANNOT** build a Windows `.exe` from Linux/Raspberry Pi
|
||||
- ✅ You **MUST** build on Windows to create a Windows executable
|
||||
- ✅ You **CAN** build on Linux to create a Linux executable
|
||||
|
||||
## Solution: Build on Windows
|
||||
|
||||
### Option 1: Use a Windows Machine
|
||||
1. Copy all project files to a Windows computer
|
||||
2. Install Python 3.8+ on Windows
|
||||
3. Run the build:
|
||||
```cmd
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
python build_windows.py
|
||||
```
|
||||
|
||||
### Option 2: Use a Windows Virtual Machine
|
||||
1. Install VirtualBox or VMware
|
||||
2. Create a Windows VM
|
||||
3. Follow the same steps as Option 1
|
||||
|
||||
### Option 3: Use Wine (Advanced, Not Recommended)
|
||||
Wine can sometimes work but is unreliable for Kivy applications.
|
||||
|
||||
## What You CAN Do on Raspberry Pi/Linux
|
||||
|
||||
### 1. Build Linux Executable
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
pip install pyinstaller
|
||||
pyinstaller --onefile --windowed main.py
|
||||
```
|
||||
This creates: `dist/main` (Linux executable)
|
||||
|
||||
### 2. Run Directly with Python (Recommended for Development)
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 3. Transfer Files to Windows for Building
|
||||
Use:
|
||||
- USB drive
|
||||
- Network share
|
||||
- Git repository
|
||||
- Cloud storage (Dropbox, Google Drive, etc.)
|
||||
|
||||
## Recommended Workflow
|
||||
|
||||
**For Distribution:**
|
||||
1. Develop on Raspberry Pi/Linux (as you're doing now)
|
||||
2. When ready to distribute, transfer project to Windows
|
||||
3. Build Windows executable on Windows machine
|
||||
4. Distribute the `.exe` file to end users
|
||||
|
||||
**For Testing:**
|
||||
- Keep developing and testing with `python main.py` on your Raspberry Pi
|
||||
- The app works perfectly without building an executable
|
||||
|
||||
## Alternative: Distribute as Python App
|
||||
|
||||
Instead of an executable, you can distribute:
|
||||
|
||||
1. **Python files + instructions**
|
||||
```
|
||||
- Give users: main.py, database_manager.py, requirements.txt
|
||||
- Users install Python and run: pip install -r requirements.txt && python main.py
|
||||
```
|
||||
|
||||
2. **Docker container** (works on any platform)
|
||||
```bash
|
||||
docker build -t database-app .
|
||||
docker run database-app
|
||||
```
|
||||
|
||||
## Files Ready for Windows Build
|
||||
|
||||
✅ All necessary files are ready:
|
||||
- `build_windows.py` - Automated build script
|
||||
- `build.bat` - Windows batch file
|
||||
- `DatabaseApp.spec` - PyInstaller configuration
|
||||
- `BUILD_WINDOWS_GUIDE.md` - Complete instructions
|
||||
- `main.py` - Main application
|
||||
- `database_manager.py` - Database logic
|
||||
- `requirements.txt` - Dependencies
|
||||
|
||||
**Just transfer these files to Windows and run `build.bat`**
|
||||
|
||||
## Questions?
|
||||
|
||||
- The app works perfectly as-is with Python on any platform
|
||||
- Building executables is only needed for distribution to users without Python
|
||||
- For personal use or development, keep using `python main.py`
|
||||
152
README.md
Normal file
152
README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Database Manager Kivy App
|
||||
|
||||
A simple Kivy application for managing a MariaDB database with two columns (id and mass) for the `offsystemsCounting` table.
|
||||
|
||||
## Features
|
||||
|
||||
- **Search**: Look up records by ID
|
||||
- **Add/Update**: Add new records or update existing ones
|
||||
- **Delete**: Remove records from the database
|
||||
- **View All**: Display all records in the database
|
||||
- **Auto-create**: Table is created automatically if it doesn't exist
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### MariaDB Server Setup
|
||||
|
||||
1. **Install MariaDB server** (if not already installed):
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install mariadb-server
|
||||
```
|
||||
|
||||
2. **Start MariaDB service**:
|
||||
```bash
|
||||
sudo systemctl start mariadb
|
||||
sudo systemctl enable mariadb
|
||||
```
|
||||
|
||||
3. **Secure MariaDB installation**:
|
||||
```bash
|
||||
sudo mysql_secure_installation
|
||||
```
|
||||
|
||||
4. **Create the database and user**:
|
||||
```bash
|
||||
sudo mysql -u root -p
|
||||
```
|
||||
|
||||
Then run these SQL commands:
|
||||
```sql
|
||||
CREATE DATABASE cantare_injectie;
|
||||
CREATE USER 'omron'@'localhost' IDENTIFIED BY 'Initial01!';
|
||||
GRANT ALL PRIVILEGES ON cantare_injectie.* TO 'omron'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
```
|
||||
|
||||
5. **Test the connection**:
|
||||
```bash
|
||||
mysql -u omron -p cantare_injectie
|
||||
```
|
||||
Enter password: `Initial01!`
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install Python 3.7+ if not already installed
|
||||
2. Install the required dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
Or using virtual environment:
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Make sure MariaDB server is running**:
|
||||
```bash
|
||||
sudo systemctl status mariadb
|
||||
```
|
||||
|
||||
2. **Run the application**:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
Or:
|
||||
```bash
|
||||
chmod +x run_app.sh
|
||||
./run_app.sh
|
||||
```
|
||||
|
||||
3. **To search for a record:**
|
||||
- Enter an ID in the "ID" field (max 20 characters)
|
||||
- Click "Search"
|
||||
- If found, the mass will be displayed in the "Mass" field
|
||||
|
||||
4. **To add or update a record:**
|
||||
- Enter both ID and mass value
|
||||
- Click "Add/Update"
|
||||
- If the ID exists, it will be updated; otherwise, a new record will be created
|
||||
|
||||
5. **To delete a record:**
|
||||
- Enter the ID to delete
|
||||
- Click "Delete"
|
||||
- Confirm the deletion in the popup
|
||||
|
||||
6. **To refresh the display:**
|
||||
- Click "Refresh All" to reload all data from the database
|
||||
|
||||
## Database Structure
|
||||
|
||||
The app connects to MariaDB database `cantare_injectie` with the following table structure:
|
||||
|
||||
```sql
|
||||
CREATE TABLE offsystemsCounting (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
mass REAL NOT NULL
|
||||
)
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `main.py`: Main Kivy application
|
||||
- `database_manager.py`: MariaDB database operations class
|
||||
- `requirements.txt`: Python dependencies
|
||||
- `test_database.py`: Test script for database operations
|
||||
- `run_app.sh`: Startup script
|
||||
- `README.md`: This documentation
|
||||
|
||||
## Connection Settings
|
||||
|
||||
The application connects to MariaDB with these settings:
|
||||
- **Host**: localhost
|
||||
- **Database**: cantare_injectie
|
||||
- **User**: omron
|
||||
- **Password**: Initial01!
|
||||
- **Table**: offsystemsCounting
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. **Connection Error 1698**:
|
||||
- Make sure MariaDB is running: `sudo systemctl start mariadb`
|
||||
- Verify user exists and has correct password
|
||||
- Check database exists: `SHOW DATABASES;`
|
||||
|
||||
2. **Access Denied**:
|
||||
- Verify user permissions: `SHOW GRANTS FOR 'omron'@'localhost';`
|
||||
- Reset password if needed: `ALTER USER 'omron'@'localhost' IDENTIFIED BY 'Initial01!';`
|
||||
|
||||
3. **Database/Table doesn't exist**:
|
||||
- The application will create the table automatically
|
||||
- Make sure the database `cantare_injectie` exists
|
||||
|
||||
## Notes
|
||||
|
||||
- IDs must be unique and max 20 characters
|
||||
- Mass values must be valid decimal numbers
|
||||
- The app includes comprehensive error handling and user feedback
|
||||
- All database operations use parameterized queries for security
|
||||
82
WINDOWS_README.md
Normal file
82
WINDOWS_README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Windows Setup Instructions
|
||||
|
||||
## Prerequisites
|
||||
1. Python 3.8 or higher
|
||||
2. MariaDB or MySQL server (local or remote)
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### 1. Install Python
|
||||
- Download from: https://www.python.org/downloads/
|
||||
- During installation, check "Add Python to PATH"
|
||||
|
||||
### 2. Setup the Application
|
||||
Open Command Prompt in the application folder and run:
|
||||
|
||||
```cmd
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
|
||||
# Activate virtual environment
|
||||
venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. Database Setup (if using local database)
|
||||
- Install MariaDB or MySQL
|
||||
- Run the setup script:
|
||||
```cmd
|
||||
mysql -u root -p < setup_user.sql
|
||||
```
|
||||
|
||||
### 4. Run the Application
|
||||
|
||||
#### Option A: Using the batch file
|
||||
Simply double-click `run_app.bat`
|
||||
|
||||
#### Option B: Using command line
|
||||
```cmd
|
||||
venv\Scripts\activate
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Configuration
|
||||
- Use the **Settings** button in the app to configure the database server IP address
|
||||
- Default connection:
|
||||
- Host: localhost
|
||||
- Database: cantare_injectie
|
||||
- User: omron
|
||||
- Password: Initial01!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Missing modules error
|
||||
```cmd
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Database connection error
|
||||
- Check if MariaDB/MySQL service is running
|
||||
- Verify database credentials
|
||||
- Use Settings button to update server IP address
|
||||
|
||||
### Kivy installation issues
|
||||
```cmd
|
||||
pip install --upgrade pip
|
||||
pip install kivy --pre --extra-index-url https://kivy.org/downloads/simple/
|
||||
```
|
||||
|
||||
## Features
|
||||
- **Fullscreen mode**: App starts in fullscreen by default
|
||||
- **Search**: Enter ID and press Enter
|
||||
- **Add/Update**: Click Add/Update button to enable editing
|
||||
- **Delete**: Use Delete button in update section
|
||||
- **Reset**: Clear all fields with Reset Values button
|
||||
- **Settings**: Configure database server IP address
|
||||
|
||||
## Keyboard Shortcuts
|
||||
- **F11** or **Esc**: Exit fullscreen
|
||||
- **Enter**: Search for ID (when in ID field)
|
||||
42
build.bat
Normal file
42
build.bat
Normal file
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
REM Batch file to build Windows executable
|
||||
|
||||
echo ========================================
|
||||
echo Building Windows Executable
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM Check if virtual environment exists
|
||||
if not exist "venv\Scripts\activate.bat" (
|
||||
echo Creating virtual environment...
|
||||
python -m venv venv
|
||||
call venv\Scripts\activate.bat
|
||||
echo Installing requirements...
|
||||
pip install -r requirements.txt
|
||||
) else (
|
||||
call venv\Scripts\activate.bat
|
||||
)
|
||||
|
||||
REM Install PyInstaller if not present
|
||||
python -c "import pyinstaller" 2>nul
|
||||
if errorlevel 1 (
|
||||
echo Installing PyInstaller...
|
||||
pip install pyinstaller
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Building executable with PyInstaller...
|
||||
echo This may take a few minutes...
|
||||
echo.
|
||||
|
||||
REM Build the executable
|
||||
python build_windows.py
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Build process completed!
|
||||
echo.
|
||||
echo Executable location: dist\DatabaseApp.exe
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
64
build_windows.py
Normal file
64
build_windows.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Script to build Windows executable using PyInstaller
|
||||
Run this on a Windows machine or use Wine on Linux
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
def build_executable():
|
||||
"""Build the Windows executable"""
|
||||
|
||||
print("Building Windows executable with PyInstaller...")
|
||||
print("Note: For best results, build on a Windows machine")
|
||||
print()
|
||||
|
||||
# Check if icon file exists
|
||||
icon_param = []
|
||||
if os.path.exists('app_icon.ico'):
|
||||
icon_param = ['--icon=app_icon.ico']
|
||||
print("Using icon file: app_icon.ico")
|
||||
else:
|
||||
print("No icon file found (optional)")
|
||||
|
||||
# PyInstaller command - simplified to avoid module collection issues
|
||||
cmd = [
|
||||
'pyinstaller',
|
||||
'--name=DatabaseApp',
|
||||
'--onefile',
|
||||
'--windowed',
|
||||
'--hidden-import=mysql.connector',
|
||||
'--hidden-import=kivy.core.window.window_sdl2',
|
||||
'--hidden-import=win32timezone',
|
||||
'--exclude-module=_tkinter',
|
||||
'--exclude-module=matplotlib',
|
||||
'--exclude-module=numpy',
|
||||
] + icon_param + ['main.py']
|
||||
|
||||
try:
|
||||
print("Running PyInstaller...")
|
||||
print("Command:", ' '.join(cmd))
|
||||
print()
|
||||
subprocess.run(cmd, check=True)
|
||||
print("\n" + "="*50)
|
||||
print("Build completed successfully!")
|
||||
print("Executable location: dist/DatabaseApp.exe")
|
||||
print("="*50)
|
||||
print("\nNote: If the executable doesn't work on Windows:")
|
||||
print("1. Build it directly on a Windows machine")
|
||||
print("2. Use the DatabaseApp.spec file: pyinstaller DatabaseApp.spec")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\nError building executable: {e}")
|
||||
print("\nTroubleshooting:")
|
||||
print("1. Make sure you're building on Windows for best compatibility")
|
||||
print("2. Try using the spec file: pyinstaller DatabaseApp.spec")
|
||||
print("3. Check BUILD_WINDOWS_GUIDE.md for more information")
|
||||
sys.exit(1)
|
||||
except FileNotFoundError:
|
||||
print("PyInstaller not found. Please install it first:")
|
||||
print("pip install pyinstaller")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
build_executable()
|
||||
149
database_manager.py
Normal file
149
database_manager.py
Normal file
@@ -0,0 +1,149 @@
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
class DatabaseManager:
|
||||
"""
|
||||
Database manager class for handling MariaDB operations.
|
||||
Connects to MariaDB server with table offsystemsCounting containing id (VARCHAR(20)) and mass (REAL).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.host = "localhost"
|
||||
self.database = "cantare_injectie"
|
||||
self.user = "omron"
|
||||
self.password = "Initial01!"
|
||||
self.connection = None
|
||||
self.init_database()
|
||||
|
||||
def get_connection(self):
|
||||
"""Get a database connection."""
|
||||
try:
|
||||
if self.connection is None or not self.connection.is_connected():
|
||||
self.connection = mysql.connector.connect(
|
||||
host=self.host,
|
||||
database=self.database,
|
||||
user=self.user,
|
||||
password=self.password
|
||||
)
|
||||
return self.connection
|
||||
except Error as e:
|
||||
print(f"Database connection error: {e}")
|
||||
return None
|
||||
|
||||
def init_database(self):
|
||||
"""Initialize the database connection and create the table if it doesn't exist."""
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
if conn and conn.is_connected():
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS offsystemsCounting (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
mass REAL NOT NULL
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
print(f"Connected to MariaDB database: {self.database}")
|
||||
print("Table 'offsystemsCounting' ready")
|
||||
except Error as e:
|
||||
print(f"Database initialization error: {e}")
|
||||
|
||||
def read_all_data(self) -> List[Tuple[str, float]]:
|
||||
"""Read all data from the database."""
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
if conn and conn.is_connected():
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, mass FROM offsystemsCounting ORDER BY id")
|
||||
return cursor.fetchall()
|
||||
except Error as e:
|
||||
print(f"Error reading data: {e}")
|
||||
return []
|
||||
|
||||
def search_by_id(self, record_id: str) -> Optional[Tuple[str, float]]:
|
||||
"""Search for a record by ID."""
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
if conn and conn.is_connected():
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, mass FROM offsystemsCounting WHERE id = %s", (record_id,))
|
||||
return cursor.fetchone()
|
||||
except Error as e:
|
||||
print(f"Error searching data: {e}")
|
||||
return None
|
||||
|
||||
def add_or_update_record(self, record_id: str, mass: float) -> bool:
|
||||
"""Add a new record or update existing one if ID already exists."""
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
if conn and conn.is_connected():
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if record exists
|
||||
existing = self.search_by_id(record_id)
|
||||
|
||||
if existing:
|
||||
# Update existing record
|
||||
cursor.execute(
|
||||
"UPDATE offsystemsCounting SET mass = %s WHERE id = %s",
|
||||
(mass, record_id)
|
||||
)
|
||||
print(f"Updated record: {record_id} = {mass}")
|
||||
else:
|
||||
# Insert new record
|
||||
cursor.execute(
|
||||
"INSERT INTO offsystemsCounting (id, mass) VALUES (%s, %s)",
|
||||
(record_id, mass)
|
||||
)
|
||||
print(f"Added new record: {record_id} = {mass}")
|
||||
|
||||
conn.commit()
|
||||
return True
|
||||
except Error as e:
|
||||
print(f"Error adding/updating record: {e}")
|
||||
return False
|
||||
|
||||
def delete_record(self, record_id: str) -> bool:
|
||||
"""Delete a record by ID."""
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
if conn and conn.is_connected():
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM offsystemsCounting WHERE id = %s", (record_id,))
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
conn.commit()
|
||||
print(f"Deleted record: {record_id}")
|
||||
return True
|
||||
else:
|
||||
print(f"No record found with ID: {record_id}")
|
||||
return False
|
||||
except Error as e:
|
||||
print(f"Error deleting record: {e}")
|
||||
return False
|
||||
|
||||
def get_record_count(self) -> int:
|
||||
"""Get the total number of records in the database."""
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
if conn and conn.is_connected():
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT COUNT(*) FROM offsystemsCounting")
|
||||
return cursor.fetchone()[0]
|
||||
except Error as e:
|
||||
print(f"Error getting record count: {e}")
|
||||
return 0
|
||||
|
||||
def close_connection(self):
|
||||
"""Close the database connection."""
|
||||
try:
|
||||
if self.connection and self.connection.is_connected():
|
||||
self.connection.close()
|
||||
print("MariaDB connection closed")
|
||||
except Error as e:
|
||||
print(f"Error closing connection: {e}")
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor to ensure connection is closed."""
|
||||
self.close_connection()
|
||||
138
import_csv.py
Executable file
138
import_csv.py
Executable file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
CSV Import Script for MariaDB Database
|
||||
This script imports data from sept.csv into the offsystemsCounting table
|
||||
"""
|
||||
|
||||
import csv
|
||||
from database_manager import DatabaseManager
|
||||
|
||||
def import_csv_data(csv_file_path):
|
||||
"""Import data from CSV file into the database."""
|
||||
print("CSV Import Script for MariaDB Database")
|
||||
print("=" * 40)
|
||||
|
||||
# Initialize database connection
|
||||
db = DatabaseManager()
|
||||
|
||||
# Check if we can connect
|
||||
conn = db.get_connection()
|
||||
if not conn or not conn.is_connected():
|
||||
print("❌ Cannot connect to MariaDB database.")
|
||||
print("Please make sure:")
|
||||
print("1. MariaDB server is running")
|
||||
print("2. Database 'cantare_injectie' exists")
|
||||
print("3. User 'omron' has proper permissions")
|
||||
return False
|
||||
|
||||
print("✅ Successfully connected to MariaDB database")
|
||||
|
||||
# Read and import CSV data
|
||||
try:
|
||||
with open(csv_file_path, 'r', newline='', encoding='utf-8') as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
|
||||
# Check if the required columns exist
|
||||
if 'id' not in reader.fieldnames or 'mass' not in reader.fieldnames:
|
||||
print("❌ CSV file must contain 'id' and 'mass' columns")
|
||||
return False
|
||||
|
||||
print(f"📁 Reading data from: {csv_file_path}")
|
||||
print(f"📋 Columns found: {reader.fieldnames}")
|
||||
|
||||
# Import data
|
||||
imported_count = 0
|
||||
updated_count = 0
|
||||
error_count = 0
|
||||
|
||||
for row_num, row in enumerate(reader, start=2): # Start at 2 because line 1 is header
|
||||
try:
|
||||
record_id = row['id'].strip()
|
||||
mass_str = row['mass'].strip()
|
||||
|
||||
# Validate data
|
||||
if not record_id:
|
||||
print(f"⚠️ Row {row_num}: Empty ID, skipping")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
if len(record_id) > 20:
|
||||
print(f"⚠️ Row {row_num}: ID '{record_id}' too long (max 20 chars), skipping")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
mass = float(mass_str)
|
||||
except ValueError:
|
||||
print(f"⚠️ Row {row_num}: Invalid mass value '{mass_str}', skipping")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
# Check if record exists
|
||||
existing = db.search_by_id(record_id)
|
||||
|
||||
# Add or update record
|
||||
success = db.add_or_update_record(record_id, mass)
|
||||
|
||||
if success:
|
||||
if existing:
|
||||
updated_count += 1
|
||||
if updated_count <= 5: # Show first 5 updates
|
||||
print(f"🔄 Updated: {record_id} = {mass}")
|
||||
else:
|
||||
imported_count += 1
|
||||
if imported_count <= 5: # Show first 5 imports
|
||||
print(f"➕ Added: {record_id} = {mass}")
|
||||
else:
|
||||
error_count += 1
|
||||
print(f"❌ Failed to import row {row_num}: {record_id}")
|
||||
|
||||
# Progress indicator
|
||||
if (imported_count + updated_count + error_count) % 100 == 0:
|
||||
print(f"📊 Progress: {imported_count + updated_count + error_count} records processed...")
|
||||
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
print(f"❌ Error processing row {row_num}: {e}")
|
||||
|
||||
# Final summary
|
||||
print("\n" + "=" * 40)
|
||||
print("📊 IMPORT SUMMARY")
|
||||
print("=" * 40)
|
||||
print(f"✅ New records added: {imported_count}")
|
||||
print(f"🔄 Records updated: {updated_count}")
|
||||
print(f"❌ Errors: {error_count}")
|
||||
print(f"📈 Total processed: {imported_count + updated_count + error_count}")
|
||||
|
||||
# Verify final count in database
|
||||
total_records = db.get_record_count()
|
||||
print(f"📋 Total records in database: {total_records}")
|
||||
|
||||
if imported_count > 0 or updated_count > 0:
|
||||
print("\n✅ CSV import completed successfully!")
|
||||
return True
|
||||
else:
|
||||
print("\n⚠️ No records were imported.")
|
||||
return False
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"❌ CSV file not found: {csv_file_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading CSV file: {e}")
|
||||
return False
|
||||
finally:
|
||||
# Close database connection
|
||||
db.close_connection()
|
||||
|
||||
if __name__ == "__main__":
|
||||
csv_file = "sept.csv"
|
||||
success = import_csv_data(csv_file)
|
||||
|
||||
if success:
|
||||
print("\n🚀 You can now run the Kivy app to view the imported data:")
|
||||
print(" python main.py")
|
||||
print(" or")
|
||||
print(" ./run_app.sh")
|
||||
else:
|
||||
print("\n❌ Import failed. Please check the errors above.")
|
||||
361
main.py
Normal file
361
main.py
Normal file
@@ -0,0 +1,361 @@
|
||||
from kivy.app import App
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from database_manager import DatabaseManager
|
||||
|
||||
class DatabaseApp(App):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.db_manager = DatabaseManager()
|
||||
|
||||
def build(self):
|
||||
# Set window to fullscreen
|
||||
Window.fullscreen = 'auto'
|
||||
# Main layout with better spacing for fullscreen
|
||||
main_layout = BoxLayout(orientation='vertical', padding=40, spacing=20)
|
||||
|
||||
# Top spacer for vertical centering
|
||||
main_layout.add_widget(Label(size_hint_y=0.15))
|
||||
|
||||
# Content container - centered
|
||||
content_layout = BoxLayout(orientation='vertical', spacing=30, size_hint_y=None)
|
||||
content_layout.bind(minimum_height=content_layout.setter('height'))
|
||||
|
||||
# Title
|
||||
title = Label(text='Database Search & Update', font_size=28, bold=True, size_hint_y=None, height=50)
|
||||
content_layout.add_widget(title)
|
||||
|
||||
# Search section
|
||||
search_layout = GridLayout(cols=2, size_hint_y=None, height=100, spacing=15, row_force_default=True, row_default_height=45)
|
||||
search_layout.add_widget(Label(text='ID:', size_hint_x=0.25, font_size=20, bold=True))
|
||||
self.id_input = TextInput(
|
||||
multiline=False,
|
||||
size_hint_x=0.75,
|
||||
hint_text='Enter ID and press Enter to search (max 20 chars)',
|
||||
readonly=False,
|
||||
font_size=20,
|
||||
padding=[10, 10]
|
||||
)
|
||||
self.id_input.bind(on_text_validate=self.search_record)
|
||||
search_layout.add_widget(self.id_input)
|
||||
search_layout.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=20, bold=True))
|
||||
self.mass_input = TextInput(
|
||||
multiline=False,
|
||||
size_hint_x=0.75,
|
||||
hint_text='Mass value (read-only)',
|
||||
readonly=True,
|
||||
font_size=20,
|
||||
padding=[10, 10]
|
||||
)
|
||||
search_layout.add_widget(self.mass_input)
|
||||
content_layout.add_widget(search_layout)
|
||||
|
||||
# Button section - larger buttons (3 columns now)
|
||||
button_layout = GridLayout(cols=3, size_hint_y=None, height=70, spacing=15)
|
||||
add_update_btn = Button(text='Add/Update', font_size=22, bold=True)
|
||||
add_update_btn.bind(on_press=self.show_update_frame)
|
||||
button_layout.add_widget(add_update_btn)
|
||||
reset_btn = Button(text='Reset Values', font_size=22, bold=True)
|
||||
reset_btn.bind(on_press=self.reset_values)
|
||||
button_layout.add_widget(reset_btn)
|
||||
settings_btn = Button(text='Settings', font_size=22, bold=True)
|
||||
settings_btn.bind(on_press=self.show_settings)
|
||||
button_layout.add_widget(settings_btn)
|
||||
content_layout.add_widget(button_layout)
|
||||
|
||||
# Extra spacing between buttons and update frame
|
||||
content_layout.add_widget(Label(size_hint_y=None, height=40))
|
||||
|
||||
# Update frame (initially disabled)
|
||||
self.update_frame = BoxLayout(orientation='vertical', padding=15, spacing=15, size_hint_y=None, height=200)
|
||||
self.update_frame_label = Label(text='Update Values', size_hint_y=None, height=40, font_size=22, bold=True)
|
||||
self.update_frame.add_widget(self.update_frame_label)
|
||||
update_inputs = GridLayout(cols=2, spacing=15, size_hint_y=None, height=100, row_force_default=True, row_default_height=45)
|
||||
update_inputs.add_widget(Label(text='ID:', size_hint_x=0.25, font_size=20, bold=True))
|
||||
self.update_id_input = TextInput(multiline=False, size_hint_x=0.75, readonly=True, font_size=20, padding=[10, 10])
|
||||
update_inputs.add_widget(self.update_id_input)
|
||||
update_inputs.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=20, bold=True))
|
||||
self.update_mass_input = TextInput(multiline=False, size_hint_x=0.75, readonly=True, font_size=20, padding=[10, 10])
|
||||
update_inputs.add_widget(self.update_mass_input)
|
||||
self.update_frame.add_widget(update_inputs)
|
||||
# Add update and delete buttons in same row
|
||||
update_buttons = GridLayout(cols=2, size_hint_y=None, height=60, spacing=15)
|
||||
self.update_confirm_btn = Button(text='Confirm Add/Update', disabled=True, font_size=22, bold=True)
|
||||
self.update_confirm_btn.bind(on_press=self.add_update_record)
|
||||
update_buttons.add_widget(self.update_confirm_btn)
|
||||
self.delete_btn = Button(text='Delete', disabled=True, font_size=22, bold=True)
|
||||
self.delete_btn.bind(on_press=self.delete_record)
|
||||
update_buttons.add_widget(self.delete_btn)
|
||||
self.update_frame.add_widget(update_buttons)
|
||||
content_layout.add_widget(self.update_frame)
|
||||
|
||||
# Initially disable update frame
|
||||
self.set_update_frame_enabled(False)
|
||||
|
||||
# Status label - larger and more prominent
|
||||
self.status_label = Label(
|
||||
text='Ready',
|
||||
size_hint_y=None,
|
||||
height=50,
|
||||
color=(0, 0.8, 0, 1),
|
||||
font_size=22,
|
||||
bold=True
|
||||
)
|
||||
content_layout.add_widget(self.status_label)
|
||||
|
||||
main_layout.add_widget(content_layout)
|
||||
|
||||
# Bottom spacer for vertical centering
|
||||
main_layout.add_widget(Label(size_hint_y=0.15))
|
||||
|
||||
# Removed database contents frame
|
||||
# Load initial data
|
||||
Clock.schedule_once(self.refresh_data, 0.1)
|
||||
|
||||
return main_layout
|
||||
def set_update_frame_enabled(self, enabled):
|
||||
self.update_id_input.readonly = not enabled
|
||||
self.update_mass_input.readonly = not enabled
|
||||
self.update_confirm_btn.disabled = not enabled
|
||||
self.delete_btn.disabled = not enabled
|
||||
|
||||
def show_update_frame(self, instance):
|
||||
# If no value in search, copy from search fields
|
||||
record_id = self.id_input.text.strip()
|
||||
mass_text = self.mass_input.text.strip()
|
||||
self.set_update_frame_enabled(True)
|
||||
# If mass field is empty, just clear update frame
|
||||
if not record_id:
|
||||
self.update_id_input.text = ''
|
||||
self.update_mass_input.text = ''
|
||||
return
|
||||
self.update_id_input.text = record_id
|
||||
self.update_mass_input.text = mass_text
|
||||
|
||||
def search_record(self, instance):
|
||||
record_id = self.id_input.text.strip()
|
||||
if not record_id:
|
||||
self.show_status("Please enter an ID to search", error=True)
|
||||
return
|
||||
|
||||
if len(record_id) > 20:
|
||||
self.show_status("ID must be 20 characters or less", error=True)
|
||||
return
|
||||
|
||||
# Show searching status
|
||||
self.show_status("Searching...", error=False)
|
||||
|
||||
try:
|
||||
record = self.db_manager.search_by_id(record_id)
|
||||
if record:
|
||||
self.mass_input.text = str(record[1]) # Set the mass field
|
||||
self.show_status(f"Found: {record[0]} = {record[1]}")
|
||||
self.highlight_record(record_id)
|
||||
else:
|
||||
self.show_status(f"ID '{record_id}' not found in database", error=True)
|
||||
self.mass_input.text = ""
|
||||
except Exception as e:
|
||||
self.show_status(f"Search error: {str(e)}", error=True)
|
||||
|
||||
def add_update_record(self, instance):
|
||||
"""Add or update a record from the update frame."""
|
||||
record_id = self.update_id_input.text.strip()
|
||||
mass_text = self.update_mass_input.text.strip()
|
||||
if not record_id or not mass_text:
|
||||
self.show_status("Please enter both ID and mass in update frame", error=True)
|
||||
return
|
||||
if len(record_id) > 20:
|
||||
self.show_status("ID must be 20 characters or less", error=True)
|
||||
return
|
||||
try:
|
||||
mass = float(mass_text)
|
||||
except ValueError:
|
||||
self.show_status("Mass must be a valid number", error=True)
|
||||
return
|
||||
success = self.db_manager.add_or_update_record(record_id, mass)
|
||||
if success:
|
||||
existing = self.db_manager.search_by_id(record_id)
|
||||
if existing:
|
||||
self.show_status(f"Successfully added/updated: {record_id} = {mass}")
|
||||
self.refresh_data(None)
|
||||
# Clear update frame after successful operation
|
||||
self.update_id_input.text = ""
|
||||
self.update_mass_input.text = ""
|
||||
self.set_update_frame_enabled(False)
|
||||
else:
|
||||
self.show_status("Operation completed but record not found", error=True)
|
||||
else:
|
||||
self.show_status("Failed to add/update record", error=True)
|
||||
|
||||
def delete_record(self, instance):
|
||||
"""Delete a record using the update frame fields."""
|
||||
record_id = self.update_id_input.text.strip()
|
||||
if not record_id:
|
||||
self.show_status("Please enter an ID in the update fields to delete", error=True)
|
||||
return
|
||||
|
||||
# Confirm deletion
|
||||
self.show_confirmation_popup(
|
||||
f"Are you sure you want to delete ID '{record_id}'?",
|
||||
lambda: self.confirm_delete(record_id)
|
||||
)
|
||||
|
||||
def confirm_delete(self, record_id):
|
||||
"""Confirm and execute deletion."""
|
||||
success = self.db_manager.delete_record(record_id)
|
||||
if success:
|
||||
self.show_status(f"Successfully deleted: {record_id}")
|
||||
self.refresh_data(None)
|
||||
self.clear_fields()
|
||||
# Clear update frame fields
|
||||
self.update_id_input.text = ""
|
||||
self.update_mass_input.text = ""
|
||||
self.set_update_frame_enabled(False)
|
||||
else:
|
||||
self.show_status(f"Failed to delete ID '{record_id}' (not found)", error=True)
|
||||
|
||||
def clear_fields(self):
|
||||
"""Clear the ID and mass fields."""
|
||||
self.id_input.text = ""
|
||||
self.mass_input.text = ""
|
||||
|
||||
def reset_values(self, instance):
|
||||
"""Reset/clear the first ID and mass fields and set focus on ID field."""
|
||||
self.id_input.text = ""
|
||||
self.mass_input.text = ""
|
||||
self.id_input.focus = True
|
||||
self.show_status("Fields cleared", error=False)
|
||||
|
||||
def refresh_data(self, instance):
|
||||
"""Refresh the data display."""
|
||||
try:
|
||||
records = self.db_manager.read_all_data()
|
||||
count = len(records)
|
||||
self.show_status(f"Data refreshed - {count} records found")
|
||||
except Exception as e:
|
||||
self.show_status(f"Error refreshing data: {str(e)}", error=True)
|
||||
|
||||
def highlight_record(self, record_id):
|
||||
"""Highlight a specific record in the display."""
|
||||
# Since we removed the data display, just show a status message
|
||||
pass
|
||||
|
||||
def show_status(self, message, error=False):
|
||||
"""Show status message."""
|
||||
self.status_label.text = message
|
||||
if error:
|
||||
self.status_label.color = (1, 0.2, 0.2, 1) # Red for errors
|
||||
else:
|
||||
self.status_label.color = (0, 0.8, 0, 1) # Green for success
|
||||
|
||||
# Clear status after 5 seconds
|
||||
Clock.schedule_once(lambda dt: self.clear_status(), 5)
|
||||
|
||||
def clear_status(self):
|
||||
"""Clear the status message."""
|
||||
self.status_label.text = "Ready"
|
||||
self.status_label.color = (0, 0.8, 0, 1)
|
||||
|
||||
def show_settings(self, instance):
|
||||
"""Show settings popup for database configuration."""
|
||||
content = BoxLayout(orientation='vertical', spacing=15, padding=20)
|
||||
|
||||
# Title
|
||||
title_label = Label(text='Database Server Settings', font_size=24, bold=True, size_hint_y=None, height=40)
|
||||
content.add_widget(title_label)
|
||||
|
||||
# IP address input
|
||||
ip_layout = BoxLayout(orientation='horizontal', size_hint_y=None, height=60, spacing=10)
|
||||
ip_layout.add_widget(Label(text='Server IP Address:', font_size=20, bold=True, size_hint_x=0.4))
|
||||
ip_input = TextInput(
|
||||
text=self.db_manager.host,
|
||||
multiline=False,
|
||||
font_size=20,
|
||||
size_hint_x=0.6,
|
||||
padding=[10, 10]
|
||||
)
|
||||
ip_layout.add_widget(ip_input)
|
||||
content.add_widget(ip_layout)
|
||||
|
||||
# Info label
|
||||
info_label = Label(
|
||||
text='Enter the IP address or hostname of the database server.\nOther connection settings remain unchanged.',
|
||||
font_size=16,
|
||||
size_hint_y=None,
|
||||
height=60
|
||||
)
|
||||
content.add_widget(info_label)
|
||||
|
||||
# Buttons
|
||||
buttons = BoxLayout(size_hint_y=None, height=60, spacing=15)
|
||||
|
||||
save_btn = Button(text='Save', font_size=20, bold=True)
|
||||
cancel_btn = Button(text='Cancel', font_size=20, bold=True)
|
||||
|
||||
buttons.add_widget(save_btn)
|
||||
buttons.add_widget(cancel_btn)
|
||||
content.add_widget(buttons)
|
||||
|
||||
popup = Popup(
|
||||
title='Settings',
|
||||
content=content,
|
||||
size_hint=(0.6, 0.5)
|
||||
)
|
||||
|
||||
def save_settings():
|
||||
new_host = ip_input.text.strip()
|
||||
if new_host:
|
||||
self.db_manager.host = new_host
|
||||
# Reconnect with new settings
|
||||
if self.db_manager.connection:
|
||||
try:
|
||||
self.db_manager.connection.close()
|
||||
except:
|
||||
pass
|
||||
self.db_manager.connection = None
|
||||
self.show_status(f"Database server updated to: {new_host}")
|
||||
popup.dismiss()
|
||||
else:
|
||||
self.show_status("Please enter a valid IP address", error=True)
|
||||
|
||||
save_btn.bind(on_press=lambda x: save_settings())
|
||||
cancel_btn.bind(on_press=popup.dismiss)
|
||||
|
||||
popup.open()
|
||||
|
||||
def show_confirmation_popup(self, message, confirm_callback):
|
||||
"""Show a confirmation popup."""
|
||||
content = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
||||
|
||||
label = Label(text=message, text_size=(300, None), halign='center')
|
||||
content.add_widget(label)
|
||||
|
||||
buttons = BoxLayout(size_hint_y=None, height=50, spacing=10)
|
||||
|
||||
yes_btn = Button(text='Yes')
|
||||
no_btn = Button(text='No')
|
||||
|
||||
buttons.add_widget(yes_btn)
|
||||
buttons.add_widget(no_btn)
|
||||
content.add_widget(buttons)
|
||||
|
||||
popup = Popup(
|
||||
title='Confirm Action',
|
||||
content=content,
|
||||
size_hint=(0.8, 0.4)
|
||||
)
|
||||
|
||||
yes_btn.bind(on_press=lambda x: (confirm_callback(), popup.dismiss()))
|
||||
no_btn.bind(on_press=popup.dismiss)
|
||||
|
||||
popup.open()
|
||||
|
||||
if __name__ == '__main__':
|
||||
DatabaseApp().run()
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
kivy>=2.1.0
|
||||
kivymd>=1.0.0
|
||||
mysql-connector-python>=8.0.0
|
||||
pyinstaller>=5.0.0
|
||||
20
run_app.sh
Executable file
20
run_app.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Startup script for the Database Manager Kivy App
|
||||
|
||||
echo "Starting Database Manager App..."
|
||||
echo "================================"
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "Virtual environment not found. Creating one..."
|
||||
python3 -m venv venv
|
||||
echo "Installing dependencies..."
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
else
|
||||
source venv/bin/activate
|
||||
fi
|
||||
|
||||
echo "Launching application..."
|
||||
python main.py
|
||||
82
setup_database.sh
Executable file
82
setup_database.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
# MariaDB Database Setup Script
|
||||
# This script helps set up the MariaDB database for the Kivy app
|
||||
|
||||
echo "MariaDB Database Setup for Kivy App"
|
||||
echo "=================================="
|
||||
echo
|
||||
|
||||
# Check if MariaDB is installed
|
||||
if ! command -v mysql &> /dev/null; then
|
||||
echo "❌ MariaDB is not installed."
|
||||
echo "Please install MariaDB first:"
|
||||
echo " sudo apt update"
|
||||
echo " sudo apt install mariadb-server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if MariaDB service is running
|
||||
if ! systemctl is-active --quiet mariadb; then
|
||||
echo "⚠️ MariaDB service is not running."
|
||||
echo "Starting MariaDB service..."
|
||||
sudo systemctl start mariadb
|
||||
|
||||
if systemctl is-active --quiet mariadb; then
|
||||
echo "✅ MariaDB service started successfully."
|
||||
else
|
||||
echo "❌ Failed to start MariaDB service."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "✅ MariaDB service is running."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Setting up database and user..."
|
||||
echo "You will be prompted for the MySQL root password."
|
||||
echo
|
||||
|
||||
# Create the setup SQL script
|
||||
cat > /tmp/setup_db.sql << EOF
|
||||
CREATE DATABASE IF NOT EXISTS cantare_injectie;
|
||||
CREATE USER IF NOT EXISTS 'omron'@'localhost' IDENTIFIED BY 'Initial01!';
|
||||
GRANT ALL PRIVILEGES ON cantare_injectie.* TO 'omron'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
USE cantare_injectie;
|
||||
CREATE TABLE IF NOT EXISTS offsystemsCountin (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
mass REAL NOT NULL
|
||||
);
|
||||
|
||||
-- Show the created database and table
|
||||
SHOW DATABASES LIKE 'cantare_injectie';
|
||||
DESCRIBE offsystemsCounting;
|
||||
EOF
|
||||
|
||||
# Execute the SQL script
|
||||
mysql -u root -p < /tmp/setup_db.sql
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo
|
||||
echo "✅ Database setup completed successfully!"
|
||||
echo
|
||||
echo "Database details:"
|
||||
echo " Host: localhost"
|
||||
echo " Database: cantare_injectie"
|
||||
echo " User: omron"
|
||||
echo " Password: Initial01!"
|
||||
echo " Table: offsystemsCounting"
|
||||
echo
|
||||
echo "You can now run the Kivy application:"
|
||||
echo " python main.py"
|
||||
echo
|
||||
else
|
||||
echo
|
||||
echo "❌ Database setup failed."
|
||||
echo "Please check the error messages above."
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/setup_db.sql
|
||||
8
setup_user.sql
Normal file
8
setup_user.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Create user and grant privileges for MariaDB
|
||||
CREATE USER IF NOT EXISTS 'omron'@'localhost' IDENTIFIED BY 'Initial01!';
|
||||
GRANT ALL PRIVILEGES ON cantare_injectie.* TO 'omron'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Show the user and their privileges
|
||||
SELECT User, Host FROM mysql.user WHERE User='omron';
|
||||
SHOW GRANTS FOR 'omron'@'localhost';
|
||||
101
test_database.py
Normal file
101
test_database.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the MariaDB Database Manager
|
||||
This script tests the basic functionality without the GUI
|
||||
"""
|
||||
|
||||
from database_manager import DatabaseManager
|
||||
|
||||
def test_database_operations():
|
||||
print("Testing MariaDB Database Manager...")
|
||||
print("-" * 40)
|
||||
|
||||
# Initialize database
|
||||
db = DatabaseManager()
|
||||
|
||||
# Test 1: Add some initial data
|
||||
print("1. Adding initial test data...")
|
||||
test_data = [
|
||||
("SYS001", 125.5),
|
||||
("SYS002", 89.7),
|
||||
("SYS003", 234.1)
|
||||
]
|
||||
|
||||
for record_id, mass in test_data:
|
||||
success = db.add_or_update_record(record_id, mass)
|
||||
print(f" Added {record_id}: {'✓' if success else '✗'}")
|
||||
|
||||
print()
|
||||
|
||||
# Test 2: Read all data
|
||||
print("2. Reading all data...")
|
||||
records = db.read_all_data()
|
||||
for record in records:
|
||||
print(f" ID: {record[0]}, Mass: {record[1]}")
|
||||
print(f" Total records: {len(records)}")
|
||||
print()
|
||||
|
||||
# Test 3: Search for existing record
|
||||
print("3. Searching for existing record 'SYS001'...")
|
||||
result = db.search_by_id("SYS001")
|
||||
if result:
|
||||
print(f" Found: {result[0]} = {result[1]}")
|
||||
else:
|
||||
print(" Not found")
|
||||
print()
|
||||
|
||||
# Test 4: Search for non-existing record
|
||||
print("4. Searching for non-existing record 'SYS999'...")
|
||||
result = db.search_by_id("SYS999")
|
||||
if result:
|
||||
print(f" Found: {result[0]} = {result[1]}")
|
||||
else:
|
||||
print(" Not found - this is expected!")
|
||||
print()
|
||||
|
||||
# Test 5: Add the missing record
|
||||
print("5. Adding the missing record 'SYS999'...")
|
||||
success = db.add_or_update_record("SYS999", 456.8)
|
||||
print(f" Added SYS999: {'✓' if success else '✗'}")
|
||||
|
||||
# Verify it was added
|
||||
result = db.search_by_id("SYS999")
|
||||
if result:
|
||||
print(f" Verification: Found {result[0]} = {result[1]}")
|
||||
print()
|
||||
|
||||
# Test 6: Update existing record
|
||||
print("6. Updating existing record 'SYS001'...")
|
||||
success = db.add_or_update_record("SYS001", 150.0)
|
||||
print(f" Updated SYS001: {'✓' if success else '✗'}")
|
||||
|
||||
result = db.search_by_id("SYS001")
|
||||
if result:
|
||||
print(f" New value: {result[0]} = {result[1]}")
|
||||
print()
|
||||
|
||||
# Test 7: Final count
|
||||
print("7. Final database state...")
|
||||
records = db.read_all_data()
|
||||
print(f" Total records: {len(records)}")
|
||||
for record in records:
|
||||
print(f" {record[0]} = {record[1]}")
|
||||
print()
|
||||
|
||||
# Test 8: Delete a record
|
||||
print("8. Testing delete functionality...")
|
||||
success = db.delete_record("SYS999")
|
||||
print(f" Deleted SYS999: {'✓' if success else '✗'}")
|
||||
|
||||
final_records = db.read_all_data()
|
||||
print(f" Final record count: {len(final_records)}")
|
||||
print()
|
||||
|
||||
# Close connection
|
||||
db.close_connection()
|
||||
|
||||
print("✓ All tests completed successfully!")
|
||||
print("Note: Test records remain in the MariaDB database.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_database_operations()
|
||||
Reference in New Issue
Block a user