Sep 11 - Reupload the code
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Migration: Dynamic QR Code Support
|
||||
====================================
|
||||
Applies the following database changes required for the Dynamic QR feature:
|
||||
|
||||
1. Adds qr_type column to qr_codes (VARCHAR 20, default 'standard')
|
||||
2. Creates qr_code_locations table
|
||||
3. Makes qr_codes.location nullable (was NOT NULL)
|
||||
4. Makes qr_codes.location_address nullable (was NOT NULL)
|
||||
|
||||
Usage (run once from the project root):
|
||||
python tools/migration_dynamic_qr_locations.py
|
||||
|
||||
Fully idempotent — safe to run multiple times without side effects.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import app, db
|
||||
|
||||
|
||||
def run_migration():
|
||||
with app.app_context():
|
||||
from sqlalchemy import text, inspect as sa_inspect
|
||||
|
||||
inspector = sa_inspect(db.engine)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Step 1: Add qr_type column to qr_codes if it does not exist yet
|
||||
# ----------------------------------------------------------------
|
||||
existing_cols = {c['name'] for c in inspector.get_columns('qr_codes')}
|
||||
|
||||
if 'qr_type' not in existing_cols:
|
||||
with db.engine.connect() as conn:
|
||||
conn.execute(text(
|
||||
"ALTER TABLE qr_codes "
|
||||
"ADD COLUMN qr_type VARCHAR(20) NOT NULL DEFAULT 'standard'"
|
||||
))
|
||||
conn.commit()
|
||||
print("✅ Added column: qr_codes.qr_type (default = 'standard')")
|
||||
else:
|
||||
print("ℹ️ Column qr_codes.qr_type already exists — skipped.")
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Step 2: Create qr_code_locations table if it does not exist yet
|
||||
# ----------------------------------------------------------------
|
||||
existing_tables = set(inspector.get_table_names())
|
||||
|
||||
if 'qr_code_locations' not in existing_tables:
|
||||
from models.qrcode import QRCodeLocation
|
||||
QRCodeLocation.__table__.create(db.engine, checkfirst=True)
|
||||
print("✅ Created table: qr_code_locations")
|
||||
else:
|
||||
print("ℹ️ Table qr_code_locations already exists — skipped.")
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Step 3: Make qr_codes.location and location_address nullable
|
||||
# Dynamic QR codes have no single fixed location/address so these
|
||||
# columns must allow NULL.
|
||||
# ----------------------------------------------------------------
|
||||
# Re-inspect to get current column definitions
|
||||
inspector2 = sa_inspect(db.engine)
|
||||
col_map = {c['name']: c for c in inspector2.get_columns('qr_codes')}
|
||||
|
||||
location_nullable = col_map.get('location', {}).get('nullable', True)
|
||||
loc_addr_nullable = col_map.get('location_address', {}).get('nullable', True)
|
||||
|
||||
if not location_nullable or not loc_addr_nullable:
|
||||
with db.engine.connect() as conn:
|
||||
if not location_nullable:
|
||||
conn.execute(text(
|
||||
"ALTER TABLE qr_codes "
|
||||
"MODIFY COLUMN location VARCHAR(100) NULL"
|
||||
))
|
||||
print("✅ Made qr_codes.location nullable")
|
||||
else:
|
||||
print("ℹ️ qr_codes.location already nullable — skipped.")
|
||||
|
||||
if not loc_addr_nullable:
|
||||
conn.execute(text(
|
||||
"ALTER TABLE qr_codes "
|
||||
"MODIFY COLUMN location_address TEXT NULL"
|
||||
))
|
||||
print("✅ Made qr_codes.location_address nullable")
|
||||
else:
|
||||
print("ℹ️ qr_codes.location_address already nullable — skipped.")
|
||||
|
||||
conn.commit()
|
||||
else:
|
||||
print("ℹ️ qr_codes.location and location_address already nullable — skipped.")
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Step 4: Add qr_address column to attendance_data if absent
|
||||
# Stores the selected location's address for dynamic QR check-ins
|
||||
# ----------------------------------------------------------------
|
||||
inspector3 = sa_inspect(db.engine)
|
||||
att_cols = {c['name'] for c in inspector3.get_columns('attendance_data')}
|
||||
if 'qr_address' not in att_cols:
|
||||
with db.engine.connect() as conn:
|
||||
conn.execute(text(
|
||||
"ALTER TABLE attendance_data ADD COLUMN qr_address TEXT NULL"
|
||||
))
|
||||
conn.commit()
|
||||
print("\u2705 Added column: attendance_data.qr_address")
|
||||
else:
|
||||
print("\u2139\ufe0f Column attendance_data.qr_address already exists \u2014 skipped.")
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Step 5: Add is_dynamic_qr column to attendance_data if absent
|
||||
# Flags records that came from a Dynamic QR code scan
|
||||
# ----------------------------------------------------------------
|
||||
if 'is_dynamic_qr' not in att_cols:
|
||||
with db.engine.connect() as conn:
|
||||
conn.execute(text(
|
||||
"ALTER TABLE attendance_data "
|
||||
"ADD COLUMN is_dynamic_qr TINYINT(1) NOT NULL DEFAULT 0"
|
||||
))
|
||||
conn.commit()
|
||||
print("\u2705 Added column: attendance_data.is_dynamic_qr")
|
||||
else:
|
||||
print("\u2139\ufe0f Column attendance_data.is_dynamic_qr already exists \u2014 skipped.")
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Step 6: Backfill is_dynamic_qr=1 on old broken records
|
||||
# Records created before this migration where location_name='Dynamic'
|
||||
# are legacy dynamic QR check-ins that were saved with the wrong name.
|
||||
# Mark them so the badge shows correctly in the attendance report.
|
||||
# ----------------------------------------------------------------
|
||||
with db.engine.connect() as conn:
|
||||
result = conn.execute(text(
|
||||
"UPDATE attendance_data "
|
||||
"SET is_dynamic_qr = 1 "
|
||||
"WHERE location_name = 'Dynamic' AND is_dynamic_qr = 0"
|
||||
))
|
||||
conn.commit()
|
||||
updated = result.rowcount
|
||||
if updated > 0:
|
||||
print(f"\u2705 Backfilled is_dynamic_qr=1 on {updated} legacy 'Dynamic' record(s)")
|
||||
else:
|
||||
print("\u2139\ufe0f No legacy 'Dynamic' records to backfill.")
|
||||
|
||||
print("\nMigration complete.")
|
||||
print("All existing QR codes remain fully unaffected (qr_type = 'standard').")
|
||||
print("All existing attendance records default to is_dynamic_qr = 0 (False).")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_migration()
|
||||
Reference in New Issue
Block a user