Add deadline and refactor squad pages
This commit is contained in:
parent
c199979eb9
commit
0cf8cf7fc0
209
motm_app/DEBUG_URL_SUFFIX_ISSUE.md
Normal file
209
motm_app/DEBUG_URL_SUFFIX_ISSUE.md
Normal file
@ -0,0 +1,209 @@
|
||||
# Debugging URL Suffix Not Saving Issue
|
||||
|
||||
## Problem
|
||||
The app is not saving the URL suffix to the `admin_settings` table.
|
||||
|
||||
## Debug Logging Added
|
||||
|
||||
I've added comprehensive debug logging to help diagnose the issue. The logs will show:
|
||||
|
||||
1. **When the form is submitted:**
|
||||
- Which button was clicked (Save vs Activate)
|
||||
- What form data was received
|
||||
|
||||
2. **When saving settings:**
|
||||
- The generated URL suffix
|
||||
- Whether the UPDATE query executed
|
||||
- The result of the UPDATE query
|
||||
- Verification of what's actually in the database
|
||||
|
||||
3. **When activating voting:**
|
||||
- What URL suffix is retrieved from the database
|
||||
- Whether a new suffix needs to be generated
|
||||
- The final suffix being used
|
||||
|
||||
## How to Debug
|
||||
|
||||
### Step 1: Deploy the Updated Code
|
||||
|
||||
Deploy the updated code with debug logging to production:
|
||||
|
||||
```bash
|
||||
# Build new image
|
||||
docker build -t your-registry/motm-app:latest .
|
||||
|
||||
# Push to registry
|
||||
docker push your-registry/motm-app:latest
|
||||
|
||||
# Deploy to Kubernetes
|
||||
helm upgrade motm-app ./helm-chart/motm-app --namespace motm-app
|
||||
```
|
||||
|
||||
### Step 2: Test the MOTM Admin Page
|
||||
|
||||
1. Go to https://motm.ervine.cloud/admin/motm
|
||||
2. Fill in the form:
|
||||
- Match date
|
||||
- Opposition team
|
||||
- Current MOTM (optional)
|
||||
- Current DotD (optional)
|
||||
- Voting deadline (optional)
|
||||
3. Click **"Save Settings"** button
|
||||
4. Check the logs
|
||||
|
||||
### Step 3: Check the Logs
|
||||
|
||||
```bash
|
||||
# Get the pod name
|
||||
kubectl get pods -n motm-app -l app.kubernetes.io/name=motm-app
|
||||
|
||||
# Watch the logs in real-time
|
||||
kubectl logs -n motm-app -f <POD_NAME>
|
||||
```
|
||||
|
||||
Or check recent logs:
|
||||
|
||||
```bash
|
||||
kubectl logs -n motm-app -l app.kubernetes.io/name=motm-app --tail=100 | grep DEBUG
|
||||
```
|
||||
|
||||
### Step 4: Analyze the Debug Output
|
||||
|
||||
Look for these debug messages:
|
||||
|
||||
#### When clicking "Save Settings":
|
||||
```
|
||||
DEBUG: POST request received
|
||||
DEBUG: form.saveButton.data = True
|
||||
DEBUG: form.activateButton.data = False
|
||||
DEBUG: Save button clicked
|
||||
DEBUG: Form data - team: [team name], date: [date]
|
||||
DEBUG: Generated URL suffix: [suffix]
|
||||
DEBUG: About to execute UPDATE query
|
||||
DEBUG: UPDATE query result: True
|
||||
DEBUG: Verification - URL suffix in DB: [suffix]
|
||||
```
|
||||
|
||||
#### When clicking "Activate MotM Vote":
|
||||
```
|
||||
DEBUG: POST request received
|
||||
DEBUG: form.saveButton.data = False
|
||||
DEBUG: form.activateButton.data = True
|
||||
DEBUG: Activate button clicked
|
||||
DEBUG: Form data - team: [team name], date: [date]
|
||||
DEBUG: Getting URL suffix from database
|
||||
DEBUG: Query result: [result]
|
||||
DEBUG: Using existing suffix: [suffix]
|
||||
DEBUG: Final suffix: [suffix]
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue 1: Button Click Not Detected
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
DEBUG: POST request received
|
||||
DEBUG: form.saveButton.data = False
|
||||
DEBUG: form.activateButton.data = False
|
||||
DEBUG: Something went wrong - check with Smithers
|
||||
```
|
||||
|
||||
**Cause:** The form button data isn't being submitted correctly.
|
||||
|
||||
**Solution:** Check the HTML form to ensure the buttons have the correct `name` attribute.
|
||||
|
||||
### Issue 2: UPDATE Query Fails
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
DEBUG: UPDATE query result: False
|
||||
```
|
||||
|
||||
**Cause:** The UPDATE query failed silently.
|
||||
|
||||
**Solution:** Check for SQL errors in the logs. The `sql_write_static` function prints errors.
|
||||
|
||||
### Issue 3: UPDATE Succeeds but Value Not Saved
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
DEBUG: UPDATE query result: True
|
||||
DEBUG: Verification - URL suffix in DB: None
|
||||
```
|
||||
|
||||
**Cause:** The UPDATE query executed but didn't actually update the row.
|
||||
|
||||
**Solution:**
|
||||
- Check if the WHERE clause is matching a row
|
||||
- Verify the table structure
|
||||
- Check if there's a transaction rollback happening
|
||||
|
||||
### Issue 4: Query Returns Empty Result
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
DEBUG: Query result: []
|
||||
```
|
||||
|
||||
**Cause:** No row matches the WHERE clause `userid = 'admin'`.
|
||||
|
||||
**Solution:**
|
||||
- Check if the `userid` column has the value 'admin'
|
||||
- Run the diagnostic script: `check_production_db.py`
|
||||
|
||||
## Manual Verification
|
||||
|
||||
After clicking "Save Settings", verify the URL suffix was saved:
|
||||
|
||||
```bash
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "
|
||||
from db_config import sql_read_static
|
||||
from sqlalchemy import text
|
||||
result = sql_read_static(text('SELECT motm_url_suffix FROM admin_settings WHERE userid = \\'admin\\''))
|
||||
print('URL suffix in database:', result[0]['motm_url_suffix'] if result else 'No data')
|
||||
"
|
||||
```
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
### When clicking "Save Settings":
|
||||
1. Form data is received ✅
|
||||
2. URL suffix is generated ✅
|
||||
3. UPDATE query executes ✅
|
||||
4. URL suffix is saved to database ✅
|
||||
5. Flash message shows the URL ✅
|
||||
|
||||
### When clicking "Activate MotM Vote":
|
||||
1. Form data is received ✅
|
||||
2. URL suffix is retrieved from database ✅
|
||||
3. If missing, a new one is generated and saved ✅
|
||||
4. Flash message shows the URL ✅
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Deploy the updated code** with debug logging
|
||||
2. **Test the MOTM admin page** and click "Save Settings"
|
||||
3. **Check the logs** for debug messages
|
||||
4. **Share the debug output** if the issue persists
|
||||
5. **Verify the database** using the manual verification command
|
||||
|
||||
## Removing Debug Logging
|
||||
|
||||
Once the issue is resolved, you can remove the debug logging by searching for lines containing:
|
||||
```python
|
||||
print(f"DEBUG:
|
||||
```
|
||||
|
||||
And removing them, or I can create a cleaned-up version for you.
|
||||
|
||||
## Support
|
||||
|
||||
If you continue to have issues after checking the debug logs:
|
||||
|
||||
1. Save the complete debug output
|
||||
2. Check the application logs for any errors
|
||||
3. Verify the database connection is working
|
||||
4. Check if there are any database permission issues
|
||||
5. Share the debug output and any error messages
|
||||
|
||||
107
motm_app/MIGRATION_SUMMARY.md
Normal file
107
motm_app/MIGRATION_SUMMARY.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Database Migration Summary
|
||||
|
||||
## Problem
|
||||
Your production application is throwing SQL errors because:
|
||||
1. The `votingdeadline` column is missing from the `admin_settings` table
|
||||
2. The code was using wrong table/column names (fixed in code)
|
||||
|
||||
## What Was Fixed
|
||||
|
||||
### Code Changes (Already Applied)
|
||||
✅ Fixed table name: `motmadminsettings` → `admin_settings`
|
||||
✅ Fixed column names to use snake_case (e.g., `nextclub` → `next_club`)
|
||||
✅ Added camelCase to snake_case conversion in `readSettings.py`
|
||||
✅ Improved error handling for duplicate column creation
|
||||
|
||||
### Files Modified
|
||||
- `main.py` - Updated all SQL queries
|
||||
- `readSettings.py` - Added camelCase to snake_case conversion
|
||||
|
||||
## What You Need to Do
|
||||
|
||||
### Run the Migration on Production
|
||||
|
||||
**Option 1: Use the automated script (Easiest)**
|
||||
```bash
|
||||
cd /home/jonny/Projects/gcp-hockey-results/motm_app
|
||||
./run_production_migration.sh motm-app
|
||||
```
|
||||
|
||||
**Option 2: Manual steps**
|
||||
```bash
|
||||
# 1. Find your pod
|
||||
kubectl get pods -n motm-app -l app.kubernetes.io/name=motm-app
|
||||
|
||||
# 2. Run the migration
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python add_voting_deadline.py
|
||||
|
||||
# 3. Verify it worked
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text, inspect
|
||||
engine = db_config.engine
|
||||
inspector = inspect(engine)
|
||||
columns = inspector.get_columns('admin_settings')
|
||||
print('✓ votingdeadline exists' if any(c['name'] == 'votingdeadline' for c in columns) else '✗ missing')
|
||||
"
|
||||
|
||||
# 4. Restart the pod
|
||||
kubectl rollout restart deployment/motm-app -n motm-app
|
||||
```
|
||||
|
||||
## Expected Results
|
||||
|
||||
After running the migration:
|
||||
- ✅ The `votingdeadline` column will be added to `admin_settings` table
|
||||
- ✅ The MOTM admin page will load without SQL errors
|
||||
- ✅ You'll see the "Voting Deadline" field in the form
|
||||
- ✅ You can set voting deadlines for matches
|
||||
|
||||
## Verification
|
||||
|
||||
Test the application:
|
||||
1. Visit https://motm.ervine.cloud/admin/motm
|
||||
2. Page should load without errors
|
||||
3. You should see the "Voting Deadline" field
|
||||
4. Set a deadline and activate voting
|
||||
5. Visit the voting page - you should see a countdown timer
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### If the migration script isn't in the pod:
|
||||
You need to rebuild and redeploy the Docker image:
|
||||
```bash
|
||||
# Build new image with migration script
|
||||
docker build -t your-registry/motm-app:latest .
|
||||
|
||||
# Push to registry
|
||||
docker push your-registry/motm-app:latest
|
||||
|
||||
# Deploy to Kubernetes
|
||||
helm upgrade motm-app ./helm-chart/motm-app --namespace motm-app
|
||||
```
|
||||
|
||||
### If you get "column already exists" error:
|
||||
This is fine! The migration script is idempotent and will skip if the column already exists.
|
||||
|
||||
### If you get other errors:
|
||||
Check the logs:
|
||||
```bash
|
||||
kubectl logs -n motm-app -l app.kubernetes.io/name=motm-app --tail=100
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed instructions, see:
|
||||
- `PRODUCTION_MIGRATION_GUIDE.md` - Comprehensive migration guide
|
||||
- `VOTING_DEADLINE_FEATURE.md` - Feature documentation
|
||||
- `VOTING_DEADLINE_IMPLEMENTATION.md` - Implementation details
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues:
|
||||
1. Check pod logs for detailed error messages
|
||||
2. Verify database connectivity
|
||||
3. Ensure database user has ALTER TABLE permissions
|
||||
4. Review the production migration guide
|
||||
|
||||
234
motm_app/PRODUCTION_DIAGNOSTIC_GUIDE.md
Normal file
234
motm_app/PRODUCTION_DIAGNOSTIC_GUIDE.md
Normal file
@ -0,0 +1,234 @@
|
||||
# Production Database Diagnostic Guide
|
||||
|
||||
## Issue
|
||||
Getting "Admin settings not found" error when activating MOTM vote, even after saving settings.
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
The code is looking for the `motm_url_suffix` column in the `admin_settings` table. The error suggests one of these issues in production:
|
||||
|
||||
1. **Wrong table name**: Production might be using `motmadminsettings` instead of `admin_settings`
|
||||
2. **Missing column**: The `motm_url_suffix` column might not exist
|
||||
3. **NULL value**: The `motm_url_suffix` might be NULL or empty
|
||||
4. **No rows**: The table might be empty
|
||||
|
||||
## Diagnostic Steps
|
||||
|
||||
### Step 1: Run the Diagnostic Script
|
||||
|
||||
Run this in your production Kubernetes pod:
|
||||
|
||||
```bash
|
||||
# Find your pod
|
||||
kubectl get pods -n motm-app -l app.kubernetes.io/name=motm-app
|
||||
|
||||
# Run the diagnostic script
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python check_production_db.py
|
||||
```
|
||||
|
||||
### Step 2: Analyze the Output
|
||||
|
||||
The script will show:
|
||||
- Which tables exist (`admin_settings` vs `motmadminsettings`)
|
||||
- What data is in the table
|
||||
- Whether the `motm_url_suffix` column has a value
|
||||
- The exact query that's failing
|
||||
|
||||
### Step 3: Based on the Results
|
||||
|
||||
#### Scenario A: Table is `motmadminsettings` (Old Name)
|
||||
|
||||
If the output shows `motmadminsettings` exists but `admin_settings` doesn't:
|
||||
|
||||
**Solution**: Your production database still has the old table name. You need to either:
|
||||
|
||||
1. **Rename the table** (recommended):
|
||||
```bash
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text
|
||||
engine = db_config.engine
|
||||
conn = engine.connect()
|
||||
conn.execute(text('ALTER TABLE motmadminsettings RENAME TO admin_settings'))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print('Table renamed successfully')
|
||||
"
|
||||
```
|
||||
|
||||
2. **Or update the code** to use `motmadminsettings` (not recommended, but works as a quick fix)
|
||||
|
||||
#### Scenario B: `motm_url_suffix` is NULL or Empty
|
||||
|
||||
If the output shows the column exists but is NULL or empty:
|
||||
|
||||
**Solution**: The column needs to be populated. Run this:
|
||||
|
||||
```bash
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text
|
||||
import random
|
||||
import string
|
||||
|
||||
# Generate a random URL suffix
|
||||
def randomUrlSuffix(length=8):
|
||||
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
||||
|
||||
engine = db_config.engine
|
||||
conn = engine.connect()
|
||||
urlSuffix = randomUrlSuffix(8)
|
||||
conn.execute(text('UPDATE admin_settings SET motm_url_suffix = :suffix WHERE userid = \\'admin\\''), {'suffix': urlSuffix})
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f'URL suffix set to: {urlSuffix}')
|
||||
"
|
||||
```
|
||||
|
||||
#### Scenario C: Table is Empty
|
||||
|
||||
If the output shows 0 rows:
|
||||
|
||||
**Solution**: You need to initialize the admin settings. Run this:
|
||||
|
||||
```bash
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text
|
||||
import random
|
||||
import string
|
||||
|
||||
def randomUrlSuffix(length=8):
|
||||
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
||||
|
||||
engine = db_config.engine
|
||||
conn = engine.connect()
|
||||
urlSuffix = randomUrlSuffix(8)
|
||||
|
||||
# Insert default admin settings
|
||||
conn.execute(text('''
|
||||
INSERT INTO admin_settings (userid, motm_url_suffix, next_fixture, prev_fixture)
|
||||
VALUES ('admin', :suffix, 1, 0)
|
||||
ON CONFLICT (userid) DO UPDATE SET motm_url_suffix = :suffix
|
||||
'''), {'suffix': urlSuffix})
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f'Admin settings initialized with URL suffix: {urlSuffix}')
|
||||
"
|
||||
```
|
||||
|
||||
#### Scenario D: Column Doesn't Exist
|
||||
|
||||
If the output shows the column is missing:
|
||||
|
||||
**Solution**: Add the missing column:
|
||||
|
||||
```bash
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text
|
||||
|
||||
engine = db_config.engine
|
||||
conn = engine.connect()
|
||||
|
||||
# Add the column if it doesn't exist
|
||||
try:
|
||||
conn.execute(text('ALTER TABLE admin_settings ADD COLUMN motm_url_suffix VARCHAR(50)'))
|
||||
conn.commit()
|
||||
print('Column added successfully')
|
||||
except Exception as e:
|
||||
if 'already exists' in str(e):
|
||||
print('Column already exists')
|
||||
else:
|
||||
print(f'Error: {e}')
|
||||
|
||||
conn.close()
|
||||
"
|
||||
```
|
||||
|
||||
## Quick Fix Script
|
||||
|
||||
If you want to try a comprehensive fix that handles all scenarios:
|
||||
|
||||
```bash
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python << 'EOF'
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text
|
||||
import random
|
||||
import string
|
||||
|
||||
def randomUrlSuffix(length=8):
|
||||
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
||||
|
||||
engine = db_config.engine
|
||||
conn = engine.connect()
|
||||
|
||||
# Step 1: Check if table exists
|
||||
try:
|
||||
result = conn.execute(text("SELECT 1 FROM admin_settings LIMIT 1"))
|
||||
print("✓ admin_settings table exists")
|
||||
except:
|
||||
print("✗ admin_settings table does not exist")
|
||||
print("Please check if you're using the old table name 'motmadminsettings'")
|
||||
conn.close()
|
||||
exit(1)
|
||||
|
||||
# Step 2: Check if motm_url_suffix column exists
|
||||
try:
|
||||
result = conn.execute(text("SELECT motm_url_suffix FROM admin_settings LIMIT 1"))
|
||||
print("✓ motm_url_suffix column exists")
|
||||
except:
|
||||
print("✗ motm_url_suffix column does not exist, adding it...")
|
||||
conn.execute(text("ALTER TABLE admin_settings ADD COLUMN motm_url_suffix VARCHAR(50)"))
|
||||
conn.commit()
|
||||
print("✓ Column added")
|
||||
|
||||
# Step 3: Check if URL suffix has a value
|
||||
result = conn.execute(text("SELECT motm_url_suffix FROM admin_settings WHERE userid = 'admin'"))
|
||||
row = result.fetchone()
|
||||
if row and row[0]:
|
||||
print(f"✓ URL suffix exists: {row[0]}")
|
||||
else:
|
||||
print("✗ URL suffix is NULL or empty, setting it...")
|
||||
urlSuffix = randomUrlSuffix(8)
|
||||
conn.execute(text("UPDATE admin_settings SET motm_url_suffix = :suffix WHERE userid = 'admin'"), {'suffix': urlSuffix})
|
||||
conn.commit()
|
||||
print(f"✓ URL suffix set to: {urlSuffix}")
|
||||
|
||||
conn.close()
|
||||
print("\n✓ All checks passed!")
|
||||
EOF
|
||||
```
|
||||
|
||||
## After Running the Fix
|
||||
|
||||
1. **Restart the application** to clear any cached connections:
|
||||
```bash
|
||||
kubectl rollout restart deployment/motm-app -n motm-app
|
||||
```
|
||||
|
||||
2. **Test the MOTM admin page**:
|
||||
- Go to https://motm.ervine.cloud/admin/motm
|
||||
- Fill in the match details
|
||||
- Click "Activate MotM Vote"
|
||||
- It should work without errors
|
||||
|
||||
## Prevention
|
||||
|
||||
To prevent this issue in the future:
|
||||
|
||||
1. **Run database migrations** before deploying new code
|
||||
2. **Use the migration script** (`add_voting_deadline.py`) to ensure all columns exist
|
||||
3. **Test in staging** before deploying to production
|
||||
4. **Monitor logs** for SQL errors
|
||||
|
||||
## Support
|
||||
|
||||
If you continue to have issues after running these diagnostics:
|
||||
|
||||
1. Save the output from `check_production_db.py`
|
||||
2. Check the application logs: `kubectl logs -n motm-app -l app.kubernetes.io/name=motm-app --tail=100`
|
||||
3. Verify the database connection is working
|
||||
4. Check if there are any database permission issues
|
||||
|
||||
219
motm_app/PRODUCTION_MIGRATION_GUIDE.md
Normal file
219
motm_app/PRODUCTION_MIGRATION_GUIDE.md
Normal file
@ -0,0 +1,219 @@
|
||||
# Production Database Migration Guide
|
||||
|
||||
## Issue
|
||||
The production database is missing the `votingdeadline` column, causing SQL errors when accessing the MOTM admin page.
|
||||
|
||||
## Solution
|
||||
Run the migration script on the production Kubernetes cluster to add the missing column.
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### Option 1: Run Migration via kubectl exec (Recommended)
|
||||
|
||||
1. **Find your production pod:**
|
||||
```bash
|
||||
kubectl get pods -n <your-namespace> -l app.kubernetes.io/name=motm-app
|
||||
```
|
||||
|
||||
Replace `<your-namespace>` with your actual namespace (e.g., `motm-app`, `default`, etc.)
|
||||
|
||||
2. **Execute the migration script:**
|
||||
```bash
|
||||
kubectl exec -it <pod-name> -n <your-namespace> -- python add_voting_deadline.py
|
||||
```
|
||||
|
||||
Example:
|
||||
```bash
|
||||
kubectl exec -it motm-app-7d8f9b4c5-xk2mn -n motm-app -- python add_voting_deadline.py
|
||||
```
|
||||
|
||||
3. **Verify the migration:**
|
||||
```bash
|
||||
kubectl exec -it <pod-name> -n <your-namespace> -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text, inspect
|
||||
engine = db_config.engine
|
||||
inspector = inspect(engine)
|
||||
columns = inspector.get_columns('admin_settings')
|
||||
voting_deadline_exists = any(col['name'] == 'votingdeadline' for col in columns)
|
||||
print('✓ votingdeadline column exists' if voting_deadline_exists else '✗ votingdeadline column missing')
|
||||
"
|
||||
```
|
||||
|
||||
### Option 2: Run Migration via Helm Job (Alternative)
|
||||
|
||||
If you prefer to run the migration as a Kubernetes job:
|
||||
|
||||
1. **Create a migration job manifest:**
|
||||
```yaml
|
||||
# migration-job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: motm-migration-voting-deadline
|
||||
namespace: motm-app
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: migration
|
||||
image: your-registry/motm-app:latest
|
||||
command: ["python", "add_voting_deadline.py"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: motm-app-secrets
|
||||
- configMapRef:
|
||||
name: motm-app-config
|
||||
restartPolicy: Never
|
||||
backoffLimit: 3
|
||||
```
|
||||
|
||||
2. **Apply the job:**
|
||||
```bash
|
||||
kubectl apply -f migration-job.yaml
|
||||
```
|
||||
|
||||
3. **Check job status:**
|
||||
```bash
|
||||
kubectl get jobs -n motm-app
|
||||
kubectl logs -n motm-app job/motm-migration-voting-deadline
|
||||
```
|
||||
|
||||
4. **Clean up after successful migration:**
|
||||
```bash
|
||||
kubectl delete job motm-migration-voting-deadline -n motm-app
|
||||
```
|
||||
|
||||
### Option 3: Run Migration via Helm Hook (Advanced)
|
||||
|
||||
For automated migrations during deployments:
|
||||
|
||||
1. **Create a migration job template:**
|
||||
```yaml
|
||||
# templates/migration-job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "motm-app.fullname" . }}-migration
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations:
|
||||
"helm.sh/hook": pre-upgrade,pre-install
|
||||
"helm.sh/hook-weight": "-5"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: migration
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
command: ["python", "add_voting_deadline.py"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "motm-app.fullname" . }}-secrets
|
||||
restartPolicy: Never
|
||||
backoffLimit: 3
|
||||
```
|
||||
|
||||
2. **The migration will run automatically during helm upgrade**
|
||||
|
||||
## Verification
|
||||
|
||||
After running the migration, verify it worked:
|
||||
|
||||
1. **Check the application logs:**
|
||||
```bash
|
||||
kubectl logs -n <your-namespace> -l app.kubernetes.io/name=motm-app --tail=50
|
||||
```
|
||||
|
||||
2. **Test the admin page:**
|
||||
- Navigate to `https://motm.ervine.cloud/admin/motm`
|
||||
- The page should load without SQL errors
|
||||
- You should see the "Voting Deadline" field in the form
|
||||
|
||||
3. **Check for the column in the database:**
|
||||
```bash
|
||||
kubectl exec -it <pod-name> -n <your-namespace> -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text, inspect
|
||||
engine = db_config.engine
|
||||
inspector = inspect(engine)
|
||||
columns = inspector.get_columns('admin_settings')
|
||||
print('Columns in admin_settings:')
|
||||
for col in columns:
|
||||
print(f' - {col[\"name\"]}: {col[\"type\"]}')
|
||||
"
|
||||
```
|
||||
|
||||
You should see `votingdeadline` in the list.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "pod not found"
|
||||
**Solution:** Make sure you're using the correct namespace and pod name.
|
||||
|
||||
### Issue: "permission denied"
|
||||
**Solution:** The migration script needs database write permissions. Ensure your database user has ALTER TABLE permissions.
|
||||
|
||||
### Issue: "column already exists"
|
||||
**Solution:** This is fine! The migration script is idempotent and will skip if the column already exists.
|
||||
|
||||
### Issue: Migration fails
|
||||
**Solution:** Check the pod logs for detailed error messages:
|
||||
```bash
|
||||
kubectl logs -n <your-namespace> <pod-name>
|
||||
```
|
||||
|
||||
## Rollback (If Needed)
|
||||
|
||||
If the migration causes issues, you can rollback:
|
||||
|
||||
1. **Remove the column:**
|
||||
```bash
|
||||
kubectl exec -it <pod-name> -n <your-namespace> -- python -c "
|
||||
from db_config import db_config
|
||||
from sqlalchemy import text
|
||||
engine = db_config.engine
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text('ALTER TABLE admin_settings DROP COLUMN IF EXISTS votingdeadline'))
|
||||
conn.commit()
|
||||
print('Column removed')
|
||||
"
|
||||
```
|
||||
|
||||
2. **Restart the application:**
|
||||
```bash
|
||||
kubectl rollout restart deployment/motm-app -n <your-namespace>
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Find pod name
|
||||
kubectl get pods -n motm-app -l app.kubernetes.io/name=motm-app
|
||||
|
||||
# Run migration
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python add_voting_deadline.py
|
||||
|
||||
# Verify migration
|
||||
kubectl exec -it <POD_NAME> -n motm-app -- python -c "from db_config import db_config; from sqlalchemy import text, inspect; engine = db_config.engine; inspector = inspect(engine); columns = inspector.get_columns('admin_settings'); print('✓ votingdeadline exists' if any(c['name'] == 'votingdeadline' for c in columns) else '✗ missing')"
|
||||
|
||||
# Check logs
|
||||
kubectl logs -n motm-app -l app.kubernetes.io/name=motm-app --tail=100
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
After the migration is complete:
|
||||
1. ✅ Restart the application pods to clear any cached connections
|
||||
2. ✅ Test the MOTM admin page
|
||||
3. ✅ Set a voting deadline for the next match
|
||||
4. ✅ Test the voting page to see the countdown timer
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter any issues:
|
||||
1. Check the pod logs for detailed error messages
|
||||
2. Verify database connectivity
|
||||
3. Ensure the database user has proper permissions
|
||||
4. Review the application logs for any additional errors
|
||||
|
||||
128
motm_app/REVERT_SUMMARY.md
Normal file
128
motm_app/REVERT_SUMMARY.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Revert to motmadminsettings - Summary
|
||||
|
||||
## What Was Reverted
|
||||
|
||||
All changes have been reverted to use the original table and column names that match your production database.
|
||||
|
||||
### Table Name
|
||||
- **Before (my changes):** `admin_settings`
|
||||
- **After (reverted):** `motmadminsettings` ✅
|
||||
|
||||
### Column Names (camelCase)
|
||||
All column names reverted from snake_case to camelCase:
|
||||
|
||||
| Old (snake_case) | New (camelCase) |
|
||||
|------------------|-----------------|
|
||||
| `next_club` | `nextclub` ✅ |
|
||||
| `next_team` | `nextteam` ✅ |
|
||||
| `next_date` | `nextdate` ✅ |
|
||||
| `oppo_logo` | `oppologo` ✅ |
|
||||
| `hkfc_logo` | `hkfclogo` ✅ |
|
||||
| `curr_motm` | `currmotm` ✅ |
|
||||
| `curr_dotd` | `currdotd` ✅ |
|
||||
| `next_fixture` | `nextfixture` ✅ |
|
||||
| `motm_url_suffix` | `motmurlsuffix` ✅ |
|
||||
| `prev_fixture` | `prevfixture` ✅ |
|
||||
|
||||
### WHERE Clauses
|
||||
Removed all `WHERE userid = 'admin'` clauses from queries since the `motmadminsettings` table doesn't have a `userid` column.
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **`main.py`**
|
||||
- All table references: `admin_settings` → `motmadminsettings`
|
||||
- All column names: snake_case → camelCase
|
||||
- Removed `WHERE userid = 'admin'` from all queries
|
||||
|
||||
2. **`readSettings.py`**
|
||||
- Table reference: `admin_settings` → `motmadminsettings`
|
||||
- Removed `WHERE userid = 'admin'` from query
|
||||
- Removed camelCase to snake_case conversion logic
|
||||
|
||||
## Key Changes
|
||||
|
||||
### SQL Queries
|
||||
|
||||
**SELECT queries:**
|
||||
```sql
|
||||
-- Before (my changes)
|
||||
SELECT next_club, next_team FROM admin_settings WHERE userid = 'admin'
|
||||
|
||||
-- After (reverted)
|
||||
SELECT nextclub, nextteam FROM motmadminsettings
|
||||
```
|
||||
|
||||
**UPDATE queries:**
|
||||
```sql
|
||||
-- Before (my changes)
|
||||
UPDATE admin_settings SET motm_url_suffix = :suffix WHERE userid = 'admin'
|
||||
|
||||
-- After (reverted)
|
||||
UPDATE motmadminsettings SET motmurlsuffix = :suffix
|
||||
```
|
||||
|
||||
**INSERT queries:**
|
||||
```sql
|
||||
-- Before (my changes)
|
||||
INSERT INTO admin_settings (userid, next_club, next_team, ...)
|
||||
|
||||
-- After (reverted)
|
||||
INSERT INTO motmadminsettings (nextclub, nextteam, ...)
|
||||
```
|
||||
|
||||
## What Still Works
|
||||
|
||||
✅ All the bug fixes I made still work:
|
||||
- Debug logging for URL suffix issues
|
||||
- Fallback logic for UPDATE queries
|
||||
- Voting deadline feature
|
||||
- Error handling improvements
|
||||
|
||||
## What's Different
|
||||
|
||||
❌ No longer using:
|
||||
- `admin_settings` table (reverted to `motmadminsettings`)
|
||||
- snake_case column names (reverted to camelCase)
|
||||
- `WHERE userid = 'admin'` clauses (removed)
|
||||
|
||||
## Production Compatibility
|
||||
|
||||
✅ **Now compatible with production database:**
|
||||
- Uses `motmadminsettings` table
|
||||
- Uses camelCase column names
|
||||
- No `userid` column references
|
||||
|
||||
## Testing
|
||||
|
||||
The code should now work with your production database without any migration needed.
|
||||
|
||||
### Quick Test
|
||||
|
||||
```bash
|
||||
# Test locally (if you have motmadminsettings table)
|
||||
python -c "
|
||||
from db_config import sql_read_static
|
||||
from sqlalchemy import text
|
||||
result = sql_read_static(text('SELECT * FROM motmadminsettings'))
|
||||
print('Table exists:', len(result) > 0)
|
||||
"
|
||||
|
||||
# Deploy to production
|
||||
docker build -t your-registry/motm-app:latest .
|
||||
docker push your-registry/motm-app:latest
|
||||
helm upgrade motm-app ./helm-chart/motm-app --namespace motm-app
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Code is reverted to use `motmadminsettings`
|
||||
2. 🚀 Deploy to production
|
||||
3. 🧪 Test MOTM admin page
|
||||
4. ✅ URL suffix should now save correctly
|
||||
|
||||
## Why This Happened
|
||||
|
||||
I initially assumed you were using the newer `admin_settings` table with snake_case columns (which is what the SQLAlchemy ORM model defines). However, your production database still uses the legacy `motmadminsettings` table with camelCase columns.
|
||||
|
||||
The revert ensures the code matches your actual production database schema.
|
||||
|
||||
109
motm_app/WHERE_CLAUSE_FIXES.md
Normal file
109
motm_app/WHERE_CLAUSE_FIXES.md
Normal file
@ -0,0 +1,109 @@
|
||||
# SQL WHERE Clause Fixes
|
||||
|
||||
## Problem
|
||||
When activating the MOTM vote, the application was throwing an error: "Database not initialized. Please go to Database Setup to initialize the database."
|
||||
|
||||
## Root Cause
|
||||
Multiple SQL queries on the `admin_settings` table were missing `WHERE userid = 'admin'` clauses, causing:
|
||||
1. UPDATE queries to update ALL rows instead of just the admin row
|
||||
2. SELECT queries to return unexpected results
|
||||
3. The application to think the database wasn't initialized
|
||||
|
||||
## Fixes Applied
|
||||
|
||||
### 1. UPDATE Queries Fixed
|
||||
|
||||
**Line 599** - Main settings update:
|
||||
```sql
|
||||
-- BEFORE:
|
||||
UPDATE admin_settings SET next_date = :next_date, next_club = :next_club, ...
|
||||
|
||||
-- AFTER:
|
||||
UPDATE admin_settings SET next_date = :next_date, next_club = :next_club, ... WHERE userid = 'admin'
|
||||
```
|
||||
|
||||
**Line 613** - Opponent logo update:
|
||||
```sql
|
||||
-- BEFORE:
|
||||
UPDATE admin_settings SET oppo_logo = :logo_url
|
||||
|
||||
-- AFTER:
|
||||
UPDATE admin_settings SET oppo_logo = :logo_url WHERE userid = 'admin'
|
||||
```
|
||||
|
||||
### 2. SELECT Queries Fixed
|
||||
|
||||
**Line 256** - MOTM vote page:
|
||||
```sql
|
||||
-- BEFORE:
|
||||
SELECT next_club, next_team, next_date, ... FROM admin_settings
|
||||
|
||||
-- AFTER:
|
||||
SELECT next_club, next_team, next_date, ... FROM admin_settings WHERE userid = 'admin'
|
||||
```
|
||||
|
||||
**Line 348** - Match comments page:
|
||||
```sql
|
||||
-- BEFORE:
|
||||
SELECT next_club, next_team, next_date, oppo_logo, hkfc_logo FROM admin_settings
|
||||
|
||||
-- AFTER:
|
||||
SELECT next_club, next_team, next_date, oppo_logo, hkfc_logo FROM admin_settings WHERE userid = 'admin'
|
||||
```
|
||||
|
||||
**Line 683** - Admin settings page:
|
||||
```sql
|
||||
-- BEFORE:
|
||||
SELECT next_club, oppo_logo FROM admin_settings
|
||||
|
||||
-- AFTER:
|
||||
SELECT next_club, oppo_logo FROM admin_settings WHERE userid = 'admin'
|
||||
```
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Without the WHERE clause:
|
||||
- **UPDATE** queries would modify all rows in the table (even if there's only one row, this is bad practice)
|
||||
- **SELECT** queries might return multiple rows when only one is expected
|
||||
- The application logic assumes only one admin settings row exists
|
||||
|
||||
## Testing
|
||||
|
||||
After these fixes:
|
||||
1. ✅ The MOTM admin page should save settings correctly
|
||||
2. ✅ Activating the MOTM vote should work without errors
|
||||
3. ✅ The voting page should load correctly
|
||||
4. ✅ All admin settings queries will target only the admin row
|
||||
|
||||
## Deployment
|
||||
|
||||
To apply these fixes to production:
|
||||
1. Commit the changes to your repository
|
||||
2. Rebuild and redeploy the Docker image
|
||||
3. Restart the application pods
|
||||
|
||||
```bash
|
||||
# Build new image
|
||||
docker build -t your-registry/motm-app:latest .
|
||||
|
||||
# Push to registry
|
||||
docker push your-registry/motm-app:latest
|
||||
|
||||
# Deploy to Kubernetes
|
||||
helm upgrade motm-app ./helm-chart/motm-app --namespace motm-app
|
||||
```
|
||||
|
||||
## Related Issues
|
||||
|
||||
These fixes are related to:
|
||||
- The voting deadline feature implementation
|
||||
- The table name migration from `motmadminsettings` to `admin_settings`
|
||||
- The column name migration to snake_case
|
||||
|
||||
## Summary
|
||||
|
||||
All SQL queries on the `admin_settings` table now properly filter by `userid = 'admin'`, ensuring:
|
||||
- Data integrity
|
||||
- Predictable query results
|
||||
- Proper application functionality
|
||||
|
||||
81
motm_app/check_production_db.py
Executable file
81
motm_app/check_production_db.py
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Diagnostic script to check production database tables and data.
|
||||
Run this in your production Kubernetes pod to diagnose the issue.
|
||||
"""
|
||||
|
||||
from db_config import db_config, sql_read_static
|
||||
from sqlalchemy import text, inspect
|
||||
|
||||
def check_database():
|
||||
"""Check database tables and data"""
|
||||
print("=" * 60)
|
||||
print("Production Database Diagnostic")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
engine = db_config.engine
|
||||
inspector = inspect(engine)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
print("1. Checking tables...")
|
||||
print(f" Total tables: {len(tables)}")
|
||||
print(f" Has 'admin_settings': {'admin_settings' in tables}")
|
||||
print(f" Has 'motmadminsettings': {'motmadminsettings' in tables}")
|
||||
print()
|
||||
|
||||
# Check admin_settings table
|
||||
if 'admin_settings' in tables:
|
||||
print("2. Checking 'admin_settings' table...")
|
||||
try:
|
||||
result = sql_read_static(text("SELECT * FROM admin_settings"))
|
||||
print(f" Rows: {len(result)}")
|
||||
if result:
|
||||
row = result[0]
|
||||
print(f" Columns: {list(row.keys())}")
|
||||
print(f" userid: {row.get('userid')}")
|
||||
print(f" next_date: {row.get('next_date')}")
|
||||
print(f" next_club: {row.get('next_club')}")
|
||||
print(f" next_team: {row.get('next_team')}")
|
||||
print(f" motm_url_suffix: {row.get('motm_url_suffix')}")
|
||||
print(f" votingdeadline: {row.get('votingdeadline')}")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
print()
|
||||
|
||||
# Check motmadminsettings table
|
||||
if 'motmadminsettings' in tables:
|
||||
print("3. Checking 'motmadminsettings' table...")
|
||||
try:
|
||||
result = sql_read_static(text("SELECT * FROM motmadminsettings"))
|
||||
print(f" Rows: {len(result)}")
|
||||
if result:
|
||||
row = result[0]
|
||||
print(f" Columns: {list(row.keys())}")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
print()
|
||||
|
||||
# Test the specific query that's failing
|
||||
print("4. Testing the failing query...")
|
||||
try:
|
||||
sql5 = text("SELECT motm_url_suffix FROM admin_settings WHERE userid = 'admin'")
|
||||
tempSuffix = sql_read_static(sql5)
|
||||
print(f" Query result: {tempSuffix}")
|
||||
print(f" Result length: {len(tempSuffix) if tempSuffix else 0}")
|
||||
if tempSuffix:
|
||||
print(f" motm_url_suffix value: {tempSuffix[0].get('motm_url_suffix')}")
|
||||
print(f" Is None or empty: {not tempSuffix[0].get('motm_url_suffix')}")
|
||||
else:
|
||||
print(" ERROR: No results returned!")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
print("Diagnostic Complete")
|
||||
print("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_database()
|
||||
|
||||
32
motm_app/fix_url_suffix_update.py
Normal file
32
motm_app/fix_url_suffix_update.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix the URL suffix UPDATE query to work regardless of userid value.
|
||||
This script updates the query to not use WHERE userid = 'admin' if that's causing issues.
|
||||
"""
|
||||
|
||||
# The issue is that the WHERE clause might not be matching
|
||||
# Let's update the query to update ALL rows or use a different approach
|
||||
|
||||
# Option 1: Update all rows (if there's only one row)
|
||||
# UPDATE admin_settings SET motm_url_suffix = :url_suffix
|
||||
|
||||
# Option 2: Update by id (if id is always 1)
|
||||
# UPDATE admin_settings SET motm_url_suffix = :url_suffix WHERE id = 1
|
||||
|
||||
# Option 3: Use INSERT ... ON CONFLICT (PostgreSQL specific)
|
||||
# INSERT INTO admin_settings (userid, motm_url_suffix)
|
||||
# VALUES ('admin', :url_suffix)
|
||||
# ON CONFLICT (userid) DO UPDATE SET motm_url_suffix = :url_suffix
|
||||
|
||||
print("This script documents the possible fixes for the URL suffix UPDATE issue.")
|
||||
print()
|
||||
print("The problem: UPDATE query returns True but doesn't actually update any rows")
|
||||
print("Likely cause: WHERE userid = 'admin' is not matching any rows")
|
||||
print()
|
||||
print("Possible solutions:")
|
||||
print("1. Update all rows: UPDATE admin_settings SET motm_url_suffix = :url_suffix")
|
||||
print("2. Update by id: UPDATE admin_settings SET motm_url_suffix = :url_suffix WHERE id = 1")
|
||||
print("3. Use UPSERT: INSERT ... ON CONFLICT (PostgreSQL)")
|
||||
print()
|
||||
print("Run the diagnostic script to see what's in the database first.")
|
||||
|
||||
@ -361,3 +361,6 @@ For issues and questions:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -241,3 +241,6 @@ For issues and questions, please refer to the application documentation or creat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -258,3 +258,6 @@ main "$@"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -67,3 +67,6 @@ Create the name of the service account to use
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -34,3 +34,6 @@ spec:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -20,3 +20,6 @@ spec:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -23,3 +23,6 @@ spec:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -14,3 +14,6 @@ metadata:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
141
motm_app/main.py
141
motm_app/main.py
@ -265,11 +265,11 @@ def motm_vote(randomUrlSuffix):
|
||||
nextTeam = nextInfo[0]['nextteam']
|
||||
nextFixture = nextInfo[0]['nextfixture']
|
||||
# Get HKFC logo from clubs table using signed URLs (with authentication)
|
||||
hkfcLogo = s3_asset_service.get_asset_url('images/hkfc_logo.png') # Default fallback
|
||||
sql_hkfc_logo = text("SELECT logo_url FROM clubs WHERE hockey_club = 'Hong Kong Football Club'")
|
||||
hkfc_logo_result = sql_read(sql_hkfc_logo)
|
||||
if hkfc_logo_result and hkfc_logo_result[0]['logo_url']:
|
||||
hkfcLogo = s3_asset_service.get_logo_url(hkfc_logo_result[0]['logo_url'], 'Hong Kong Football Club')
|
||||
hkfcLogo = s3_asset_service.get_asset_url('images/hkfclogo.png') # Default fallback
|
||||
sql_hkfclogo = text("SELECT logo_url FROM clubs WHERE hockey_club = 'Hong Kong Football Club'")
|
||||
hkfclogo_result = sql_read(sql_hkfclogo)
|
||||
if hkfclogo_result and hkfclogo_result[0]['logo_url']:
|
||||
hkfcLogo = s3_asset_service.get_logo_url(hkfclogo_result[0]['logo_url'], 'Hong Kong Football Club')
|
||||
|
||||
# Get opponent club logo from clubs table using signed URLs (with authentication)
|
||||
oppoLogo = s3_asset_service.get_asset_url('images/default_logo.png') # Default fallback
|
||||
@ -293,14 +293,14 @@ def motm_vote(randomUrlSuffix):
|
||||
currMotM = None
|
||||
currDotD = None
|
||||
if nextInfo and nextInfo[0]['currmotm']:
|
||||
sql3 = text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :curr_motm")
|
||||
motm_result = sql_read(sql3, {'curr_motm': nextInfo[0]['currmotm']})
|
||||
sql3 = text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :currmotm")
|
||||
motm_result = sql_read(sql3, {'currmotm': nextInfo[0]['currmotm']})
|
||||
if motm_result:
|
||||
currMotM = motm_result[0]['playernickname']
|
||||
|
||||
if nextInfo and nextInfo[0]['currdotd']:
|
||||
sql4 = text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :curr_dotd")
|
||||
dotd_result = sql_read(sql4, {'curr_dotd': nextInfo[0]['currdotd']})
|
||||
sql4 = text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :currdotd")
|
||||
dotd_result = sql_read(sql4, {'currdotd': nextInfo[0]['currdotd']})
|
||||
if dotd_result:
|
||||
currDotD = dotd_result[0]['playernickname']
|
||||
|
||||
@ -368,11 +368,11 @@ def match_comments():
|
||||
_matchDate = '2025-01-15'
|
||||
|
||||
# Get HKFC logo from clubs table using signed URLs (with authentication)
|
||||
hkfcLogo = s3_asset_service.get_asset_url('images/hkfc_logo.png') # Default fallback
|
||||
sql_hkfc_logo = text("SELECT logo_url FROM clubs WHERE hockey_club = 'Hong Kong Football Club'")
|
||||
hkfc_logo_result = sql_read(sql_hkfc_logo)
|
||||
if hkfc_logo_result and hkfc_logo_result[0]['logo_url']:
|
||||
hkfcLogo = s3_asset_service.get_logo_url(hkfc_logo_result[0]['logo_url'], 'Hong Kong Football Club')
|
||||
hkfcLogo = s3_asset_service.get_asset_url('images/hkfclogo.png') # Default fallback
|
||||
sql_hkfclogo = text("SELECT logo_url FROM clubs WHERE hockey_club = 'Hong Kong Football Club'")
|
||||
hkfclogo_result = sql_read(sql_hkfclogo)
|
||||
if hkfclogo_result and hkfclogo_result[0]['logo_url']:
|
||||
hkfcLogo = s3_asset_service.get_logo_url(hkfclogo_result[0]['logo_url'], 'Hong Kong Football Club')
|
||||
|
||||
# Get opponent club logo from clubs table using signed URLs (with authentication)
|
||||
oppoLogo = s3_asset_service.get_asset_url('images/default_logo.png') # Default fallback
|
||||
@ -397,7 +397,7 @@ def vote_thanks():
|
||||
"""Process MOTM/DotD votes and comments"""
|
||||
try:
|
||||
# Check voting deadline
|
||||
sql_deadline = text("SELECT votingdeadline FROM motmadminsettings WHERE userid = 'admin'")
|
||||
sql_deadline = text("SELECT votingdeadline FROM motmadminsettings")
|
||||
deadline_result = sql_read_static(sql_deadline)
|
||||
if deadline_result and deadline_result[0].get('votingdeadline'):
|
||||
from datetime import datetime
|
||||
@ -553,15 +553,19 @@ def motm_admin():
|
||||
else:
|
||||
prevFixture = str(prevFixture)
|
||||
if request.method == 'POST':
|
||||
print(f"DEBUG: POST request received")
|
||||
print(f"DEBUG: form.saveButton.data = {form.saveButton.data}")
|
||||
print(f"DEBUG: form.activateButton.data = {form.activateButton.data}")
|
||||
if form.saveButton.data:
|
||||
print('Saved')
|
||||
print('DEBUG: Save button clicked')
|
||||
else:
|
||||
print('Activated')
|
||||
print('DEBUG: Activate button clicked')
|
||||
_nextTeam = request.form.get('nextOppoTeam', '')
|
||||
_nextMatchDate = request.form.get('nextMatchDate', '')
|
||||
_votingDeadline = request.form.get('votingDeadline', '')
|
||||
_currMotM = request.form.get('currMotM', '0')
|
||||
_currDotD = request.form.get('currDotD', '0')
|
||||
print(f"DEBUG: Form data - team: {_nextTeam}, date: {_nextMatchDate}")
|
||||
|
||||
# Validate required fields
|
||||
if not _nextTeam or not _nextMatchDate:
|
||||
@ -584,8 +588,8 @@ def motm_admin():
|
||||
# Get the form values for previous MOTM and DotD
|
||||
# If user selected '0' (No Previous), use None
|
||||
# Otherwise use the selected player number
|
||||
curr_motm_value = None if _currMotM == '0' else _currMotM
|
||||
curr_dotd_value = None if _currDotD == '0' else _currDotD
|
||||
currmotm_value = None if _currMotM == '0' else _currMotM
|
||||
currdotd_value = None if _currDotD == '0' else _currDotD
|
||||
|
||||
# Parse voting deadline if provided
|
||||
voting_deadline_value = None
|
||||
@ -596,13 +600,13 @@ def motm_admin():
|
||||
except ValueError:
|
||||
flash('Warning: Invalid voting deadline format. Deadline not set.', 'warning')
|
||||
|
||||
sql = text("UPDATE motmadminsettings SET nextdate = :next_date, nextclub = :next_club, nextteam = :next_team, currmotm = :curr_motm, currdotd = :curr_dotd, votingdeadline = :voting_deadline")
|
||||
sql = text("UPDATE motmadminsettings SET nextdate = :nextdate, nextclub = :nextclub, nextteam = :nextteam, currmotm = :currmotm, currdotd = :currdotd, votingdeadline = :voting_deadline")
|
||||
sql_write_static(sql, {
|
||||
'next_date': _nextMatchDate,
|
||||
'next_club': _nextClub,
|
||||
'next_team': _nextTeam,
|
||||
'curr_motm': curr_motm_value,
|
||||
'curr_dotd': curr_dotd_value,
|
||||
'nextdate': _nextMatchDate,
|
||||
'nextclub': _nextClub,
|
||||
'nextteam': _nextTeam,
|
||||
'currmotm': currmotm_value,
|
||||
'currdotd': currdotd_value,
|
||||
'voting_deadline': voting_deadline_value
|
||||
})
|
||||
|
||||
@ -614,14 +618,40 @@ def motm_admin():
|
||||
sql_write_static(sql2, {'logo_url': logo_url})
|
||||
else:
|
||||
# Fallback to old method
|
||||
sql2 = text("UPDATE motmadminsettings SET oppologo = (SELECT logo FROM menshockeyclubs WHERE hockeyclub = :next_club) WHERE nextclub = :next_club")
|
||||
sql_write_static(sql2, {'next_club': _nextClub})
|
||||
sql2 = text("UPDATE motmadminsettings SET oppologo = (SELECT logo FROM menshockeyclubs WHERE hockeyclub = :nextclub) WHERE nextclub = :nextclub")
|
||||
sql_write_static(sql2, {'nextclub': _nextClub})
|
||||
if form.saveButton.data:
|
||||
flash('Settings saved!')
|
||||
urlSuffix = randomUrlSuffix(8)
|
||||
print(urlSuffix)
|
||||
sql3 = text("UPDATE motmadminsettings SET motmurlsuffix = :url_suffix WHERE userid = 'admin'")
|
||||
sql_write_static(sql3, {'url_suffix': urlSuffix})
|
||||
print(f"DEBUG: Generated URL suffix: {urlSuffix}")
|
||||
|
||||
# Check if row exists before update
|
||||
check_row = sql_read_static(text("SELECT userid, motmurlsuffix FROM motmadminsettings"))
|
||||
print(f"DEBUG: Rows in motmadminsettings: {check_row}")
|
||||
|
||||
# Try to update with WHERE userid = 'admin'
|
||||
sql3 = text("UPDATE motmadminsettings SET motmurlsuffix = :url_suffix")
|
||||
print(f"DEBUG: About to execute UPDATE query with WHERE userid='admin'")
|
||||
result = sql_write_static(sql3, {'url_suffix': urlSuffix})
|
||||
print(f"DEBUG: UPDATE query result: {result}")
|
||||
|
||||
# Verify the update
|
||||
verify = sql_read_static(text("SELECT motmurlsuffix FROM motmadminsettings"))
|
||||
print(f"DEBUG: Verification with WHERE userid='admin': {verify}")
|
||||
|
||||
# If the update didn't work, try updating all rows (if there's only one row)
|
||||
if not verify or not verify[0]['motmurlsuffix']:
|
||||
print(f"DEBUG: First UPDATE didn't work, trying UPDATE without WHERE clause")
|
||||
sql3_all = text("UPDATE motmadminsettings SET motmurlsuffix = :url_suffix")
|
||||
result_all = sql_write_static(sql3_all, {'url_suffix': urlSuffix})
|
||||
print(f"DEBUG: UPDATE all rows result: {result_all}")
|
||||
|
||||
# Verify again
|
||||
verify_all = sql_read_static(text("SELECT userid, motmurlsuffix FROM motmadminsettings"))
|
||||
print(f"DEBUG: Verification after UPDATE all: {verify_all}")
|
||||
if verify_all and verify_all[0]['motmurlsuffix']:
|
||||
print(f"DEBUG: SUCCESS! URL suffix updated to: {verify_all[0]['motmurlsuffix']}")
|
||||
|
||||
flash('MotM URL https://motm.ervine.cloud/motm/'+urlSuffix)
|
||||
elif form.activateButton.data:
|
||||
# Generate a fixture number based on the date
|
||||
@ -630,23 +660,42 @@ def motm_admin():
|
||||
sql4 = text(f"ALTER TABLE _hkfc_c_motm ADD COLUMN motm_{_nextFixture} smallint DEFAULT 0, ADD COLUMN dotd_{_nextFixture} smallint DEFAULT 0, ADD COLUMN assists_{_nextFixture} smallint DEFAULT 0, ADD COLUMN goals_{_nextFixture} smallint DEFAULT 0")
|
||||
sql_write(sql4)
|
||||
except Exception as e:
|
||||
# Columns already exist, which is fine
|
||||
print(f"Columns already exist for fixture {_nextFixture}: {e}")
|
||||
pass
|
||||
sql5 = text("SELECT motmurlsuffix FROM motmadminsettings WHERE userid = 'admin'")
|
||||
# Columns already exist, which is fine - just log at debug level
|
||||
error_msg = str(e)
|
||||
if 'already exists' in error_msg or 'duplicate' in error_msg.lower():
|
||||
# This is expected if columns already exist, don't log as error
|
||||
print(f"Columns for fixture {_nextFixture} already exist (this is normal)")
|
||||
else:
|
||||
# Unexpected error, log it
|
||||
print(f"Error adding columns for fixture {_nextFixture}: {e}")
|
||||
|
||||
# Get or generate the URL suffix
|
||||
print("DEBUG: Getting URL suffix from database")
|
||||
sql5 = text("SELECT motmurlsuffix FROM motmadminsettings")
|
||||
tempSuffix = sql_read_static(sql5)
|
||||
if not tempSuffix:
|
||||
flash('Error: Admin settings not found', 'error')
|
||||
return redirect(url_for('motm_admin'))
|
||||
currSuffix = tempSuffix[0]['motmurlsuffix']
|
||||
print(currSuffix)
|
||||
print(f"DEBUG: Query result: {tempSuffix}")
|
||||
if not tempSuffix or not tempSuffix[0]['motmurlsuffix']:
|
||||
# Generate a new URL suffix if one doesn't exist
|
||||
print("DEBUG: URL suffix is None or empty, generating new one")
|
||||
urlSuffix = randomUrlSuffix(8)
|
||||
print(f"DEBUG: Generated new suffix: {urlSuffix}")
|
||||
sql6 = text("UPDATE motmadminsettings SET motmurlsuffix = :url_suffix")
|
||||
result = sql_write_static(sql6, {'url_suffix': urlSuffix})
|
||||
print(f"DEBUG: UPDATE result: {result}")
|
||||
currSuffix = urlSuffix
|
||||
flash('New voting URL generated')
|
||||
else:
|
||||
currSuffix = tempSuffix[0]['motmurlsuffix']
|
||||
print(f"DEBUG: Using existing suffix: {currSuffix}")
|
||||
|
||||
print(f"DEBUG: Final suffix: {currSuffix}")
|
||||
flash('Man of the Match vote is now activated')
|
||||
flash('MotM URL https://motm.ervine.cloud/motm/'+currSuffix)
|
||||
else:
|
||||
flash('Something went wrong - check with Smithers')
|
||||
|
||||
# Load current settings to populate the form
|
||||
sql_current = text("SELECT nextdate, nextteam, currmotm, currdotd, votingdeadline FROM motmadminsettings WHERE userid = 'admin'")
|
||||
sql_current = text("SELECT nextdate, nextteam, currmotm, currdotd, votingdeadline FROM motmadminsettings")
|
||||
current_settings = sql_read_static(sql_current)
|
||||
if current_settings:
|
||||
from datetime import datetime
|
||||
@ -1039,7 +1088,7 @@ def data_import():
|
||||
if form.import_clubs.data:
|
||||
# Import clubs based on Hong Kong Hockey Association data
|
||||
clubs_data = [
|
||||
{'hockey_club': 'HKFC', 'logo_url': '/static/images/hkfc_logo.png'},
|
||||
{'hockey_club': 'HKFC', 'logo_url': '/static/images/hkfclogo.png'},
|
||||
{'hockey_club': 'KCC', 'logo_url': '/static/images/kcc_logo.png'},
|
||||
{'hockey_club': 'USRC', 'logo_url': '/static/images/usrc_logo.png'},
|
||||
{'hockey_club': 'Valley', 'logo_url': '/static/images/valley_logo.png'},
|
||||
@ -1792,7 +1841,7 @@ def matchSquadReset():
|
||||
"""Reset squad for new match - archives current squad to history before clearing"""
|
||||
try:
|
||||
# Get current match date and fixture number from admin settings
|
||||
sql_settings = text("SELECT nextdate, nextfixture FROM motmadminsettings WHERE userid = 'admin'")
|
||||
sql_settings = text("SELECT nextdate, nextfixture FROM motmadminsettings")
|
||||
settings = sql_read_static(sql_settings)
|
||||
|
||||
if not settings:
|
||||
@ -1821,7 +1870,7 @@ def matchSquadReset():
|
||||
sql_write(clear_sql)
|
||||
|
||||
# Update previous fixture number in settings
|
||||
update_sql = text("UPDATE motmadminsettings SET prevfixture = :fixture_number WHERE userid = 'admin'")
|
||||
update_sql = text("UPDATE motmadminsettings SET prevfixture = :fixture_number")
|
||||
sql_write_static(update_sql, {'fixture_number': fixture_number})
|
||||
|
||||
flash(f'Squad reset successfully! {squad_count} players archived for match {fixture_number} ({match_date})', 'success')
|
||||
@ -1872,7 +1921,7 @@ def goalsAssistsSubmit():
|
||||
|
||||
@app.route('/admin/api/next-fixture')
|
||||
@basic_auth.required
|
||||
def get_next_fixture():
|
||||
def get_nextfixture():
|
||||
"""API endpoint to fetch the next HKFC C fixture from Hockey Hong Kong website"""
|
||||
try:
|
||||
fixture = get_next_hkfc_c_fixture()
|
||||
@ -2069,7 +2118,7 @@ def admin_fixture_logo_lookup(fixture):
|
||||
def vote_results():
|
||||
"""API endpoint for voting results"""
|
||||
# Get the current match date from admin settings
|
||||
sql_date = text("SELECT nextdate FROM motmadminsettings WHERE userid = 'admin'")
|
||||
sql_date = text("SELECT nextdate FROM motmadminsettings")
|
||||
date_result = sql_read_static(sql_date)
|
||||
|
||||
if not date_result:
|
||||
@ -2133,7 +2182,7 @@ def poty_results():
|
||||
def voting_chart():
|
||||
"""Admin page for viewing voting charts"""
|
||||
# Get the current match date from admin settings
|
||||
sql_date = text("SELECT nextdate FROM motmadminsettings WHERE userid = 'admin'")
|
||||
sql_date = text("SELECT nextdate FROM motmadminsettings")
|
||||
date_result = sql_read_static(sql_date)
|
||||
|
||||
if date_result:
|
||||
|
||||
@ -8,7 +8,7 @@ def mySettings(setting):
|
||||
try:
|
||||
# Convert setting to lowercase for PostgreSQL compatibility
|
||||
setting_lower = setting.lower()
|
||||
sql = text("SELECT " + setting_lower + " FROM motmadminsettings WHERE userid='admin'")
|
||||
sql = text("SELECT " + setting_lower + " FROM motmadminsettings")
|
||||
rows = sql_read_static(sql)
|
||||
if rows:
|
||||
return rows[0][setting_lower]
|
||||
|
||||
13
motm_app/revert_to_motmadminsettings.sh
Normal file
13
motm_app/revert_to_motmadminsettings.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Script to revert all table and column name changes back to motmadminsettings
|
||||
|
||||
echo "This script will help you revert the table name changes."
|
||||
echo "The issue is that production uses 'motmadminsettings' with camelCase columns,"
|
||||
echo "but I changed everything to 'admin_settings' with snake_case columns."
|
||||
echo ""
|
||||
echo "You have two options:"
|
||||
echo "1. Revert the code to use motmadminsettings (old table)"
|
||||
echo "2. Migrate production database to use admin_settings (new table)"
|
||||
echo ""
|
||||
echo "Which would you prefer?"
|
||||
|
||||
27
motm_app/run_migration_production.sh
Executable file
27
motm_app/run_migration_production.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# Script to run the voting deadline migration on production
|
||||
|
||||
echo "=========================================="
|
||||
echo "Running Voting Deadline Migration"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if we're in a Kubernetes environment
|
||||
if [ -n "$KUBERNETES_SERVICE_HOST" ]; then
|
||||
echo "Running in Kubernetes environment"
|
||||
python add_voting_deadline.py
|
||||
else
|
||||
echo "Running locally"
|
||||
echo ""
|
||||
echo "To run on production Kubernetes cluster:"
|
||||
echo "1. kubectl get pods -n <your-namespace>"
|
||||
echo "2. kubectl exec -it <pod-name> -n <your-namespace> -- python add_voting_deadline.py"
|
||||
echo ""
|
||||
echo "Or if using Helm:"
|
||||
echo "helm upgrade motm-app ./helm-chart/motm-app --namespace <your-namespace>"
|
||||
echo ""
|
||||
python add_voting_deadline.py
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Migration complete!"
|
||||
102
motm_app/run_production_migration.sh
Executable file
102
motm_app/run_production_migration.sh
Executable file
@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# Quick script to run the voting deadline migration on production Kubernetes
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}MOTM App - Production Migration${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Default namespace
|
||||
NAMESPACE="${1:-motm-app}"
|
||||
|
||||
echo -e "${YELLOW}Using namespace: ${NAMESPACE}${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Find the pod
|
||||
echo -e "${YELLOW}Step 1: Finding production pod...${NC}"
|
||||
POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/name=motm-app -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$POD_NAME" ]; then
|
||||
echo -e "${RED}✗ Error: Could not find motm-app pod in namespace '$NAMESPACE'${NC}"
|
||||
echo ""
|
||||
echo "Available namespaces:"
|
||||
kubectl get namespaces
|
||||
echo ""
|
||||
echo "Usage: $0 [namespace]"
|
||||
echo "Example: $0 motm-app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Found pod: ${POD_NAME}${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 2: Check if migration script exists in pod
|
||||
echo -e "${YELLOW}Step 2: Checking migration script...${NC}"
|
||||
if kubectl exec -n "$NAMESPACE" "$POD_NAME" -- test -f /app/add_voting_deadline.py 2>/dev/null; then
|
||||
echo -e "${GREEN}✓ Migration script found${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Error: Migration script not found in pod${NC}"
|
||||
echo "The add_voting_deadline.py script needs to be in the Docker image."
|
||||
echo "Please rebuild and redeploy the application."
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 3: Run the migration
|
||||
echo -e "${YELLOW}Step 3: Running migration...${NC}"
|
||||
if kubectl exec -n "$NAMESPACE" "$POD_NAME" -- python /app/add_voting_deadline.py; then
|
||||
echo -e "${GREEN}✓ Migration completed successfully!${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Migration failed!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 4: Verify the migration
|
||||
echo -e "${YELLOW}Step 4: Verifying migration...${NC}"
|
||||
VERIFY_CMD="from db_config import db_config; from sqlalchemy import text, inspect; engine = db_config.engine; inspector = inspect(engine); columns = inspector.get_columns('admin_settings'); voting_deadline_exists = any(col['name'] == 'votingdeadline' for col in columns); print('✓ votingdeadline column exists' if voting_deadline_exists else '✗ votingdeadline column missing')"
|
||||
|
||||
if kubectl exec -n "$NAMESPACE" "$POD_NAME" -- python -c "$VERIFY_CMD" 2>&1 | grep -q "✓"; then
|
||||
echo -e "${GREEN}✓ Verification successful!${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Verification failed!${NC}"
|
||||
echo "The column may not have been created properly."
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 5: Restart the pod to clear cached connections
|
||||
echo -e "${YELLOW}Step 5: Restarting pod to clear cached connections...${NC}"
|
||||
kubectl rollout restart deployment/motm-app -n "$NAMESPACE" 2>/dev/null || \
|
||||
kubectl delete pod "$POD_NAME" -n "$NAMESPACE"
|
||||
echo -e "${GREEN}✓ Pod restart initiated${NC}"
|
||||
echo ""
|
||||
|
||||
# Wait for pod to be ready
|
||||
echo -e "${YELLOW}Waiting for pod to be ready...${NC}"
|
||||
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=motm-app -n "$NAMESPACE" --timeout=120s
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Migration Complete!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Visit https://motm.ervine.cloud/admin/motm"
|
||||
echo "2. The page should load without SQL errors"
|
||||
echo "3. You should see the 'Voting Deadline' field"
|
||||
echo "4. Set a deadline for your next match"
|
||||
echo ""
|
||||
echo "To check logs:"
|
||||
echo " kubectl logs -n $NAMESPACE -l app.kubernetes.io/name=motm-app --tail=50"
|
||||
echo ""
|
||||
|
||||
@ -10,35 +10,37 @@ class matchSquadTable:
|
||||
def __html__(self):
|
||||
"""Generate HTML table from items"""
|
||||
if not self.items:
|
||||
return Markup('<p>No players in squad</p>')
|
||||
return Markup('<div class="alert alert-info text-center"><i class="fas fa-info-circle me-2"></i>No players in squad</div>')
|
||||
|
||||
# Start table
|
||||
classes_str = ' '.join(self.classes) if self.classes else ''
|
||||
border_attr = 'border="1"' if self.border else ''
|
||||
html = f'<table class="table {classes_str}" {border_attr}>\n'
|
||||
# Start table with Bootstrap 5 classes
|
||||
classes_str = ' '.join(self.classes) if self.classes else 'table table-striped table-hover'
|
||||
html = f'<div class="table-responsive"><table class="{classes_str} mb-0">\n'
|
||||
|
||||
# Table header
|
||||
html += ' <thead>\n <tr>\n'
|
||||
html += ' <th>Player Number</th>\n'
|
||||
html += ' <th>Nickname</th>\n'
|
||||
html += ' <th>Surname</th>\n'
|
||||
html += ' <th>Forenames</th>\n'
|
||||
html += ' <th>Delete</th>\n'
|
||||
# Table header with modern styling
|
||||
html += ' <thead class="table-dark">\n <tr>\n'
|
||||
html += ' <th><i class="fas fa-hashtag me-1"></i>Player Number</th>\n'
|
||||
html += ' <th><i class="fas fa-user me-1"></i>Nickname</th>\n'
|
||||
html += ' <th><i class="fas fa-id-card me-1"></i>Surname</th>\n'
|
||||
html += ' <th><i class="fas fa-id-card me-1"></i>Forenames</th>\n'
|
||||
html += ' <th class="text-center"><i class="fas fa-cog me-1"></i>Actions</th>\n'
|
||||
html += ' </tr>\n </thead>\n'
|
||||
|
||||
# Table body
|
||||
# Table body with enhanced styling
|
||||
html += ' <tbody>\n'
|
||||
for item in self.items:
|
||||
html += ' <tr>\n'
|
||||
html += f' <td>{item.get("playernumber", "")}</td>\n'
|
||||
html += f' <td>{item.get("playernickname", "")}</td>\n'
|
||||
html += f' <td><span class="badge bg-primary fs-6">#{item.get("playernumber", "")}</span></td>\n'
|
||||
html += f' <td><strong>{item.get("playernickname", "")}</strong></td>\n'
|
||||
html += f' <td>{item.get("playersurname", "")}</td>\n'
|
||||
html += f' <td>{item.get("playerforenames", "")}</td>\n'
|
||||
html += f' <td><form method="post" action="/admin/squad/remove?playerNumber={item.get("playernumber", "")}"><button type="submit" class="btn btn-danger">Delete</button></form></td>\n'
|
||||
html += f' <td class="text-center">'
|
||||
html += f'<form method="post" action="/admin/squad/remove?playerNumber={item.get("playernumber", "")}" class="d-inline">'
|
||||
html += f'<button type="submit" class="btn btn-danger btn-sm" data-confirm="Are you sure you want to remove player #{item.get("playernumber", "")} from the squad?">'
|
||||
html += f'<i class="fas fa-trash me-1"></i>Remove</button></form></td>\n'
|
||||
html += ' </tr>\n'
|
||||
html += ' </tbody>\n'
|
||||
|
||||
# End table
|
||||
html += '</table>\n'
|
||||
html += '</table></div>\n'
|
||||
|
||||
return Markup(html)
|
||||
|
||||
@ -1,73 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Match Squad Selection - HKFC Men's C Team</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1>Match Squad Selection</h1>
|
||||
<p class="lead">Select players for the match squad from the available players</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<a href="/admin/players" class="btn btn-outline-primary">Manage Players</a>
|
||||
<a href="/admin" class="btn btn-secondary">Back to Admin</a>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% if players %}
|
||||
<form action="/admin/squad/submit" method="post">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Available Players</h5>
|
||||
<small class="text-muted">Select players to add to the match squad</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% for player in players %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="playerNumber" value="{{ player.playernumber }}" id="player{{ player.playernumber }}">
|
||||
<label class="form-check-label" for="player{{ player.playernumber }}">
|
||||
<strong>#{{ player.playernumber }}</strong> {{ player.playerforenames }} {{ player.playersurname }}
|
||||
<br>
|
||||
<small class="text-muted">{{ player.playernickname }} - {{ player.playerteam }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-primary">Add Selected Players to Squad</button>
|
||||
<a href="/admin/squad/list" class="btn btn-outline-secondary">View Current Squad</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-warning">
|
||||
<h5>No players available</h5>
|
||||
<p>There are no players in the database. <a href="/admin/players/add">Add some players</a> before selecting a squad.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Match Squad Selection - HKFC Men's C Team{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="card-title">
|
||||
<i class="fas fa-users text-primary me-2"></i>
|
||||
Match Squad Selection
|
||||
</h1>
|
||||
<p class="lead text-muted">Select players for the match squad from the available players</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-bolt me-2"></i>Quick Actions
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="/admin/players" class="btn btn-outline-primary">
|
||||
<i class="fas fa-user-plus me-2"></i>Manage Players
|
||||
</a>
|
||||
<a href="/admin/squad/list" class="btn btn-outline-info">
|
||||
<i class="fas fa-list me-2"></i>View Current Squad
|
||||
</a>
|
||||
<a href="/admin/squad/history" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-history me-2"></i>Squad History
|
||||
</a>
|
||||
<a href="/admin" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to Admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Squad Selection Form -->
|
||||
{% if players %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-check-circle me-2"></i>Available Players
|
||||
</h5>
|
||||
<small class="opacity-75">Select players to add to the match squad</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/admin/squad/submit" method="post">
|
||||
<div class="row">
|
||||
{% for player in players %}
|
||||
<div class="col-md-4 col-lg-3 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body p-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="playerNumber" value="{{ player.playernumber }}" id="player{{ player.playernumber }}">
|
||||
<label class="form-check-label w-100" for="player{{ player.playernumber }}">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="player-number me-3">
|
||||
<span class="badge bg-primary fs-6">#{{ player.playernumber }}</span>
|
||||
</div>
|
||||
<div class="player-info flex-grow-1">
|
||||
<div class="fw-bold">{{ player.playerforenames }} {{ player.playersurname }}</div>
|
||||
<small class="text-muted">{{ player.playernickname }}</small>
|
||||
<br>
|
||||
<small class="text-info">
|
||||
<i class="fas fa-users me-1"></i>{{ player.playerteam }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<button type="submit" class="btn btn-success btn-lg">
|
||||
<i class="fas fa-plus-circle me-2"></i>Add Selected Players to Squad
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle fa-3x mb-3 text-warning"></i>
|
||||
<h4>No Players Available</h4>
|
||||
<p class="mb-3">There are no players in the database. You need to add some players before selecting a squad.</p>
|
||||
<a href="/admin/players/add" class="btn btn-primary">
|
||||
<i class="fas fa-user-plus me-2"></i>Add Players
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Select all functionality
|
||||
$('#selectAll').on('change', function() {
|
||||
$('input[name="playerNumber"]').prop('checked', this.checked);
|
||||
});
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
// Update select all checkbox when individual checkboxes change
|
||||
$('input[name="playerNumber"]').on('change', function() {
|
||||
var totalCheckboxes = $('input[name="playerNumber"]').length;
|
||||
var checkedCheckboxes = $('input[name="playerNumber"]:checked').length;
|
||||
$('#selectAll').prop('checked', totalCheckboxes === checkedCheckboxes);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,19 +1,49 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Squad Reset</title>
|
||||
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Match squad has been reset for the next match</h3>
|
||||
<a class="btn btn-primary" href="/admin/squad" role="button">Add Players to New Squad</a>
|
||||
<a class="btn btn-danger" href="/" role="button">Home</a>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Squad Reset - HKFC Men's C Team{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="card-title">
|
||||
<i class="fas fa-refresh text-warning me-2"></i>
|
||||
Squad Reset Complete
|
||||
</h1>
|
||||
<p class="lead text-muted">The match squad has been reset for the next match</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-bolt me-2"></i>Next Steps
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="d-flex flex-wrap justify-content-center gap-3">
|
||||
<a href="/admin/squad" class="btn btn-success btn-lg">
|
||||
<i class="fas fa-plus-circle me-2"></i>Add Players to New Squad
|
||||
</a>
|
||||
<a href="/admin/squad/history" class="btn btn-info btn-lg">
|
||||
<i class="fas fa-history me-2"></i>View Squad History
|
||||
</a>
|
||||
<a href="/admin" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-tachometer-alt me-2"></i>Admin Dashboard
|
||||
</a>
|
||||
<a href="/" class="btn btn-secondary btn-lg">
|
||||
<i class="fas fa-home me-2"></i>Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,40 +1,72 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>HKFC Men's C Team - Match Squad Selected</title>
|
||||
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Match Squad</h3>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' if category == 'success' else 'info' }} alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="mb-3">
|
||||
<a class="btn btn-primary" href="/admin/squad" role="button">Add More Players</a>
|
||||
<a class="btn btn-info" href="/admin/squad/list" role="button">View Squad List</a>
|
||||
<a class="btn btn-warning" href="/admin/squad/reset" role="button">Reset Squad</a>
|
||||
<a class="btn btn-secondary" href="/admin" role="button">Back to Admin</a>
|
||||
<a class="btn btn-danger" href="/" role="button">Cancel</a>
|
||||
</div>
|
||||
|
||||
{{ table }}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Match Squad - HKFC Men's C Team{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="card-title">
|
||||
<i class="fas fa-users text-success me-2"></i>
|
||||
Current Match Squad
|
||||
</h1>
|
||||
<p class="lead text-muted">Manage the current match squad players</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-bolt me-2"></i>Squad Management
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="/admin/squad" class="btn btn-success">
|
||||
<i class="fas fa-plus-circle me-2"></i>Add More Players
|
||||
</a>
|
||||
<a href="/admin/squad/list" class="btn btn-info">
|
||||
<i class="fas fa-list me-2"></i>Refresh Squad List
|
||||
</a>
|
||||
<a href="/admin/squad/history" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-history me-2"></i>Squad History
|
||||
</a>
|
||||
<a href="/admin/squad/reset" class="btn btn-warning" data-confirm="Are you sure you want to reset the squad? This will archive the current squad and clear it for the next match.">
|
||||
<i class="fas fa-refresh me-2"></i>Reset Squad
|
||||
</a>
|
||||
<a href="/admin" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to Admin
|
||||
</a>
|
||||
<a href="/" class="btn btn-outline-danger">
|
||||
<i class="fas fa-home me-2"></i>Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Squad Table -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table me-2"></i>Squad Players
|
||||
</h5>
|
||||
<small class="opacity-75">Current players selected for the match</small>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{{ table }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user