1+ from django .core .validators import MinValueValidator , MaxValueValidator
12from django .db import models
23from django .contrib .auth .models import User
34import uuid
45from datetime import timedelta
56from django .utils .timezone import now
7+ from rest_framework .exceptions import ValidationError
68
79
810class Booking (models .Model ):
911 PENDING = 'Pending'
1012 APPROVED = 'Approved'
1113 REJECTED = 'Rejected'
14+ EXPIRED = 'Expired'
15+ CANCELLED = 'Cancelled'
16+ FULFILLED = 'Fulfilled'
1217
1318 BOOKING_STATUS_TYPES = [
1419 (PENDING , 'Pending' ),
1520 (APPROVED , 'Approved' ),
1621 (REJECTED , 'Rejected' ),
22+ (EXPIRED , 'Expired' ),
23+ (CANCELLED , 'Cancelled' ),
24+ (FULFILLED , 'Fulfilled' ),
1725 ]
1826
1927 session_date = models .DateTimeField ()
2028 session_updated_at = models .DateTimeField (auto_now = True )
29+ session_end_time = models .DateTimeField (null = True )
2130 booking_status = models .CharField (max_length = 20 , choices = BOOKING_STATUS_TYPES , default = PENDING )
2231 session_price = models .DecimalField (max_digits = 10 , decimal_places = 2 )
23- student = models .ForeignKey (User , on_delete = models .CASCADE , related_name = "student " )
32+ student = models .ForeignKey (User , on_delete = models .CASCADE , related_name = "student_bookings " )
2433 tutor = models .ForeignKey (User , on_delete = models .CASCADE , related_name = "tutor" )
2534 description = models .TextField (null = True , blank = True )
2635 payment_gateway_ref = models .UUIDField (primary_key = True , default = uuid .uuid4 , editable = False )
@@ -39,19 +48,65 @@ def is_paid(self):
3948 return self .payment_gateway_ref is not None
4049
4150 def reschedule_booking (self , new_date ):
51+ if self .session_date < now ():
52+ return False
4253 self .session_date = new_date
4354 self .save ()
4455
4556 def cancel_booking (self ):
4657 if self .session_date < now ():
4758 return False
4859 self .booking_status = self .REJECTED
49- self .save ()
60+ self .save (update_fields = [ 'booking_status' ] )
5061 return True
5162
52- def booking_end_time (self , duration_in_minutes = 60 ):
53- return self .session_date + timedelta (minutes = duration_in_minutes )
63+ def booking_duration (self ):
64+ if not self .session_end_time :
65+ return timedelta (0 )
66+ return self .session_end_time - self .session_date
5467
5568 @classmethod
5669 def get_bookings_by_status (cls , status ):
5770 return cls .objects .filter (booking_status = status )
71+
72+ def is_expired (self ):
73+ return self .session_end_time < now ()
74+
75+ def clean (self ):
76+ if self .session_end_time <= self .session_date :
77+ raise ValidationError ("Session end time must be after the session start time." )
78+
79+ def save (self , * args , ** kwargs ):
80+ """Run full validation before saving."""
81+ self .full_clean () # Ensures validation runs before saving
82+ super ().save (* args , ** kwargs )
83+
84+
85+ class Review (models .Model ):
86+ tutor = models .ForeignKey (User , on_delete = models .CASCADE , related_name = "reviews" )
87+ reviewer = models .ForeignKey (User , on_delete = models .CASCADE , related_name = "reviewer" )
88+ rating = models .PositiveSmallIntegerField (validators = [MinValueValidator (1 ), MaxValueValidator (5 )])
89+ feedback = models .TextField (null = True , blank = True )
90+ created_at = models .DateTimeField (auto_now_add = True )
91+
92+ is_visible = models .BooleanField (default = True ) # Whether the review is visible to others
93+ is_moderated = models .BooleanField (
94+ default = False ) # Whether the review has been moderated (e.g., checked for inappropriate content)
95+
96+ def __str__ (self ):
97+ return f"Review for { self .tutor .username } by { self .reviewer .username } "
98+
99+ class Meta :
100+ ordering = ['-created_at' ] # Sort reviews by most recent first
101+ unique_together = ('tutor' , 'reviewer' )
102+
103+ def is_valid_review (self ):
104+ """Checks if the review has a valid rating and comments."""
105+ return self .rating is not None and self .feedback .strip () != ""
106+
107+ @staticmethod
108+ def average_rating (tutor ):
109+ """Calculates the average rating for a tutor based on all reviews."""
110+ reviews = Review .objects .filter (tutor = tutor )
111+ total_rating = sum ([review .rating for review in reviews ])
112+ return total_rating / len (reviews ) if reviews else 0
0 commit comments