Jacksonnavigator7's picture
Update app.py
db116ef verified
from flask import Flask, render_template_string, request, redirect, url_for, flash, session
import sqlite3
import os
from datetime import datetime, timedelta
import calendar
import hashlib
import secrets
import logging
# Set up logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
app = Flask(__name__)
# Generate a random secret key at startup (more secure than hardcoded)
app.secret_key = os.environ.get("SECRET_KEY", secrets.token_hex(16))
logger.info(f"App initialized with secret key")
# Hugging Face specific environment variables
HF_SPACE_ID = os.environ.get("HF_SPACE_ID", "")
logger.info(f"HF_SPACE_ID: {HF_SPACE_ID}")
# For Hugging Face Spaces, prioritize their specific persistent storage paths
# These locations are known to work with Hugging Face Spaces
DB_PATHS = [
# Hugging Face specific paths
os.path.join(os.environ.get("PERSISTENT_DIR", "/tmp"), 'attendance.db'), # New persistent directory in HF
os.path.join("/data", "attendance.db"), # Common persistent path in Hugging Face
# Fallback options
os.path.join(os.environ.get("HOME", ""), 'attendance.db'), # Home directory
os.path.join(os.environ.get("HUGGINGFACE_DATA_DIR", "."), 'attendance.db'), # HF data dir if available
'/tmp/attendance.db', # Standard temp directory
'./attendance.db' # Current directory as last resort
]
# Find a writable location for the database
DB_PATH = None
for path in DB_PATHS:
try:
dir_path = os.path.dirname(path)
if dir_path: # If there's a directory component
os.makedirs(dir_path, exist_ok=True)
# Test if we can write to this location
with open(path, 'a'):
pass
DB_PATH = path
logger.info(f"Using database at: {DB_PATH}")
break
except (IOError, OSError, PermissionError) as e:
logger.warning(f"Cannot use {path}: {e}")
continue
if DB_PATH is None:
logger.error("Could not find a writable location for the database")
raise RuntimeError("Could not find a writable location for the database")
# Set default teacher password (override with environment variable in production)
DEFAULT_TEACHER_PASSWORD = os.environ.get("TEACHER_PASSWORD", "admin123")
# Hash the default password
DEFAULT_TEACHER_HASH = hashlib.sha256(DEFAULT_TEACHER_PASSWORD.encode()).hexdigest()
logger.info(f"Teacher password hash initialized")
# HTML Templates as strings
BASE_HTML = '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Student Attendance System{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.navbar {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card {
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.card-header {
border-radius: 8px 8px 0 0 !important;
}
.btn {
border-radius: 4px;
padding: 8px 16px;
}
.btn-primary {
background-color: #0d6efd;
}
.btn-success {
background-color: #198754;
}
.btn-danger {
background-color: #dc3545;
}
.table th {
background-color: #f1f3f5;
}
.progress {
height: 10px;
border-radius: 5px;
}
footer {
background-color: #f1f3f5;
border-top: 1px solid #e1e4e8;
}
@media (max-width: 768px) {
.card-body {
padding: 1rem;
}
.btn {
width: 100%;
margin-bottom: 0.5rem;
}
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="#">Student Attendance System</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
{% if session.get('student_name') %}
<li class="nav-item">
<span class="nav-link">Welcome, {{ session.get('student_name') }}</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
</li>
{% elif session.get('is_teacher') %}
<li class="nav-item">
<span class="nav-link">Teacher Mode</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('teacher_dashboard') }}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('teacher_report') }}">Monthly Report</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category if category != 'message' else 'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<footer class="mt-5 py-3 text-center text-muted">
<div class="container">
<p>Student Attendance System &copy; {{ now().year }}</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>'''
LOGIN_HTML = '''{% extends "base.html" %}
{% block title %}Student Login | Attendance System{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Student Login</h4>
</div>
<div class="card-body">
<form action="{{ url_for('login') }}" method="post">
<div class="mb-3">
<label for="student_name" class="form-label">Your Full Name</label>
<input type="text" class="form-control" id="student_name" name="student_name" required>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
<hr>
<div class="text-center">
<a href="{{ url_for('teacher_login') }}" class="btn btn-outline-secondary">Teacher Login</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}'''
ATTENDANCE_HTML = '''{% extends "base.html" %}
{% block title %}Mark Attendance | Attendance System{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Welcome, {{ student_name }}</h4>
</div>
<div class="card-body text-center">
<h5 class="mb-4">Today's Attendance</h5>
{% if already_marked %}
<div class="alert alert-success">
You have already marked your attendance for today.
</div>
{% elif window_open %}
<div class="alert alert-info">
Attendance window is currently open! Please mark your attendance.
</div>
<form action="{{ url_for('mark_attendance') }}" method="post">
<button type="submit" class="btn btn-lg btn-success">
Mark Present
</button>
</form>
{% else %}
<div class="alert alert-warning">
Attendance window is currently closed. Please wait for your teacher to open it.
</div>
{% endif %}
</div>
<div class="card-footer">
<div class="text-center">
<a href="{{ url_for('logout') }}" class="btn btn-outline-secondary">Logout</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}'''
TEACHER_LOGIN_HTML = '''{% extends "base.html" %}
{% block title %}Teacher Login | Attendance System{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Teacher Login</h4>
</div>
<div class="card-body">
<form action="{{ url_for('teacher_login') }}" method="post">
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
<div class="form-text">Default password is "admin123" (without quotes)</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
<hr>
<div class="text-center">
<a href="{{ url_for('login') }}" class="btn btn-outline-secondary">Student Login</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}'''
TEACHER_DASHBOARD_HTML = '''{% extends "base.html" %}
{% block title %}Teacher Dashboard | Attendance System{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col-md-8">
<h3>Teacher Dashboard</h3>
</div>
<div class="col-md-4 text-end">
<a href="{{ url_for('teacher_report') }}" class="btn btn-info">View Monthly Report</a>
</div>
</div>
<div class="row">
<!-- Attendance Window Control -->
<div class="col-md-4">
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Attendance Window</h5>
</div>
<div class="card-body">
{% if is_open %}
<div class="alert alert-success">
<strong>Status:</strong> Open<br>
<strong>Opened at:</strong> {{ opened_at }}<br>
<strong>Closes at:</strong> {{ closes_at }}
</div>
<form action="{{ url_for('close_window') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-danger">Close Window Now</button>
</form>
{% else %}
<div class="alert alert-secondary">
<strong>Status:</strong> Closed
</div>
<form action="{{ url_for('open_window') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="duration" class="form-label">Duration (minutes)</label>
<input type="number" class="form-control" id="duration" name="duration" value="10" min="1" max="60">
</div>
<button type="submit" class="btn btn-success">Open Window</button>
</form>
{% endif %}
</div>
</div>
<!-- Add Student Card -->
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Add New Student</h5>
</div>
<div class="card-body">
<form action="{{ url_for('add_student') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="student_name" class="form-label">Student Name</label>
<input type="text" class="form-control" id="student_name" name="student_name" required>
</div>
<button type="submit" class="btn btn-primary">Add Student</button>
</form>
</div>
</div>
</div>
<!-- Today's Attendance -->
<div class="col-md-8">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Today's Attendance</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col">
<div class="d-flex justify-content-between align-items-center">
<h6>Total Students: {{ students|length }}</h6>
<h6>Present: {{ present_students|length }} / {{ students|length }}</h6>
</div>
<div class="progress">
{% if students|length > 0 %}
<div class="progress-bar bg-success" role="progressbar"
style="width: {{ (present_students|length / students|length) * 100 }}%"
aria-valuenow="{{ present_students|length }}"
aria-valuemin="0"
aria-valuemax="{{ students|length }}"></div>
{% else %}
<div class="progress-bar bg-success" role="progressbar"
style="width: 0%"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="0"></div>
{% endif %}
</div>
</div>
</div>
{% if students|length > 0 %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Student Name</th>
<th class="text-center">Status</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
{% for student in students %}
<tr>
<td>{{ student }}</td>
<td class="text-center">
{% if student in present_students %}
<span class="badge bg-success">Present</span>
{% else %}
<span class="badge bg-danger">Absent</span>
{% endif %}
</td>
<td class="text-center">
<form action="{{ url_for('toggle_attendance') }}" method="post" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="student_name" value="{{ student }}">
{% if student in present_students %}
<button type="submit" class="btn btn-sm btn-outline-danger">Mark Absent</button>
{% else %}
<button type="submit" class="btn btn-sm btn-outline-success">Mark Present</button>
{% endif %}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">
No students registered yet. Add students using the form on the left.
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}'''
TEACHER_REPORT_HTML = '''{% extends "base.html" %}
{% block title %}Monthly Report | Attendance System{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col-md-6">
<h3>Monthly Attendance Report</h3>
</div>
<div class="col-md-6 text-end">
<form action="{{ url_for('teacher_report') }}" method="get" class="row g-2">
<div class="col-auto">
<select name="month" class="form-select">
{% for m in range(1, 13) %}
<option value="{{ m }}" {% if m == selected_month %}selected{% endif %}>
{{ month_names[m-1] }}
</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<select name="year" class="form-select">
{% for y in years %}
<option value="{{ y }}" {% if y == selected_year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">View</button>
</div>
<div class="col-auto">
<a href="{{ url_for('teacher_dashboard') }}" class="btn btn-secondary">Back to Dashboard</a>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Student Attendance Summary for {{ month_names[selected_month-1] }} {{ selected_year }}</h5>
</div>
<div class="card-body">
{% if attendance_data|length > 0 %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Student Name</th>
<th class="text-center">Days Present</th>
<th class="text-center">Total Days</th>
<th class="text-center">Attendance Percentage</th>
<th class="text-center">Status</th>
</tr>
</thead>
<tbody>
{% for student in attendance_data %}
<tr>
<td>{{ student.name }}</td>
<td class="text-center">{{ student.days_present }}</td>
<td class="text-center">{{ student.total_days }}</td>
<td class="text-center">{{ student.percentage }}%</td>
<td class="text-center">
{% if student.percentage >= 75 %}
<span class="badge bg-success">Good</span>
{% elif student.percentage >= 60 %}
<span class="badge bg-warning text-dark">Average</span>
{% else %}
<span class="badge bg-danger">Poor</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">
No attendance data available for {{ month_names[selected_month-1] }} {{ selected_year }}.
</div>
{% endif %}
</div>
<div class="card-footer">
<div class="small text-muted">
<ul>
<li>Good: Attendance ≥ 75%</li>
<li>Average: Attendance between 60% and 75%</li>
<li>Poor: Attendance < 60%</li>
</ul>
</div>
</div>
</div>
{% endblock %}'''
# Register jinja2 globals
app.jinja_env.globals.update(now=datetime.now)
# Setup CSRF protection
def generate_csrf_token():
if 'csrf_token' not in session:
session['csrf_token'] = secrets.token_hex(16)
return session['csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
# Database connection helper with context manager
def get_db_connection():
try:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # This enables column access by name
return conn
except sqlite3.Error as e:
logger.error(f"Database connection error: {e}")
raise
# Database initialization
def init_db():
try:
conn = get_db_connection()
cursor = conn.cursor()
# Create students table
cursor.execute('''
CREATE TABLE IF NOT EXISTS students (
student_name TEXT PRIMARY KEY
)
''')
# Create attendance_records table
cursor.execute('''
CREATE TABLE IF NOT EXISTS attendance_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_name TEXT,
timestamp TEXT,
FOREIGN KEY (student_name) REFERENCES students (student_name)
)
''')
# Create attendance_window table
cursor.execute('''
CREATE TABLE IF NOT EXISTS attendance_window (
is_open INTEGER DEFAULT 0,
opened_at TEXT,
closes_at TEXT
)
''')
# Check if attendance_window has any rows
cursor.execute('SELECT COUNT(*) FROM attendance_window')
if cursor.fetchone()[0] == 0:
cursor.execute('INSERT INTO attendance_window (is_open, opened_at, closes_at) VALUES (0, NULL, NULL)')
conn.commit()
conn.close()
logger.info("Database initialized successfully")
except Exception as e:
logger.error(f"Error initializing database: {e}")
raise
# Initialize database on startup
try:
init_db()
logger.info("Database initialization completed")
except Exception as e:
logger.error(f"Failed to initialize database: {e}")
raise RuntimeError(f"Database initialization failed: {e}")
# Helper function to check if attendance window is open
def is_attendance_window_open():
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT is_open, closes_at FROM attendance_window')
result = cursor.fetchone()
conn.close()
if not result:
logger.warning("No attendance window record found")
return False
if result['is_open'] == 1 and result['closes_at'] is not None:
closes_at = datetime.strptime(result['closes_at'], '%Y-%m-%d %H:%M:%S')
if closes_at > datetime.now():
return True
else:
# Auto-close window if time has passed
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('UPDATE attendance_window SET is_open = 0')
conn.commit()
conn.close()
logger.info("Auto-closed expired attendance window")
return False
except Exception as e:
logger.error(f"Error checking attendance window: {e}")
return False
# Helper function to check if student has already marked attendance today
def has_marked_attendance_today(student_name):
try:
conn = get_db_connection()
cursor = conn.cursor()
today = datetime.now().strftime('%Y-%m-%d')
cursor.execute(
'SELECT COUNT(*) as count FROM attendance_records WHERE student_name = ? AND date(timestamp) = ?',
(student_name, today)
)
result = cursor.fetchone()
conn.close()
return result and result['count'] > 0
except Exception as e:
logger.error(f"Error checking attendance: {e}")
return False
# CSRF protection decorator
def csrf_protected(f):
def decorated_function(*args, **kwargs):
# Skip CSRF check for GET requests
if request.method == "POST":
token = session.get('csrf_token')
if not token or token != request.form.get('csrf_token'):
logger.warning("CSRF validation failed")
flash('CSRF validation failed. Please try again.', 'danger')
return redirect(url_for('login'))
return f(*args, **kwargs)
decorated_function.__name__ = f.__name__
return decorated_function
# Home route (redirects to login)
@app.route('/')
def home():
return redirect(url_for('login'))
# Student login route
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
student_name = request.form.get('student_name', '').strip()
if not student_name:
flash('Please enter your name', 'warning')
return redirect(url_for('login'))
try:
conn = get_db_connection()
cursor = conn.cursor()
# Check if student exists, if not register them
cursor.execute('SELECT student_name FROM students WHERE student_name = ?', (student_name,))
if cursor.fetchone() is None:
cursor.execute('INSERT INTO students (student_name) VALUES (?)', (student_name,))
conn.commit()
flash(f'New student {student_name} registered!', 'success')
conn.close()
# Set session and redirect to attendance page
session['student_name'] = student_name
return redirect(url_for('attendance'))
except Exception as e:
logger.error(f"Login error: {e}")
flash(f'An error occurred during login. Please try again.', 'danger')
return redirect(url_for('login'))
return render_template_string(LOGIN_HTML)
# Student attendance route
@app.route('/attendance')
def attendance():
if 'student_name' not in session:
flash('Please login first', 'warning')
return redirect(url_for('login'))
student_name = session['student_name']
window_open = is_attendance_window_open()
already_marked = has_marked_attendance_today(student_name)
return render_template_string(ATTENDANCE_HTML,
student_name=student_name,
window_open=window_open,
already_marked=already_marked)
# Mark attendance route
@app.route('/mark_attendance', methods=['POST'])
def mark_attendance():
if 'student_name' not in session:
flash('Please login first', 'warning')
return redirect(url_for('login'))
student_name = session['student_name']
# Check if window is open and attendance not already marked
if not is_attendance_window_open():
flash('Attendance window is closed', 'warning')
return redirect(url_for('attendance'))
if has_marked_attendance_today(student_name):
flash('You have already marked your attendance today', 'info')
return redirect(url_for('attendance'))
try:
# Mark attendance
conn = get_db_connection()
cursor = conn.cursor()
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
cursor.execute(
'INSERT INTO attendance_records (student_name, timestamp) VALUES (?, ?)',
(student_name, current_time)
)
conn.commit()
conn.close()
flash('Attendance marked successfully!', 'success')
except Exception as e:
logger.error(f"Error marking attendance: {e}")
flash('Error marking attendance. Please try again.', 'danger')
return redirect(url_for('attendance'))
# Student logout route
@app.route('/logout')
def logout():
session.pop('student_name', None)
session.pop('is_teacher', None)
session.pop('csrf_token', None)
flash('You have been logged out', 'info')
return redirect(url_for('login'))
# Teacher login route
@app.route('/teacher', methods=['GET', 'POST'])
def teacher_login():
if request.method == 'POST':
password = request.form.get('password', '')
# Hash the entered password and compare with stored hash
password_hash = hashlib.sha256(password.encode()).hexdigest()
if password_hash == DEFAULT_TEACHER_HASH:
session['is_teacher'] = True
flash('Welcome, Teacher!', 'success')
return redirect(url_for('teacher_dashboard'))
else:
logger.warning("Failed teacher login attempt")
flash('Incorrect password', 'danger')
return render_template_string(TEACHER_LOGIN_HTML)
@app.route('/teacher/dashboard')
def teacher_dashboard():
if not session.get('is_teacher'):
flash('Please login as a teacher first', 'warning')
return redirect(url_for('teacher_login'))
try:
conn = get_db_connection()
cursor = conn.cursor()
# Get all students
cursor.execute('SELECT student_name FROM students ORDER BY student_name')
students = [row['student_name'] for row in cursor.fetchall()]
# Get today's attendance
today = datetime.now().strftime('%Y-%m-%d')
cursor.execute(
'SELECT DISTINCT student_name FROM attendance_records WHERE date(timestamp) = ?',
(today,)
)
present_students = [row['student_name'] for row in cursor.fetchall()]
# Get attendance window status
cursor.execute('SELECT is_open, opened_at, closes_at FROM attendance_window')
window = cursor.fetchone()
is_open = window and window['is_open'] == 1
opened_at = window and window['opened_at']
closes_at = window and window['closes_at']
conn.close()
return render_template_string(TEACHER_DASHBOARD_HTML,
students=students,
present_students=present_students,
is_open=is_open,
opened_at=opened_at,
closes_at=closes_at)
except Exception as e:
logger.error(f"Error loading dashboard: {e}")
flash('Error loading dashboard data', 'danger')
return redirect(url_for('login'))
# Open attendance window route
@app.route('/teacher/open_window', methods=['POST'])
@csrf_protected
def open_window():
if not session.get('is_teacher'):
flash('Please login as a teacher first', 'warning')
return redirect(url_for('teacher_login'))
try:
duration = int(request.form.get('duration', 10))
if duration < 1 or duration > 60:
duration = 10 # Default to 10 minutes if invalid
now = datetime.now()
closes_at = now + timedelta(minutes=duration)
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(
'UPDATE attendance_window SET is_open = 1, opened_at = ?, closes_at = ?',
(now.strftime('%Y-%m-%d %H:%M:%S'), closes_at.strftime('%Y-%m-%d %H:%M:%S'))
)
conn.commit()
conn.close()
flash(f'Attendance window opened for {duration} minutes', 'success')
except Exception as e:
logger.error(f"Error opening attendance window: {e}")
flash('Error opening attendance window', 'danger')
return redirect(url_for('teacher_dashboard'))
# Close attendance window route
@app.route('/teacher/close_window', methods=['POST'])
@csrf_protected
def close_window():
if not session.get('is_teacher'):
flash('Please login as a teacher first', 'warning')
return redirect(url_for('teacher_login'))
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('UPDATE attendance_window SET is_open = 0')
conn.commit()
conn.close()
flash('Attendance window closed', 'success')
except Exception as e:
logger.error(f"Error closing attendance window: {e}")
flash('Error closing attendance window', 'danger')
return redirect(url_for('teacher_dashboard'))
# Add student route
@app.route('/teacher/add_student', methods=['POST'])
@csrf_protected
def add_student():
if not session.get('is_teacher'):
flash('Please login as a teacher first', 'warning')
return redirect(url_for('teacher_login'))
student_name = request.form.get('student_name', '').strip()
if not student_name:
flash('Please enter a student name', 'warning')
return redirect(url_for('teacher_dashboard'))
try:
conn = get_db_connection()
cursor = conn.cursor()
# Check if student already exists
cursor.execute('SELECT student_name FROM students WHERE student_name = ?', (student_name,))
if cursor.fetchone() is not None:
flash(f'Student {student_name} already exists', 'warning')
else:
cursor.execute('INSERT INTO students (student_name) VALUES (?)', (student_name,))
conn.commit()
flash(f'Student {student_name} added successfully', 'success')
conn.close()
except Exception as e:
logger.error(f"Error adding student: {e}")
flash('Error adding student', 'danger')
return redirect(url_for('teacher_dashboard'))
# Toggle student attendance route
@app.route('/teacher/toggle_attendance', methods=['POST'])
@csrf_protected
def toggle_attendance():
if not session.get('is_teacher'):
flash('Please login as a teacher first', 'warning')
return redirect(url_for('teacher_login'))
student_name = request.form.get('student_name', '').strip()
if not student_name:
flash('Invalid student selected', 'warning')
return redirect(url_for('teacher_dashboard'))
try:
conn = get_db_connection()
cursor = conn.cursor()
today = datetime.now().strftime('%Y-%m-%d')
# Check if student has already marked attendance today
cursor.execute(
'SELECT id FROM attendance_records WHERE student_name = ? AND date(timestamp) = ?',
(student_name, today)
)
record = cursor.fetchone()
if record:
# Delete attendance record if exists
cursor.execute('DELETE FROM attendance_records WHERE id = ?', (record['id'],))
status_msg = f'Attendance for {student_name} marked as absent'
else:
# Add attendance record if not exists
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
cursor.execute(
'INSERT INTO attendance_records (student_name, timestamp) VALUES (?, ?)',
(student_name, current_time)
)
status_msg = f'Attendance for {student_name} marked as present'
conn.commit()
conn.close()
flash(status_msg, 'success')
except Exception as e:
logger.error(f"Error toggling attendance: {e}")
flash('Error updating attendance', 'danger')
return redirect(url_for('teacher_dashboard'))
# Monthly attendance report route
@app.route('/teacher/report')
def teacher_report():
if not session.get('is_teacher'):
flash('Please login as a teacher first', 'warning')
return redirect(url_for('teacher_login'))
# Get selected month and year
current_date = datetime.now()
selected_month = int(request.args.get('month', current_date.month))
selected_year = int(request.args.get('year', current_date.year))
try:
conn = get_db_connection()
cursor = conn.cursor()
# Get all years that have attendance records
cursor.execute('SELECT DISTINCT strftime("%Y", timestamp) as year FROM attendance_records ORDER BY year DESC')
years = [int(row['year']) for row in cursor.fetchall()]
# If no records, use current year
if not years:
years = [current_date.year]
# Get all students
cursor.execute('SELECT student_name FROM students ORDER BY student_name')
all_students = [row['student_name'] for row in cursor.fetchall()]
# Calculate days in selected month
days_in_month = calendar.monthrange(selected_year, selected_month)[1]
# Get the month start and end dates
month_start = datetime(selected_year, selected_month, 1).strftime('%Y-%m-%d')
month_end = datetime(selected_year, selected_month, days_in_month).strftime('%Y-%m-%d')
# Count the number of days each student was present in the selected month
attendance_data = []
for student in all_students:
cursor.execute(
'''SELECT COUNT(DISTINCT date(timestamp)) as days_present
FROM attendance_records
WHERE student_name = ? AND
date(timestamp) BETWEEN ? AND ?''',
(student, month_start, month_end)
)
result = cursor.fetchone()
days_present = result['days_present'] if result else 0
# Calculate attendance percentage
# For current month, use days elapsed instead of all days in month
if selected_month == current_date.month and selected_year == current_date.year:
total_days = min(current_date.day, days_in_month)
else:
total_days = days_in_month
percentage = round((days_present / total_days) * 100) if total_days > 0 else 0
attendance_data.append({
'name': student,
'days_present': days_present,
'total_days': total_days,
'percentage': percentage
})
conn.close()
month_names = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
]
return render_template_string(TEACHER_REPORT_HTML,
attendance_data=attendance_data,
selected_month=selected_month,
selected_year=selected_year,
month_names=month_names,
years=years)
except Exception as e:
logger.error(f"Error generating report: {e}")
flash('Error generating attendance report', 'danger')
return redirect(url_for('teacher_dashboard'))
# Base template route
@app.route('/base.html')
def base_template():
return render_template_string(BASE_HTML)
if __name__ == '__main__':
# Get port from environment or use default
port = int(os.environ.get('PORT', 8080))
app.run(host='0.0.0.0', port=port, debug=False)