File size: 8,614 Bytes
f742815 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | """
Attendance Code Module for Attendr
Implements KR Rule #5: Auto-Refresh Code Confirmation
∀x ((ValidCodeEntry(User,x) ∧ WithinCycle(Code,x)) → MarkPresent(System,x))
"""
import random
import string
from datetime import datetime, timedelta
from models import AttendanceSession, db
from config import Config
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class AttendanceCodeModule:
"""Handles attendance code generation, validation, and auto-refresh"""
def __init__(self, code_length=None, refresh_interval=None):
"""
Initialize attendance code module
Args:
code_length: Length of the code (default from config)
refresh_interval: Code refresh interval in seconds (default from config)
"""
self.code_length = code_length or Config.CODE_LENGTH
self.refresh_interval = refresh_interval or Config.CODE_REFRESH_INTERVAL
logger.info(f"Attendance Code Module initialized: length={self.code_length}, refresh={self.refresh_interval}s")
def generate_code(self):
"""
Generate a random attendance code
Returns:
String code (e.g., "A3X9K2")
"""
# Use uppercase letters and digits for clarity
characters = string.ascii_uppercase + string.digits
code = ''.join(random.choice(characters) for _ in range(self.code_length))
logger.info(f"Generated new code: {code}")
return code
def create_session_code(self, session_id):
"""
Create and store a new code for a session
Args:
session_id: ID of the attendance session
Returns:
tuple: (success, code or error_message)
"""
try:
session = AttendanceSession.query.get(session_id)
if not session:
return False, "Session not found"
if not session.is_active:
return False, "Session is not active"
# Generate new code
new_code = self.generate_code()
# Update session
session.current_code = new_code
session.code_generated_at = datetime.utcnow()
db.session.commit()
logger.info(f"Session {session_id} code updated to: {new_code}")
return True, new_code
except Exception as e:
db.session.rollback()
logger.error(f"Error creating session code: {str(e)}")
return False, f"Failed to create code: {str(e)}"
def is_code_valid(self, session_id, entered_code):
"""
Validate if entered code matches current session code and is within refresh cycle
Implements: ValidCodeEntry(User,x) ∧ WithinCycle(Code,x)
Args:
session_id: ID of the attendance session
entered_code: Code entered by student
Returns:
tuple: (is_valid: bool, message: str)
"""
try:
session = AttendanceSession.query.get(session_id)
if not session:
return False, "Session not found"
if not session.is_active:
return False, "Session is no longer active"
if not session.current_code:
return False, "No active code for this session"
# Check if code matches (case-insensitive)
if session.current_code.upper() != entered_code.upper():
logger.warning(f"Code mismatch: expected {session.current_code}, got {entered_code}")
return False, "Invalid code. Please check and try again."
# Check if code is within refresh cycle
if not session.code_generated_at:
return False, "Code timestamp not found"
time_elapsed = (datetime.utcnow() - session.code_generated_at).total_seconds()
if time_elapsed > self.refresh_interval:
logger.warning(f"Code expired: {time_elapsed:.0f}s elapsed (max: {self.refresh_interval}s)")
return False, f"Code has expired. Please use the current code displayed by your lecturer."
# Code is valid
time_remaining = self.refresh_interval - time_elapsed
logger.info(f"Code validated successfully. Time remaining: {time_remaining:.0f}s")
return True, "Code verified successfully"
except Exception as e:
logger.error(f"Code validation error: {str(e)}")
return False, f"Validation failed: {str(e)}"
def should_refresh_code(self, session_id):
"""
Check if code should be refreshed based on time elapsed
Args:
session_id: ID of the attendance session
Returns:
bool: True if code should be refreshed
"""
try:
session = AttendanceSession.query.get(session_id)
if not session or not session.is_active:
return False
if not session.code_generated_at:
return True # No code generated yet
time_elapsed = (datetime.utcnow() - session.code_generated_at).total_seconds()
return time_elapsed >= self.refresh_interval
except Exception as e:
logger.error(f"Error checking refresh status: {str(e)}")
return False
def auto_refresh_code(self, session_id):
"""
Automatically refresh code if needed
Args:
session_id: ID of the attendance session
Returns:
tuple: (refreshed: bool, code or None)
"""
try:
if self.should_refresh_code(session_id):
success, code = self.create_session_code(session_id)
if success:
logger.info(f"Code auto-refreshed for session {session_id}")
return True, code
else:
return False, None
else:
# Return current code
session = AttendanceSession.query.get(session_id)
if session and session.current_code:
return False, session.current_code
return False, None
except Exception as e:
logger.error(f"Auto-refresh error: {str(e)}")
return False, None
def get_code_status(self, session_id):
"""
Get current code status including time remaining
Args:
session_id: ID of the attendance session
Returns:
dict: Code status information
"""
try:
session = AttendanceSession.query.get(session_id)
if not session:
return {'error': 'Session not found'}
if not session.is_active:
return {'error': 'Session is not active'}
if not session.current_code or not session.code_generated_at:
return {
'code': None,
'time_remaining': 0,
'needs_refresh': True
}
time_elapsed = (datetime.utcnow() - session.code_generated_at).total_seconds()
time_remaining = max(0, self.refresh_interval - time_elapsed)
needs_refresh = time_elapsed >= self.refresh_interval
return {
'code': session.current_code,
'time_remaining': int(time_remaining),
'time_elapsed': int(time_elapsed),
'refresh_interval': self.refresh_interval,
'needs_refresh': needs_refresh,
'generated_at': session.code_generated_at.isoformat()
}
except Exception as e:
logger.error(f"Error getting code status: {str(e)}")
return {'error': str(e)}
# Singleton instance
attendance_code_module = AttendanceCodeModule()
|