Django QuerySet filter by Range with DateTimeField

Question:

I tried to fix my queryset method, checked some answers on Stackoverflow, but still couldn’t do it.

Basically i want to validate my form, so when there is an lesson within ceratin range, a Student cannot propose this specific timerange to it’s lesson.

The problem is that when i try to find a queryset withing DateTimeField that exists, i still got an empty queryset:

My model:

class Lesson(models.Model):
    student = models.ForeignKey(User, on_delete=models.CASCADE)
    subject = models.ForeignKey(Classroom, on_delete=models.CASCADE)
    description = models.CharField(max_length = 200)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    accepted = models.BooleanField(default=False)

Method in form:

def clean(self):
    cleaned_data = super().clean()
    lesson_start_time = cleaned_data.get('start_time')
    lesson_end_time = cleaned_data.get('end_time')
    
    queryset = Lesson.objects.filter(start_time__range=(lesson_start_time,lesson_end_time))
    print(lesson_start_time) #2022-08-23 15:44:00+00:00
    print(lesson_end_time) #2022-08-23 15:36:00+00:00
    print(queryset) # <QuerySet []>
    
    if lesson_end_time < lesson_start_time:
        raise ValidationError("End time cannot be lower than start time!")

And there is for sure a Lesson Object within this range.

What i know for now is that it requeries something else to filter DateTimeField by range, but everything that i tried didn’t work (for example: Django – Filter objects by DateTimeField with a date range).

Could you please help me?

Asked By: Patryk Karbowy

||

Answers:

That is possible: two ranges can overlap, without that the start time or end time is within the "range" of the other object. For example if the second object is fully contained by the first object.

You can fix this by first determining when two ranges do not overlap. Two ranges [s1, e1] and [s2, e2] do not overlap if s1<e2, or e1<s2. We can negate this expression to know when two intervals overlap: s1≤e2, and e1≥s2.

This thus means that we can check this with:

def clean(self):
    cleaned_data = super().clean()
    start = cleaned_data['start_time']
    end = cleaned_data['end_time']

    queryset = Lesson.objects.exclude(pk=self.instance.pk).filter(
        start_time__lte=end, end_time__gte=start
    )
    if end <= start:
        raise ValidationError('End time cannot be lower than start time!')
    if queryset.exists():
        raise ValidationError('There is an item with overlap!')
    return cleaned_data
Answered By: Willem Van Onsem