app.models
Domain models for the room-booking system.
This package contains the SQLModel definitions representing the core entities
of the application. All models are re-exported here for convenient importing
and to ensure proper registration with SQLAlchemy's metadata before database
initialization.
Exports:
User: Represents an authenticated person in the system.Room: Represents a physical bookable space.TimeSlot: Represents a specific bookable time window for a room.Booking: Represents a confirmed reservation.Notification: Represents a system alert or message.
1""" 2Domain models for the room-booking system. 3 4This package contains the `SQLModel` definitions representing the core entities 5of the application. All models are re-exported here for convenient importing 6and to ensure proper registration with SQLAlchemy's metadata before database 7initialization. 8 9**Exports:** 10 11- `User`: Represents an authenticated person in the system. 12- `Room`: Represents a physical bookable space. 13- `TimeSlot`: Represents a specific bookable time window for a room. 14- `Booking`: Represents a confirmed reservation. 15- `Notification`: Represents a system alert or message. 16""" 17 18from .booking import ( 19 Booking, 20 BookingStatus, 21 RecurrenceFrequency, 22 TimeSlot, 23 TimeslotStatus, 24) 25from .notification import Notification, NotificationType 26from .room import Room 27from .user import User, UserRole 28 29__all__ = [ 30 "User", 31 "UserRole", 32 "Room", 33 "TimeSlot", 34 "TimeslotStatus", 35 "RecurrenceFrequency", 36 "BookingStatus", 37 "Booking", 38 "NotificationType", 39 "Notification", 40]
32class User(SQLModelBaseUserDB, table=True): 33 """ 34 Represents any authenticated person in the system. 35 36 This class inherits from `SQLModelBaseUserDB` which provides core authentication 37 attributes, and it adds a specific role attribute for access control. 38 """ 39 40 name: str = Field(default="") 41 """The display name of the user. Defaults to empty string.""" 42 43 role: UserRole = Field(default=UserRole.STUDENT) 44 """The system role of the user (e.g., student, teacher, admin). Defaults to `UserRole.STUDENT`.""" 45 46 @field_validator("role", mode="before") 47 @classmethod 48 def validate_role(cls, v: object) -> UserRole: 49 """ 50 Validates and coerces the incoming value for the user's role. 51 """ 52 if isinstance(v, UserRole): 53 return v 54 try: 55 return UserRole(v) 56 except ValueError: 57 allowed = ", ".join(r.value for r in UserRole) 58 raise ValueError(f"Invalid role '{v}'. Must be one of: {allowed}")
Represents any authenticated person in the system.
This class inherits from SQLModelBaseUserDB which provides core authentication
attributes, and it adds a specific role attribute for access control.
The system role of the user (e.g., student, teacher, admin). Defaults to UserRole.STUDENT.
46 @field_validator("role", mode="before") 47 @classmethod 48 def validate_role(cls, v: object) -> UserRole: 49 """ 50 Validates and coerces the incoming value for the user's role. 51 """ 52 if isinstance(v, UserRole): 53 return v 54 try: 55 return UserRole(v) 56 except ValueError: 57 allowed = ", ".join(r.value for r in UserRole) 58 raise ValueError(f"Invalid role '{v}'. Must be one of: {allowed}")
Validates and coerces the incoming value for the user's role.
19class UserRole(str, enum.Enum): 20 """ 21 Enumeration of allowed roles for a User in the room-booking system. 22 """ 23 24 STUDENT = "student" 25 """A standard user with base booking privileges.""" 26 TEACHER = "teacher" 27 """An instructor user with elevated booking privileges.""" 28 ADMIN = "admin" 29 """A system administrator with full access rights."""
Enumeration of allowed roles for a User in the room-booking system.
17class Room(SQLModel, table=True): 18 """ 19 Represents a bookable physical space at the campus. 20 """ 21 22 id: Optional[int] = Field(default=None, primary_key=True) 23 """The primary key of the room.""" 24 25 name: str = Field(sa_column=Column(String, unique=True, nullable=False)) 26 """The human-readable name of the room (e.g., `"A-203"`). Must be unique.""" 27 28 capacity: int = Field(nullable=False, ge=1) 29 """The maximum number of people the room can accommodate. Must be `>= 1`.""" 30 31 time_slots: List["TimeSlot"] = Relationship(back_populates="room") # type: ignore # noqa: F821
Represents a bookable physical space at the campus.
131class TimeSlot(SQLModel, table=True): 132 """ 133 Represents any room request or timeslot unit in the system. 134 """ 135 136 id: int | None = Field(default=None, primary_key=True) 137 """The primary key for the timeslot.""" 138 room_id: int = Field(foreign_key="room.id", nullable=False) 139 """Foreign key linking to the associated `Room`.""" 140 slot_date: date = Field(nullable=False) 141 """The specific date of the timeslot.""" 142 start_time: time = Field(nullable=False) 143 """The start time of the timeslot window.""" 144 end_time: time = Field(nullable=False) 145 """The end time of the timeslot window.""" 146 status: TimeslotStatus = Field(default=TimeslotStatus.AVAILABLE) 147 """Current status of the slot (`available`, `held`, `booked`).""" 148 booking_id: int | None = Field(default=None, foreign_key="booking.id") 149 """Foreign key linking to a confirmed `Booking`.""" 150 booking: Optional["Booking"] = Relationship(back_populates="timeSlots") # noqa: F821 151 """The Booking this timeslot belongs to, if any.""" 152 153 room: Optional["Room"] = Relationship(back_populates="time_slots") # noqa: F821 154 155 def hold(self): 156 """ 157 Temporarily reserve the room timeslot. 158 """ 159 if self.status != TimeslotStatus.AVAILABLE: 160 raise ValueError("Only available timeslots can be held.") 161 self.status = TimeslotStatus.HELD 162 163 def book(self): 164 """ 165 Confirm a reservation for the room timeslot. 166 """ 167 if self.status != TimeslotStatus.HELD: 168 raise ValueError("Only held timeslots can be booked.") 169 self.status = TimeslotStatus.BOOKED 170 171 def release(self): 172 """ 173 Cancel a reservation or hold on the room timeslot, freeing it. 174 """ 175 if self.status != TimeslotStatus.HELD and self.status != TimeslotStatus.BOOKED: 176 raise ValueError("Only held or booked timeslots can be released.") 177 self.status = TimeslotStatus.AVAILABLE
Represents any room request or timeslot unit in the system.
The Booking this timeslot belongs to, if any.
155 def hold(self): 156 """ 157 Temporarily reserve the room timeslot. 158 """ 159 if self.status != TimeslotStatus.AVAILABLE: 160 raise ValueError("Only available timeslots can be held.") 161 self.status = TimeslotStatus.HELD
Temporarily reserve the room timeslot.
163 def book(self): 164 """ 165 Confirm a reservation for the room timeslot. 166 """ 167 if self.status != TimeslotStatus.HELD: 168 raise ValueError("Only held timeslots can be booked.") 169 self.status = TimeslotStatus.BOOKED
Confirm a reservation for the room timeslot.
171 def release(self): 172 """ 173 Cancel a reservation or hold on the room timeslot, freeing it. 174 """ 175 if self.status != TimeslotStatus.HELD and self.status != TimeslotStatus.BOOKED: 176 raise ValueError("Only held or booked timeslots can be released.") 177 self.status = TimeslotStatus.AVAILABLE
Cancel a reservation or hold on the room timeslot, freeing it.
118class TimeslotStatus(str, Enum): 119 """ 120 Allowed states for a room timeslot in the room-booking system. 121 """ 122 123 AVAILABLE = "available" 124 """The timeslot is free to be held or booked.""" 125 HELD = "held" 126 """The timeslot is temporarily reserved.""" 127 BOOKED = "booked" 128 """The timeslot is fully confirmed and reserved."""
Allowed states for a room timeslot in the room-booking system.
33class RecurrenceFrequency(str, Enum): 34 """ 35 Allowed recurrence frequency values. 36 """ 37 38 NONE = "none" 39 WEEKLY = "weekly"
Allowed recurrence frequency values.
22class BookingStatus(str, Enum): 23 """ 24 Allowed lifecycle statuses for any given booking. 25 """ 26 27 PENDING = "pending" 28 APPROVED = "approved" 29 DENIED = "denied" 30 CANCELLED = "cancelled"
Allowed lifecycle statuses for any given booking.
42class Booking(SQLModel, table=True): 43 """ 44 Represents the room booking request made by a given user. 45 """ 46 47 id: Optional[int] = Field(default=None, primary_key=True) 48 userID: UUID = Field(foreign_key="user.id", nullable=False) 49 submittedByRole: UserRole = Field(default=UserRole.STUDENT, nullable=False) 50 roomID: int = Field(foreign_key="room.id", nullable=False) 51 status: BookingStatus = Field(default=BookingStatus.PENDING) 52 recurrenceFrequency: RecurrenceFrequency = Field(default=RecurrenceFrequency.NONE) 53 recurrenceEndDate: Optional[date] = Field(default=None, nullable=True) 54 createdAt: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) 55 56 # One Booking -> many TimeSlots 57 timeSlots: List["TimeSlot"] = Relationship(back_populates="booking") # noqa: F821 58 59 @field_validator("status", mode="before") 60 @classmethod 61 def validate_status(cls, a: object) -> BookingStatus: 62 """ 63 Ensure the status value is a valid BookingStatus. 64 """ 65 if isinstance(a, BookingStatus): 66 return a 67 try: 68 return BookingStatus(a) 69 except ValueError: 70 allowed = ", ".join(t.value for t in BookingStatus) 71 raise ValueError(f"Invalid status '{a}'. Must be one of: {allowed}") 72 73 @field_validator("recurrenceFrequency", mode="before") 74 @classmethod 75 def validate_recurrence_frequency(cls, a: object) -> RecurrenceFrequency: 76 """ 77 Ensure the recurrenceFrequency value is a valid RecurrenceFrequency. 78 """ 79 if isinstance(a, RecurrenceFrequency): 80 return a 81 try: 82 return RecurrenceFrequency(a) 83 except ValueError: 84 allowed = ", ".join(t.value for t in RecurrenceFrequency) 85 raise ValueError(f"Invalid recurrence '{a}'. Must be one of: {allowed}") 86 87 @field_validator("submittedByRole", mode="before") 88 @classmethod 89 def validate_submitted_by_role(cls, a: object) -> UserRole: 90 """ 91 Ensure the submittedByRole value is a valid UserRole. 92 """ 93 if isinstance(a, UserRole): 94 return a 95 try: 96 return UserRole(a) 97 except ValueError: 98 allowed = ", ".join(role.value for role in UserRole) 99 raise ValueError( 100 f"Invalid submittedByRole '{a}'. Must be one of: {allowed}" 101 ) 102 103 @model_validator(mode="after") 104 def validate_recurrence_end_date(self) -> "Booking": 105 """ 106 Weekly bookings must supply a recurrenceEndDate. 107 """ 108 if ( 109 self.recurrenceFrequency == RecurrenceFrequency.WEEKLY 110 and self.recurrenceEndDate is None 111 ): 112 raise ValueError( 113 "recurrenceEndDate must not be None when recurrenceFrequency is 'weekly'." 114 ) 115 return self
Represents the room booking request made by a given user.
59 @field_validator("status", mode="before") 60 @classmethod 61 def validate_status(cls, a: object) -> BookingStatus: 62 """ 63 Ensure the status value is a valid BookingStatus. 64 """ 65 if isinstance(a, BookingStatus): 66 return a 67 try: 68 return BookingStatus(a) 69 except ValueError: 70 allowed = ", ".join(t.value for t in BookingStatus) 71 raise ValueError(f"Invalid status '{a}'. Must be one of: {allowed}")
Ensure the status value is a valid BookingStatus.
73 @field_validator("recurrenceFrequency", mode="before") 74 @classmethod 75 def validate_recurrence_frequency(cls, a: object) -> RecurrenceFrequency: 76 """ 77 Ensure the recurrenceFrequency value is a valid RecurrenceFrequency. 78 """ 79 if isinstance(a, RecurrenceFrequency): 80 return a 81 try: 82 return RecurrenceFrequency(a) 83 except ValueError: 84 allowed = ", ".join(t.value for t in RecurrenceFrequency) 85 raise ValueError(f"Invalid recurrence '{a}'. Must be one of: {allowed}")
Ensure the recurrenceFrequency value is a valid RecurrenceFrequency.
87 @field_validator("submittedByRole", mode="before") 88 @classmethod 89 def validate_submitted_by_role(cls, a: object) -> UserRole: 90 """ 91 Ensure the submittedByRole value is a valid UserRole. 92 """ 93 if isinstance(a, UserRole): 94 return a 95 try: 96 return UserRole(a) 97 except ValueError: 98 allowed = ", ".join(role.value for role in UserRole) 99 raise ValueError( 100 f"Invalid submittedByRole '{a}'. Must be one of: {allowed}" 101 )
Ensure the submittedByRole value is a valid UserRole.
103 @model_validator(mode="after") 104 def validate_recurrence_end_date(self) -> "Booking": 105 """ 106 Weekly bookings must supply a recurrenceEndDate. 107 """ 108 if ( 109 self.recurrenceFrequency == RecurrenceFrequency.WEEKLY 110 and self.recurrenceEndDate is None 111 ): 112 raise ValueError( 113 "recurrenceEndDate must not be None when recurrenceFrequency is 'weekly'." 114 ) 115 return self
Weekly bookings must supply a recurrenceEndDate.
20class NotificationType(str, enum.Enum): 21 """ 22 Allowed notification type values. 23 """ 24 25 APPROVED = "approved" 26 DENIED = "denied" 27 CANCELLED = "cancelled"
Allowed notification type values.
30class Notification(SQLModel, table=True): 31 """ 32 Represents an in-app notification sent to a user about their booking. 33 """ 34 35 id: Optional[int] = Field(default=None, primary_key=True) 36 userID: UUID = Field(foreign_key="user.id", nullable=False) 37 bookingID: int = Field(foreign_key="booking.id", nullable=False) 38 message: str = Field(nullable=False) 39 type: NotificationType = Field(nullable=False) 40 isRead: bool = Field(default=False) 41 createdAt: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) 42 43 @field_validator("type", mode="before") 44 @classmethod 45 def validate_type(cls, a: object) -> NotificationType: 46 """ 47 Ensure the type value is a valid NotificationType. 48 """ 49 if isinstance(a, NotificationType): 50 return a 51 try: 52 return NotificationType(a) 53 except ValueError: 54 allowed = ", ".join(t.value for t in NotificationType) 55 raise ValueError( 56 f"Invalid Notification Type '{a}'. Must be one of: {allowed}" 57 )
Represents an in-app notification sent to a user about their booking.
43 @field_validator("type", mode="before") 44 @classmethod 45 def validate_type(cls, a: object) -> NotificationType: 46 """ 47 Ensure the type value is a valid NotificationType. 48 """ 49 if isinstance(a, NotificationType): 50 return a 51 try: 52 return NotificationType(a) 53 except ValueError: 54 allowed = ", ".join(t.value for t in NotificationType) 55 raise ValueError( 56 f"Invalid Notification Type '{a}'. Must be one of: {allowed}" 57 )
Ensure the type value is a valid NotificationType.