add license

This commit is contained in:
Alejandro Vazquez
2026-03-26 10:14:40 -03:00
commit 48bace5167
29 changed files with 2802 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
from .user import User
from .classroom import Classroom
from .subject import Subject
from .reservation import Reservation
__all__ = ['User', 'Classroom', 'Subject', 'Reservation']
+79
View File
@@ -0,0 +1,79 @@
from app import db
from datetime import datetime
class Classroom(db.Model):
__tablename__ = 'classrooms'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(20), unique=True, nullable=False, index=True)
name = db.Column(db.String(100), nullable=False)
building = db.Column(db.String(50), nullable=False)
floor = db.Column(db.Integer, nullable=False)
capacity = db.Column(db.Integer, nullable=False)
description = db.Column(db.Text)
campus_location = db.Column(db.String(50), default='Main Campus')
is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
reservations = db.relationship('Reservation', backref='classroom', lazy=True, cascade='all, delete-orphan')
# Resources relationship (教室资源)
resources = db.relationship('ClassroomResource', backref='classroom', lazy=True, cascade='all, delete-orphan')
def to_dict(self):
"""Convert classroom to dictionary"""
return {
'id': self.id,
'code': self.code,
'name': self.name,
'building': self.building,
'floor': self.floor,
'capacity': self.capacity,
'description': self.description,
'campus_location': self.campus_location,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'resources': [resource.to_dict() for resource in self.resources]
}
def get_full_location(self):
"""Get full classroom location"""
return f"{self.building} - Floor {self.floor}"
def __repr__(self):
return f'<Classroom {self.code}>'
class ClassroomResource(db.Model):
__tablename__ = 'classroom_resources'
id = db.Column(db.Integer, primary_key=True)
classroom_id = db.Column(db.Integer, db.ForeignKey('classrooms.id'), nullable=False)
resource_type = db.Column(db.String(50), nullable=False) # PROJECTOR, WHITEBOARD, COMPUTER, etc.
brand = db.Column(db.String(100))
model = db.Column(db.String(100))
specifications = db.Column(db.Text)
quantity = db.Column(db.Integer, default=1)
is_functional = db.Column(db.Boolean, default=True)
last_maintenance = db.Column(db.DateTime)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
"""Convert resource to dictionary"""
return {
'id': self.id,
'classroom_id': self.classroom_id,
'resource_type': self.resource_type,
'brand': self.brand,
'model': self.model,
'specifications': self.specifications,
'quantity': self.quantity,
'is_functional': self.is_functional,
'last_maintenance': self.last_maintenance.isoformat() if self.last_maintenance else None,
'created_at': self.created_at.isoformat() if self.created_at else None
}
def __repr__(self):
return f'<Resource {self.resource_type} for Classroom {self.classroom_id}>'
+88
View File
@@ -0,0 +1,88 @@
from app import db
from datetime import datetime, timedelta
from enum import Enum
class ReservationStatus(Enum):
PENDING = 'PENDING'
CONFIRMED = 'CONFIRMED'
CANCELLED = 'CANCELLED'
COMPLETED = 'COMPLETED'
NO_SHOW = 'NO_SHOW'
class Reservation(db.Model):
__tablename__ = 'reservations'
id = db.Column(db.Integer, primary_key=True)
classroom_id = db.Column(db.Integer, db.ForeignKey('classrooms.id'), nullable=False)
commission_id = db.Column(db.Integer, db.ForeignKey('commissions.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
start_time = db.Column(db.DateTime, nullable=False)
end_time = db.Column(db.DateTime, nullable=False)
purpose = db.Column(db.String(500), nullable=False)
status = db.Column(db.Enum(ReservationStatus), default=ReservationStatus.PENDING, nullable=False)
expected_attendees = db.Column(db.Integer, nullable=False)
notes = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
"""Convert reservation to dictionary"""
return {
'id': self.id,
'classroom_id': self.classroom_id,
'commission_id': self.commission_id,
'user_id': self.user_id,
'start_time': self.start_time.isoformat() if self.start_time else None,
'end_time': self.end_time.isoformat() if self.end_time else None,
'purpose': self.purpose,
'status': self.status.value if self.status else None,
'expected_attendees': self.expected_attendees,
'notes': self.notes,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'classroom': self.classroom.to_dict() if self.classroom else None,
'commission': self.commission.to_dict() if self.commission else None,
'user': self.user.to_dict() if self.user else None
}
def is_confirmed(self):
"""Check if reservation is confirmed"""
return self.status == ReservationStatus.CONFIRMED
def can_be_cancelled(self):
"""Check if reservation can be cancelled (24 hours before start)"""
from datetime import timedelta
return self.start_time > datetime.utcnow() + timedelta(hours=24)
def check_conflict(self, other_reservation):
"""Check if this reservation conflicts with another"""
if self.classroom_id != other_reservation.classroom_id:
return False
if self.status != ReservationStatus.CONFIRMED:
return False
if other_reservation.status != ReservationStatus.CONFIRMED:
return False
# Check time overlap
return (self.start_time < other_reservation.end_time and
self.end_time > other_reservation.start_time)
@staticmethod
def find_conflicts(classroom_id, start_time, end_time, exclude_reservation_id=None):
"""Find conflicting reservations for a specific classroom and time slot"""
query = Reservation.query.filter(
Reservation.classroom_id == classroom_id,
Reservation.status == ReservationStatus.CONFIRMED,
Reservation.start_time < end_time,
Reservation.end_time > start_time
)
if exclude_reservation_id:
query = query.filter(Reservation.id != exclude_reservation_id)
return query.all()
def __repr__(self):
return f'<Reservation {self.id}>'
+84
View File
@@ -0,0 +1,84 @@
from app import db
from datetime import datetime
class Subject(db.Model):
__tablename__ = 'subjects'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(20), unique=True, nullable=False, index=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
department = db.Column(db.String(100), nullable=False)
credits = db.Column(db.Integer)
duration_minutes = db.Column(db.Integer, default=90)
is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
commissions = db.relationship('Commission', backref='subject', lazy=True, cascade='all, delete-orphan')
def to_dict(self):
"""Convert subject to dictionary"""
return {
'id': self.id,
'code': self.code,
'name': self.name,
'description': self.description,
'department': self.department,
'credits': self.credits,
'duration_minutes': self.duration_minutes,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
def __repr__(self):
return f'<Subject {self.code}>'
class Commission(db.Model):
__tablename__ = 'commissions'
id = db.Column(db.Integer, primary_key=True)
subject_id = db.Column(db.Integer, db.ForeignKey('subjects.id'), nullable=False)
code = db.Column(db.String(50), nullable=False)
semester = db.Column(db.String(20), nullable=False) # Fall, Spring, Summer
year = db.Column(db.Integer, nullable=False)
teacher_name = db.Column(db.String(100), nullable=False)
max_students = db.Column(db.Integer, default=50)
current_students = db.Column(db.Integer, default=0)
schedule = db.Column(db.String(255)) # e.g., "MWF 9:00-10:30 AM"
is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
reservations = db.relationship('Reservation', backref='commission', lazy=True, cascade='all, delete-orphan')
# Unique constraint
__table_args__ = (db.UniqueConstraint('subject_id', 'code', 'semester', 'year'),)
def to_dict(self):
"""Convert commission to dictionary"""
return {
'id': self.id,
'subject_id': self.subject_id,
'code': self.code,
'semester': self.semester,
'year': self.year,
'teacher_name': self.teacher_name,
'max_students': self.max_students,
'current_students': self.current_students,
'schedule': self.schedule,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'subject': self.subject.to_dict() if self.subject else None
}
def get_full_code(self):
"""Get full commission code"""
return f"{self.subject.code if self.subject else 'N/A'}-{self.code}-{self.semester.upper()}{self.year if self.year else ''}"
def __repr__(self):
return f'<Commission {self.get_full_code()}>'
+51
View File
@@ -0,0 +1,51 @@
from app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255), nullable=False)
name = db.Column(db.String(100), nullable=False)
role = db.Column(db.String(20), default='admin', nullable=False) # admin, staff
is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime)
# Relationships
reservations = db.relationship('Reservation', backref='user', lazy=True, cascade='all, delete-orphan')
def set_password(self, password):
"""Set password hash"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Check password"""
return check_password_hash(self.password_hash, password)
def is_admin(self):
"""Check if user is admin"""
return self.role == 'admin'
def update_last_login(self):
"""Update last login timestamp"""
self.last_login = datetime.utcnow()
db.session.commit()
def to_dict(self):
"""Convert user to dictionary"""
return {
'id': self.id,
'email': self.email,
'name': self.name,
'role': self.role,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'last_login': self.last_login.isoformat() if self.last_login else None
}
def __repr__(self):
return f'<User {self.email}>'