Creating an advanced-level Hotel Management System (HMS) with Flask and Bootstrap 5 involves designing a robust database structure, implementing backend logic, and creating responsive frontend pages. Below is a detailed implementation:

Database Structure

The database will be designed using SQLAlchemy (ORM for Flask). Here's the schema:

Tables

  • Users: Stores user information (guests, staff, and admins).
  • Guests: Stores guest information.
  • Staff: Stores staff information.
  • Rooms: Stores room details.
  • Bookings: Stores booking details.
  • Payments: Stores payment details.
  • Services: Stores hotel services (e.g., spa, restaurant).

from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password = db.Column(db.String(200), nullable=False)
role = db.Column(db.String(20), default='guest') # Roles: guest, staff, admin
guest = db.relationship('Guest', backref='user', uselist=False)
staff = db.relationship('Staff', backref='user', uselist=False)
class Guest(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
phone = db.Column(db.String(15), nullable=False)
bookings = db.relationship('Booking', backref='guest', lazy=True)
payments = db.relationship('Payment', backref='guest', lazy=True)
class Staff(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
role = db.Column(db.String(50), nullable=False) # e.g., receptionist, manager
services = db.relationship('Service', backref='staff', lazy=True)
class Room(db.Model):
id = db.Column(db.Integer, primary_key=True)
room_number = db.Column(db.String(10), unique=True, nullable=False)
room_type = db.Column(db.String(50), nullable=False) # e.g., single, double, suite
price_per_night = db.Column(db.Float, nullable=False)
is_available = db.Column(db.Boolean, default=True)
bookings = db.relationship('Booking', backref='room', lazy=True)
class Booking(db.Model):
id = db.Column(db.Integer, primary_key=True)
guest_id = db.Column(db.Integer, db.ForeignKey('guest.id'), nullable=False)
room_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=False)
check_in_date = db.Column(db.DateTime, nullable=False)
check_out_date = db.Column(db.DateTime, nullable=False)
total_price = db.Column(db.Float, nullable=False)
payment = db.relationship('Payment', backref='booking', uselist=False)
class Payment(db.Model):
id = db.Column(db.Integer, primary_key=True)
booking_id = db.Column(db.Integer, db.ForeignKey('booking.id'), nullable=False)
payment_date = db.Column(db.DateTime, nullable=False)
amount = db.Column(db.Float, nullable=False)
status = db.Column(db.String(20), default='Pending') # Status: Pending, Completed
class Service(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # e.g., spa, restaurant
staff_id = db.Column(db.Integer, db.ForeignKey('staff.id'), nullable=False)
price = db.Column(db.Float, nullable=False)

Flask Application Structure

Here’s the directory structure for the project:


hotel_management_system/

├── app.py
├── models.py
├── forms.py
├── templates/
│ ├── base.html
│ ├── index.html
│ ├── login.html
│ ├── register.html
│ ├── guest_dashboard.html
│ ├── staff_dashboard.html
│ ├── admin_dashboard.html
│ ├── book_room.html
│ ├── view_bookings.html
│ ├── view_payments.html
│ ├── manage_rooms.html
│ ├── manage_services.html
│ └── manage_staff.html
├── static/
│ ├── css/
│ │ └── styles.css
│ ├── js/
│ │ └── scripts.js
│ └── images/
└── requirements.txt

Flask Application Code

app.py


from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_sqlalchemy import SQLAlchemy
from models import db, User, Guest, Staff, Room, Booking, Payment, Service
from forms import LoginForm, RegistrationForm, BookingForm, PaymentForm, RoomForm, ServiceForm
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hotel_management.db'
db.init_app(app)
# Routes
@app.route('/')
def index():
return render_template('index.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data, password=form.password.data)
db.session.add(user)
db.session.commit()
flash('Registration successful! Please login.', 'success')
return redirect(url_for('login'))
return render_template('register.html', form=form)
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and user.password == form.password.data: # Use hashed passwords in production
session['user_id'] = user.id
session['role'] = user.role
flash('Login successful!', 'success')
return redirect(url_for('guest_dashboard') if user.role == 'guest' else 'staff_dashboard' if user.role == 'staff' else 'admin_dashboard')
flash('Login failed. Check your email and password.', 'danger')
return render_template('login.html', form=form)
@app.route('/guest_dashboard')
def guest_dashboard():
if 'user_id' not in session or session['role'] != 'guest':
return redirect(url_for('login'))
guest = Guest.query.filter_by(user_id=session['user_id']).first()
return render_template('guest_dashboard.html', guest=guest)
@app.route('/staff_dashboard')
def staff_dashboard():
if 'user_id' not in session or session['role'] != 'staff':
return redirect(url_for('login'))
staff = Staff.query.filter_by(user_id=session['user_id']).first()
return render_template('staff_dashboard.html', staff=staff)
@app.route('/admin_dashboard')
def admin_dashboard():
if 'user_id' not in session or session['role'] != 'admin':
return redirect(url_for('login'))
return render_template('admin_dashboard.html')
@app.route('/book_room', methods=['GET', 'POST'])
def book_room():
if 'user_id' not in session or session['role'] != 'guest':
return redirect(url_for('login'))
form = BookingForm()
if form.validate_on_submit():
booking = Booking(guest_id=session['user_id'], room_id=form.room_id.data, check_in_date=form.check_in_date.data, check_out_date=form.check_out_date.data, total_price=form.total_price.data)
db.session.add(booking)
db.session.commit()
flash('Room booked successfully!', 'success')
return redirect(url_for('guest_dashboard'))
return render_template('book_room.html', form=form)
@app.route('/view_bookings')
def view_bookings():
if 'user_id' not in session:
return redirect(url_for('login'))
bookings = Booking.query.filter_by(guest_id=session['user_id']).all()
return render_template('view_bookings.html', bookings=bookings)
@app.route('/view_payments')
def view_payments():
if 'user_id' not in session or session['role'] != 'guest':
return redirect(url_for('login'))
payments = Payment.query.filter_by(guest_id=session['user_id']).all()
return render_template('view_payments.html', payments=payments)
@app.route('/manage_rooms', methods=['GET', 'POST'])
def manage_rooms():
if 'user_id' not in session or session['role'] != 'admin':
return redirect(url_for('login'))
form = RoomForm()
if form.validate_on_submit():
room = Room(room_number=form.room_number.data, room_type=form.room_type.data, price_per_night=form.price_per_night.data)
db.session.add(room)
db.session.commit()
flash('Room added successfully!', 'success')
return redirect(url_for('manage_rooms'))
rooms = Room.query.all()
return render_template('manage_rooms.html', form=form, rooms=rooms)
@app.route('/manage_services', methods=['GET', 'POST'])
def manage_services():
if 'user_id' not in session or session['role'] != 'admin':
return redirect(url_for('login'))
form = ServiceForm()
if form.validate_on_submit():
service = Service(name=form.name.data, staff_id=form.staff_id.data, price=form.price.data)
db.session.add(service)
db.session.commit()
flash('Service added successfully!', 'success')
return redirect(url_for('manage_services'))
services = Service.query.all()
return render_template('manage_services.html', form=form, services=services)
@app.route('/manage_staff', methods=['GET', 'POST'])
def manage_staff():
if 'user_id' not in session or session['role'] != 'admin':
return redirect(url_for('login'))
# Logic for managing staff (add, edit, delete) can be implemented here
staff_members = Staff.query.all()
return render_template('manage_staff.html', staff_members=staff_members)
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)

models.py


from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password = db.Column(db.String(200), nullable=False)
role = db.Column(db.String(20), default='guest') # Roles: guest, staff, admin
guest = db.relationship('Guest', backref='user', uselist=False)
staff = db.relationship('Staff', backref='user', uselist=False)
class Guest(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
phone = db.Column(db.String(15), nullable=False)
bookings = db.relationship('Booking', backref='guest', lazy=True)
payments = db.relationship('Payment', backref='guest', lazy=True)
class Staff(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
role = db.Column(db.String(50), nullable=False) # e.g., receptionist, manager
services = db.relationship('Service', backref='staff', lazy=True)
class Room(db.Model):
id = db.Column(db.Integer, primary_key=True)
room_number = db.Column(db.String(10), unique=True, nullable=False)
room_type = db.Column(db.String(50), nullable=False) # e.g., single, double, suite
price_per_night = db.Column(db.Float, nullable=False)
is_available = db.Column(db.Boolean, default=True)
bookings = db.relationship('Booking', backref='room', lazy=True)
class Booking(db.Model):
id = db.Column(db.Integer, primary_key=True)
guest_id = db.Column(db.Integer, db.ForeignKey('guest.id'), nullable=False)
room_id = db.Column(db.Integer, db.ForeignKey('room.id'), nullable=False)
check_in_date = db.Column(db.DateTime, nullable=False)
check_out_date = db.Column(db.DateTime, nullable=False)
total_price = db.Column(db.Float, nullable=False)
payment = db.relationship('Payment', backref='booking', uselist=False)
class Payment(db.Model):
id = db.Column(db.Integer, primary_key=True)
booking_id = db.Column(db.Integer, db.ForeignKey('booking.id'), nullable=False)
payment_date = db.Column(db.DateTime, nullable=False)
amount = db.Column(db.Float, nullable=False)
status = db.Column(db.String(20), default='Pending') # Status: Pending, Completed
class Service(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # e.g., spa, restaurant
staff_id = db.Column(db.Integer, db.ForeignKey('staff.id'), nullable=False)
price = db.Column(db.Float, nullable=False)

forms.py


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, DateTimeField, FloatField, SelectField, SubmitField
from wtforms.validators import DataRequired, Email, Length
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=50)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=200)])
submit = SubmitField('Register')
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
class BookingForm(FlaskForm):
room_id = SelectField('Room', coerce=int, validators=[DataRequired()])
check_in_date = DateTimeField('Check-in Date', format='%Y-%m-%d %H:%M:%S', validators=[DataRequired()])
check_out_date = DateTimeField('Check-out Date', format='%Y-%m-%d %H:%M:%S', validators=[DataRequired()])
total_price = FloatField('Total Price', validators=[DataRequired()])
submit = SubmitField('Book Room')
class PaymentForm(FlaskForm):
amount = FloatField('Amount', validators=[DataRequired()])
submit = SubmitField('Make Payment')
class RoomForm(FlaskForm):
room_number = StringField('Room Number', validators=[DataRequired()])
room_type = StringField('Room Type', validators=[DataRequired()])
price_per_night = FloatField('Price per Night', validators=[DataRequired()])
submit = SubmitField('Add Room')
class ServiceForm(FlaskForm):
name = StringField('Service Name', validators=[DataRequired()])
staff_id = SelectField('Staff', coerce=int, validators=[DataRequired()])
price = FloatField('Price', validators=[DataRequired()])
submit = SubmitField('Add Service')

base.html


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<title>{% block title %}Hotel Management System{% endblock %}</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">Hotel Management</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
{% if 'user_id' in session %}
{% if session['role'] == 'guest' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('guest_dashboard') }}">Dashboard</a>
</li>
{% elif session['role'] == 'staff' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('staff_dashboard') }}">Dashboard</a>
</li>
{% elif session['role'] == 'admin' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_dashboard') }}">Dashboard</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('login') }}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('register') }}">Register</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-info">
{% for message in messages %}
{{ message }}<br>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

index.html


{% extends 'base.html' %}
{% block content %}
<h1>Welcome to the Hotel Management System</h1>
<p>Experience luxury and comfort during your stay!</p>
<a href="{{ url_for('register') }}" class="btn btn-primary">Register</a>
<a href="{{ url_for('login') }}" class="btn btn-secondary">Login</a>
{% endblock %}

login.html


{% extends 'base.html' %}
{% block content %}
<h2>Login</h2>
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control") }}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
{% endblock %}

guest_dashboard.html


{% extends 'base.html' %}
{% block content %}
<h2>Guest Dashboard</h2>
<p>Welcome, {{ guest.name }}!</p>
<a href="{{ url_for('book_room') }}" class="btn btn-primary">Book Room</a>
<a href="{{ url_for('view_bookings') }}" class="btn btn-secondary">View Bookings</a>
<a href="{{ url_for ```html
('view_payments') }}" class="btn btn-info">View Payments</a>
{% endblock %}

staff_dashboard.html


{% extends 'base.html' %}
{% block content %}
<h2>Staff Dashboard</h2>
<p>Welcome, {{ staff.name }}!</p>
<!-- Add staff-specific actions here -->
{% endblock %}

admin_dashboard.html


{% extends 'base.html' %}
{% block content %}
<h2>Admin Dashboard</h2>
<p>Welcome, Admin!</p>
<a href="{{ url_for('manage_rooms') }}" class="btn btn-primary">Manage Rooms</a>
<a href="{{ url_for('manage_services') }}" class="btn btn-secondary">Manage Services</a>
<a href="{{ url_for('manage_staff') }}" class="btn btn-info">Manage Staff</a>
{% endblock %}

book_room.html


{% extends 'base.html' %}
{% block content %}
<h2>Book Room</h2>
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.room_id.label(class="form-label") }}
{{ form.room_id(class="form-control") }}
</div>
<div class="mb-3">
{{ form.check_in_date.label(class="form-label") }}
{{ form.check_in_date(class="form-control") }}
</div>
<div class="mb-3">
{{ form.check_out_date.label(class="form-label") }}
{{ form.check_out_date(class="form-control") }}
</div>
<div class="mb-3">
{{ form.total_price.label(class="form-label") }}
{{ form.total_price(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
{% endblock %}

view_bookings.html


{% extends 'base.html' %}
{% block content %}
<h2>Your Bookings</h2>
<table class="table">
<thead>
<tr>
<th>Room</th>
<th>Check-in Date</th>
<th>Check-out Date</th>
<th>Total Price</th>
</tr>
</thead>
<tbody>
{% for booking in bookings %}
<tr>
<td>{{ booking.room.room_number }}</td>
<td>{{ booking.check_in_date }}</td>
<td>{{ booking.check_out_date }}</td>
<td>{{ booking.total_price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

view_payments.html


{% extends 'base.html' %}
{% block content %}
<h2>Your Payments</h2>
<table class="table">
<thead>
<tr>
<th>Payment Date</th>
<th>Amount</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for payment in payments %}
<tr>
<td>{{ payment.payment_date }}</td>
<td>{{ payment.amount }}</td>
<td>{{ payment.status }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

manage_rooms.html


{% extends 'base.html' %}
{% block content %}
<h2>Manage Rooms</h2>
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.room_number.label(class="form-label") }}
{{ form.room_number(class="form-control") }}
</div>
<div class="mb-3">
{{ form.room_type.label(class="form-label") }}
{{ form.room_type(class="form-control") }}
</div>
<div class="mb-3">
{{ form.price_per_night.label(class="form-label") }}
{{ form.price_per_night(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
<table class="table mt-4">
<thead>
<tr>
<th>Room Number</th>
<th>Room Type</th>
<th>Price per Night</th>
</tr>
</thead>
<tbody>
{% for room in rooms %}
<tr>
<td>{{ room.room_number }}</td>
<td>{{ room.room_type }}</ ```html
<td>{{ room.price_per_night }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

manage_services.html


{% extends 'base.html' %}
{% block content %}
<h2>Manage Services</h2>
<form method="POST">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.name.label(class="form-label") }}
{{ form.name(class="form-control") }}
</div>
<div class="mb-3">
{{ form.staff_id.label(class="form-label") }}
{{ form.staff_id(class="form-control") }}
</div>
<div class="mb-3">
{{ form.price.label(class="form-label") }}
{{ form.price(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
<table class="table mt-4">
<thead>
<tr>
<th>Service Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for service in services %}
<tr>
<td>{{ service.name }}</td>
<td>{{ service.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

manage_staff.html


{% extends 'base.html' %}
{% block content %}
<h2>Manage Staff</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
</tr>
</thead>
<tbody>
{% for staff_member in staff_members %}
<tr>
<td>{{ staff_member.name }}</td>
<td>{{ staff_member.role }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

Static Files

styles.css


body {
background-color: #f8f9fa;
}
.navbar {
margin-bottom: 20px;
}
.table {
margin-top: 20px;
}

scripts.js


// Custom JavaScript can be added here
document.addEventListener('DOMContentLoaded', function() {
console.log("Hotel Management System Loaded");
});

Requirements


Flask==2.0.1
Flask-SQLAlchemy==2.5.1
Flask-WTF==0.15.1
WTForms==2.3.3

This structure provides a comprehensive foundation for an advanced Hotel Management System using Flask and Bootstrap 5. You can expand upon this by adding features such as room availability checks, advanced reporting, user management functionalities, and service booking options.