Ограничение для запрета NaN в числовых столбцах postgres с помощью Django ORM

Postgresql допускает NaN значения в числовых столбцах согласно документации здесь.

При определении таблиц Postgres с помощью Django ORM, столбец DecimalField транслируется в столбец numeric в Postgres. Даже если вы определите столбец следующим образом:

from django.db import models

# You can insert NaN to this column without any issue
numeric_field = models.DecimalField(max_digits=32, decimal_places=8, blank=False, null=False)

Есть ли способ использовать синтаксис Python/Django, чтобы запретить значения NaN в этом сценарии? Родное решение Postgres, вероятно, заключается в использовании какого-то ограничения. Но возможно ли это с помощью синтаксиса Django?

У меня нет базы данных PostgreSQL для тестирования, но вы можете попробовать создать ограничение базы данных, используя поиск на основе IsNull looukup:

from decimal import Decimal
from django.db.models import (
    CheckConstraint,
    DecimalField,
    Field,
    Model,
    Q,
)
from django.db.models.lookups import (
    BuiltinLookup,
)


@Field.register_lookup
class IsNaN(BuiltinLookup):
    lookup_name = "isnan"
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        if not isinstance(self.rhs, bool):
            raise ValueError(
                "The QuerySet value for an isnan lookup must be True or False."
            )
        sql, params = self.process_lhs(compiler, connection)
        if self.rhs:
            return '%s = "NaN"' % sql, params
        else:
            return '%s <> "NaN"' % sql, params


class Item(Model):
    numeric_field = DecimalField(
        max_digits=32,
        decimal_places=8,
        blank=False,
        null=False,
    )

    class Meta:
        constraints = [
            CheckConstraint(
                check=Q(numeric_field__isnan=False),
                name="numeric_field_not_isnan",
            ),
        ]

@MT0 ответил на вопрос. Я просто хочу добавить, что если вы используете DemimalField и манипулируете данными в Django, вы обычно не можете вставить NaN или Infinity в базу данных. Действительно, Django сам проверяет, является ли значение бесконечным (NaN не считается конечным).

Django сначала «подготавливает» значения для представления в базе данных, и делает это с помощью метода .get_db_prep_save(…) [GitHub]:

def get_db_prep_save(self, value, connection):
    if hasattr(value, "as_sql"):
        return value
    return connection.ops.adapt_decimalfield_value(
        self.to_python(value), self.max_digits, self.decimal_places
    )

Which calls the .to_python(…) function. The .to_python(…) method [GitHub] then checks if the Decimal is finite:

def to_python(self, value):
    # …
    if not decimal_value.is_finite():
        raise exceptions.ValidationError(
            self.error_messages["invalid"],
            code="invalid",
            params={"value": value},
        )

Конечно, лучше добавить ограничение, так как все равно возможно, что некоторые запросы на обновление в конечном итоге приведут к NaN, но для простых вставок/обновлений через Django этого не должно произойти.

Вернуться на верх