""" Library Management System - Main Application """ from flask import Flask, render_template, request, redirect, url_for, flash, jsonify from flask_login import LoginManager, login_user, logout_user, login_required, current_user from functools import wraps import bcrypt from datetime import datetime import uuid from models import User, Book, Issue import database as db app = Flask(__name__) app.secret_key = 'your-secret-key-here-change-in-production' # Initialize Flask-Login login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' # Initialize database db.init_db() @login_manager.user_loader def load_user(user_id): return db.get_user(user_id) # Role-based access decorators def admin_required(f): @wraps(f) @login_required def decorated_function(*args, **kwargs): if not current_user.is_admin(): flash('Access denied. Admin privileges required.', 'danger') return redirect(url_for('dashboard')) return f(*args, **kwargs) return decorated_function def librarian_required(f): @wraps(f) @login_required def decorated_function(*args, **kwargs): if not current_user.can_manage_books(): flash('Access denied. Librarian or Admin privileges required.', 'danger') return redirect(url_for('dashboard')) return f(*args, **kwargs) return decorated_function # Authentication routes @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('dashboard')) if request.method == 'POST': email = request.form.get('email') password = request.form.get('password') user = db.get_user_by_email(email) if user and bcrypt.checkpw(password.encode(), user.password.encode()): login_user(user) flash(f'Welcome back, {user.name}!', 'success') return redirect(url_for('dashboard')) flash('Invalid email or password', 'danger') return render_template('login.html') @app.route('/logout') @login_required def logout(): logout_user() flash('You have been logged out successfully', 'success') return redirect(url_for('login')) # Dashboard @app.route('/') @app.route('/dashboard') @login_required def dashboard(): stats = db.get_stats() if current_user.is_student(): # Student dashboard books = db.get_all_books() my_issues = db.get_user_issues(current_user.id) return render_template('student_dashboard.html', stats=stats, books=books, my_issues=my_issues) else: # Admin/Librarian dashboard recent_issues = db.get_active_issues()[:10] overdue = db.get_overdue_issues() return render_template('dashboard.html', stats=stats, recent_issues=recent_issues, overdue=overdue) # Student Management @app.route('/students') @librarian_required def students(): # Get search query search_query = request.args.get('q', '').strip().lower() # Get all students all_students = db.get_students() # Filter by search query if provided if search_query: students = [s for s in all_students if search_query in s.id.lower() or search_query in s.name.lower()] else: students = all_students # Get ONLY ACTIVE issued books for each student student_active_issues = {} for student in students: all_issues = db.get_user_issues(student.id) # Filter only active (not returned) active_issues = [issue for issue in all_issues if issue.status == 'issued'] student_active_issues[student.id] = active_issues return render_template('students.html', students=students, student_issues=student_active_issues, search_query=search_query) @app.route('/students/add', methods=['GET', 'POST']) @librarian_required def add_student(): if request.method == 'POST': student_id = request.form.get('student_id') name = request.form.get('name') email = request.form.get('email') role = request.form.get('role', 'student') # Get role from form department = request.form.get('department') contact = request.form.get('contact') password = request.form.get('password', 'student123') # Check if student ID already exists if db.get_user(student_id): flash('User ID already exists', 'danger') return redirect(url_for('add_student')) # Hash password hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) student = User( id=student_id, name=name, email=email, password=hashed_password.decode(), role=role, # Use role from form department=department, contact=contact ) db.add_user(student) flash(f'{role.capitalize()} {name} added successfully', 'success') return redirect(url_for('students')) return render_template('add_student.html') @app.route('/students/edit/', methods=['GET', 'POST']) @librarian_required def edit_student(student_id): student = db.get_user(student_id) if not student: flash('User not found', 'danger') return redirect(url_for('students')) if request.method == 'POST': student.name = request.form.get('name') student.email = request.form.get('email') student.role = request.form.get('role', 'student') # Get role from form student.department = request.form.get('department') student.contact = request.form.get('contact') db.update_user(student) flash(f'{student.role.capitalize()} {student.name} updated successfully', 'success') return redirect(url_for('students')) return render_template('edit_student.html', student=student) @app.route('/students/delete/', methods=['POST']) @librarian_required def delete_student(student_id): db.delete_user(student_id) flash('Student deleted successfully', 'success') return redirect(url_for('students')) @app.route('/students/view/') @librarian_required def view_student(student_id): student = db.get_user(student_id) if not student: flash('Student not found', 'danger') return redirect(url_for('students')) # Get all issues for this student all_issues = db.get_user_issues(student_id) # Categorize issues active_issues = [i for i in all_issues if i.status == 'issued'] returned_issues = [i for i in all_issues if i.status == 'returned'] current_overdue = len([i for i in active_issues if i.is_overdue()]) # Calculate total times student has been overdue (including past returned books) total_overdue_times = len([i for i in all_issues if i.is_overdue() or (i.status == 'returned' and i.days_overdue() > 0)]) # Get books info for issues issues_with_books = [] for issue in all_issues: book = db.get_book(issue.book_id) issues_with_books.append({ 'issue': issue, 'book': book }) return render_template('view_student.html', student=student, issues_with_books=issues_with_books, active_count=len(active_issues), returned_count=len(returned_issues), overdue_count=current_overdue, total_overdue_times=total_overdue_times) # Book Management @app.route('/books') @login_required def books(): query = request.args.get('q', '') category = request.args.get('category', '') available_only = request.args.get('available') == 'true' books = db.search_books(query, category, available_only) categories = list(set(b.category for b in db.get_all_books())) return render_template('books.html', books=books, categories=categories, query=query, selected_category=category, available_only=available_only) @app.route('/books/add', methods=['GET', 'POST']) @librarian_required def add_book(): if request.method == 'POST': book = Book( book_id=request.form.get('book_id'), title=request.form.get('title'), author=request.form.get('author'), isbn=request.form.get('isbn'), category=request.form.get('category'), publisher=request.form.get('publisher'), year=int(request.form.get('year')), total_copies=int(request.form.get('total_copies')), shelf_no=request.form.get('shelf_no'), cover_image=request.form.get('cover_image') ) db.add_book(book) flash(f'Book "{book.title}" added successfully', 'success') return redirect(url_for('books')) return render_template('add_book.html') @app.route('/books/edit/', methods=['GET', 'POST']) @librarian_required def edit_book(book_id): book = db.get_book(book_id) if not book: flash('Book not found', 'danger') return redirect(url_for('books')) if request.method == 'POST': book.title = request.form.get('title') book.author = request.form.get('author') book.isbn = request.form.get('isbn') book.category = request.form.get('category') book.publisher = request.form.get('publisher') book.year = int(request.form.get('year')) book.total_copies = int(request.form.get('total_copies')) book.shelf_no = request.form.get('shelf_no') book.cover_image = request.form.get('cover_image') db.update_book(book) flash(f'Book "{book.title}" updated successfully', 'success') return redirect(url_for('books')) return render_template('edit_book.html', book=book) @app.route('/books/delete/', methods=['POST']) @librarian_required def delete_book(book_id): book = db.get_book(book_id) if book: db.delete_book(book_id) flash(f'Book "{book.title}" deleted successfully', 'success') return redirect(url_for('books')) # Issue/Borrow System @app.route('/issue', methods=['GET', 'POST']) @librarian_required def issue_book(): if request.method == 'POST': student_id = request.form.get('student_id') book_id = request.form.get('book_id') student = db.get_user(student_id) book = db.get_book(book_id) if not student: flash('Student not found', 'danger') return redirect(url_for('issue_book')) if not book: flash('Book not found', 'danger') return redirect(url_for('issue_book')) if not book.is_available(): flash('No copies available for this book', 'danger') return redirect(url_for('issue_book')) # Create issue record issue = Issue( issue_id=str(uuid.uuid4())[:8], student_id=student_id, book_id=book_id ) db.add_issue(issue) # Update book availability db.update_book_copies(book_id, book.available_copies - 1) flash(f'Book "{book.title}" issued to {student.name}', 'success') return redirect(url_for('dashboard')) students = db.get_students() books = db.search_books(available_only=True) return render_template('issue_book.html', students=students, books=books) # Return System @app.route('/return', methods=['GET', 'POST']) @librarian_required def return_book(): if request.method == 'POST': student_id = request.form.get('student_id') book_id = request.form.get('book_id') # Find the active issue issues = [i for i in db.get_user_issues(student_id) if i.book_id == book_id and i.status == 'issued'] if not issues: flash('No active issue found for this book and student', 'danger') return redirect(url_for('return_book')) issue = issues[0] issue.return_date = datetime.now() issue.status = 'returned' # Calculate fine if overdue if issue.is_overdue(): fine_amount = issue.calculate_fine() fine = Fine( fine_id=str(uuid.uuid4())[:8], issue_id=issue.issue_id, student_id=student_id, amount=fine_amount ) db.add_fine(fine) flash(f'Book returned. Fine: {fine_amount} BDT for {issue.days_overdue()} days overdue', 'warning') else: flash('Book returned successfully', 'success') db.update_issue(issue) # Update book availability book = db.get_book(book_id) db.update_book_copies(book_id, book.available_copies + 1) return redirect(url_for('dashboard')) active_issues = db.get_active_issues() return render_template('return_book.html', issues=active_issues) # Reports @app.route('/reports') @librarian_required def reports(): stats = db.get_stats() all_issues = db.get_all_issues() overdue = db.get_overdue_issues() return render_template('reports.html', stats=stats, issues=all_issues, overdue=overdue) if __name__ == '__main__': import os port = int(os.environ.get('PORT', 7860)) app.run(debug=False, host='0.0.0.0', port=port)