Реализация ограничения базы данных Django 2.2 для нескольких столбцов - PullRequest
0 голосов
/ 24 октября 2019

У меня есть модель Django с начальной датой / временем и конечной датой / временем, где все четыре компонента могут (независимо) иметь нулевое значение (и существует семантическая разница между нулевым / неизвестным значением и известным значением). Я пытаюсь реализовать ограничение базы данных [ 1 , 2 ], чтобы проверить, что если они ненулевые, то дата / время начала предшествуют дате / времени окончания.

Я реализовал ограничение двумя различными способами (прокомментировал как Вариант 1, одно ограничение и Вариант 2 как два ограничения) ниже:

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'
      ),
    ]

Когда я запускаю makemigrations оба вариантауспешно.

В варианте 1, когда я пытаюсь использовать модель в тесте:

class EventModelTest( TestCase ):
  def test_simple(self):
    obj = Event.objects.create()
    self.assertTrue( isinstance( obj, Event ) )

Я получаю ошибку:

django.db.utils.DatabaseError: malformed database schema (packagename_event) - no such column: new_packagename_event.start_time

Эта ошибка исчезаетесли я закомментирую строку, помеченную # this line (но выполнение этого приведет к некорректной работе функции ограничения).

Опция 2, кажется, работает отлично, но менее очевидно, что она будет правильно рассматривать нулевые значения.

  1. Эквивалентны ли проверочные ограничения в варианте 1 и варианте 2?
  2. Почему отказывает вариант 1 и что можно сделать, чтобы его исправить? Это потому, что я пытаюсь сравнить значение одного и того же столбца (столбцов) в двух местах в одном и том же ограничении или есть другая причина?

1 Ответ

0 голосов
/ 25 октября 2019

Найден третий способ реализации ограничений, чтобы было более очевидно, что значения NULL считаются правильно с использованием двух ограничений:

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 3
      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__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' ) )
                ),
        name  = 'not_start_date_eq_end_date_and_start_time_gt_end_time'
      ),
    ]

Два ограничения будут перекрываться точно в случаях, когда:

  • start_date равно нулю;
  • end_date равно нулю;или
  • start_date < end_date

Остальные способы проверки могут проходить, когда первое ограничение равно start_date = end_date, а второе ограничение:

  • start_time равно нулю;
  • end_time равно нулю;или
  • start_time <= end_time

Что соответствует всем возможным случаям в Варианте 1.


При дальнейшем тестировании модель с приведенными ниже ограничениями демонстрирует то же самоепроблема:

class SimpleModel( models.Model ):
  value = models.IntegerField()

  class Meta:
    constraints = [
      models.CheckConstraint(
        check = ( models.Q( value__gte = 0 )
                & ( models.Q( value__gte = 0 )
                  | models.Q( value__gte = 0 ) # this line
                  )
                ),
        name  = "simplemodel_check1"
      ),
      models.CheckConstraint(
        check = ( models.Q( value__gte = 0 )
                & ( models.Q( value__gte = 0 )
                  & models.Q( value__gte = 0 )
                  )
                ),
        name  = "simplemodel_check2"
      ),
      models.CheckConstraint(
        check = ( models.Q( value__gte = 0 ) | models.Q( value__gte = 0 ) ),
        name  = "simplemodel_check3"
      ),
      models.CheckConstraint(
        check = ( models.Q( value__gte = 0 ) & models.Q( value__gte = 0 ) ),
        name  = "simplemodel_check4"
      ),
    ]

2-е, 3-е и 4-е ограничения работают без проблем, но 1-е ограничение вызывает исключение при попытке создать экземпляр модели с ошибкой:

django.db.utils.DatabaseError: malformed database schema (packagename_event) - no such column: new_packagename_simplemodel.value

Кажется, это проблема с комбинацией & и |.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...