setup update 2
This commit is contained in:
+13
-12
@@ -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
@@ -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
@@ -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
@@ -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')
|
||||||
|
|||||||
+19
-3
@@ -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
|
||||||
|
|
||||||
@@ -13,29 +14,44 @@ main_bp = Blueprint('main', __name__)
|
|||||||
def dashboard():
|
def dashboard():
|
||||||
"""Main dashboard page"""
|
"""Main dashboard page"""
|
||||||
# Get statistics
|
# Get statistics
|
||||||
|
try:
|
||||||
total_classrooms = Classroom.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()
|
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()
|
||||||
|
try:
|
||||||
today_reservations = Reservation.query.filter(
|
today_reservations = Reservation.query.filter(
|
||||||
func.date(Reservation.start_time) == today
|
func.date(Reservation.start_time) == today
|
||||||
).count()
|
).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)
|
||||||
|
try:
|
||||||
week_reservations = Reservation.query.filter(
|
week_reservations = Reservation.query.filter(
|
||||||
func.date(Reservation.start_time) >= week_start,
|
func.date(Reservation.start_time) >= week_start,
|
||||||
func.date(Reservation.start_time) <= week_end
|
func.date(Reservation.start_time) <= week_end
|
||||||
).count()
|
).count()
|
||||||
|
except:
|
||||||
|
week_reservations = 0
|
||||||
|
|
||||||
# Get upcoming reservations (next 7 days)
|
# Get upcoming reservations (next 7 days)
|
||||||
|
try:
|
||||||
upcoming_reservations = Reservation.query.filter(
|
upcoming_reservations = Reservation.query.filter(
|
||||||
Reservation.start_time >= datetime.utcnow(),
|
Reservation.start_time >= datetime.utcnow(),
|
||||||
Reservation.start_time <= datetime.utcnow() + timedelta(days=7),
|
Reservation.start_time <= datetime.utcnow() + timedelta(days=7)
|
||||||
Reservation.status.in_(['CONFIRMED', 'PENDING'])
|
).limit(10).all()
|
||||||
).join(Classroom).join(Subject).limit(10).all()
|
except:
|
||||||
|
upcoming_reservations = []
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
'total_classrooms': total_classrooms,
|
'total_classrooms': total_classrooms,
|
||||||
|
|||||||
+4
-10
@@ -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 -->
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -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
@@ -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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user