Files
TimeTracker/docker/migrate-logo-upload.py
Dries Peeters d230a41e8a feat: enhance web interface layout and fix logo import circular dependency
- Improve web interface layout for better user-friendliness and mobile responsiveness
  * Update CSS variables for consistent spacing and component sizing
  * Enhance card layouts with improved padding, borders, and shadows
  * Optimize button and form element dimensions for better touch targets
  * Add hover effects and animations for improved user interaction
  * Implement responsive grid system with mobile-first approach

- Refactor mobile JavaScript to prevent duplicate initialization
  * Consolidate mobile enhancements into dedicated utility classes
  * Add initialization guards to prevent double loading
  * Implement MobileUtils and MobileNavigation classes
  * Remove duplicate event listeners and mobile enhancements

- Fix circular import issue in logo handling
  * Replace problematic 'from app import app' with Flask's current_app
  * Add error handling for cases where current_app is unavailable
  * Improve logo path resolution with fallback mechanisms
  * Fix settings model to use proper Flask context

- Clean up template code and remove duplication
  * Remove duplicate mobile enhancements from base template
  * Clean up dashboard template JavaScript
  * Centralize all mobile functionality in mobile.js
  * Add proper error handling and debugging

- Update CSS variables and spacing system
  * Introduce --section-spacing and --card-spacing variables
  * Add mobile-specific spacing variables
  * Improve border-radius and shadow consistency
  * Enhance typography and visual hierarchy

This commit resolves the double loading issue and logo import errors while
significantly improving the overall user experience and mobile responsiveness
of the web interface.
2025-08-30 10:09:06 +02:00

204 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Migration script to update logo system from file paths to file uploads
"""
import os
import sys
from sqlalchemy import create_engine, text, MetaData, Table, Column, String
from sqlalchemy.orm import sessionmaker
def migrate_logo_system():
"""Migrate from company_logo_path to company_logo_filename"""
# Database connection
database_url = os.getenv('DATABASE_URL', 'sqlite:///timetracker.db')
try:
engine = create_engine(database_url)
metadata = MetaData()
# Check if settings table exists
inspector = engine.inspect(engine)
if 'settings' not in inspector.get_table_names():
print("❌ Settings table does not exist. Please run the company branding migration first.")
return False
# Get current columns
columns = [col['name'] for col in inspector.get_columns('settings')]
print(f"Current columns: {columns}")
# Check if migration is needed
if 'company_logo_filename' in columns and 'company_logo_path' not in columns:
print("✅ Logo upload system already migrated")
return True
if 'company_logo_path' not in columns:
print("❌ company_logo_path column not found. Please run the company branding migration first.")
return False
# Create the uploads directory structure
create_uploads_directories()
# Rename column from company_logo_path to company_logo_filename
with engine.connect() as conn:
# For SQLite
if 'sqlite' in database_url:
# SQLite doesn't support ALTER COLUMN, so we need to recreate the table
print("🔄 SQLite detected - recreating table structure...")
# Get current data
result = conn.execute(text("SELECT * FROM settings"))
rows = result.fetchall()
column_names = result.keys()
if rows:
# Create new table with updated schema
conn.execute(text("""
CREATE TABLE settings_new (
id INTEGER PRIMARY KEY,
timezone VARCHAR(50) NOT NULL DEFAULT 'Europe/Rome',
currency VARCHAR(3) NOT NULL DEFAULT 'EUR',
rounding_minutes INTEGER NOT NULL DEFAULT 1,
single_active_timer BOOLEAN NOT NULL DEFAULT 1,
allow_self_register BOOLEAN NOT NULL DEFAULT 1,
idle_timeout_minutes INTEGER NOT NULL DEFAULT 30,
backup_retention_days INTEGER NOT NULL DEFAULT 30,
backup_time VARCHAR(5) NOT NULL DEFAULT '02:00',
export_delimiter VARCHAR(1) NOT NULL DEFAULT ',',
company_name VARCHAR(200) NOT NULL DEFAULT 'Your Company Name',
company_address TEXT NOT NULL DEFAULT 'Your Company Address',
company_email VARCHAR(200) NOT NULL DEFAULT 'info@yourcompany.com',
company_phone VARCHAR(50) NOT NULL DEFAULT '+1 (555) 123-4567',
company_website VARCHAR(200) NOT NULL DEFAULT 'www.yourcompany.com',
company_logo_filename VARCHAR(255) DEFAULT '',
company_tax_id VARCHAR(100) DEFAULT '',
company_bank_info TEXT DEFAULT '',
invoice_prefix VARCHAR(10) NOT NULL DEFAULT 'INV',
invoice_start_number INTEGER NOT NULL DEFAULT 1000,
invoice_terms TEXT NOT NULL DEFAULT 'Payment is due within 30 days of invoice date.',
invoice_notes TEXT NOT NULL DEFAULT 'Thank you for your business!',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"""))
# Copy data, converting company_logo_path to company_logo_filename
for row in rows:
row_dict = dict(zip(column_names, row))
# Convert logo path to filename if it exists
logo_filename = ''
if row_dict.get('company_logo_path'):
old_path = row_dict['company_logo_path']
if old_path and os.path.exists(old_path):
# Extract filename from path
logo_filename = os.path.basename(old_path)
print(f"📁 Converting logo path: {old_path} -> {logo_filename}")
else:
print(f"⚠️ Logo path not found: {old_path}")
# Insert into new table
conn.execute(text("""
INSERT INTO settings_new (
id, timezone, currency, rounding_minutes, single_active_timer,
allow_self_register, idle_timeout_minutes, backup_retention_days,
backup_time, export_delimiter, company_name, company_address,
company_email, company_phone, company_website, company_logo_filename,
company_tax_id, company_bank_info, invoice_prefix, invoice_start_number,
invoice_terms, invoice_notes, created_at, updated_at
) VALUES (
:id, :timezone, :currency, :rounding_minutes, :single_active_timer,
:allow_self_register, :idle_timeout_minutes, :backup_retention_days,
:backup_time, :export_delimiter, :company_name, :company_address,
:company_email, :company_phone, :company_website, :company_logo_filename,
:company_tax_id, :company_bank_info, :invoice_prefix, :invoice_start_number,
:invoice_terms, :invoice_notes, :created_at, :updated_at
)
"""), {
'id': row_dict.get('id'),
'timezone': row_dict.get('timezone', 'Europe/Rome'),
'currency': row_dict.get('currency', 'EUR'),
'rounding_minutes': row_dict.get('rounding_minutes', 1),
'single_active_timer': row_dict.get('single_active_timer', True),
'allow_self_register': row_dict.get('allow_self_register', True),
'idle_timeout_minutes': row_dict.get('idle_timeout_minutes', 30),
'backup_retention_days': row_dict.get('backup_retention_days', 30),
'backup_time': row_dict.get('backup_time', '02:00'),
'export_delimiter': row_dict.get('export_delimiter', ','),
'company_name': row_dict.get('company_name', 'Your Company Name'),
'company_address': row_dict.get('company_address', 'Your Company Address'),
'company_email': row_dict.get('company_email', 'info@yourcompany.com'),
'company_phone': row_dict.get('company_phone', '+1 (555) 123-4567'),
'company_website': row_dict.get('company_website', 'www.yourcompany.com'),
'company_logo_filename': logo_filename,
'company_tax_id': row_dict.get('company_tax_id', ''),
'company_bank_info': row_dict.get('company_bank_info', ''),
'invoice_prefix': row_dict.get('invoice_prefix', 'INV'),
'invoice_start_number': row_dict.get('invoice_start_number', 1000),
'invoice_terms': row_dict.get('invoice_terms', 'Payment is due within 30 days of invoice date.'),
'invoice_notes': row_dict.get('invoice_notes', 'Thank you for your business!'),
'created_at': row_dict.get('created_at'),
'updated_at': row_dict.get('updated_at')
})
# Drop old table and rename new one
conn.execute(text("DROP TABLE settings"))
conn.execute(text("ALTER TABLE settings_new RENAME TO settings"))
else:
# No data to migrate, just update schema
conn.execute(text("ALTER TABLE settings RENAME COLUMN company_logo_path TO company_logo_filename"))
else:
# For other databases (PostgreSQL, MySQL)
print("🔄 Updating column name...")
conn.execute(text("ALTER TABLE settings RENAME COLUMN company_logo_path TO company_logo_filename"))
conn.commit()
print("✅ Logo upload system migration completed successfully")
return True
except Exception as e:
print(f"❌ Migration failed: {e}")
return False
def create_uploads_directories():
"""Create the uploads directory structure"""
try:
# Get the app root directory
app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
uploads_dir = os.path.join(app_root, 'app', 'static', 'uploads', 'logos')
# Create directories
os.makedirs(uploads_dir, exist_ok=True)
# Create .gitkeep file to preserve directory in git
gitkeep_file = os.path.join(uploads_dir, '.gitkeep')
if not os.path.exists(gitkeep_file):
with open(gitkeep_file, 'w') as f:
f.write('# This file ensures the uploads directory is preserved in git\n')
print(f"✅ Created uploads directory: {uploads_dir}")
return True
except Exception as e:
print(f"❌ Failed to create uploads directories: {e}")
return False
if __name__ == '__main__':
print("🚀 Starting logo upload system migration...")
success = migrate_logo_system()
if success:
print("🎉 Migration completed successfully!")
print("\nNext steps:")
print("1. Restart your application")
print("2. Go to Admin → Settings")
print("3. Upload your company logo using the new file upload interface")
print("4. The logo will automatically appear throughout the application")
else:
print("💥 Migration failed. Please check the error messages above.")
sys.exit(1)