11from django .conf import settings
22from django .core .validators import MaxValueValidator , MinValueValidator
3+ from django .core .exceptions import ValidationError
34from django .db import models
45from django .dispatch import receiver
56from email_tools .emails import send_email
67from phonenumber_field .modelfields import PhoneNumberField
7-
8+ from schedule . models import Event , Occurrence
89
910User = settings .AUTH_USER_MODEL
1011
@@ -447,3 +448,71 @@ class Meta:
447448
448449 def __str__ (self ):
449450 return f"{ self .user } : { self .metric } "
451+
452+ class Booking (models .Model ):
453+ """
454+ Booking within an occurrence.
455+ Bookings can only be created with start times of 5-minute intervals.
456+ """
457+
458+ occurrence = models .ForeignKey (Occurrence , on_delete = models .CASCADE , related_name = "bookings" )
459+ user = models .ForeignKey (User , on_delete = models .CASCADE , blank = True , null = True )
460+ start = models .DateTimeField ("start" , db_index = True )
461+ end = models .DateTimeField ("end" , db_index = True )
462+
463+ class Meta :
464+ verbose_name = ("booking" )
465+ verbose_name_plural = ("bookings" )
466+ ordering = ["start" ]
467+ index_together = (("start" , "end" ),)
468+
469+ def clean (self ):
470+ if self .start >= self .end :
471+ raise ValidationError ('Start time must be before end time.' )
472+
473+ if self .start .minute % 5 != 0 :
474+ raise ValidationError ('Start time must be on a 5-minute interval (e.g., :00, :05, :10, :15, etc).' )
475+
476+ if self .start < self .occurrence .start or self .end > self .occurrence .end :
477+ raise ValidationError ('Booking times must be within the occurrence\' s start and end times.' )
478+
479+ duration = self .end - self .start
480+ duration_minutes = duration .total_seconds () / 60
481+
482+ if duration_minutes != self .occurrence .interval :
483+ raise ValidationError (f'Booking duration must be { self .occurrence .interval } minutes.' )
484+
485+ overlapping_bookings = Booking .objects .filter (
486+ occurrence = self .occurrence ,
487+ start__lt = self .end ,
488+ end__gt = self .start
489+ ).exclude (id = self .id )
490+ if overlapping_bookings .exists ():
491+ raise ValidationError ('Booking times cannot overlap with existing bookings.' )
492+
493+ super ().clean ()
494+
495+ def save (self , * args , ** kwargs ):
496+ self .clean ()
497+ super ().save (* args , ** kwargs )
498+
499+ def __str__ (self ):
500+ start_str = self .start .strftime ("%Y-%m-%d %H:%M:%S" )
501+ end_str = self .end .strftime ("%Y-%m-%d %H:%M:%S" )
502+ return f"{ start_str } to { end_str } "
503+
504+ Event .add_to_class ('location' , models .CharField (max_length = 255 , blank = True ))
505+ Occurrence .add_to_class ('location' , models .CharField (max_length = 255 , blank = True ))
506+ Occurrence .add_to_class ('interval' , models .IntegerField (blank = True , null = True ))
507+
508+ def new_occurrence_init (self , * args , ** kwargs ):
509+ super (Occurrence , self ).__init__ (* args , ** kwargs )
510+ event = kwargs .get ("event" , None )
511+ if not self .title and event :
512+ self .title = event .title
513+ if not self .description and event :
514+ self .description = event .description
515+ if not self .location and event :
516+ self .location = event .location
517+
518+ Occurrence .__init__ = new_occurrence_init
0 commit comments