File size: 7,778 Bytes
ecd52b4 e57781a ecd52b4 e57781a ecd52b4 | 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 | from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import json
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(db.Model):
"""User model for authentication - supports both students and lecturers"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
matric_no = db.Column(db.String(50), unique=True, nullable=False) # A/B for students, L for lecturers
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=True)
password_hash = db.Column(db.String(255), nullable=False)
role = db.Column(db.String(20), nullable=False) # 'student', 'lecturer', or 'admin'
is_active = db.Column(db.Boolean, default=False)
status = db.Column(db.String(20), default='pending') # 'pending', 'approved', 'rejected'
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password):
"""Hash and set password"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Verify password"""
return check_password_hash(self.password_hash, password)
@staticmethod
def validate_matric_no(matric_no, role):
"""Validate matric number format based on role"""
if not matric_no:
return False, "Matric number is required"
matric_no = matric_no.upper().strip()
if role == 'student':
# Students must start with A or B
if not (matric_no.startswith('A') or matric_no.startswith('B')):
return False, "Student matric number must start with 'A' or 'B'"
elif role == 'lecturer':
# Lecturers must start with L
if not matric_no.startswith('L'):
return False, "Lecturer matric number must start with 'L'"
else:
return False, "Invalid role"
return True, None
def to_dict(self):
return {
'id': self.id,
'matric_no': self.matric_no,
'name': self.name,
'email': self.email,
'role': self.role,
'is_active': self.is_active,
'status': self.status,
'created_at': self.created_at.isoformat() + 'Z' if self.created_at else None
}
class Student(db.Model):
"""Student model - stores student information and face encodings"""
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), unique=True) # Link to User
student_id = db.Column(db.String(50), unique=True, nullable=False)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=True)
face_encoding = db.Column(db.Text) # Stored as JSON string
registered_at = db.Column(db.DateTime, default=datetime.utcnow)
is_active = db.Column(db.Boolean, default=True)
# Relationships
attendance_records = db.relationship('AttendanceRecord', backref='student', lazy=True)
user = db.relationship('User', backref='student_profile', uselist=False)
def get_face_encoding(self):
"""Deserialize face encoding from JSON"""
if self.face_encoding:
return json.loads(self.face_encoding)
return None
def set_face_encoding(self, encoding):
"""Serialize face encoding to JSON"""
if encoding is not None:
self.face_encoding = json.dumps(encoding.tolist())
def to_dict(self):
return {
'id': self.id,
'student_id': self.student_id,
'name': self.name,
'email': self.email,
'registered_at': self.registered_at.isoformat() + 'Z' if self.registered_at else None,
'is_active': self.is_active
}
class Classroom(db.Model):
"""Classroom model - stores classroom locations with GPS coordinates"""
__tablename__ = 'classrooms'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
building = db.Column(db.String(100))
latitude = db.Column(db.Float, nullable=False)
longitude = db.Column(db.Float, nullable=False)
radius_meters = db.Column(db.Integer, default=50) # Geofencing radius
# Relationships
sessions = db.relationship('AttendanceSession', backref='classroom', lazy=True)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'building': self.building,
'latitude': self.latitude,
'longitude': self.longitude,
'radius_meters': self.radius_meters
}
class AttendanceSession(db.Model):
"""Attendance session - represents a class session with auto-refresh codes"""
__tablename__ = 'attendance_sessions'
id = db.Column(db.Integer, primary_key=True)
course_name = db.Column(db.String(100), nullable=False)
classroom_id = db.Column(db.Integer, db.ForeignKey('classrooms.id'), nullable=False)
lecturer_name = db.Column(db.String(100), nullable=False)
# Session timing
start_time = db.Column(db.DateTime, default=datetime.utcnow)
end_time = db.Column(db.DateTime)
is_active = db.Column(db.Boolean, default=True)
# Auto-refresh attendance code
current_code = db.Column(db.String(10))
code_generated_at = db.Column(db.DateTime)
# Relationships
attendance_records = db.relationship('AttendanceRecord', backref='session', lazy=True)
def to_dict(self):
return {
'id': self.id,
'course_name': self.course_name,
'classroom_id': self.classroom_id,
'classroom': self.classroom.to_dict() if self.classroom else None,
'lecturer_name': self.lecturer_name,
'start_time': self.start_time.isoformat() + 'Z' if self.start_time else None,
'end_time': self.end_time.isoformat() + 'Z' if self.end_time else None,
'is_active': self.is_active,
'current_code': self.current_code,
'code_generated_at': self.code_generated_at.isoformat() + 'Z' if self.code_generated_at else None
}
class AttendanceRecord(db.Model):
"""Attendance record - individual student attendance for a session"""
__tablename__ = 'attendance_records'
id = db.Column(db.Integer, primary_key=True)
session_id = db.Column(db.Integer, db.ForeignKey('attendance_sessions.id'), nullable=False)
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
# Verification details
marked_at = db.Column(db.DateTime, default=datetime.utcnow)
face_verified = db.Column(db.Boolean, default=False)
location_verified = db.Column(db.Boolean, default=False)
code_verified = db.Column(db.Boolean, default=False)
# Location data
student_latitude = db.Column(db.Float)
student_longitude = db.Column(db.Float)
distance_from_classroom = db.Column(db.Float) # In meters
# Status
status = db.Column(db.String(20), default='present') # present, late, absent
def to_dict(self):
return {
'id': self.id,
'session_id': self.session_id,
'student': self.student.to_dict() if self.student else None,
'marked_at': self.marked_at.isoformat() + 'Z' if self.marked_at else None,
'face_verified': self.face_verified,
'location_verified': self.location_verified,
'code_verified': self.code_verified,
'distance_from_classroom': self.distance_from_classroom,
'status': self.status
}
|