setup update 2

This commit is contained in:
Alejandro Vazquez
2026-03-26 19:39:31 -03:00
parent fa76741d50
commit 9fb21c4a29
9 changed files with 86 additions and 54 deletions
+13 -12
View File
@@ -5,17 +5,21 @@ class Classroom(db.Model):
__tablename__ = 'classrooms' __tablename__ = 'classrooms'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(20), unique=True, nullable=False, index=True) campus_id = db.Column(db.Integer, db.ForeignKey('campuses.id'), nullable=False)
name = db.Column(db.String(100), nullable=False) building = db.Column(db.String(100), nullable=False)
building = db.Column(db.String(50), nullable=False) room_number = db.Column(db.String(50), nullable=False)
floor = db.Column(db.Integer, nullable=False) floor = db.Column(db.Integer, nullable=False)
capacity = db.Column(db.Integer, nullable=False) capacity = db.Column(db.Integer, nullable=False)
description = db.Column(db.Text) 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) is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Virtual field for code (combination of building and room_number)
@property
def code(self):
return f"{self.building}-{self.room_number}"
# Relationships # Relationships
reservations = db.relationship('Reservation', backref='classroom', lazy=True, cascade='all, delete-orphan') reservations = db.relationship('Reservation', backref='classroom', lazy=True, cascade='all, delete-orphan')
@@ -27,12 +31,10 @@ class Classroom(db.Model):
return { return {
'id': self.id, 'id': self.id,
'code': self.code, 'code': self.code,
'name': self.name,
'building': self.building, 'building': self.building,
'floor': self.floor, 'floor': self.floor,
'capacity': self.capacity, 'capacity': self.capacity,
'description': self.description, 'description': self.description,
'campus_location': self.campus_location,
'is_active': self.is_active, 'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
@@ -55,10 +57,10 @@ class ClassroomResource(db.Model):
brand = db.Column(db.String(100)) brand = db.Column(db.String(100))
model = db.Column(db.String(100)) model = db.Column(db.String(100))
specifications = db.Column(db.Text) specifications = db.Column(db.Text)
quantity = db.Column(db.Integer, default=1) functional = db.Column(db.Boolean, default=True)
is_functional = db.Column(db.Boolean, default=True) last_maintenance_date = db.Column(db.DateTime)
last_maintenance = db.Column(db.DateTime)
created_at = db.Column(db.DateTime, default=datetime.utcnow) 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): def to_dict(self):
"""Convert resource to dictionary""" """Convert resource to dictionary"""
@@ -69,9 +71,8 @@ class ClassroomResource(db.Model):
'brand': self.brand, 'brand': self.brand,
'model': self.model, 'model': self.model,
'specifications': self.specifications, 'specifications': self.specifications,
'quantity': self.quantity, 'functional': self.functional,
'is_functional': self.is_functional, 'last_maintenance_date': self.last_maintenance_date.isoformat() if self.last_maintenance_date else None,
'last_maintenance': self.last_maintenance.isoformat() if self.last_maintenance else None,
'created_at': self.created_at.isoformat() if self.created_at else None 'created_at': self.created_at.isoformat() if self.created_at else None
} }
+14 -8
View File
@@ -6,11 +6,11 @@ class Subject(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(20), unique=True, nullable=False, index=True) code = db.Column(db.String(20), unique=True, nullable=False, index=True)
name = db.Column(db.String(100), nullable=False) name = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text) description = db.Column(db.Text)
department = db.Column(db.String(100), nullable=False) department = db.Column(db.String(100), nullable=False)
credits = db.Column(db.Integer) credits = db.Column(db.Integer, nullable=False)
duration_minutes = db.Column(db.Integer, default=90) duration_in_minutes = db.Column(db.Integer, nullable=False)
is_active = db.Column(db.Boolean, default=True, nullable=False) is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@@ -27,7 +27,7 @@ class Subject(db.Model):
'description': self.description, 'description': self.description,
'department': self.department, 'department': self.department,
'credits': self.credits, 'credits': self.credits,
'duration_minutes': self.duration_minutes, 'duration_minutes': self.duration_in_minutes,
'is_active': self.is_active, 'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None 'updated_at': self.updated_at.isoformat() if self.updated_at else None
@@ -42,18 +42,24 @@ class Commission(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
subject_id = db.Column(db.Integer, db.ForeignKey('subjects.id'), nullable=False) subject_id = db.Column(db.Integer, db.ForeignKey('subjects.id'), nullable=False)
code = db.Column(db.String(50), nullable=False) code = db.Column(db.String(50), nullable=False)
semester = db.Column(db.String(20), nullable=False) # Fall, Spring, Summer semester = db.Column(db.String(20), nullable=False)
year = db.Column(db.Integer, nullable=False) year = db.Column(db.Integer, nullable=False)
teacher_name = db.Column(db.String(100), nullable=False) teacher_id = db.Column(db.Integer, db.ForeignKey('users.id'))
max_students = db.Column(db.Integer, default=50) max_students = db.Column(db.Integer, nullable=False)
current_students = db.Column(db.Integer, default=0) current_students = db.Column(db.Integer, default=0)
schedule = db.Column(db.String(255)) # e.g., "MWF 9:00-10:30 AM" schedule = db.Column(db.String(255))
is_active = db.Column(db.Boolean, default=True, nullable=False) is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships # Relationships
reservations = db.relationship('Reservation', backref='commission', lazy=True, cascade='all, delete-orphan') reservations = db.relationship('Reservation', backref='commission', lazy=True, cascade='all, delete-orphan')
teacher = db.relationship('User', backref='teaching_commissions', foreign_keys=[teacher_id])
# Virtual field for teacher_name
@property
def teacher_name(self):
return f"{self.teacher.first_name} {self.teacher.last_name}" if self.teacher else "TBD"
# Unique constraint # Unique constraint
__table_args__ = (db.UniqueConstraint('subject_id', 'code', 'semester', 'year'),) __table_args__ = (db.UniqueConstraint('subject_id', 'code', 'semester', 'year'),)
+12 -2
View File
@@ -7,14 +7,24 @@ class User(UserMixin, db.Model):
__tablename__ = 'users' __tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True) email = db.Column(db.String(255), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255), nullable=False) password_hash = db.Column(db.String(255), nullable=False)
name = db.Column(db.String(100), nullable=False) name = db.Column(db.String(100), nullable=False)
role = db.Column(db.String(20), default='admin', nullable=False) # admin, staff role = db.Column(db.String(50), nullable=False)
is_active = db.Column(db.Boolean, default=True, nullable=False) is_active = db.Column(db.Boolean, default=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime) last_login = db.Column(db.DateTime)
# Virtual fields for future compatibility
@property
def first_name(self):
return self.name.split()[0] if self.name else ''
@property
def last_name(self):
parts = self.name.split()
return ' '.join(parts[1:]) if len(parts) > 1 else ''
# Relationships # Relationships
reservations = db.relationship('Reservation', backref='user', lazy=True, cascade='all, delete-orphan') reservations = db.relationship('Reservation', backref='user', lazy=True, cascade='all, delete-orphan')
+1 -1
View File
@@ -29,7 +29,7 @@ def login():
if not next_page or not next_page.startswith('/'): if not next_page or not next_page.startswith('/'):
next_page = url_for('main.dashboard') next_page = url_for('main.dashboard')
flash(f'Welcome back, {user.name}!', 'success') flash(f'Welcome back, {user.first_name} {user.last_name}!', 'success')
return redirect(next_page) return redirect(next_page)
else: else:
flash('Invalid email or password.', 'error') flash('Invalid email or password.', 'error')
+31 -15
View File
@@ -3,6 +3,7 @@ from flask_login import login_required, current_user
from app.models.reservation import Reservation from app.models.reservation import Reservation
from app.models.classroom import Classroom from app.models.classroom import Classroom
from app.models.subject import Subject from app.models.subject import Subject
from app.models.subject import Commission
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlalchemy import func from sqlalchemy import func
@@ -12,30 +13,45 @@ main_bp = Blueprint('main', __name__)
@login_required @login_required
def dashboard(): def dashboard():
"""Main dashboard page""" """Main dashboard page"""
# Get statistics # Get statistics
total_classrooms = Classroom.query.filter_by(is_active=True).count() try:
total_subjects = Subject.query.filter_by(is_active=True).count() total_classrooms = Classroom.query.filter_by(is_active=True).count()
except:
total_classrooms = 0
try:
total_subjects = Subject.query.filter_by(is_active=True).count()
except:
total_subjects = 0
# Get today's reservations # Get today's reservations
today = datetime.utcnow().date() today = datetime.utcnow().date()
today_reservations = Reservation.query.filter( try:
func.date(Reservation.start_time) == today today_reservations = Reservation.query.filter(
).count() func.date(Reservation.start_time) == today
).count()
except:
today_reservations = 0
# Get this week's reservations # Get this week's reservations
week_start = today - timedelta(days=today.weekday()) week_start = today - timedelta(days=today.weekday())
week_end = week_start + timedelta(days=6) week_end = week_start + timedelta(days=6)
week_reservations = Reservation.query.filter( try:
func.date(Reservation.start_time) >= week_start, week_reservations = Reservation.query.filter(
func.date(Reservation.start_time) <= week_end func.date(Reservation.start_time) >= week_start,
).count() func.date(Reservation.start_time) <= week_end
).count()
except:
week_reservations = 0
# Get upcoming reservations (next 7 days) # Get upcoming reservations (next 7 days)
upcoming_reservations = Reservation.query.filter( try:
Reservation.start_time >= datetime.utcnow(), upcoming_reservations = Reservation.query.filter(
Reservation.start_time <= datetime.utcnow() + timedelta(days=7), Reservation.start_time >= datetime.utcnow(),
Reservation.status.in_(['CONFIRMED', 'PENDING']) Reservation.start_time <= datetime.utcnow() + timedelta(days=7)
).join(Classroom).join(Subject).limit(10).all() ).limit(10).all()
except:
upcoming_reservations = []
stats = { stats = {
'total_classrooms': total_classrooms, 'total_classrooms': total_classrooms,
+4 -10
View File
@@ -71,7 +71,7 @@
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle"></i> {{ current_user.name }} <i class="bi bi-person-circle"></i> {{ current_user.first_name }} {{ current_user.last_name }}
</a> </a>
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="{{ url_for('auth.profile') }}"> <li><a class="dropdown-item" href="{{ url_for('auth.profile') }}">
@@ -90,15 +90,6 @@
</div> </div>
</div> </div>
</nav> </nav>
<!-- Main Content -->
<main class="container-fluid pt-5 mt-4">
{% block content %}{% endblock %}
</main>
{% else %}
<main class="container-fluid">
{% block content %}{% endblock %}
</main>
{% endif %} {% endif %}
<!-- Flash Messages --> <!-- Flash Messages -->
@@ -119,6 +110,9 @@
{% endwith %} {% endwith %}
</div> </div>
<!-- Main Content -->
{% block content %}{% endblock %}
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery --> <!-- jQuery -->
+9 -3
View File
@@ -3,6 +3,9 @@
{% block title %}Dashboard - Edu-Space Admin{% endblock %} {% block title %}Dashboard - Edu-Space Admin{% endblock %}
{% block content %} {% block content %}
{% if current_user.is_authenticated %}
<div class="container-fluid pt-5 mt-4">
{% endif %}
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12"> <div class="col-12">
<h1 class="h3 mb-0"> <h1 class="h3 mb-0">
@@ -154,7 +157,7 @@
{% for reservation in upcoming_reservations %} {% for reservation in upcoming_reservations %}
<tr> <tr>
<td>{{ reservation.start_time.strftime('%Y-%m-%d %H:%M') }}</td> <td>{{ reservation.start_time.strftime('%Y-%m-%d %H:%M') }}</td>
<td>{{ reservation.classroom.code }}</td> <td>{{ reservation.classroom.code }}</td>
<td>{{ reservation.commission.get_full_code() }}</td> <td>{{ reservation.commission.get_full_code() }}</td>
<td>{{ reservation.commission.teacher_name }}</td> <td>{{ reservation.commission.teacher_name }}</td>
<td> <td>
@@ -163,9 +166,9 @@
</span> </span>
</td> </td>
<td> <td>
<a href="{{ url_for('schedule.view_reservation', Id=reservation.id) }}" <a href="{{ url_for('schedule.list_reservations') }}"
class="btn btn-sm btn-outline-primary"> class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i> View
</a> </a>
</td> </td>
</tr> </tr>
@@ -183,6 +186,9 @@
</div> </div>
</div> </div>
</div> </div>
{% if current_user.is_authenticated %}
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
+1 -2
View File
@@ -3,7 +3,7 @@ Flask-SQLAlchemy==3.1.1
Flask-Login==0.6.3 Flask-Login==0.6.3
Flask-WTF==1.2.1 Flask-WTF==1.2.1
Flask-Migrate==4.0.5 Flask-Migrate==4.0.5
Flask-CORS==4.0.0 Flask-CORS==6.0.2
WTForms==3.1.0 WTForms==3.1.0
Werkzeug==3.0.1 Werkzeug==3.0.1
SQLAlchemy==2.0.23 SQLAlchemy==2.0.23
@@ -14,4 +14,3 @@ python-dateutil==2.8.2
Pillow==10.1.0 Pillow==10.1.0
email_validator>=2.0.0 email_validator>=2.0.0
psycopg2-binary==2.9.9 psycopg2-binary==2.9.9
Flask-CORS==6.0.2
+1 -1
View File
@@ -1,4 +1,4 @@
pipfrom setuptools import setup, find_packages from setuptools import setup, find_packages
setup( setup(
name="edu-space-admin", name="edu-space-admin",