djangodjango-modelsdjango-ormdjango-postgresql

Django validating time slot


This is my models to store availability of particular time when a new booking there

class TimeSlot(models.Model):
day = models.ForeignKey(
    Day,
    on_delete=models.CASCADE,
    related_name="time"
)
booking = models.ForeignKey(
    Booking,
    on_delete=models.CASCADE,
    related_name="time"
)
start_hour = models.TimeField()
end_hour = models.TimeField()

class Meta:
    unique_together = [('end_hour', 'start_hour',)]


def clean(self):
    pass

Currently it's allowing booking even those are considered as duplicate in terms of end_hour and start_hour. I want to prevent the slot, so that no new booking shouln't placed between a range that already booked.

Can anyone know how to do it with the range?


Solution

  • I assume the problem is that start_hour and end_hour that fall within an already existing time range are allowed to be added. Of course the unique_together constraint cannot handle this as it only deals with uniqueness not uniqueness in a range. Instead you can override your models clean method and perform this validation there:

    from django.db.models import Q
    from django.core.exceptions import ValidationError
    
    
    class TimeSlot(models.Model):
        day = models.ForeignKey(
            Day,
            on_delete=models.CASCADE,
            related_name="time"
        )
        booking = models.ForeignKey(
            Booking,
            on_delete=models.CASCADE,
            related_name="time"
        )
        start_hour = models.TimeField()
        end_hour = models.TimeField()
        
        class Meta:
            unique_together = [('end_hour', 'start_hour',)]
        
        
        def clean(self):
            start_hour_in_range = Q(start_hour__lte=self.start_hour, end_hour__gte=self.start_hour)
            end_hour_in_range = Q(start_hour__lte=self.end_hour, end_hour__gte=self.end_hour)
            # Queryset that finds all clashing timeslots with the same day
            queryset = self._meta.default_manager.filter(start_hour_in_range | end_hour_in_range, day=self.day)
            if self.pk:
                queryset = queryset.exclude(pk=self.pk) # Exclude this object if it is already saved to the database
            if queryset.exists():
                raise ValidationError('An existing timeslot clashes with the given one!')
    

    Next if you are using a ModelForm this method would be called automatically or if you are not you can call instance.full_clean() which will call this method and all other cleaning methods on the model (clean_fields and validate_unique).