A production-ready RESTful API for managing workspace room bookings with JWT authentication, role-based access control, and comprehensive booking management.
- Overview
- Technology Stack
- Setup Instructions
- API Documentation & Usage
- Assumptions Made
- Database Schema
- Business Rules
- Testing
- Deployment
This system manages a virtual workspace with 15 rooms:
- 8 Private Rooms (P01-P08) - For individual users
- 4 Conference Rooms (C01-C04) - For teams of 3+ members
- 3 Shared Desks (S01-S03) - For up to 4 users each
✅ User registration and JWT authentication
✅ Role-based access control (Admin/User)
✅ Smart room allocation based on requirements
✅ Booking time slots: 9 AM - 6 PM (hourly)
✅ Double-booking prevention with database locking
✅ Booking history tracking
✅ Children handling (counted but don't occupy seats)
✅ Swagger/OpenAPI documentation
✅ Docker containerization
✅ Comprehensive test suite
- Python 3.12+
- Django 4.2.7 - Web framework
- Django REST Framework 3.14.0 - API framework
- djangorestframework-simplejwt 5.3.0 - JWT authentication
- drf-spectacular - OpenAPI/Swagger documentation
- PostgreSQL 15 (Production)
- SQLite (Development)
- Docker & Docker Compose
- Gunicorn - WSGI server
- Docker
- Docker Compose
- Navigate to project directory
cd /path/to/booking_system- Start the application
docker-compose up --buildThis automatically:
- Starts PostgreSQL database
- Runs migrations
- Initializes 15 rooms
- Starts Django server on port 8000
- Create admin user (in another terminal)
docker-compose exec web python manage.py createsuperuser- Access the application
- API Base: http://localhost:8000/api/v1/
- Swagger Docs: http://localhost:8000/api/docs/
- Admin Panel: http://localhost:8000/admin/
- Python 3.11+
- pip
- virtualenv
- Create virtual environment
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate- Install dependencies
pip install -r requirements.txt- Configure environment
Create .env file in project root:
SECRET_KEY=your-secret-key-change-in-production
DEBUG=True
DB_ENGINE=sqlite
DB_NAME=db.sqlite3- Create migrations directory (if needed)
mkdir -p bookings/migrations
touch bookings/migrations/__init__.py- Run migrations
python manage.py makemigrations
python manage.py migrate- Initialize 15 rooms
python manage.py init_roomsOutput:
Successfully created 15 rooms: 8 Private, 4 Conference, 3 Shared Desks
- Create admin user
python manage.py createsuperuser- Start development server
python manage.py runserver- Access the application
- API: http://localhost:8000/api/v1/
- Swagger: http://localhost:8000/api/docs/
- Admin: http://localhost:8000/admin/
http://localhost:8000/api/v1/
- Swagger UI: http://localhost:8000/api/docs/
- OpenAPI Schema: http://localhost:8000/api/schema/
Endpoint:
POST /api/v1/register/
Content-Type: application/jsonRequest:
{
"username": "john_doe",
"email": "john@example.com",
"password": "SecurePass123",
"password_confirm": "SecurePass123",
"name": "John Doe",
"age": 30,
"gender": "M"
}Response (201 Created):
{
"message": "Registration successful!",
"user": {
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"profile": {
"name": "John Doe",
"age": 30,
"gender": "M"
}
},
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}cURL Example:
curl -X POST http://localhost:8000/api/v1/register/ \
-H "Content-Type: application/json" \
-d '{
"username": "john_doe",
"email": "john@example.com",
"password": "SecurePass123",
"password_confirm": "SecurePass123",
"name": "John Doe",
"age": 30,
"gender": "M"
}'Endpoint:
POST /api/token/
Content-Type: application/jsonRequest:
{
"username": "john_doe",
"password": "SecurePass123"
}Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}cURL Example:
curl -X POST http://localhost:8000/api/token/ \
-H "Content-Type: application/json" \
-d '{"username": "john_doe", "password": "SecurePass123"}'Note: Save the access token - you'll need it for all authenticated requests!
Endpoint:
POST /api/token/refresh/Request:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}Endpoint:
POST /api/v1/bookings/
Authorization: Bearer <access_token>
Content-Type: application/jsonPrivate Room Booking:
{
"room_type": "PRIVATE",
"date": "2025-10-15",
"time_slot": "10:00:00",
"user_id": 1
}Conference Room Booking:
{
"room_type": "CONFERENCE",
"date": "2025-10-15",
"time_slot": "14:00:00",
"team_id": 1
}Shared Desk Booking:
{
"room_type": "SHARED_DESK",
"date": "2025-10-15",
"time_slot": "11:00:00",
"user_ids": [1, 2, 3]
}Response (201 Created):
{
"id": 1,
"booking_id": "BK20251015100000P01",
"room": {
"id": 1,
"room_number": "P01",
"room_type": "PRIVATE",
"capacity": 1
},
"user": {
"id": 1,
"name": "John Doe",
"age": 30,
"gender": "M",
"is_child": false
},
"team": null,
"date": "2025-10-15",
"time_slot": "10:00:00",
"status": "ACTIVE",
"created_at": "2025-10-15T10:00:00Z"
}cURL Example:
curl -X POST http://localhost:8000/api/v1/bookings/ \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{
"room_type": "PRIVATE",
"date": "2025-10-15",
"time_slot": "10:00:00",
"user_id": 1
}'Endpoint:
GET /api/v1/bookings/my-bookings/
Authorization: Bearer <access_token>Response:
{
"user_info": {
"name": "John Doe",
"username": "john_doe"
},
"total_bookings": 3,
"active_bookings": 2,
"cancelled_bookings": 1,
"active": [
{
"id": 1,
"booking_id": "BK20251015100000P01",
"room": {
"room_number": "P01",
"room_type": "PRIVATE"
},
"date": "2025-10-15",
"time_slot": "10:00:00",
"status": "ACTIVE"
},
{
"booking_id": "BK20251016140000C01",
"room": {
"room_number": "C01",
"room_type": "CONFERENCE"
},
"date": "2025-10-16",
"time_slot": "14:00:00",
"status": "ACTIVE"
}
],
"cancelled": [
{
"booking_id": "BK20251012110000P02",
"date": "2025-10-12",
"status": "CANCELLED"
}
]
}cURL Example:
curl -H "Authorization: Bearer eyJhbGc..." \
http://localhost:8000/api/v1/bookings/my-bookings/Endpoint:
GET /api/v1/bookings/
Authorization: Bearer <access_token>Response (Paginated):
{
"count": 10,
"next": "http://localhost:8000/api/v1/bookings/?page=2",
"previous": null,
"results": [
{
"id": 1,
"booking_id": "BK20251015100000P01",
"room": {...},
"user": {...},
"date": "2025-10-15",
"time_slot": "10:00:00",
"status": "ACTIVE"
}
]
}Endpoint:
GET /api/v1/bookings/{booking_id}/
Authorization: Bearer <access_token>Example:
curl -H "Authorization: Bearer eyJhbGc..." \
http://localhost:8000/api/v1/bookings/BK20251015100000P01/Endpoint:
POST /api/v1/cancel/{booking_id}/
Authorization: Bearer <access_token>Response (200 OK):
{
"message": "Booking cancelled successfully"
}cURL Example:
curl -X POST http://localhost:8000/api/v1/cancel/BK20251015100000P01/ \
-H "Authorization: Bearer eyJhbGc..."Note: Only the booking owner or admin can cancel a booking.
Endpoint:
GET /api/v1/rooms/
Authorization: Bearer <access_token>Response:
[
{
"id": 1,
"room_number": "P01",
"room_type": "PRIVATE",
"capacity": 1,
"created_at": "2025-10-12T09:00:00Z"
},
{
"id": 9,
"room_number": "C01",
"room_type": "CONFERENCE",
"capacity": 6,
"created_at": "2025-10-12T09:00:00Z"
},
{
"id": 13,
"room_number": "S01",
"room_type": "SHARED_DESK",
"capacity": 4,
"created_at": "2025-10-12T09:00:00Z"
}
]Endpoint:
GET /api/v1/rooms/available/?date=2025-10-15&time_slot=10:00:00&room_type=PRIVATE
Authorization: Bearer <access_token>Query Parameters:
date(required): Date in YYYY-MM-DD formattime_slot(required): Time in HH:MM:SS formatroom_type(optional): PRIVATE, CONFERENCE, or SHARED_DESK
Response:
{
"date": "2025-10-15",
"time_slot": "10:00:00",
"available_rooms": [
{
"id": 2,
"room_number": "P02",
"room_type": "PRIVATE",
"capacity": 1
},
{
"id": 3,
"room_number": "P03",
"room_type": "PRIVATE",
"capacity": 1
}
],
"count": 7
}cURL Example:
curl -H "Authorization: Bearer eyJhbGc..." \
"http://localhost:8000/api/v1/rooms/available/?date=2025-10-15&time_slot=10:00:00&room_type=PRIVATE"Endpoint:
GET /api/v1/rooms/availability/?date=2025-10-15
Authorization: Bearer <access_token>Shows availability for all hourly slots from 9 AM to 6 PM.
Endpoint:
POST /api/v1/teams/
Authorization: Bearer <access_token>
Content-Type: application/jsonRequest:
{
"name": "Development Team",
"member_ids": [1, 2, 3, 4]
}Response:
{
"id": 1,
"name": "Development Team",
"members": [
{
"id": 1,
"name": "John Doe",
"age": 30,
"gender": "M",
"is_child": false
},
...
],
"size": 4,
"adult_count": 4,
"created_at": "2025-10-12T10:00:00Z"
}cURL Example:
curl -X POST http://localhost:8000/api/v1/teams/ \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{"name": "Dev Team", "member_ids": [1, 2, 3]}'Endpoint:
GET /api/v1/teams/
Authorization: Bearer <access_token>Endpoint:
GET /api/v1/users/
Authorization: Bearer <access_token>Endpoint:
POST /api/v1/users/
Authorization: Bearer <admin_token>Request:
{
"name": "Jane Smith",
"age": 28,
"gender": "F"
}# Step 1: Register
REGISTER_RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/register/ \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"email": "alice@example.com",
"password": "Pass123456",
"password_confirm": "Pass123456",
"name": "Alice Johnson",
"age": 28,
"gender": "F"
}')
# Extract token
TOKEN=$(echo $REGISTER_RESPONSE | jq -r '.access')
USER_ID=$(echo $REGISTER_RESPONSE | jq -r '.user.profile.id')
echo "Token: $TOKEN"
echo "User ID: $USER_ID"
# Step 2: Check available rooms
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8000/api/v1/rooms/available/?date=2025-10-15&time_slot=10:00:00"
# Step 3: Book a private room
BOOKING_RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/bookings/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"room_type\": \"PRIVATE\",
\"date\": \"2025-10-15\",
\"time_slot\": \"10:00:00\",
\"user_id\": $USER_ID
}")
BOOKING_ID=$(echo $BOOKING_RESPONSE | jq -r '.booking_id')
echo "Booking ID: $BOOKING_ID"
# Step 4: Check my bookings
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8000/api/v1/bookings/my-bookings/
# Step 5: Cancel booking (if needed)
curl -X POST http://localhost:8000/api/v1/cancel/$BOOKING_ID/ \
-H "Authorization: Bearer $TOKEN"# Assuming 3 users already created with IDs 1, 2, 3
# Step 1: Login as admin
ADMIN_TOKEN=$(curl -s -X POST http://localhost:8000/api/token/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}' | jq -r '.access')
# Step 2: Create team
TEAM_RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/teams/ \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Alpha Team",
"member_ids": [1, 2, 3]
}')
TEAM_ID=$(echo $TEAM_RESPONSE | jq -r '.id')
# Step 3: Book conference room
curl -X POST http://localhost:8000/api/v1/bookings/ \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"room_type\": \"CONFERENCE\",
\"date\": \"2025-10-15\",
\"time_slot\": \"14:00:00\",
\"team_id\": $TEAM_ID
}"# Book shared desk for 3 users
curl -X POST http://localhost:8000/api/v1/bookings/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"room_type": "SHARED_DESK",
"date": "2025-10-15",
"time_slot": "11:00:00",
"user_ids": [1, 2, 3]
}'import requests
import json
BASE_URL = "http://localhost:8000/api/v1"
# 1. Register
register_data = {
"username": "john",
"email": "john@test.com",
"password": "Pass123",
"password_confirm": "Pass123",
"name": "John",
"age": 30,
"gender": "M"
}
response = requests.post(f"{BASE_URL}/register/", json=register_data)
result = response.json()
token = result['access']
user_id = result['user']['id']
# 2. Check available rooms
headers = {'Authorization': f'Bearer {token}'}
params = {
'date': '2025-10-15',
'time_slot': '10:00:00',
'room_type': 'PRIVATE'
}
response = requests.get(f"{BASE_URL}/rooms/available/", headers=headers, params=params)
available = response.json()
print(f"Available rooms: {available['count']}")
# 3. Book a room
booking_data = {
"room_type": "PRIVATE",
"date": "2025-10-15",
"time_slot": "10:00:00",
"user_id": user_id
}
response = requests.post(f"{BASE_URL}/bookings/", headers=headers, json=booking_data)
booking = response.json()
print(f"Booking created: {booking['booking_id']}")
# 4. Check my bookings
response = requests.get(f"{BASE_URL}/bookings/my-bookings/", headers=headers)
my_bookings = response.json()
print(f"Total bookings: {my_bookings['total_bookings']}")
print(f"Active bookings: {my_bookings['active_bookings']}")
# 5. Cancel booking
booking_id = booking['booking_id']
response = requests.post(f"{BASE_URL}/cancel/{booking_id}/", headers=headers)
print(response.json()['message'])Assumption: Bookings are hourly slots from 9 AM to 6 PM
Rationale:
- Standard business hours
- Easy to understand and implement
- Aligns with typical office workspace usage
- Each slot is 1 hour (e.g., 10:00-11:00)
Implementation:
# Valid times: 09:00, 10:00, 11:00, ..., 17:00
# Invalid times: 08:00, 18:00, 20:00, etc.
CHECK(time_slot >= '09:00:00' AND time_slot < '18:00:00')Assumption: Two-tier user system (Auth + Profile)
Rationale:
- Auth User: Handles login, security, permissions
- Booking User: Handles booking-specific data (name, age, gender)
- Linked via OneToOne for flexibility
- Allows walk-in bookings without auth accounts
Implementation:
class User(models.Model):
auth_user = models.OneToOneField(AuthUser, null=True, blank=True)
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
gender = models.CharField(max_length=1)Assumption: Children (age < 10) count in team size but don't need seats
Rationale:
- Reflects real-world office scenarios
- Children accompany adults but don't occupy workspace
- Still counted for fire safety/headcount purposes
Implementation:
@property
def is_child(self):
return self.age < 10
@property
def adult_count(self):
return self.members.filter(age__gte=10).count()Assumption: Fixed capacities per room type
Rationale:
- Private: 1 person (individual workspace)
- Conference: 6 seats (teams of 3-6)
- Shared Desk: 4 seats (hot-desking)
Implementation:
# Room initialization
PRIVATE: capacity=1
CONFERENCE: capacity=6
SHARED_DESK: capacity=4Assumption: One active booking per user per time slot
Rationale:
- Prevents users from booking multiple rooms simultaneously
- Realistic constraint (can't be in two places at once)
- Simplifies conflict detection
Implementation:
# Check before booking
existing = Booking.objects.filter(
user=user,
date=date,
time_slot=time_slot,
status='ACTIVE'
).exists()
if existing:
raise ValueError("User already has a booking")Assumption: Conference rooms require minimum 3 members
Rationale:
- Conference rooms designed for team collaboration
- Below 3 people should use private rooms or shared desks
- Efficient resource utilization
Implementation:
if room_type == 'CONFERENCE' and team.size < 3:
raise ValueError("Conference rooms require teams of 3 or more")Assumption: Soft delete (status change, not physical deletion)
Rationale:
- Audit trail for compliance
- Booking history preserved
- Can analyze cancellation patterns
- Slot immediately freed for others
Implementation:
booking.status = 'CANCELLED' # Not booking.delete()Assumption: High concurrent booking attempts expected
Rationale:
- Multiple users booking simultaneously
- Need to prevent race conditions
- Database-level protection required
Implementation:
with transaction.atomic():
Room.objects.filter(...).select_for_update() # Row-level locking
# Booking creationAssumption: JWT tokens for stateless authentication
Rationale:
- Scalable (no server-side sessions)
- Mobile/SPA friendly
- 1-hour access token (security)
- 7-day refresh token (convenience)
Implementation:
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(hours=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}Assumption: Two user roles (Admin and Regular User)
Rationale:
- Admins: Full system access, manage all bookings
- Users: Create/view own bookings only
- Admins can help users with issues
Implementation:
# Regular users see only their bookings
if not user.is_staff:
queryset = queryset.filter(user=user)
# Only owners or admins can cancel
if not (user.is_staff or booking.user == user):
return 403 ForbiddenAssumption: Shared desks fill sequentially (seat 1, 2, 3, 4)
Rationale:
- Organized seating arrangement
- Easy to track occupancy
- Clear seat assignment
Implementation:
for i, user in enumerate(users, 1):
SharedDeskBooking.objects.create(
booking=booking,
user=user,
seat_number=i # 1, 2, 3, 4
)Assumption: Shared desks can be partially filled (1-4 users)
Rationale:
- Efficient resource utilization
- Flexibility for users
- If only 2 users need a desk, don't waste 4-seat capacity
Implementation:
if 1 <= len(user_ids) <= 4:
# Allow booking
else:
raise ValueError("1-4 users required")Assumption: Cannot book past dates
Rationale:
- Logical constraint
- Prevents data entry errors
- Database constraint for integrity
Implementation:
CHECK(date >= NOW()) # Database level
if date < datetime.now().date(): # Application level
raise ValueError("Cannot book past dates")Assumption: Auto-generated unique ID format
Rationale:
- Easy to read:
BK20251015100000P01 - Contains timestamp and room number
- Guaranteed unique
- User-friendly
Implementation:
timestamp = timezone.now().strftime('%Y%m%d%H%M%S')
booking_id = f"BK{timestamp}{room.room_number}"
# Example: BK20251015100000P01Assumption: RESTful API with standard HTTP methods
Rationale:
- Industry standard
- Predictable endpoints
- Easy to integrate
- Well-documented patterns
Implementation:
POST /api/v1/bookings/ → Create booking
GET /api/v1/bookings/ → List bookings
POST /api/v1/cancel/{id}/ → Cancel booking
GET /api/v1/rooms/available/ → Check availability
class User(models.Model):
id = AutoField (primary key, auto-generated)
auth_user = OneToOneField(AuthUser, null=True)
name = CharField(max_length=100)
age = PositiveIntegerField
gender = CharField(choices=['M', 'F', 'O'])
created_at = DateTimeField(auto_now_add=True)Properties:
is_child→ Returns True if age < 10
class Team(models.Model):
id = AutoField (primary key, auto-generated)
name = CharField(max_length=100)
members = ManyToManyField(User)
created_at = DateTimeField(auto_now_add=True)Properties:
size→ Total member countadult_count→ Members with age >= 10
class Room(models.Model):
id = AutoField (primary key, auto-generated)
room_number = CharField(max_length=10, unique=True)
room_type = CharField(choices=['PRIVATE', 'CONFERENCE', 'SHARED_DESK'])
capacity = PositiveIntegerField
created_at = DateTimeField(auto_now_add=True)Room Distribution:
- P01-P08: Private Rooms (capacity=1)
- C01-C04: Conference Rooms (capacity=6)
- S01-S03: Shared Desks (capacity=4)
class Booking(models.Model):
id = AutoField (primary key, auto-generated)
booking_id = CharField(max_length=50, unique=True)
room = ForeignKey(Room)
user = ForeignKey(User, null=True)
team = ForeignKey(Team, null=True)
date = DateField
time_slot = TimeField
status = CharField(choices=['ACTIVE', 'CANCELLED'], default='ACTIVE')
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)Constraints:
UNIQUE TOGETHER (room, date, time_slot, status)
CHECK (time_slot >= '09:00:00' AND time_slot < '18:00:00')
INDEX (date, time_slot)
INDEX (room, date, time_slot)
INDEX (user, date, time_slot)
INDEX (booking_id)class SharedDeskBooking(models.Model):
id = AutoField (primary key, auto-generated)
booking = ForeignKey(Booking)
user = ForeignKey(User)
seat_number = PositiveIntegerField (1-4)Constraints:
UNIQUE TOGETHER (booking, seat_number)Why separate User from Auth User?
- Django's User is for authentication
- Our User has booking-specific fields (age, gender)
- Flexibility: Can have booking users without auth (walk-ins)
- Clear separation of concerns
Why soft delete (status field)?
- Preserve booking history for audits
- Analyze cancellation patterns
- Maintain data integrity
- Slot immediately freed (status='CANCELLED')
Why SharedDeskBooking table?
- Track individual seats on shared desks
- Support partial fills (e.g., 2 out of 4 seats)
- Clear seat assignment (1, 2, 3, 4)
- Multiple users per desk booking
Why indexes on date, time_slot, room?
- Fast availability queries
- Common filter combinations
- Improved query performance
- Essential for concurrent bookings
✅ Individual users only
✅ One person per room
✅ 8 rooms available (P01-P08)
❌ Cannot be booked by teams
❌ Cannot be shared
✅ Teams of 3+ members only
✅ Children counted in team size
✅ 4 rooms available (C01-C04)
❌ Cannot be booked by individuals
❌ Teams < 3 members rejected
✅ 1-4 users per desk
✅ Partial fills allowed
✅ Sequential seat assignment
✅ 3 desks available (S01-S03)
❌ Cannot exceed 4 users
# Time Slot Validation
09:00:00 <= time_slot < 18:00:00
# Date Validation
date >= today()
# User/Team Validation
(user_id XOR team_id) OR (user_ids for shared desks)
# Room Type Validation
PRIVATE → requires user_id
CONFERENCE → requires team_id AND team.size >= 3
SHARED_DESK → requires user_ids (1-4 items)
# Double Booking Prevention
No two active bookings for same (room, date, time_slot)
No user can have multiple bookings at same (date, time_slot)| Action | Regular User | Admin |
|---|---|---|
| Register | ✅ | ✅ |
| Login | ✅ | ✅ |
| View rooms | ✅ | ✅ |
| Check availability | ✅ | ✅ |
| Create booking | ✅ Own | ✅ Any |
| View bookings | ✅ Own only | ✅ All |
| Cancel booking | ✅ Own only | ✅ Any |
| Create users | ❌ | ✅ |
| Create teams | ❌ | ✅ |
| Admin panel | ❌ | ✅ |
# All tests
python manage.py test
# With Docker
docker-compose exec web python manage.py test
# Specific test class
python manage.py test bookings.tests.BookingServiceTest
# Verbose output
python manage.py test --verbosity=21. UserModelTest - User model functionality
- User creation
- is_child property (age < 10)
2. TeamModelTest - Team model functionality
- Team creation
- Team size calculation
- Adult count (excluding children)
3. RoomModelTest - Room model functionality
- Room creation for all types
- Room capacity validation
4. BookingServiceTest - Business logic
- Private room booking success/failure
- Conference room booking with team validation
- Team size validation (minimum 3 members)
- Shared desk booking (1-4 users)
- Shared desk capacity limits
- Double-booking prevention
- Invalid time slot rejection
- Past date rejection
- Available rooms calculation
5. UserAPITest - User API endpoints
- Create user via API
- List users
6. TeamAPITest - Team API endpoints
- Create team with members
- Team size validation
7. BookingAPITest - Booking API endpoints
- Create private/conference/shared bookings
- List bookings with pagination
- Cancel booking
- Get booking details
- Check available rooms
- Invalid date rejection
- Invalid time slot rejection
8. RoomAvailabilityTest - Availability checking
- 15 rooms initialization
- Availability decreases after booking
- Availability summary endpoint
9. UserRegistrationTest - User registration
- Successful registration
- Password mismatch validation
- Duplicate username validation
- Auto-login after registration
10. JWTAuthenticationTest - JWT authentication
- Obtain JWT token
- Access protected endpoint with token
- Reject access without token
11. MyBookingsTest - Booking history
- Empty booking history
- Booking history with active bookings
- Booking history with cancelled bookings
- Active vs cancelled separation
12. PermissionTest - Authorization
- Regular user cannot cancel others' bookings
- Admin can cancel any booking
- User can cancel own booking
- Role-based access control
13. ConcurrencyTest - Race conditions
- Concurrent booking prevention
- select_for_update() locking
- Only one booking succeeds
14. BookingIDTest - ID generation
- Unique booking ID generation
- Booking ID format (BK + timestamp + room)
15. SharedDeskTest - Shared desk specifics
- Partial fills (1-4 users)
- Sequential seat assignment
- Maximum capacity enforcement
16. CancellationTest - Cancellation logic
- Slot freed after cancellation
- Cancelled bookings preserved (soft delete)
17. ValidationTest - All validations
- Time before 9 AM rejected
- Time after 6 PM rejected
- Past dates rejected
18. RoomInitializationTest - Room setup
- Exactly 15 rooms created
- Correct room numbers (P01-P08, C01-C04, S01-S03)
| Category | Tests | Status |
|---|---|---|
| Models | 7 tests | ✅ |
| Business Logic | 12 tests | ✅ |
| API Endpoints | 15 tests | ✅ |
| Authentication | 5 tests | ✅ |
| Permissions | 3 tests | ✅ |
| Concurrency | 2 tests | ✅ |
| Validations | 8 tests | ✅ |
| Room Management | 4 tests | ✅ |
| Total | 56 tests | ✅ |
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........................................................
----------------------------------------------------------------------
Ran 56 tests in 3.5s
OK
Destroying test database for alias 'default'...✅ User Management
- Registration, login, authentication
- Two-tier user system (Auth + Profile)
- Child vs adult users
✅ Room Management
- All 3 room types
- Capacity validation
- 15 rooms initialization
✅ Booking Logic
- All room types (Private, Conference, Shared)
- Time slot validation (9 AM - 6 PM)
- Date validation (no past dates)
- Double-booking prevention
- One slot per user rule
✅ Team Logic
- Team size validation (min 3 for conference)
- Team member management
- Child counting
✅ Shared Desk Logic
- Partial fills (1-4 users)
- Sequential seats (1, 2, 3, 4)
- Maximum capacity (4 users)
✅ Cancellation
- Slot freed immediately
- Soft delete (status change)
- Permission validation
✅ Concurrency
- Row-level locking
- Atomic transactions
- Race condition prevention
✅ Permissions
- Admin vs regular user
- Owner-based access
- JWT authentication
✅ Edge Cases
- Invalid time slots
- Past dates
- Duplicate bookings
- Team size violations
- Capacity limits
- Update
.env:
SECRET_KEY=generate-strong-random-key-here
DEBUG=False
DB_ENGINE=postgresql
DB_NAME=booking_system
DB_USER=booking_user
DB_PASSWORD=strong-password-here
DB_HOST=db
DB_PORT=5432
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
CORS_ALLOWED_ORIGINS=https://yourdomain.com- Deploy:
docker-compose up -d --build
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py init_rooms
docker-compose exec web python manage.py createsuperuser
docker-compose exec web python manage.py collectstatic --noinput- Authentication required on all endpoints
- JWT tokens with 1-hour expiry
- HTTPS enforcement (when DEBUG=False)
- Secure cookies
- CSRF protection
- XSS protection headers
- SQL injection prevention (ORM)
- Password strength validation
- CORS whitelist
- Database constraints
- Row-level locking
400 Bad Request
{
"error": "User already has a booking for this time slot"
}401 Unauthorized
{
"detail": "Authentication credentials were not provided."
}403 Forbidden
{
"error": "You do not have permission to cancel this booking"
}404 Not Found
{
"error": "Booking not found or already cancelled"
}Issue: "No such table: bookings_booking"
Solution: Run python manage.py makemigrations && python manage.py migrate
Issue: "Authentication credentials were not provided"
Solution: Add Authorization: Bearer <token> header
Issue: "No available rooms"
Solution: All rooms booked for that slot, try different time
Issue: "Conference rooms require teams of 3 or more"
Solution: Add more members to the team
Issue: "User already has a booking"
Solution: Cancel existing booking first or choose different time
booking_system/
├── booking_system/ # Django project settings
│ ├── settings.py # Configuration
│ ├── urls.py # Main URL routing
│ ├── wsgi.py # WSGI application
│ └── asgi.py # ASGI application
├── bookings/ # Main application
│ ├── models.py # Database models
│ ├── serializers.py # API serializers
│ ├── registration_serializers.py # Registration logic
│ ├── views.py # API views
│ ├── services.py # Business logic layer
│ ├── permissions.py # Custom permissions
│ ├── urls.py # App URL routing
│ ├── admin.py # Django admin config
│ ├── tests.py # Test suite
│ └── management/
│ └── commands/
│ └── init_rooms.py # Initialize 15 rooms
├── Dockerfile # Docker image definition
├── docker-compose.yml # Docker orchestration
├── requirements.txt # Python dependencies
├── .env # Environment variables
├── .gitignore # Git ignore rules
└── README.md # This file
Swagger UI: http://localhost:8000/api/docs/
- Try all endpoints
- See request/response formats
- Test authentication
- Explore schema
Django Admin: http://localhost:8000/admin/
- Manage users
- View all bookings
- Manage rooms and teams
- System administration
# View application logs
tail -f booking_system.log
# Docker logs
docker-compose logs -f web| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/v1/register/ |
POST | No | Register new user |
/api/token/ |
POST | No | Login (get token) |
/api/token/refresh/ |
POST | No | Refresh token |
/api/v1/users/ |
GET/POST | Yes | Manage users |
/api/v1/teams/ |
GET/POST | Yes | Manage teams |
/api/v1/rooms/ |
GET | Yes | List rooms |
/api/v1/rooms/available/ |
GET | Yes | Check availability |
/api/v1/bookings/ |
GET/POST | Yes | List/Create bookings |
/api/v1/bookings/my-bookings/ |
GET | Yes | User's history |
/api/v1/bookings/{id}/ |
GET | Yes | Booking details |
/api/v1/cancel/{id}/ |
POST | Yes | Cancel booking |
09:00 - 10:00
10:00 - 11:00
11:00 - 12:00
12:00 - 13:00
13:00 - 14:00
14:00 - 15:00
15:00 - 16:00
16:00 - 17:00
17:00 - 18:00
- PRIVATE - Individual users (8 rooms)
- CONFERENCE - Teams 3+ (4 rooms)
- SHARED_DESK - 1-4 users (3 rooms)
This project was created as part of a technical assessment for a Virtual Workspace Room Booking System.
Status: ✅ Production Ready
Version: 1.0.0
Last Updated: October 2025
For questions or issues, refer to the Swagger documentation at http://localhost:8000/api/docs/