fix: db update bug, add action log with 30-day purge, rebuild exe

- main.py: _pending_record_id locks resolved DB key at Add/Update time;
  show original barcode in update frame; auto-focus mass field on open;
  clear all fields and return focus to ID input after confirm/reset
- database_manager.py: buffered=True cursors on all SELECTs; no
  fetchall() after DML; replace ON DUPLICATE KEY UPDATE VALUES() with
  explicit UPDATE then INSERT fallback; add app_actions.log with
  structured per-action entries; purge_old_action_logs(30) on startup
- dist/DatabaseApp.exe: rebuilt single-file Windows binary (30.9 MB)
- remove unused files: README, WINDOWS_README, run_app.sh,
  setup_database.sh, setup_user.sql, test_database.py, sept.csv"
This commit is contained in:
2026-04-09 11:00:37 +03:00
parent 3604a46421
commit 704e01669f
10 changed files with 243 additions and 10615 deletions

152
README.md
View File

@@ -1,152 +0,0 @@
# 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

View File

@@ -1,82 +0,0 @@
# 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)

View File

@@ -4,6 +4,8 @@ from typing import List, Tuple, Optional
import json import json
import os import os
import sys import sys
import logging
from datetime import datetime, timedelta
# When frozen by PyInstaller, __file__ points to a temp folder that is deleted on exit. # When frozen by PyInstaller, __file__ points to a temp folder that is deleted on exit.
# sys.executable points to the .exe location, which is persistent. # sys.executable points to the .exe location, which is persistent.
@@ -12,7 +14,59 @@ if getattr(sys, 'frozen', False):
else: else:
_BASE_DIR = os.path.dirname(os.path.abspath(__file__)) _BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(_BASE_DIR, 'config.json') CONFIG_FILE = os.path.join(_BASE_DIR, 'config.json')
LOG_FILE = os.path.join(_BASE_DIR, 'db_debug.log')
ACTION_LOG_FILE = os.path.join(_BASE_DIR, 'app_actions.log')
# ---- Action log helpers (module-level, no class dependency) ----
def _log_action(action: str, record_id: str = '', detail: str = '') -> None:
"""Append one structured line to app_actions.log."""
ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
line = f"{ts} | {action:<18} | id={record_id:<20} | {detail}\n"
try:
with open(ACTION_LOG_FILE, 'a', encoding='utf-8') as f:
f.write(line)
except Exception as e:
print(f"Action log write error: {e}")
def purge_old_action_logs(days: int = 30) -> None:
"""Remove lines older than *days* from app_actions.log."""
if not os.path.exists(ACTION_LOG_FILE):
return
cutoff = datetime.now() - timedelta(days=days)
kept = []
removed = 0
try:
with open(ACTION_LOG_FILE, 'r', encoding='utf-8') as f:
for line in f:
# Every valid line starts with YYYY-MM-DD HH:MM:SS
try:
ts = datetime.strptime(line[:19], '%Y-%m-%d %H:%M:%S')
if ts >= cutoff:
kept.append(line)
else:
removed += 1
except ValueError:
kept.append(line) # malformed line — keep it
with open(ACTION_LOG_FILE, 'w', encoding='utf-8') as f:
f.writelines(kept)
if removed:
_log_action('LOG_PURGE', '', f'removed {removed} entries older than {days} days')
print(f"Action log purged: {removed} old entries removed")
except Exception as e:
print(f"Action log purge error: {e}")
# File logger appends on every run so history is preserved
logging.basicConfig(
filename=LOG_FILE,
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
log = logging.getLogger('db_manager')
log.info('=== DatabaseManager module loaded ===')
class DatabaseManager: class DatabaseManager:
""" """
@@ -61,7 +115,8 @@ class DatabaseManager:
user=self.user, user=self.user,
password=self.password, password=self.password,
connection_timeout=5, connection_timeout=5,
use_pure=True use_pure=True,
autocommit=True
) )
if test_conn.is_connected(): if test_conn.is_connected():
test_conn.close() test_conn.close()
@@ -70,123 +125,187 @@ class DatabaseManager:
return False, str(e) return False, str(e)
return False, "Connection failed" return False, "Connection failed"
def _new_conn(self):
"""Open and return a fresh connection. Caller must close it."""
return mysql.connector.connect(
host=self.host,
database=self.database,
user=self.user,
password=self.password,
connection_timeout=5,
use_pure=True,
autocommit=True
)
def get_connection(self): def get_connection(self):
"""Get a database connection.""" """Get a reusable connection (kept for test_connection compatibility)."""
try: try:
if self.connection is None or not self.connection.is_connected(): if self.connection is None or not self.connection.is_connected():
self.connection = mysql.connector.connect( log.info(f'Opening persistent connection to {self.host}/{self.database}')
host=self.host, self.connection = self._new_conn()
database=self.database, log.info('Connection opened OK')
user=self.user,
password=self.password,
connection_timeout=5,
use_pure=True
)
return self.connection return self.connection
except Error as e: except Error as e:
log.error(f'Connection error: {e}')
print(f"Database connection error: {e}") print(f"Database connection error: {e}")
return None return None
def init_database(self): def init_database(self):
"""Initialize the database connection and create the table if it doesn't exist.""" """Initialize the database connection and create the table if it doesn't exist."""
# Purge action log entries older than 30 days on every startup
purge_old_action_logs(30)
_log_action('APP_START', '', f'host={self.host}')
try: try:
conn = self.get_connection() conn = self._new_conn()
if conn and conn.is_connected(): cursor = conn.cursor(buffered=True)
cursor = conn.cursor() cursor.execute('''
cursor.execute(''' CREATE TABLE IF NOT EXISTS offsystemsCounting (
CREATE TABLE IF NOT EXISTS offsystemsCounting ( id VARCHAR(20) PRIMARY KEY,
id VARCHAR(20) PRIMARY KEY, mass REAL NOT NULL
mass REAL NOT NULL )
) ''')
''') # Add t_update column if it doesn't exist yet (MySQL-compatible check)
conn.commit() cursor.execute("""
print(f"Connected to MariaDB database: {self.database}") SELECT COUNT(*) FROM information_schema.COLUMNS
print("Table 'offsystemsCounting' ready") WHERE TABLE_SCHEMA = %s
AND TABLE_NAME = 'offsystemsCounting'
AND COLUMN_NAME = 't_update'
""", (self.database,))
col_exists = cursor.fetchone()[0]
cursor.close()
if not col_exists:
log.info("Adding t_update column to offsystemsCounting")
c2 = conn.cursor()
c2.execute("ALTER TABLE offsystemsCounting ADD COLUMN t_update DATETIME DEFAULT NULL")
c2.close()
log.info("t_update column added")
conn.close()
log.info("init_database complete")
print(f"Connected to MariaDB database: {self.database}")
print("Table 'offsystemsCounting' ready")
except Error as e: except Error as e:
log.error(f"Database initialization error: {e}")
print(f"Database initialization error: {e}") print(f"Database initialization error: {e}")
def read_all_data(self) -> List[Tuple[str, float]]: def read_all_data(self) -> List[Tuple[str, float]]:
"""Read all data from the database.""" """Read all data from the database."""
try: try:
conn = self.get_connection() conn = self._new_conn()
if conn and conn.is_connected(): cursor = conn.cursor(buffered=True)
cursor = conn.cursor() cursor.execute("SELECT id, mass FROM offsystemsCounting ORDER BY id")
cursor.execute("SELECT id, mass FROM offsystemsCounting ORDER BY id") rows = cursor.fetchall()
return cursor.fetchall() cursor.close()
conn.close()
return rows
except Error as e: except Error as e:
log.error(f"read_all_data error: {e}")
print(f"Error reading data: {e}") print(f"Error reading data: {e}")
return [] return []
def search_by_id(self, record_id: str) -> Optional[Tuple[str, float]]: def search_by_id(self, record_id: str) -> Optional[Tuple]:
"""Search for a record by ID.""" """Search for a record by ID. Returns (id, mass, t_update) or None."""
log.info(f'search_by_id: looking up id={record_id!r}')
try: try:
conn = self.get_connection() conn = self._new_conn()
if conn and conn.is_connected(): cursor = conn.cursor(buffered=True)
cursor = conn.cursor() cursor.execute("SELECT id, mass, t_update FROM offsystemsCounting WHERE id = %s", (record_id,))
cursor.execute("SELECT id, mass FROM offsystemsCounting WHERE id = %s", (record_id,)) row = cursor.fetchone()
return cursor.fetchone() # buffered=True already fetched the full result; no extra drain needed
cursor.close()
conn.close()
log.info(f'search_by_id: result={row}')
if row:
_log_action('SEARCH_FOUND', record_id, f'mass={row[1]}, t_update={row[2]}')
else:
_log_action('SEARCH_NOT_FOUND', record_id, '')
return row
except Error as e: except Error as e:
log.error(f'search_by_id error: {e}')
_log_action('SEARCH_ERROR', record_id, str(e))
print(f"Error searching data: {e}") print(f"Error searching data: {e}")
return None return None
def add_or_update_record(self, record_id: str, mass: float) -> bool: def add_or_update_record(self, record_id: str, mass: float) -> bool:
"""Add a new record or update existing one if ID already exists.""" """Update mass/t_update for an existing record, or INSERT if it doesn't exist yet."""
log.info(f'add_or_update_record: id={record_id!r} mass={mass}')
try: try:
conn = self.get_connection() conn = self._new_conn()
if conn and conn.is_connected(): cursor = conn.cursor(buffered=True)
cursor = conn.cursor()
# Try UPDATE first
# Check if record exists update_sql = (
existing = self.search_by_id(record_id) "UPDATE offsystemsCounting "
"SET mass = %s, t_update = NOW() "
if existing: "WHERE id = %s"
# Update existing record )
cursor.execute( log.debug(f'Executing SQL: {update_sql} | params=({mass}, {record_id!r})')
"UPDATE offsystemsCounting SET mass = %s WHERE id = %s", cursor.execute(update_sql, (mass, record_id))
(mass, record_id) affected = cursor.rowcount
)
print(f"Updated record: {record_id} = {mass}") if affected == 0:
else: # Record does not exist yet — INSERT it
# Insert new record insert_sql = (
cursor.execute( "INSERT INTO offsystemsCounting (id, mass, t_update) "
"INSERT INTO offsystemsCounting (id, mass) VALUES (%s, %s)", "VALUES (%s, %s, NOW())"
(record_id, mass) )
) log.debug(f'Executing SQL: {insert_sql} | params=({record_id!r}, {mass})')
print(f"Added new record: {record_id} = {mass}") cursor.execute(insert_sql, (record_id, mass))
affected = cursor.rowcount
conn.commit() log.info(f'add_or_update_record: inserted new record, rowcount={affected}')
return True _log_action('INSERT', record_id, f'mass={mass}')
print(f"Inserted new record: {record_id} = {mass}")
else:
log.info(f'add_or_update_record: updated existing record, rowcount={affected}')
_log_action('UPDATE', record_id, f'mass={mass}')
print(f"Updated record: {record_id} = {mass} (rowcount={affected})")
cursor.close()
conn.close()
return True
except Error as e: except Error as e:
log.error(f'add_or_update_record error: {e}')
_log_action('UPDATE_ERROR', record_id, str(e))
print(f"Error adding/updating record: {e}") print(f"Error adding/updating record: {e}")
return False return False
def delete_record(self, record_id: str) -> bool: def delete_record(self, record_id: str) -> bool:
"""Delete a record by ID.""" """Delete a record by ID."""
log.info(f'delete_record: id={record_id!r}')
try: try:
conn = self.get_connection() conn = self._new_conn()
if conn and conn.is_connected(): cursor = conn.cursor(buffered=True)
cursor = conn.cursor() cursor.execute("DELETE FROM offsystemsCounting WHERE id = %s", (record_id,))
cursor.execute("DELETE FROM offsystemsCounting WHERE id = %s", (record_id,)) deleted = cursor.rowcount
# DML produces no result set — do NOT fetchall()
if cursor.rowcount > 0: cursor.close()
conn.commit() conn.close()
print(f"Deleted record: {record_id}") if deleted > 0:
return True log.info(f'delete_record: deleted {deleted} row(s)')
else: _log_action('DELETE', record_id, f'rows_deleted={deleted}')
print(f"No record found with ID: {record_id}") print(f"Deleted record: {record_id}")
return False return True
else:
log.info(f'delete_record: no row found for id={record_id!r}')
_log_action('DELETE_NOT_FOUND', record_id, '')
print(f"No record found with ID: {record_id}")
return False
except Error as e: except Error as e:
log.error(f'delete_record error: {e}')
_log_action('DELETE_ERROR', record_id, str(e))
print(f"Error deleting record: {e}") print(f"Error deleting record: {e}")
return False return False
def get_record_count(self) -> int: def get_record_count(self) -> int:
"""Get the total number of records in the database.""" """Get the total number of records in the database."""
try: try:
conn = self.get_connection() conn = self._new_conn()
if conn and conn.is_connected(): cursor = conn.cursor(buffered=True)
cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM offsystemsCounting")
cursor.execute("SELECT COUNT(*) FROM offsystemsCounting") count = cursor.fetchone()[0]
return cursor.fetchone()[0] # fetchone() on a buffered cursor is fully consumed — no drain needed
cursor.close()
conn.close()
return count
except Error as e: except Error as e:
print(f"Error getting record count: {e}") print(f"Error getting record count: {e}")
return 0 return 0

BIN
dist/DatabaseApp.exe vendored

Binary file not shown.

53
main.py
View File

@@ -19,6 +19,7 @@ class DatabaseApp(App):
super().__init__(**kwargs) super().__init__(**kwargs)
self.db_manager = DatabaseManager() self.db_manager = DatabaseManager()
self.active_numpad_input = None self.active_numpad_input = None
self._pending_record_id = None # resolved (trimmed) ID locked at show_update_frame time
def build(self): def build(self):
# Set window to fullscreen first so Window.height reflects the screen # Set window to fullscreen first so Window.height reflects the screen
@@ -112,12 +113,23 @@ class DatabaseApp(App):
self.id_input.bind(focus=self.on_id_input_focus) self.id_input.bind(focus=self.on_id_input_focus)
search_layout.add_widget(self.id_input) search_layout.add_widget(self.id_input)
search_layout.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=f_normal, bold=True)) search_layout.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=f_normal, bold=True))
# Mass input + last-update label share the 0.75 right side equally
mass_row = BoxLayout(orientation='horizontal', size_hint_x=0.75, spacing=sp)
self.mass_input = TextInput( self.mass_input = TextInput(
multiline=False, size_hint_x=0.75, multiline=False, size_hint_x=0.5,
hint_text='Mass (read-only)', hint_text='Mass (read-only)',
readonly=True, font_size=f_normal, padding=[7, 7] readonly=True, font_size=f_normal, padding=[7, 7]
) )
search_layout.add_widget(self.mass_input) mass_row.add_widget(self.mass_input)
self.last_update_label = Label(
text='Last update: never',
size_hint_x=0.5, font_size=max(9, int(13 * s)),
bold=False, color=(0.7, 0.7, 0.7, 1),
halign='left', valign='middle'
)
self.last_update_label.bind(size=self.last_update_label.setter('text_size'))
mass_row.add_widget(self.last_update_label)
search_layout.add_widget(mass_row)
content_layout.add_widget(search_layout) content_layout.add_widget(search_layout)
# Mode indicator row # Mode indicator row
@@ -271,7 +283,7 @@ class DatabaseApp(App):
self._refocus_active() self._refocus_active()
def set_update_frame_enabled(self, enabled): def set_update_frame_enabled(self, enabled):
self.update_id_input.readonly = not enabled self.update_id_input.readonly = True # ID is always readonly always set from search
self.update_mass_input.readonly = not enabled self.update_mass_input.readonly = not enabled
self.update_confirm_btn.disabled = not enabled self.update_confirm_btn.disabled = not enabled
self.delete_btn.disabled = not enabled self.delete_btn.disabled = not enabled
@@ -313,17 +325,21 @@ class DatabaseApp(App):
self.override_btn.background_color = (0.6, 0.2, 0.6, 1) if is_override else (0.3, 0.3, 0.3, 1) self.override_btn.background_color = (0.6, 0.2, 0.6, 1) if is_override else (0.3, 0.3, 0.3, 1)
def show_update_frame(self, instance): def show_update_frame(self, instance):
# If no value in search, copy from search fields
record_id = self.id_input.text.strip() record_id = self.id_input.text.strip()
mass_text = self.mass_input.text.strip() mass_text = self.mass_input.text.strip()
self.set_update_frame_enabled(True) self.set_update_frame_enabled(True)
# If mass field is empty, just clear update frame
if not record_id: if not record_id:
self.update_id_input.text = '' self.update_id_input.text = ''
self.update_mass_input.text = '' self.update_mass_input.text = ''
self._pending_record_id = None
return return
self.update_id_input.text = record_id # Lock in the resolved (trimmed) DB id now; display original scan for the operator
self._pending_record_id = self._resolve_id(record_id)
self.update_id_input.text = record_id # show original barcode value
self.update_mass_input.text = mass_text self.update_mass_input.text = mass_text
# Direct numpad and keyboard focus to mass field so operator can immediately enter new mass
self.active_numpad_input = self.update_mass_input
Clock.schedule_once(lambda dt: setattr(self.update_mass_input, 'focus', True), 0.05)
def search_record(self, instance): def search_record(self, instance):
record_id = self.id_input.text.strip() record_id = self.id_input.text.strip()
@@ -344,11 +360,17 @@ class DatabaseApp(App):
def _update(dt): def _update(dt):
if record: if record:
self.mass_input.text = str(record[1]) self.mass_input.text = str(record[1])
t_update = record[2] if len(record) > 2 else None
if t_update:
self.last_update_label.text = f'Last update: {t_update.strftime("%d/%m/%Y %H:%M")}'
else:
self.last_update_label.text = 'Last update: never'
self.show_status(f"Found: {record[0]} = {record[1]}") self.show_status(f"Found: {record[0]} = {record[1]}")
self.highlight_record(resolved_id) self.highlight_record(resolved_id)
else: else:
self.show_status(f"ID '{record_id}' not found in database", error=True)
self.mass_input.text = "" self.mass_input.text = ""
self.last_update_label.text = 'Last update: never'
self.show_status(f"ID '{record_id}' not found in database", error=True)
Clock.schedule_once(_update) Clock.schedule_once(_update)
except Exception as e: except Exception as e:
err = str(e) err = str(e)
@@ -357,7 +379,7 @@ class DatabaseApp(App):
def add_update_record(self, instance): def add_update_record(self, instance):
"""Add or update a record from the update frame.""" """Add or update a record from the update frame."""
record_id = self.update_id_input.text.strip() record_id = self._pending_record_id
mass_text = self.update_mass_input.text.strip() mass_text = self.update_mass_input.text.strip()
if not record_id or not mass_text: if not record_id or not mass_text:
self.show_status("Please enter both ID and mass in update frame", error=True) self.show_status("Please enter both ID and mass in update frame", error=True)
@@ -378,9 +400,16 @@ class DatabaseApp(App):
def _update(dt): def _update(dt):
if success: if success:
self.show_status(f"Successfully added/updated: {record_id} = {mass}") self.show_status(f"Successfully added/updated: {record_id} = {mass}")
# Clear all fields and return focus to ID input
self.update_id_input.text = "" self.update_id_input.text = ""
self.update_mass_input.text = "" self.update_mass_input.text = ""
self.id_input.text = ""
self.mass_input.text = ""
self.last_update_label.text = 'Last update: never'
self._pending_record_id = None
self.set_update_frame_enabled(False) self.set_update_frame_enabled(False)
self.active_numpad_input = self.id_input
Clock.schedule_once(lambda dt2: setattr(self.id_input, 'focus', True), 0.05)
self.refresh_data(None) self.refresh_data(None)
else: else:
self.show_status("Failed to add/update record", error=True) self.show_status("Failed to add/update record", error=True)
@@ -392,11 +421,10 @@ class DatabaseApp(App):
def delete_record(self, instance): def delete_record(self, instance):
"""Delete a record using the update frame fields.""" """Delete a record using the update frame fields."""
record_id = self.update_id_input.text.strip() record_id = self._pending_record_id
if not record_id: if not record_id:
self.show_status("Please enter an ID in the update fields to delete", error=True) self.show_status("Please enter an ID in the update fields to delete", error=True)
return return
# Confirm deletion # Confirm deletion
self.show_confirmation_popup( self.show_confirmation_popup(
f"Are you sure you want to delete ID '{record_id}'?", f"Are you sure you want to delete ID '{record_id}'?",
@@ -435,6 +463,11 @@ class DatabaseApp(App):
"""Reset/clear the first ID and mass fields and set focus on ID field.""" """Reset/clear the first ID and mass fields and set focus on ID field."""
self.id_input.text = "" self.id_input.text = ""
self.mass_input.text = "" self.mass_input.text = ""
self.last_update_label.text = 'Last update: never'
self.update_id_input.text = ""
self.update_mass_input.text = ""
self._pending_record_id = None
self.set_update_frame_enabled(False)
self.active_numpad_input = self.id_input self.active_numpad_input = self.id_input
self.id_input.focus = True self.id_input.focus = True
self.show_status("Fields cleared", error=False) self.show_status("Fields cleared", error=False)

View File

@@ -1,20 +0,0 @@
#!/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

10079
sept.csv

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +0,0 @@
#!/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

View File

@@ -1,8 +0,0 @@
-- 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';

View File

@@ -1,101 +0,0 @@
#!/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()