| 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 |
|
|
| |
| logging.basicConfig(level=logging.DEBUG) |
| logger = logging.getLogger(__name__) |
|
|
| app = Flask(__name__) |
|
|
| |
| app.secret_key = os.environ.get("SECRET_KEY", secrets.token_hex(16)) |
| logger.info(f"App initialized with secret key") |
|
|
| |
| HF_SPACE_ID = os.environ.get("HF_SPACE_ID", "") |
| logger.info(f"HF_SPACE_ID: {HF_SPACE_ID}") |
|
|
| |
| |
| DB_PATHS = [ |
| |
| os.path.join(os.environ.get("PERSISTENT_DIR", "/tmp"), 'attendance.db'), |
| os.path.join("/data", "attendance.db"), |
| |
| os.path.join(os.environ.get("HOME", ""), 'attendance.db'), |
| os.path.join(os.environ.get("HUGGINGFACE_DATA_DIR", "."), 'attendance.db'), |
| '/tmp/attendance.db', |
| './attendance.db' |
| ] |
|
|
| |
| DB_PATH = None |
| for path in DB_PATHS: |
| try: |
| dir_path = os.path.dirname(path) |
| if dir_path: |
| os.makedirs(dir_path, exist_ok=True) |
| |
| |
| 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") |
|
|
| |
| DEFAULT_TEACHER_PASSWORD = os.environ.get("TEACHER_PASSWORD", "admin123") |
|
|
| |
| DEFAULT_TEACHER_HASH = hashlib.sha256(DEFAULT_TEACHER_PASSWORD.encode()).hexdigest() |
| logger.info(f"Teacher password hash initialized") |
|
|
| |
| 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 © {{ 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 %}''' |
|
|
| |
| app.jinja_env.globals.update(now=datetime.now) |
|
|
| |
| 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 |
|
|
| |
| def get_db_connection(): |
| try: |
| conn = sqlite3.connect(DB_PATH) |
| conn.row_factory = sqlite3.Row |
| return conn |
| except sqlite3.Error as e: |
| logger.error(f"Database connection error: {e}") |
| raise |
|
|
| |
| def init_db(): |
| try: |
| conn = get_db_connection() |
| cursor = conn.cursor() |
| |
| |
| cursor.execute(''' |
| CREATE TABLE IF NOT EXISTS students ( |
| student_name TEXT PRIMARY KEY |
| ) |
| ''') |
| |
| |
| 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) |
| ) |
| ''') |
| |
| |
| cursor.execute(''' |
| CREATE TABLE IF NOT EXISTS attendance_window ( |
| is_open INTEGER DEFAULT 0, |
| opened_at TEXT, |
| closes_at TEXT |
| ) |
| ''') |
| |
| |
| 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 |
|
|
| |
| 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}") |
|
|
| |
| 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: |
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| def csrf_protected(f): |
| def decorated_function(*args, **kwargs): |
| |
| 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 |
|
|
| |
| @app.route('/') |
| def home(): |
| return redirect(url_for('login')) |
|
|
| |
| @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() |
| |
| |
| 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() |
| |
| |
| 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) |
|
|
| |
| @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) |
|
|
| |
| @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'] |
| |
| |
| 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: |
| |
| 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')) |
|
|
| |
| @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')) |
|
|
| |
| @app.route('/teacher', methods=['GET', 'POST']) |
| def teacher_login(): |
| if request.method == 'POST': |
| password = request.form.get('password', '') |
| |
| |
| 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() |
| |
| |
| cursor.execute('SELECT student_name FROM students ORDER BY student_name') |
| students = [row['student_name'] for row in cursor.fetchall()] |
| |
| |
| 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()] |
| |
| |
| 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')) |
|
|
| |
| @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 |
| |
| 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')) |
|
|
| |
| @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')) |
|
|
| |
| @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() |
| |
| |
| 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')) |
|
|
| |
| @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') |
| |
| |
| cursor.execute( |
| 'SELECT id FROM attendance_records WHERE student_name = ? AND date(timestamp) = ?', |
| (student_name, today) |
| ) |
| record = cursor.fetchone() |
| |
| if record: |
| |
| cursor.execute('DELETE FROM attendance_records WHERE id = ?', (record['id'],)) |
| status_msg = f'Attendance for {student_name} marked as absent' |
| else: |
| |
| 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')) |
|
|
| |
| @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')) |
| |
| |
| 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() |
| |
| |
| 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 not years: |
| years = [current_date.year] |
| |
| |
| cursor.execute('SELECT student_name FROM students ORDER BY student_name') |
| all_students = [row['student_name'] for row in cursor.fetchall()] |
| |
| |
| days_in_month = calendar.monthrange(selected_year, selected_month)[1] |
| |
| |
| 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') |
| |
| |
| 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 |
| |
| |
| |
| 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')) |
|
|
| |
| @app.route('/base.html') |
| def base_template(): |
| return render_template_string(BASE_HTML) |
|
|
| if __name__ == '__main__': |
| |
| port = int(os.environ.get('PORT', 8080)) |
| app.run(host='0.0.0.0', port=port, debug=False) |