Files
GOV_QR_Codes_Management/tools/migration_dynamic_qr_locations.py
T

151 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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()