Проблема с Django CheckConstraint
Я пытаюсь добавить несколько новых полей в существующую модель, а также ограничение, связанное с этими новыми полями:
class User(models.Model):
username = models.CharField(max_length=32)
# New fields ##################################
has_garden = models.BooleanField(default=False)
garden_description = models.CharField(
max_length=32,
null=True,
blank=True,
)
class Meta:
constraints = [
models.CheckConstraint(
check=Q(has_garden=models.Value(True))
& Q(garden_description__isnull=True),
name="garden_description_if_has_garden",
)
]
Проблема заключается в том, что при запуске миграций я получаю следующую ошибку:
django.db.utils.IntegrityError: check constraint "garden_description_if_has_garden" is violated by some row
Но я не понимаю, как нарушается ограничение, если ни одно User
не имеет has_garden
, поле только создается, а также его значение по умолчанию False
🤔.
Я использую django 3.2 с postgresql.
Как правильно добавить это ограничение? Если это будет полезно, вот автогенерируемая миграция:
# Generated by Django 3.2.25 on 2025-01-15 23:52
import django.db.models.expressions
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("some_app", "0066_user"),
]
operations = [
migrations.AddField(
model_name="user",
name="garden_description",
field=models.CharField(blank=True, max_length=32, null=True),
),
migrations.AddField(
model_name="user",
name="has_garden",
field=models.BooleanField(default=False),
),
migrations.AddConstraint(
model_name="user",
constraint=models.CheckConstraint(
check=models.Q(
("has_garden", django.db.models.expressions.Value(True)),
("garden_description__isnull", True),
),
name="garden_description_if_has_garden",
),
),
]
Здесь мы добавляем ограничение, которое проверяет либо если has_garden истинно и garden_description не равно null, либо если has_garden ложно и garden_descriptionn равно null
class User(models.Model):
username = models.CharField(max_length=32)
# New fields ##################################
has_garden = models.BooleanField(default=False)
garden_description = models.CharField(
max_length=32,
null=True,
blank=True,
)
class Meta:
constraints = [
models.CheckConstraint(
check=(
Q(has_garden=models.Value(True))
& Q(garden_description__isnull=False)
| (
Q(has_garden=models.Value(False))
& Q(garden_description__isnull=True)
)
),
name="garden_description_if_has_garden",
)
]
Ограничение не является импликацией, оно говорит, что для каждого User
, has_garden
должно быть True
и garden_description
должно быть NULL
.
Если вы хотите, чтобы у людей, имеющих сад, в описании не было установлено значение NULL
, вы можете использовать:
class User(models.Model):
# …
class Meta:
constraints = [
models.CheckConstraint(
check=Q(has_garden=False) | Q(garden_description__isnull=False),
name='garden_description_if_has_garden',
)
]
Однако это позволяет указать garden_description
(то есть не NULL
), если у человека нет сада, что, вероятно, не имеет особого смысла. Мы можем преобразовать его в импликацию с помощью:
class User(models.Model):
# …
class Meta:
constraints = [
models.CheckConstraint(
check=Q(has_garden=True) ^ Q(garden_description=None),
name='garden_description_if_has_garden',
)
]
или для django-4.0 и старше:
class User(models.Model):
# .
class Meta:
constraints = [
models.CheckConstraint(
check=Q(has_garden=True, garden_description__isnull=False)
| Q(has_garden=False, garden_description=None),
name='garden_description_if_has_garden',
)
]
При этом я не думаю, что вам вообще нужен флаг has_garden
: почему бы просто не определить, есть ли у человека флаг, основываясь на том, что garden_description
является NULL
или нет, так:
class User(models.Model):
# …
# no has_garden field
garden_description = models.CharField(
max_length=32,
null=True,
blank=True,
)
@property
def has_garden(self):
return self.garden_description is not None