LMS / app.py
Faizur Rahman Zunayed
Initial commit: Library Management System
58668c3
"""
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/<student_id>', 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/<student_id>', 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/<student_id>')
@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/<book_id>', 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/<book_id>', 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)