Пользовательское преобразование Django с оператором case для приведения строки к int или null

Я пытаюсь создать пользовательский django lookup as_int, чтобы можно было легко сравнивать и упорядочивать содержимое текстовых полей, если они содержат только число, и рассматривать их как NULL, если они имеют неправильный формат.

Например:

Comment.objects.filter(body__as_int__gt=5)
# Should treat a comment with body="37" as 37
# Should treat a comment with body="abc" as NULL

Вот что у меня есть на данный момент:

class AsIntTransform(Transform):
    lookup_name = "as_int"
    output_field = models.IntegerField()

    def as_sql(self, compiler, connection):
        case_expr = Case(
            When(
                Regex(self.lhs, r"^\d+$"),
                then=Cast(self.lhs, models.IntegerField()),
            ),
            default=Value(None),
        )
        return case_expr.as_sql(compiler, connection)


models.CharField.register_lookup(AsIntTransform)
models.TextField.register_lookup(AsIntTransform)

При попытке отфильтровать с помощью приведенного выше кода возникает ошибка:

    field_list = name.split(LOOKUP_SEP)
                 ^^^^^^^^^^
AttributeError: 'Col' object has no attribute 'split'

Похоже, что "Col", на который он ссылается, находится self.lhs в трансформации, но я не уверен, что я должен изменить, чтобы это сработало?

Проблема, с которой вы столкнулись, связана с тем, как атрибут self.lhs используется в методе as_sql. Функция Regex ожидает строкового представления столбца, но self.lhs - это объект Col, что и вызывает ошибку.

Чтобы исправить это, необходимо убедиться, что столбец правильно указан в выражении SQL. Вот исправленная версия вашего класса AsIntTransform:

from django.db import models
from django.db.models import Transform, Case, When, Value
from django.db.models.functions import Cast, Regex

class AsIntTransform(Transform):
    lookup_name = "as_int"
    output_field = models.IntegerField()

    def as_sql(self, compiler, connection):
        lhs_sql, params = self.lhs.as_sql(compiler, connection)
        case_expr = Case(
            When(
                Regex(lhs_sql, r"^\d+$"),
                then=Cast(lhs_sql, models.IntegerField()),
            ),
            default=Value(None),
        )
        return case_expr.as_sql(compiler, connection)

models.CharField.register_lookup(AsIntTransform)
models.TextField.register_lookup(AsIntTransform)

В этой исправленной версии self.lhs.as_sql(compiler, connection) преобразует self.lhs в SQL-выражение, которое затем используется в функциях Regex и Cast. Это должно устранить ошибку AttributeError и позволить вашему пользовательскому поиску функционировать как положено.

Теперь вы можете фильтровать с помощью поиска as_int следующим образом:

Comment.objects.filter(body__as_int__gt=5)
Вернуться на верх