I have a Django model with start date/time and end date/time where all four components may (independently) be a null value (and there is a semantic difference between a null/unknown value and a known value). I am trying to implement a database constraint [1, 2] to check that if they are non-null that the start date/time is before the end date/time.
I have implemented the constraint in two different ways (commented as Option 1, a single constraint, and Option 2, as two constraints) below:
from django.db import models
class Event( models.Model ):
start_date = models.DateField( blank = True, null = True )
start_time = models.TimeField( blank = True, null = True )
end_date = models.DateField( blank = True, null = True )
end_time = models.TimeField( blank = True, null = True )
class Meta:
constraints = [
# Option 1
models.CheckConstraint(
check = ( models.Q( start_date__isnull = True )
| models.Q( end_date__isnull = True )
| models.Q( start_date__lt = models.F( 'end_date' ) )
| ( ( models.Q( start_time__isnull = True )
| models.Q( end_time__isnull = True )
| models.Q( start_time__lte = models.F( 'end_time' ) )
)
& models.Q( start_date = models.F( 'end_date' ) ) # This line
)
),
name = 'start_date_and_time_lte_end_date_and_time'
),
# Option 2
models.CheckConstraint(
check = ( models.Q( start_date__isnull = True )
| models.Q( end_date__isnull = True )
| models.Q( start_date__lte = models.F( 'end_date' ) )
),
name = 'start_date_lte_end_date'
),
models.CheckConstraint(
check = ~( models.Q( start_date = models.F( 'end_date' ) )
& models.Q( start_time_gt = models.F( 'end_time' ) )
),
name = 'not_start_date_eq_end_date_and_start_time_gt_end_time'
),
]
When I run makemigrations
both options succeed.
With Option 1, when I try to use the model in a test:
class EventModelTest( TestCase ):
def test_simple(self):
obj = Event.objects.create()
self.assertTrue( isinstance( obj, Event ) )
I get the error:
django.db.utils.DatabaseError: malformed database schema (packagename_event) - no such column: new_packagename_event.start_time
This error goes away if I comment out the line marked # this line
(but doing that would make the constraint function incorrectly).
Option 2 appears to work perfectly but is less obvious that it is going to consider null values correctly.
- Are the check constraints in option 1 and option 2 equivalent?
- Why does option 1 fail and what can be done to fix it? Is it because I am trying to compare the value of the same column(s) in two places in the same constraint or is there another reason?