Created
July 15, 2025 16:52
-
-
Save Irfan-Habeeb/24d3ee1a53a5f7362551e4df9c323f92 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from datetime import datetime | |
| def safe_parse_date(date_str): | |
| try: | |
| return datetime.strptime(date_str, '%Y-%m-%d').date() | |
| except (ValueError, TypeError): | |
| return None | |
| from flask import render_template, request, redirect, url_for, flash, session, make_response, jsonify | |
| from app import app, db | |
| from models import Admin, Student, Tutor, Attendance, StudentTutorLink | |
| from datetime import date | |
| from utils import generate_parent_invoice, generate_tutor_invoice | |
| import logging | |
| from functools import wraps | |
| logger = logging.getLogger(__name__) | |
| # Admin authentication decorator | |
| def admin_required(f): | |
| @wraps(f) | |
| def decorated_function(*args, **kwargs): | |
| if 'admin_id' not in session: | |
| flash('Please log in to access the admin dashboard.', 'error') | |
| return redirect(url_for('admin_login')) | |
| return f(*args, **kwargs) | |
| return decorated_function | |
| @app.route('/') | |
| def index(): | |
| return redirect(url_for('tutor_login')) | |
| # Admin Authentication Routes (Secret URL) | |
| @app.route('/admin-login', methods=['GET', 'POST']) | |
| def admin_login(): | |
| if request.method == 'POST': | |
| username = request.form['username'] | |
| password = request.form['password'] | |
| admin = Admin.query.filter_by(username=username).first() | |
| if admin and admin.check_password(password): | |
| session['admin_id'] = admin.id | |
| session['admin_username'] = admin.username | |
| flash(f'Welcome back, {admin.username}!', 'success') | |
| return redirect(url_for('admin_dashboard')) | |
| else: | |
| flash('Invalid username or password.', 'error') | |
| return render_template('admin_login.html') | |
| @app.route('/admin_logout') | |
| def admin_logout(): | |
| session.pop('admin_id', None) | |
| session.pop('admin_username', None) | |
| flash('You have been logged out.', 'info') | |
| return redirect(url_for('admin_login')) | |
| @app.route('/admin') | |
| @admin_required | |
| def admin_dashboard(): | |
| from datetime import datetime, timedelta | |
| from sqlalchemy import func, extract | |
| # Get filter parameters | |
| filter_period = request.args.get('filter_period', 'week') | |
| start_date = request.args.get('start_date') | |
| end_date = request.args.get('end_date') | |
| # Set date range based on filter | |
| today = datetime.now().date() | |
| if filter_period == 'week': | |
| start_date = today - timedelta(days=7) | |
| end_date = today | |
| elif filter_period == 'month': | |
| start_date = today.replace(day=1) | |
| end_date = today | |
| elif filter_period == 'year': | |
| start_date = today.replace(month=1, day=1) | |
| end_date = today | |
| elif filter_period == 'custom' and start_date and end_date: | |
| start_date = datetime.strptime(start_date, '%Y-%m-%d').date() | |
| end_date = datetime.strptime(end_date, '%Y-%m-%d').date() | |
| else: | |
| start_date = today - timedelta(days=7) | |
| end_date = today | |
| total_students = Student.query.count() | |
| total_tutors = Tutor.query.count() | |
| total_attendance = Attendance.query.count() | |
| # ✅ Revenue: fix broken SQL by calculating in Python | |
| student_class_counts = ( | |
| db.session.query( | |
| Student.id, | |
| Student.per_class_fee, | |
| func.count(Attendance.id) | |
| ) | |
| .join(Attendance) | |
| .filter(Attendance.date.between(start_date, end_date)) | |
| .group_by(Student.id, Student.per_class_fee) | |
| .all() | |
| ) | |
| total_revenue = sum(fee * count for _, fee, count in student_class_counts) | |
| total_payout = db.session.query( | |
| func.sum(StudentTutorLink.pay_per_class) | |
| ).select_from(StudentTutorLink).join(Attendance, | |
| (StudentTutorLink.student_id == Attendance.student_id) & | |
| (StudentTutorLink.tutor_id == Attendance.tutor_id) | |
| ).filter( | |
| Attendance.date.between(start_date, end_date) | |
| ).scalar() or 0 | |
| net_balance = total_revenue - total_payout | |
| top_students = db.session.query( | |
| Student.name, | |
| func.count(Attendance.id).label('class_count') | |
| ).join(Attendance).filter( | |
| Attendance.date.between(start_date, end_date) | |
| ).group_by(Student.id, Student.name).order_by( | |
| func.count(Attendance.id).desc() | |
| ).limit(10).all() | |
| monthly_earnings = db.session.query( | |
| extract('month', Attendance.date).label('month'), | |
| extract('year', Attendance.date).label('year'), | |
| func.sum(Student.per_class_fee).label('revenue'), | |
| func.sum(StudentTutorLink.pay_per_class).label('payout') | |
| ).select_from(Attendance).join(Student).join(StudentTutorLink, | |
| (StudentTutorLink.student_id == Attendance.student_id) & | |
| (StudentTutorLink.tutor_id == Attendance.tutor_id) | |
| ).filter( | |
| Attendance.date >= (today - timedelta(days=365)) | |
| ).group_by( | |
| extract('month', Attendance.date), | |
| extract('year', Attendance.date) | |
| ).order_by('year', 'month').all() | |
| return render_template('admin_dashboard.html', | |
| total_students=total_students, | |
| total_tutors=total_tutors, | |
| total_attendance=total_attendance, | |
| total_revenue=total_revenue, | |
| total_payout=total_payout, | |
| net_balance=net_balance, | |
| top_students=top_students, | |
| monthly_earnings=monthly_earnings, | |
| filter_period=filter_period, | |
| start_date=start_date, | |
| end_date=end_date | |
| ) | |
| # Separate student and tutor views | |
| @app.route('/admin/students') | |
| @admin_required | |
| def admin_students(): | |
| # Search and filter parameters | |
| student_search = request.args.get('student_search', '') | |
| student_class_filter = request.args.get('student_class_filter', '') | |
| student_tutor_filter = request.args.get('student_tutor_filter', '') | |
| # Pagination parameters | |
| student_page = request.args.get('student_page', 1, type=int) | |
| per_page = 20 | |
| # Build student query | |
| student_query = Student.query | |
| if student_search: | |
| student_query = student_query.filter(Student.name.contains(student_search)) | |
| if student_class_filter: | |
| student_query = student_query.filter(Student.class_level == student_class_filter) | |
| # Get paginated results | |
| students = student_query.paginate(page=student_page, per_page=per_page, error_out=False) | |
| # Get filter options | |
| all_tutors = Tutor.query.all() | |
| student_classes = db.session.query(Student.class_level).distinct().all() | |
| return render_template('admin_students.html', | |
| students=students, | |
| all_tutors=all_tutors, | |
| student_classes=[c[0] for c in student_classes], | |
| student_search=student_search, | |
| student_class_filter=student_class_filter, | |
| student_tutor_filter=student_tutor_filter) | |
| @app.route('/admin/tutors') | |
| @admin_required | |
| def admin_tutors(): | |
| # Search and filter parameters | |
| tutor_search = request.args.get('tutor_search', '') | |
| tutor_class_filter = request.args.get('tutor_class_filter', '') | |
| # Pagination parameters | |
| tutor_page = request.args.get('tutor_page', 1, type=int) | |
| per_page = 20 | |
| # Build tutor query | |
| tutor_query = Tutor.query | |
| if tutor_search: | |
| tutor_query = tutor_query.filter(Tutor.name.contains(tutor_search)) | |
| if tutor_class_filter: | |
| tutor_query = tutor_query.filter(Tutor.class_group == tutor_class_filter) | |
| # Get paginated results | |
| tutors = tutor_query.paginate(page=tutor_page, per_page=per_page, error_out=False) | |
| # Get filter options | |
| tutor_classes = db.session.query(Tutor.class_group).distinct().all() | |
| return render_template('admin_tutors.html', | |
| tutors=tutors, | |
| tutor_classes=[c[0] for c in tutor_classes], | |
| tutor_search=tutor_search, | |
| tutor_class_filter=tutor_class_filter) | |
| # Payment tracking routes | |
| @app.route('/admin/payments') | |
| @admin_required | |
| def admin_payments(): | |
| tab = request.args.get('tab', 'students') | |
| if tab == 'students': | |
| students = Student.query.all() | |
| return render_template('admin_payments.html', students=students, tab=tab) | |
| else: | |
| tutors = Tutor.query.all() | |
| return render_template('admin_payments.html', tutors=tutors, tab=tab) | |
| @app.route('/admin/update_payment_status', methods=['POST']) | |
| @admin_required | |
| def update_payment_status(): | |
| entity_type = request.form['entity_type'] | |
| entity_id = request.form['entity_id'] | |
| status = request.form['status'] | |
| payment_date = request.form.get('payment_date') | |
| if entity_type == 'student': | |
| student = Student.query.get_or_404(entity_id) | |
| student.payment_status = status | |
| if payment_date: | |
| student.last_payment_date = datetime.strptime(payment_date, '%Y-%m-%d').date() | |
| db.session.commit() | |
| flash('Student payment status updated successfully!', 'success') | |
| else: | |
| tutor = Tutor.query.get_or_404(entity_id) | |
| tutor.payment_status = status | |
| if payment_date: | |
| tutor.last_payment_date = datetime.strptime(payment_date, '%Y-%m-%d').date() | |
| db.session.commit() | |
| flash('Tutor payment status updated successfully!', 'success') | |
| return redirect(url_for('admin_payments', tab=entity_type + 's')) | |
| # Student Management Routes | |
| @app.route('/add_student', methods=['GET', 'POST']) | |
| @admin_required | |
| def add_student(): | |
| if request.method == 'POST': | |
| try: | |
| name = request.form['name'] | |
| parent_name = request.form['parent_name'] | |
| class_level = request.form['class_level'] | |
| per_class_fee = int(request.form['per_class_fee']) | |
| subjects = request.form['subjects'] | |
| # Get selected tutors and their pay rates | |
| selected_tutors = request.form.getlist('selected_tutors') | |
| tutor_pay_rates = {} | |
| for tutor_id in selected_tutors: | |
| pay_rate = request.form.get(f'pay_rate_{tutor_id}') | |
| if pay_rate: | |
| tutor_pay_rates[int(tutor_id)] = int(pay_rate) | |
| student = Student( | |
| name=name, | |
| parent_name=parent_name, | |
| class_level=class_level, | |
| per_class_fee=per_class_fee, | |
| subjects=subjects | |
| ) | |
| db.session.add(student) | |
| db.session.flush() # To get student ID | |
| # Create tutor links | |
| for tutor_id, pay_rate in tutor_pay_rates.items(): | |
| link = StudentTutorLink( | |
| student_id=student.id, | |
| tutor_id=tutor_id, | |
| pay_per_class=pay_rate | |
| ) | |
| db.session.add(link) | |
| db.session.commit() | |
| flash('Student added successfully!', 'success') | |
| return redirect(url_for('admin_students')) | |
| except Exception as e: | |
| logger.error(f"Error adding student: {e}") | |
| flash('Error adding student. Please try again.', 'error') | |
| tutors = Tutor.query.all() | |
| return render_template('add_student.html', tutors=tutors) | |
| @app.route('/edit_student/<int:student_id>', methods=['GET', 'POST']) | |
| @admin_required | |
| def edit_student(student_id): | |
| student = Student.query.get_or_404(student_id) | |
| if request.method == 'POST': | |
| try: | |
| student.name = request.form['name'] | |
| student.parent_name = request.form['parent_name'] | |
| student.class_level = request.form['class_level'] | |
| student.per_class_fee = int(request.form['per_class_fee']) | |
| student.subjects = request.form['subjects'] | |
| # Get selected tutors and their pay rates | |
| selected_tutors = request.form.getlist('selected_tutors') | |
| tutor_pay_rates = {} | |
| for tutor_id in selected_tutors: | |
| pay_rate = request.form.get(f'pay_rate_{tutor_id}') | |
| if pay_rate: | |
| tutor_pay_rates[int(tutor_id)] = int(pay_rate) | |
| # Remove existing tutor links | |
| StudentTutorLink.query.filter_by(student_id=student.id).delete() | |
| # Create new tutor links | |
| for tutor_id, pay_rate in tutor_pay_rates.items(): | |
| link = StudentTutorLink( | |
| student_id=student.id, | |
| tutor_id=tutor_id, | |
| pay_per_class=pay_rate | |
| ) | |
| db.session.add(link) | |
| db.session.commit() | |
| flash('Student updated successfully!', 'success') | |
| return redirect(url_for('admin_students')) | |
| except Exception as e: | |
| logger.error(f"Error updating student: {e}") | |
| flash('Error updating student. Please try again.', 'error') | |
| tutors = Tutor.query.all() | |
| # Get current tutor assignments | |
| current_links = {link.tutor_id: link.pay_per_class for link in student.tutor_links} | |
| return render_template('edit_student.html', student=student, tutors=tutors, current_links=current_links) | |
| @app.route('/delete_student/<int:student_id>') | |
| @admin_required | |
| def delete_student(student_id): | |
| try: | |
| student = Student.query.get_or_404(student_id) | |
| # Delete associated attendance records | |
| Attendance.query.filter_by(student_id=student_id).delete() | |
| db.session.delete(student) | |
| db.session.commit() | |
| flash('Student deleted successfully!', 'success') | |
| except Exception as e: | |
| logger.error(f"Error deleting student: {e}") | |
| flash('Error deleting student. Please try again.', 'error') | |
| return redirect(url_for('admin_dashboard')) | |
| # Tutor Management Routes | |
| @app.route('/generate_tutor_credentials', methods=['POST']) | |
| @admin_required | |
| def generate_tutor_credentials(): | |
| from datetime import datetime | |
| name = request.form['name'] | |
| dob = request.form['date_of_birth'] | |
| mobile = request.form['mobile_number'] | |
| # Parse DOB | |
| dob_date = datetime.strptime(dob, '%Y-%m-%d').date() | |
| # Generate username: first name + day + month of DOB | |
| first_name = name.split()[0].lower() | |
| username = f"{first_name}{dob_date.day:02d}{dob_date.month:02d}" | |
| # Check if username exists and modify if needed | |
| counter = 1 | |
| original_username = username | |
| while Tutor.query.filter_by(username=username).first(): | |
| username = f"{original_username}{counter}" | |
| counter += 1 | |
| # Generate password: mobile number + year of birth | |
| password = f"{mobile}{dob_date.year}" | |
| return jsonify({ | |
| 'username': username, | |
| 'password': password | |
| }) | |
| @app.route('/add_tutor', methods=['GET', 'POST']) | |
| @admin_required | |
| def add_tutor(): | |
| if request.method == 'POST': | |
| try: | |
| name = request.form['name'] | |
| class_group = request.form['class_group'] | |
| date_of_birth = datetime.strptime(request.form['date_of_birth'], '%Y-%m-%d').date() | |
| mobile_number = request.form['mobile_number'] | |
| payment_details = request.form.get('payment_details', '') | |
| username = request.form['username'] | |
| password = request.form['password'] | |
| # Check if username already exists | |
| existing_tutor = Tutor.query.filter_by(username=username).first() | |
| if existing_tutor: | |
| flash('Username already exists. Please choose a different username.', 'error') | |
| return render_template('add_tutor.html') | |
| tutor = Tutor( | |
| name=name, | |
| class_group=class_group, | |
| date_of_birth=date_of_birth, | |
| mobile_number=mobile_number, | |
| payment_details=payment_details, | |
| username=username | |
| ) | |
| tutor.set_password(password) | |
| db.session.add(tutor) | |
| db.session.commit() | |
| flash('Tutor added successfully!', 'success') | |
| return redirect(url_for('admin_tutors')) | |
| except Exception as e: | |
| logger.error(f"Error adding tutor: {e}") | |
| flash('Error adding tutor. Please try again.', 'error') | |
| return render_template('add_tutor.html') | |
| @app.route('/edit_tutor/<int:tutor_id>', methods=['GET', 'POST']) | |
| @admin_required | |
| def edit_tutor(tutor_id): | |
| tutor = Tutor.query.get_or_404(tutor_id) | |
| if request.method == 'POST': | |
| try: | |
| tutor.name = request.form['name'] | |
| tutor.class_group = request.form['class_group'] | |
| tutor.per_class_pay = int(request.form['per_class_pay']) | |
| # Check if username changed and if new username exists | |
| new_username = request.form['username'] | |
| if new_username != tutor.username: | |
| existing_tutor = Tutor.query.filter_by(username=new_username).first() | |
| if existing_tutor: | |
| flash('Username already exists. Please choose a different username.', 'error') | |
| return render_template('edit_tutor.html', tutor=tutor) | |
| tutor.username = new_username | |
| # Update password if provided | |
| new_password = request.form.get('password') | |
| if new_password: | |
| tutor.set_password(new_password) | |
| db.session.commit() | |
| flash('Tutor updated successfully!', 'success') | |
| return redirect(url_for('admin_tutors')) | |
| except Exception as e: | |
| logger.error(f"Error updating tutor: {e}") | |
| flash('Error updating tutor. Please try again.', 'error') | |
| return render_template('edit_tutor.html', tutor=tutor) | |
| @app.route('/delete_tutor/<int:tutor_id>') | |
| @admin_required | |
| def delete_tutor(tutor_id): | |
| try: | |
| tutor = Tutor.query.get_or_404(tutor_id) | |
| # Update students to remove this tutor assignment | |
| Student.query.filter_by(assigned_tutor_id=tutor_id).update({'assigned_tutor_id': None}) | |
| # Delete associated attendance records | |
| Attendance.query.filter_by(tutor_id=tutor_id).delete() | |
| db.session.delete(tutor) | |
| db.session.commit() | |
| flash('Tutor deleted successfully!', 'success') | |
| except Exception as e: | |
| logger.error(f"Error deleting tutor: {e}") | |
| flash('Error deleting tutor. Please try again.', 'error') | |
| return redirect(url_for('admin_dashboard')) | |
| # Tutor Login and Attendance Routes | |
| @app.route('/login', methods=['GET', 'POST']) | |
| @app.route('/tutor_login', methods=['GET', 'POST']) | |
| def tutor_login(): | |
| if request.method == 'POST': | |
| username = request.form['username'] | |
| password = request.form['password'] | |
| tutor = Tutor.query.filter_by(username=username).first() | |
| if tutor and tutor.check_password(password): | |
| session['tutor_id'] = tutor.id | |
| session['tutor_name'] = tutor.name | |
| flash(f'Welcome, {tutor.name}!', 'success') | |
| return redirect(url_for('tutor_dashboard')) | |
| else: | |
| flash('Invalid username or password.', 'error') | |
| return render_template('tutor_login.html') | |
| @app.route('/tutor_logout') | |
| def tutor_logout(): | |
| session.pop('tutor_id', None) | |
| session.pop('tutor_name', None) | |
| flash('You have been logged out.', 'info') | |
| return redirect(url_for('tutor_login')) | |
| @app.route('/tutor_dashboard') | |
| def tutor_dashboard(): | |
| if 'tutor_id' not in session: | |
| flash('Please log in to access this page.', 'error') | |
| return redirect(url_for('tutor_login')) | |
| from datetime import datetime, timedelta | |
| tutor_id = session['tutor_id'] | |
| tutor = Tutor.query.get_or_404(tutor_id) | |
| # Get filter parameters | |
| filter_period = request.args.get('filter_period', 'week') | |
| start_date = request.args.get('start_date') | |
| end_date = request.args.get('end_date') | |
| # Set date range based on filter | |
| today = datetime.now().date() | |
| if filter_period == 'day': | |
| start_date = end_date = today | |
| elif filter_period == 'week': | |
| start_date = today - timedelta(days=7) | |
| end_date = today | |
| elif filter_period == 'month': | |
| start_date = today.replace(day=1) | |
| end_date = today | |
| elif filter_period == 'custom' and start_date and end_date: | |
| start_date = datetime.strptime(start_date, '%Y-%m-%d').date() | |
| end_date = datetime.strptime(end_date, '%Y-%m-%d').date() | |
| else: | |
| start_date = today - timedelta(days=7) | |
| end_date = today | |
| # Get attendance records with filters | |
| attendance_query = Attendance.query.filter( | |
| Attendance.tutor_id == tutor_id, | |
| Attendance.date.between(start_date, end_date) | |
| ).order_by(Attendance.created_at.desc()) | |
| recent_attendance = attendance_query.limit(10).all() | |
| # Get assigned students through links | |
| assigned_students = db.session.query(Student).join(StudentTutorLink).filter( | |
| StudentTutorLink.tutor_id == tutor_id | |
| ).all() | |
| # Calculate student performance averages | |
| student_performance = db.session.query( | |
| Student.name, | |
| func.avg(Attendance.rating).label('avg_rating'), | |
| func.count(Attendance.id).label('class_count') | |
| ).join(Attendance).filter( | |
| Attendance.tutor_id == tutor_id, | |
| Attendance.date.between(start_date, end_date) | |
| ).group_by(Student.id, Student.name).all() | |
| return render_template('tutor_dashboard.html', | |
| tutor=tutor, | |
| assigned_students=assigned_students, | |
| recent_attendance=recent_attendance, | |
| student_performance=student_performance, | |
| filter_period=filter_period, | |
| start_date=start_date, | |
| end_date=end_date) | |
| @app.route('/submit', methods=['GET', 'POST']) | |
| def tutor_submit(): | |
| if 'tutor_id' not in session: | |
| flash('Please log in to access this page.', 'error') | |
| return redirect(url_for('tutor_login')) | |
| tutor_id = session['tutor_id'] | |
| tutor = Tutor.query.get_or_404(tutor_id) | |
| # Get assigned students through links | |
| assigned_students = db.session.query(Student).join(StudentTutorLink).filter( | |
| StudentTutorLink.tutor_id == tutor_id | |
| ).all() | |
| if request.method == 'POST': | |
| try: | |
| student_id = int(request.form['student_id']) | |
| subject = request.form['subject'] | |
| attendance_date = datetime.strptime(request.form['date'], '%Y-%m-%d').date() | |
| start_time = datetime.strptime(request.form['start_time'], '%H:%M').time() | |
| end_time = datetime.strptime(request.form['end_time'], '%H:%M').time() | |
| rating = int(request.form['rating']) | |
| remarks = request.form.get('remarks', '') | |
| attendance = Attendance( | |
| tutor_id=tutor_id, | |
| student_id=student_id, | |
| subject=subject, | |
| date=attendance_date, | |
| start_time=start_time, | |
| end_time=end_time, | |
| rating=rating, | |
| remarks=remarks | |
| ) | |
| db.session.add(attendance) | |
| db.session.commit() | |
| flash('Attendance submitted successfully!', 'success') | |
| return redirect(url_for('tutor_dashboard')) | |
| except Exception as e: | |
| logger.error(f"Error submitting attendance: {e}") | |
| flash('Error submitting attendance. Please try again.', 'error') | |
| return render_template('tutor_submit.html', | |
| tutor=tutor, | |
| students=assigned_students, | |
| today=date.today().strftime('%Y-%m-%d')) | |
| # AJAX route to get student subjects | |
| @app.route('/get_student_subjects/<int:student_id>') | |
| def get_student_subjects(student_id): | |
| student = Student.query.get_or_404(student_id) | |
| subjects = student.get_subjects_list() | |
| return jsonify({'subjects': subjects}) | |
| # Student Profile Route | |
| @app.route('/student_profile/<int:student_id>') | |
| @admin_required | |
| def student_profile(student_id): | |
| student = Student.query.get_or_404(student_id) | |
| # Filter parameters | |
| subject_filter = request.args.get('subject_filter', '') | |
| month_filter = request.args.get('month_filter', '') | |
| start_date = request.args.get('start_date', '') | |
| end_date = request.args.get('end_date', '') | |
| # Build attendance query | |
| attendance_query = Attendance.query.filter_by(student_id=student_id) | |
| if subject_filter: | |
| attendance_query = attendance_query.filter(Attendance.subject == subject_filter) | |
| if month_filter: | |
| year, month = month_filter.split('-') | |
| from datetime import datetime | |
| start_month = datetime(int(year), int(month), 1).date() | |
| if int(month) == 12: | |
| end_month = datetime(int(year) + 1, 1, 1).date() | |
| else: | |
| end_month = datetime(int(year), int(month) + 1, 1).date() | |
| attendance_query = attendance_query.filter( | |
| Attendance.date >= start_month, | |
| Attendance.date < end_month | |
| ) | |
| elif safe_parse_date(start_date) and safe_parse_date(end_date): | |
| start = safe_parse_date(start_date) | |
| end = safe_parse_date(end_date) | |
| attendance_query = attendance_query.filter( | |
| Attendance.date >= safe_parse_date(start_date), | |
| Attendance.date <= safe_parse_date(end_date) | |
| ) | |
| attendance_records = attendance_query.order_by(Attendance.date.desc()).all() | |
| # Group by subject | |
| subject_attendance = {} | |
| for record in attendance_records: | |
| if record.subject not in subject_attendance: | |
| subject_attendance[record.subject] = [] | |
| subject_attendance[record.subject].append(record) | |
| # Get available subjects and months for filters | |
| all_subjects = db.session.query(Attendance.subject).filter_by(student_id=student_id).distinct().all() | |
| available_subjects = [s[0] for s in all_subjects] | |
| return render_template('student_profile.html', | |
| student=student, | |
| subject_attendance=subject_attendance, | |
| attendance_records=attendance_records, | |
| available_subjects=available_subjects, | |
| subject_filter=subject_filter, | |
| month_filter=month_filter, | |
| start_date=start_date, | |
| end_date=end_date) | |
| # Tutor Profile Route | |
| @app.route('/tutor_profile/<int:tutor_id>') | |
| @admin_required | |
| def tutor_profile(tutor_id): | |
| tutor = Tutor.query.get_or_404(tutor_id) | |
| # Filter parameters | |
| subject_filter = request.args.get('subject_filter', '') | |
| month_filter = request.args.get('month_filter', '') | |
| start_date = request.args.get('start_date', '') | |
| end_date = request.args.get('end_date', '') | |
| # Build attendance query | |
| attendance_query = db.session.query(Attendance, Student).join( | |
| Student, Attendance.student_id == Student.id | |
| ).filter(Attendance.tutor_id == tutor_id) | |
| if subject_filter: | |
| attendance_query = attendance_query.filter(Attendance.subject == subject_filter) | |
| if month_filter: | |
| year, month = month_filter.split('-') | |
| from datetime import datetime | |
| start_month = datetime(int(year), int(month), 1).date() | |
| if int(month) == 12: | |
| end_month = datetime(int(year) + 1, 1, 1).date() | |
| else: | |
| end_month = datetime(int(year), int(month) + 1, 1).date() | |
| attendance_query = attendance_query.filter( | |
| Attendance.date >= start_month, | |
| Attendance.date < end_month | |
| ) | |
| elif safe_parse_date(start_date) and safe_parse_date(end_date): | |
| start = safe_parse_date(start_date) | |
| end = safe_parse_date(end_date) | |
| attendance_query = attendance_query.filter( | |
| Attendance.date >= safe_parse_date(start_date), | |
| Attendance.date <= safe_parse_date(end_date) | |
| ) | |
| attendance_records = attendance_query.order_by(Attendance.date.desc()).all() | |
| # Get available subjects for filters | |
| all_subjects = db.session.query(Attendance.subject).filter_by(tutor_id=tutor_id).distinct().all() | |
| available_subjects = [s[0] for s in all_subjects] | |
| return render_template('tutor_profile.html', | |
| tutor=tutor, | |
| attendance_records=attendance_records, | |
| available_subjects=available_subjects, | |
| subject_filter=subject_filter, | |
| month_filter=month_filter, | |
| start_date=start_date, | |
| end_date=end_date) | |
| # Attendance Management Routes | |
| @app.route('/view_attendance') | |
| @admin_required | |
| def view_attendance(): | |
| attendance_records = db.session.query(Attendance, Student, Tutor).join( | |
| Student, Attendance.student_id == Student.id | |
| ).join( | |
| Tutor, Attendance.tutor_id == Tutor.id | |
| ).order_by(Attendance.date.desc()).all() | |
| return render_template('view_attendance.html', attendance_records=attendance_records) | |
| @app.route('/delete_attendance/<int:attendance_id>') | |
| def delete_attendance(attendance_id): | |
| # Check if user is admin or the tutor who created this attendance | |
| attendance = Attendance.query.get_or_404(attendance_id) | |
| is_admin = 'admin_id' in session | |
| is_tutor = 'tutor_id' in session and session['tutor_id'] == attendance.tutor_id | |
| if not (is_admin or is_tutor): | |
| flash('You do not have permission to delete this attendance record.', 'error') | |
| return redirect(url_for('tutor_login')) | |
| try: | |
| db.session.delete(attendance) | |
| db.session.commit() | |
| flash('Attendance record deleted successfully!', 'success') | |
| except Exception as e: | |
| logger.error(f"Error deleting attendance: {e}") | |
| flash('Error deleting attendance record. Please try again.', 'error') | |
| if is_admin: | |
| return redirect(url_for('view_attendance')) | |
| else: | |
| return redirect(url_for('tutor_dashboard')) | |
| @app.route('/delete_all_attendance/<int:tutor_id>') | |
| @admin_required | |
| def delete_all_attendance(tutor_id): | |
| try: | |
| Attendance.query.filter_by(tutor_id=tutor_id).delete() | |
| db.session.commit() | |
| flash('All attendance records for this tutor deleted successfully!', 'success') | |
| except Exception as e: | |
| logger.error(f"Error deleting all attendance: {e}") | |
| flash('Error deleting attendance records. Please try again.', 'error') | |
| return redirect(url_for('view_attendance')) | |
| # PDF Invoice Routes | |
| @app.route('/generate_parent_invoice/<int:student_id>') | |
| @admin_required | |
| def handle_generate_parent_invoice(student_id): | |
| try: | |
| student = Student.query.get_or_404(student_id) | |
| # Get filter parameters for filtered invoice | |
| subject_filter = request.args.get('subject_filter', '') | |
| month_filter = request.args.get('month_filter', '') | |
| start_date = request.args.get('start_date', '') | |
| end_date = request.args.get('end_date', '') | |
| pdf_buffer = generate_parent_invoice(student, subject_filter, month_filter, start_date, end_date) | |
| response = make_response(pdf_buffer.getvalue()) | |
| response.headers['Content-Type'] = 'application/pdf' | |
| # Add filter info to filename | |
| filename_suffix = "" | |
| if month_filter: | |
| filename_suffix = f"_{month_filter}" | |
| elif start_date and end_date: | |
| filename_suffix = f"_{start_date}_to_{end_date}" | |
| response.headers['Content-Disposition'] = f'attachment; filename=parent_invoice_{student.name.replace(" ", "_")}{filename_suffix}.pdf' | |
| return response | |
| except Exception as e: | |
| logger.error(f"Error generating parent invoice: {e}") | |
| flash('Error generating invoice. Please try again.', 'error') | |
| return redirect(url_for('admin_dashboard')) | |
| @app.route('/generate_tutor_invoice/<int:tutor_id>') | |
| @admin_required | |
| def handle_generate_tutor_invoice(tutor_id): | |
| try: | |
| tutor = Tutor.query.get_or_404(tutor_id) | |
| # Get filter parameters for filtered invoice | |
| subject_filter = request.args.get('subject_filter', '') | |
| month_filter = request.args.get('month_filter', '') | |
| start_date = request.args.get('start_date', '') | |
| end_date = request.args.get('end_date', '') | |
| pdf_buffer = generate_tutor_invoice(tutor, subject_filter, month_filter, start_date, end_date) | |
| response = make_response(pdf_buffer.getvalue()) | |
| response.headers['Content-Type'] = 'application/pdf' | |
| # Add filter info to filename | |
| filename_suffix = "" | |
| if month_filter: | |
| filename_suffix = f"_{month_filter}" | |
| elif start_date and end_date: | |
| filename_suffix = f"_{start_date}_to_{end_date}" | |
| response.headers['Content-Disposition'] = f'attachment; filename=tutor_invoice_{tutor.name.replace(" ", "_")}{filename_suffix}.pdf' | |
| return response | |
| except Exception as e: | |
| logger.error(f"Error generating tutor invoice: {e}") | |
| flash('Error generating invoice. Please try again.', 'error') | |
| return redirect(url_for('admin_dashboard')) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment