TabularInline and date range overlap constraint

I use the following formset to avoid overlapping date ranges:

class OccupancyInlineFormset(BaseInlineFormSet):
    def clean(self):
        super().clean()
        for form in self.forms:
            conflicts = Occupancy.objects.filter(unit=form.cleaned_data['unit'], begin__lte=form.cleaned_data['end'], end__gte=form.cleaned_data['begin'])
        if any(conflicts):
            raise ValidationError(_('Overlapping occupancies!'), code='overlap')

This works well in principle, but my current Occupancy instance always needs to have an end date of 9999-12-31. When I change this date in the admin form for the current instance (and add a new instance with an ende date of 9999-12-31), the clean() function will always raise an exception based on the values stored in the database. I do not really see how I could avoid this without changing the offending end date (9999-12-31 → 2023-01-31) first in a different (unconstrained) form, which defeats the purpose of the TabularInline form. Thanks for any help!

After thinking the problem through, I arrived at the following (clunky, but working) solution:

def clean(self):
    super().clean()
    infinity = datetime.date(9999,12,31)
    for form in self.forms:
        try:
            current_id = form.cleaned_data['id'].pk
        except AttributeError:
            current_id = None
        unit = form.cleaned_data['unit']
        begin = form.cleaned_data['begin']
        end = form.cleaned_data['end']
        # Exclude the instance corresponding to the form and the current occupancy (infinity end date) from the set of conflicting instances
        conflicts = Occupancy.objects.filter(unit=unit, begin__lte=end, end__gte=begin).exclude(pk=current_id).exclude(end=infinity)
    if any(conflicts):
        raise forms.ValidationError(_('Overlapping occupancies!'), code='overlap')
    # if the new occupancy has an infinity end date ...
    elif end == infinity:
        try:
            current_occupancy = Occupancy.objects.get(unit=unit, end=datetime.date(9999,12,31))
            # ... and is not identical with the current occupancy ...
            if current_occupancy.pk != current_id:
                # ... update the current occupancy to end just before the new (infinite) occupancy
                current_occupancy.end = begin - datetime.timedelta(days=1)
                current_occupancy.save()
        except ObjectDoesNotExist:
            pass

This takes into account that an instance may overlap (at least while cleaning the input) with itself, and that a new occupancy with an infinite end date should replace the former "current" occupancy.

Back to Top