Проблема с 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',
            )
        ]

или для и старше:

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
Вернуться на верх