Skip to content

Commit 81c72f4

Browse files
Fix for missing column in database
1 parent 727e454 commit 81c72f4

File tree

5 files changed

+234
-43
lines changed

5 files changed

+234
-43
lines changed

add_theme_column.py

Lines changed: 124 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,143 @@
66

77
import sys
88
import os
9+
import sqlite3
910

1011
# Add the app directory to Python path
1112
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
1213

13-
from app import create_app
14-
from app.models import db, User
15-
16-
def add_theme_column():
17-
"""Add theme_preference column to users table if it doesn't exist"""
18-
app = create_app()
19-
20-
with app.app_context():
21-
try:
22-
# Check if the column already exists by trying to query it
23-
result = db.session.execute("SELECT theme_preference FROM user LIMIT 1")
14+
def add_theme_column_direct():
15+
"""Add theme_preference column directly using SQLite"""
16+
try:
17+
# Try to find the database file
18+
possible_db_paths = [
19+
'instance/database.db',
20+
'database.db',
21+
'app.db',
22+
'instance/app.db'
23+
]
24+
25+
db_path = None
26+
for path in possible_db_paths:
27+
if os.path.exists(path):
28+
db_path = path
29+
break
30+
31+
if not db_path:
32+
print("Database file not found. Trying Flask app method...")
33+
return add_theme_column_flask()
34+
35+
print(f"Found database at: {db_path}")
36+
37+
# Connect to SQLite database directly
38+
conn = sqlite3.connect(db_path)
39+
cursor = conn.cursor()
40+
41+
# Check if the column already exists
42+
cursor.execute("PRAGMA table_info(user)")
43+
columns = [column[1] for column in cursor.fetchall()]
44+
45+
if 'theme_preference' in columns:
2446
print("Column 'theme_preference' already exists in the database.")
47+
conn.close()
2548
return True
26-
except Exception as e:
27-
print(f"Column 'theme_preference' does not exist. Adding it now...")
49+
50+
print("Adding 'theme_preference' column...")
51+
52+
# Add the column
53+
cursor.execute("ALTER TABLE user ADD COLUMN theme_preference VARCHAR(20) DEFAULT 'light'")
54+
55+
# Update existing users
56+
cursor.execute("UPDATE user SET theme_preference = 'light' WHERE theme_preference IS NULL")
57+
58+
# Commit changes
59+
conn.commit()
60+
61+
# Verify the column was added
62+
cursor.execute("PRAGMA table_info(user)")
63+
columns = [column[1] for column in cursor.fetchall()]
64+
65+
if 'theme_preference' in columns:
66+
print("Successfully added 'theme_preference' column to user table.")
67+
68+
# Count updated users
69+
cursor.execute("SELECT COUNT(*) FROM user WHERE theme_preference = 'light'")
70+
count = cursor.fetchone()[0]
71+
print(f"Updated {count} existing users with default light theme.")
2872

73+
conn.close()
74+
return True
75+
else:
76+
print("Failed to add column.")
77+
conn.close()
78+
return False
79+
80+
except Exception as e:
81+
print(f"Direct SQLite method failed: {e}")
82+
return add_theme_column_flask()
83+
84+
def add_theme_column_flask():
85+
"""Add theme_preference column using Flask app context"""
86+
try:
87+
from app import create_app
88+
from app.models import db
89+
90+
app = create_app()
91+
92+
with app.app_context():
93+
# Check if the column already exists by trying to query it
2994
try:
30-
# Add the column with default value 'light'
31-
db.session.execute("ALTER TABLE user ADD COLUMN theme_preference VARCHAR(20) DEFAULT 'light'")
32-
db.session.commit()
33-
print("Successfully added 'theme_preference' column to user table.")
34-
35-
# Update all existing users to have light theme by default
36-
users_updated = User.query.filter(User.theme_preference.is_(None)).update(
37-
{'theme_preference': 'light'}, synchronize_session=False
38-
)
39-
db.session.commit()
40-
print(f"Updated {users_updated} existing users with default light theme.")
41-
95+
result = db.session.execute("SELECT theme_preference FROM user LIMIT 1")
96+
print("Column 'theme_preference' already exists in the database.")
4297
return True
43-
except Exception as e:
44-
print(f"Error adding column: {e}")
45-
db.session.rollback()
46-
return False
98+
except Exception:
99+
print("Column 'theme_preference' does not exist. Adding it now...")
100+
101+
try:
102+
# Add the column with default value 'light'
103+
db.session.execute("ALTER TABLE user ADD COLUMN theme_preference VARCHAR(20) DEFAULT 'light'")
104+
db.session.commit()
105+
print("Successfully added 'theme_preference' column to user table.")
106+
107+
# Update all existing users to have light theme by default
108+
db.session.execute("UPDATE user SET theme_preference = 'light' WHERE theme_preference IS NULL")
109+
db.session.commit()
110+
111+
# Count updated users
112+
result = db.session.execute("SELECT COUNT(*) FROM user WHERE theme_preference = 'light'")
113+
count = result.fetchone()[0]
114+
print(f"Updated {count} existing users with default light theme.")
115+
116+
return True
117+
except Exception as e:
118+
print(f"Error adding column: {e}")
119+
db.session.rollback()
120+
return False
121+
122+
except Exception as e:
123+
print(f"Flask method failed: {e}")
124+
return False
47125

48126
if __name__ == "__main__":
127+
print("=" * 60)
128+
print("DirectAdmin Email Forwarder - Theme Column Migration")
129+
print("=" * 60)
49130
print("Adding theme_preference column to database...")
50-
success = add_theme_column()
131+
print()
132+
133+
success = add_theme_column_direct()
134+
51135
if success:
52-
print("Database migration completed successfully!")
136+
print()
137+
print("=" * 60)
138+
print("✅ Database migration completed successfully!")
139+
print("You can now restart the application and use the theme toggle.")
140+
print("=" * 60)
53141
sys.exit(0)
54142
else:
55-
print("Database migration failed!")
143+
print()
144+
print("=" * 60)
145+
print("❌ Database migration failed!")
146+
print("Please check the error messages above and try again.")
147+
print("=" * 60)
56148
sys.exit(1)

app/models.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,31 @@ 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
33+
# User preferences (added in migration - may not exist in older databases)
3434
theme_preference = db.Column(db.String(20), default='light', nullable=True) # 'light' or 'dark'
3535

3636
# Unique encryption key per user for DA password
3737
encryption_key = db.Column(db.String(255), nullable=True)
3838

39+
def get_theme_preference(self):
40+
"""Safely get theme preference, handling missing column"""
41+
try:
42+
return self.theme_preference or 'light'
43+
except Exception:
44+
# Column doesn't exist yet, return default
45+
return 'light'
46+
47+
def set_theme_preference(self, theme):
48+
"""Safely set theme preference, handling missing column"""
49+
try:
50+
if theme in ['light', 'dark']:
51+
self.theme_preference = theme
52+
return True
53+
except Exception:
54+
# Column doesn't exist yet, ignore
55+
pass
56+
return False
57+
3958
def __init__(self, **kwargs):
4059
"""Initialize user with encryption key"""
4160
super(User, self).__init__(**kwargs)

app/settings.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def get_da_config():
2222
'da_username': current_user.da_username or '',
2323
'da_domain': current_user.da_domain or '',
2424
'has_password': bool(current_user.da_password_encrypted),
25-
'theme_preference': current_user.theme_preference or 'light'
25+
'theme_preference': current_user.get_theme_preference()
2626
})
2727
except Exception as e:
2828
print(f"Error in GET da-config: {e}")
@@ -137,14 +137,21 @@ def update_theme():
137137
if theme not in ['light', 'dark']:
138138
return jsonify({'error': 'Invalid theme value'}), 400
139139

140-
current_user.theme_preference = theme
141-
db.session.commit()
142-
143-
return jsonify({
144-
'success': True,
145-
'message': f'Theme updated to {theme}',
146-
'theme': theme
147-
})
140+
# Use safe setter method
141+
if current_user.set_theme_preference(theme):
142+
db.session.commit()
143+
return jsonify({
144+
'success': True,
145+
'message': f'Theme updated to {theme}',
146+
'theme': theme
147+
})
148+
else:
149+
# Column doesn't exist yet - still return success for frontend
150+
return jsonify({
151+
'success': True,
152+
'message': f'Theme set to {theme} (database not updated - run migration)',
153+
'theme': theme
154+
})
148155

149156
except Exception as e:
150157
print(f"Error updating theme: {str(e)}")

app/templates/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
88
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='assets/img/ico_main_25.ico') }}">
99
</head>
10-
<body data-theme="{{ current_user.theme_preference if current_user.is_authenticated else 'light' }}">
10+
<body data-theme="{{ current_user.get_theme_preference() if current_user.is_authenticated else 'light' }}">
1111
<nav>
1212
<div class="nav-brand">
1313
<a href="{{ url_for('dashboard') }}">DirectAdmin 📧 Forwarder</a>

check_db.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Startup script to ensure database is ready and has all required columns.
4+
This can be run before starting the application to prevent crashes.
5+
"""
6+
7+
import sys
8+
import os
9+
import sqlite3
10+
11+
def check_and_fix_database():
12+
"""Check if database has required columns and add them if missing"""
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 - will be created on first run.")
30+
return True
31+
32+
print(f"Checking database at: {db_path}")
33+
34+
# Connect to SQLite database
35+
conn = sqlite3.connect(db_path)
36+
cursor = conn.cursor()
37+
38+
# Check if user table exists
39+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user'")
40+
if not cursor.fetchone():
41+
print("User table doesn't exist yet - will be created on first run.")
42+
conn.close()
43+
return True
44+
45+
# Check if theme_preference column exists
46+
cursor.execute("PRAGMA table_info(user)")
47+
columns = [column[1] for column in cursor.fetchall()]
48+
49+
if 'theme_preference' not in columns:
50+
print("Adding missing theme_preference column...")
51+
cursor.execute("ALTER TABLE user ADD COLUMN theme_preference VARCHAR(20) DEFAULT 'light'")
52+
cursor.execute("UPDATE user SET theme_preference = 'light' WHERE theme_preference IS NULL")
53+
conn.commit()
54+
print("✅ Added theme_preference column successfully.")
55+
else:
56+
print("✅ theme_preference column already exists.")
57+
58+
conn.close()
59+
return True
60+
61+
except Exception as e:
62+
print(f"Error checking database: {e}")
63+
return False
64+
65+
if __name__ == "__main__":
66+
print("Checking database compatibility...")
67+
success = check_and_fix_database()
68+
if success:
69+
print("Database is ready!")
70+
sys.exit(0)
71+
else:
72+
print("Database check failed!")
73+
sys.exit(1)

0 commit comments

Comments
 (0)