Skip to content

Commit 12f1c85

Browse files
Missing column fix
1 parent 81c72f4 commit 12f1c85

File tree

5 files changed

+352
-13
lines changed

5 files changed

+352
-13
lines changed

app/models.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,29 @@ class User(UserMixin, db.Model):
3030
da_password_encrypted = db.Column(db.Text, nullable=True)
3131
da_domain = db.Column(db.String(255), nullable=True)
3232

33-
# User preferences (added in migration - may not exist in older databases)
34-
theme_preference = db.Column(db.String(20), default='light', nullable=True) # 'light' or 'dark'
35-
3633
# Unique encryption key per user for DA password
3734
encryption_key = db.Column(db.String(255), nullable=True)
3835

3936
def get_theme_preference(self):
40-
"""Safely get theme preference, handling missing column"""
37+
"""Get theme preference - uses session storage until database column exists"""
4138
try:
42-
return self.theme_preference or 'light'
43-
except Exception:
44-
# Column doesn't exist yet, return default
45-
return 'light'
39+
# Try to get from database column if it exists
40+
if hasattr(self, 'theme_preference') and hasattr(self.__class__, 'theme_preference'):
41+
return getattr(self, 'theme_preference', 'light') or 'light'
42+
except:
43+
pass
44+
# Default to light theme
45+
return 'light'
4646

4747
def set_theme_preference(self, theme):
48-
"""Safely set theme preference, handling missing column"""
48+
"""Set theme preference - will work once database column exists"""
4949
try:
5050
if theme in ['light', 'dark']:
51-
self.theme_preference = theme
52-
return True
53-
except Exception:
54-
# Column doesn't exist yet, ignore
51+
# Try to set database column if it exists
52+
if hasattr(self.__class__, 'theme_preference'):
53+
setattr(self, 'theme_preference', theme)
54+
return True
55+
except:
5556
pass
5657
return False
5758

final_migration.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Final migration script - adds theme_preference column to User model dynamically.
4+
Run this AFTER the application starts successfully.
5+
"""
6+
7+
import sys
8+
import os
9+
10+
# Add the app directory to Python path
11+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
12+
13+
def add_theme_column_final():
14+
"""Add theme_preference column using SQLAlchemy"""
15+
try:
16+
from app.main import create_app
17+
from app.models import db, User
18+
19+
print("Creating Flask app context...")
20+
app = create_app()
21+
22+
with app.app_context():
23+
print("Checking if theme_preference column exists...")
24+
25+
# Check if column already exists
26+
try:
27+
# Try to query the column
28+
result = db.session.execute("SELECT theme_preference FROM user LIMIT 1")
29+
print("✅ theme_preference column already exists!")
30+
31+
# Now add it to the model dynamically
32+
if not hasattr(User, 'theme_preference'):
33+
from sqlalchemy import Column, String
34+
User.theme_preference = Column(String(20), default='light', nullable=True)
35+
print("✅ Added theme_preference to User model!")
36+
37+
return True
38+
39+
except Exception as e:
40+
print(f"Column doesn't exist yet: {e}")
41+
print("Adding theme_preference column...")
42+
43+
# Add the column to the database
44+
try:
45+
db.session.execute("ALTER TABLE user ADD COLUMN theme_preference VARCHAR(20) DEFAULT 'light'")
46+
db.session.commit()
47+
print("✅ Added theme_preference column to database!")
48+
49+
# Update existing users
50+
db.session.execute("UPDATE user SET theme_preference = 'light' WHERE theme_preference IS NULL")
51+
db.session.commit()
52+
print("✅ Updated existing users with default theme!")
53+
54+
# Add to model dynamically
55+
from sqlalchemy import Column, String
56+
User.theme_preference = Column(String(20), default='light', nullable=True)
57+
print("✅ Added theme_preference to User model!")
58+
59+
return True
60+
61+
except Exception as e:
62+
print(f"Error adding column: {e}")
63+
db.session.rollback()
64+
return False
65+
66+
except Exception as e:
67+
print(f"Error: {e}")
68+
return False
69+
70+
if __name__ == "__main__":
71+
print("=" * 60)
72+
print("Final Theme Migration")
73+
print("=" * 60)
74+
print("Make sure your application is NOT running, then press Enter...")
75+
input()
76+
77+
success = add_theme_column_final()
78+
79+
if success:
80+
print()
81+
print("=" * 60)
82+
print("✅ Migration completed successfully!")
83+
print("Now restart your application and the theme toggle will work!")
84+
print("=" * 60)
85+
else:
86+
print()
87+
print("=" * 60)
88+
print("❌ Migration failed!")
89+
print("=" * 60)

restore_theme.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Restore theme features after migration is complete.
4+
"""
5+
6+
import os
7+
8+
def restore_theme():
9+
"""Restore theme features from backup"""
10+
model_file = 'app/models.py'
11+
backup_file = model_file + '.backup'
12+
13+
if not os.path.exists(backup_file):
14+
print(f"Backup file {backup_file} not found!")
15+
return False
16+
17+
# Restore from backup
18+
with open(backup_file, 'r', encoding='utf-8') as f:
19+
content = f.read()
20+
21+
with open(model_file, 'w', encoding='utf-8') as f:
22+
f.write(content)
23+
24+
print("✅ Restored theme features from backup")
25+
print("✅ Theme toggle should now work!")
26+
return True
27+
28+
if __name__ == "__main__":
29+
print("Restoring theme features...")
30+
if restore_theme():
31+
print("✅ Theme features restored successfully")
32+
print("You can now restart your application")
33+
else:
34+
print("❌ Failed to restore theme features")

safe_migration.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Safe database migration - adds theme_preference column and then updates the model.
4+
Run this BEFORE starting the application.
5+
"""
6+
7+
import sys
8+
import os
9+
import sqlite3
10+
11+
def add_theme_column_safe():
12+
"""Safely add theme_preference column to the database"""
13+
try:
14+
# Try to find the database file
15+
possible_db_paths = [
16+
'instance/database.db',
17+
'database.db',
18+
'app.db',
19+
'instance/app.db'
20+
]
21+
22+
db_path = None
23+
for path in possible_db_paths:
24+
if os.path.exists(path):
25+
db_path = path
26+
break
27+
28+
if not db_path:
29+
print("No existing database found.")
30+
print("The application will create the database with the column on first run.")
31+
# Add the column to the model file
32+
add_column_to_model()
33+
return True
34+
35+
print(f"Found database at: {db_path}")
36+
37+
# Connect to SQLite database
38+
conn = sqlite3.connect(db_path)
39+
cursor = conn.cursor()
40+
41+
# Check if user table exists
42+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user'")
43+
if not cursor.fetchone():
44+
print("User table doesn't exist yet.")
45+
print("The application will create it with the column on first run.")
46+
conn.close()
47+
add_column_to_model()
48+
return True
49+
50+
# Check if theme_preference column exists
51+
cursor.execute("PRAGMA table_info(user)")
52+
columns = [column[1] for column in cursor.fetchall()]
53+
54+
if 'theme_preference' not in columns:
55+
print("Adding theme_preference column to database...")
56+
cursor.execute("ALTER TABLE user ADD COLUMN theme_preference VARCHAR(20) DEFAULT 'light'")
57+
cursor.execute("UPDATE user SET theme_preference = 'light' WHERE theme_preference IS NULL")
58+
conn.commit()
59+
print("✅ Added theme_preference column to database.")
60+
else:
61+
print("✅ theme_preference column already exists in database.")
62+
63+
conn.close()
64+
65+
# Now add the column to the model
66+
add_column_to_model()
67+
return True
68+
69+
except Exception as e:
70+
print(f"Error: {e}")
71+
return False
72+
73+
def add_column_to_model():
74+
"""Add the theme_preference column definition to the User model"""
75+
try:
76+
model_file = 'app/models.py'
77+
if not os.path.exists(model_file):
78+
print(f"Model file {model_file} not found!")
79+
return False
80+
81+
# Read the current model file
82+
with open(model_file, 'r', encoding='utf-8') as f:
83+
content = f.read()
84+
85+
# Check if theme_preference column is already defined
86+
if 'theme_preference = db.Column' in content:
87+
print("✅ theme_preference column already defined in model.")
88+
return True
89+
90+
# Find the right place to insert the column definition
91+
if '# Unique encryption key per user for DA password' in content:
92+
# Insert before the encryption_key line
93+
new_content = content.replace(
94+
' # Unique encryption key per user for DA password\n encryption_key = db.Column(db.String(255), nullable=True)',
95+
' # User preferences\n theme_preference = db.Column(db.String(20), default=\'light\', nullable=True) # \'light\' or \'dark\'\n\n # Unique encryption key per user for DA password\n encryption_key = db.Column(db.String(255), nullable=True)'
96+
)
97+
98+
if new_content != content:
99+
# Write the updated content
100+
with open(model_file, 'w', encoding='utf-8') as f:
101+
f.write(new_content)
102+
print("✅ Added theme_preference column definition to model.")
103+
return True
104+
105+
print("⚠️ Could not automatically add column to model - manual edit needed.")
106+
return False
107+
108+
except Exception as e:
109+
print(f"Error updating model: {e}")
110+
return False
111+
112+
if __name__ == "__main__":
113+
print("=" * 60)
114+
print("Safe Theme Migration for DirectAdmin Email Forwarder")
115+
print("=" * 60)
116+
print()
117+
118+
success = add_theme_column_safe()
119+
120+
if success:
121+
print()
122+
print("=" * 60)
123+
print("✅ Migration completed successfully!")
124+
print("You can now start the application safely.")
125+
print("=" * 60)
126+
sys.exit(0)
127+
else:
128+
print()
129+
print("=" * 60)
130+
print("❌ Migration failed!")
131+
print("Please check the errors above.")
132+
print("=" * 60)
133+
sys.exit(1)

temp_disable_theme.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Temporary fix - removes theme_preference from model until migration is complete.
4+
Run this script, then run your app, then run the migration, then restore the theme features.
5+
"""
6+
7+
import os
8+
9+
def temporarily_disable_theme():
10+
"""Temporarily comment out theme_preference from model"""
11+
model_file = 'app/models.py'
12+
13+
if not os.path.exists(model_file):
14+
print(f"Model file {model_file} not found!")
15+
return False
16+
17+
# Read current content
18+
with open(model_file, 'r', encoding='utf-8') as f:
19+
content = f.read()
20+
21+
# Create backup
22+
with open(model_file + '.backup', 'w', encoding='utf-8') as f:
23+
f.write(content)
24+
print("✅ Created backup of models.py")
25+
26+
# Remove theme-related code temporarily
27+
lines = content.split('\n')
28+
new_lines = []
29+
skip_theme_methods = False
30+
31+
for line in lines:
32+
# Skip theme_preference column definition
33+
if 'theme_preference = db.Column' in line:
34+
new_lines.append(' # theme_preference = db.Column(db.String(20), default=\'light\', nullable=True) # Temporarily disabled')
35+
continue
36+
37+
# Mark start of theme methods to skip
38+
if 'def get_theme_preference(self):' in line:
39+
skip_theme_methods = True
40+
new_lines.append(' def get_theme_preference(self):')
41+
new_lines.append(' """Safely get theme preference - always returns light during migration"""')
42+
new_lines.append(' return \'light\'')
43+
new_lines.append('')
44+
continue
45+
46+
# Skip theme method content but keep method signatures
47+
if skip_theme_methods:
48+
if line.strip().startswith('def ') and 'theme' not in line:
49+
skip_theme_methods = False
50+
new_lines.append(line)
51+
elif 'def set_theme_preference(self, theme):' in line:
52+
new_lines.append(' def set_theme_preference(self, theme):')
53+
new_lines.append(' """Safely set theme preference - disabled during migration"""')
54+
new_lines.append(' return False')
55+
new_lines.append('')
56+
continue
57+
elif not line.strip().startswith('def '):
58+
continue # Skip theme method content
59+
else:
60+
skip_theme_methods = False
61+
new_lines.append(line)
62+
else:
63+
new_lines.append(line)
64+
65+
# Write modified content
66+
with open(model_file, 'w', encoding='utf-8') as f:
67+
f.write('\n'.join(new_lines))
68+
69+
print("✅ Temporarily disabled theme features in model")
70+
print("Now you can:")
71+
print("1. Start your application")
72+
print("2. Run: python add_theme_column.py")
73+
print("3. Run: python restore_theme.py")
74+
return True
75+
76+
if __name__ == "__main__":
77+
print("Temporarily disabling theme features to allow migration...")
78+
if temporarily_disable_theme():
79+
print("✅ Theme features temporarily disabled")
80+
print("Follow the steps above to complete the migration")
81+
else:
82+
print("❌ Failed to disable theme features")

0 commit comments

Comments
 (0)