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