Spaces:
No application file
No application file
| """ | |
| 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() | |
| def load_user(user_id): | |
| return db.get_user(user_id) | |
| # Role-based access decorators | |
| def admin_required(f): | |
| 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): | |
| 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 | |
| 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') | |
| def logout(): | |
| logout_user() | |
| flash('You have been logged out successfully', 'success') | |
| return redirect(url_for('login')) | |
| # Dashboard | |
| 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 | |
| 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) | |
| 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') | |
| 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) | |
| def delete_student(student_id): | |
| db.delete_user(student_id) | |
| flash('Student deleted successfully', 'success') | |
| return redirect(url_for('students')) | |
| 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 | |
| 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) | |
| 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') | |
| 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) | |
| 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 | |
| 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 | |
| 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 | |
| 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) | |