Search code examples
pythondjangopostgresqldjango-models

Prevent DateRangeField overlap in Django model?


Now that Django supports the DateRangeField, is there a 'Pythonic' way to prevent records from having overlapping date ranges?

Hypothetical use case

One hypothetical use case would be a booking system, where you don't want people to book the same resource at the same time.

Hypothetical example code

class Booking(models.model):
    # The resource to be reserved
    resource = models.ForeignKey('Resource')
    # When to reserve the resource
    date_range = models.DateRangeField()

    class Meta:
        unique_together = ('resource', 'date_range',)

Solution

  • I know that the answer is old, but now you can just create a constraint in the meta of the model, that will make Postgres handle this

    from django.contrib.postgres.constraints import ExclusionConstraint
    from django.contrib.postgres.fields import DateTimeRangeField, RangeOperators
    from django.db import models
    from django.db.models import Q
    
    class Room(models.Model):
        number = models.IntegerField()
    
    
    class Reservation(models.Model):
        room = models.ForeignKey('Room', on_delete=models.CASCADE)
        timespan = DateTimeRangeField()
        cancelled = models.BooleanField(default=False)
    
        class Meta:
            constraints = [
                ExclusionConstraint(
                    name='exclude_overlapping_reservations',
                    expressions=[
                        ('timespan', RangeOperators.OVERLAPS),
                        ('room', RangeOperators.EQUAL),
                    ],
                    condition=Q(cancelled=False),
                ),
            ]
    

    Postgress Coonstraints

    In order for this to work, you need to also activate the btree_gist extension on PostgreSQL.

    You can do this by adding the following in lines in a migration file

    from django.contrib.postgres.operations import BtreeGistExtension
    
    class Migration(migrations.Migration):
    
        operations = [
             BtreeGistExtension(),
             ...
        ]