Как переписать это ограничение postgres, чтобы оно больше соответствовало django 2.2?

Это необработанный запрос, который я пишу для postgres для ограничения проверки

   ALTER TABLE rea_asplinkage ADD CONSTRAINT asp_sub_project_positive_integer
        CHECK (
            jsonb_typeof(linkage-> 'root' -> 'in_sub_project') is not distinct from 'number'
        and (linkage->'root'->>'in_sub_project')::numeric % 1 = 0
        and (linkage->'root'->>'in_sub_project')::numeric > 0
        );

И способ, которым я создаю миграцию, таков:

# Generated by Django 2.2.10 on 2020-05-16 12:59

from django.db import connection, migrations



class Migration(migrations.Migration):

    dependencies = [("rea", "0029_asplinkage")]

    operations = [
        migrations.RunSQL(
            sql="""
            ALTER TABLE rea_asplinkage ADD CONSTRAINT asp_sub_project_positive_integer
            CHECK (
                jsonb_typeof(linkage-> 'root' -> 'in_sub_project') is not distinct from 'number'
            and (linkage->'root'->>'in_sub_project')::numeric % 1 = 0
            and (linkage->'root'->>'in_sub_project')::numeric > 0
            );
            """,
            reverse_sql="""
                ALTER TABLE rea_asplinkage DROP CONSTRAINT "asp_sub_project_positive_integer";
            """,
        )
    ]

И это работает.

Но это означает, что моя исходная модель не показывает ограничение в class Meta модели ASPLinkage.

class ASPLinkage(TimeStampedModel, SoftDeletableModel, PersonStampedModel, OrganizationOwnedModel):
    linkage = JSONField(default=default_linkage_for_asp)

    objects = OrganizationOwnedSoftDeletableManager()

Я пробовал ExpressionWrapper и RawSQL при создании ограничений внутри класса Meta, но это все равно не работает.

Для справки я просмотрел примеры, найденные в https://github.com/django/django/blob/master/tests/constraints/models.py#L12

Я также просмотрел раздельную миграцию базы данных и состояния через https://realpython.com/create-django-index-without-downtime/#when-django-generates-a-new-migration

Но я все еще не могу заставить его работать

Так это вообще возможно?

Обновлять

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

  1. Я хочу написать ограничения для JSONField.
  2. Я могу сделать это прямо на Postgres
  3. Поэтому я могу сделать это, используя необработанный sql в файле миграции.
  4. Но я не могу сделать то же самое, используя мета/CheckConstraint модели Django, как это обычно делают все. См. https://docs.djangoproject.com/en/3.0/ref/models/constraints/
  5. Итак, как мне переписать этот необработанный sql, чтобы создать ограничение на jsonfield в postgres, но в стиле Django?

person Kim Stacks    schedule 16.05.2020    source источник
comment
Вы просто используете это поле для хранения целого числа? Если да, то можете ли вы использовать models.PositiveIntegerField вместо этого?   -  person voodoo-burger    schedule 16.05.2020
comment
поскольку мне нужно обрабатывать полуструктурированные данные, я выбрал jsonb. поэтому jsonb является жестким ограничением. Но в то же время я хочу обеспечить максимальную целостность данных на уровне базы данных. так что мой ответ на ваш вопрос НЕТ. это не просто хранить 1 поле   -  person Kim Stacks    schedule 17.05.2020


Ответы (1)


Чтобы добиться этого в Django 2.2 вы потребуется зарегистрировать два новых JSONField преобразования/поиска, так как поддержка условных выражений была только добавлено в грядущей версии 3.1.

Сначала вы захотите зарегистрировать поиск для доступа к ключу JSONField

from django.db import models
from django.db.models.lookups import Lookup
from django.contrib.postgres.fields.jsonb import (
    KeyTransform, KeyTransformTextLookupMixin
)

@KeyTransform.register_lookup
class KeyTransformIsInteger(KeyTransformTextLookupMixin, Lookup):
    lookup_name = 'is_int'
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        key_expr = KeyTransform(
            self.lhs.key_name, *self.lhs.source_expressions, **self.lhs.extra
        )
        key_sql, key_params = self.process_lhs(
            compiler, connection, lhs=key_expr
        )
        lhs_sql, lhs_params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        sql = "(jsonb_typeof(%s) = %%s AND mod(%s::numeric, %%s) = %%s) IS %s" % (
            key_sql, lhs_sql, rhs_sql
        )
        params = [
            *key_params, 'number',
            *lhs_params, 1, 0,
            *rhs_params,
        ]
        return sql, params

@KeyTransform.register_lookup
class KeyTransformIntegerGt(KeyTransformTextLookupMixin, Lookup):
    lookup_name = 'int_gt'
    prepare_rhs = False

    def as_sql(self, compiler, connection):
        lhs_sql, lhs_params = self.process_lhs(compiler, connection)
        rhs_sql, rhs_params = self.process_rhs(compiler, connection)
        sql = "%s::int > %s" % (lhs_sql, rhs_sql)
        params = [*lhs_params, *rhs_params]
        return sql, params

Как только это будет сделано, вы сможете определить свое ограничение, например

CheckConstraint(
    check=Q(
        linkage__root__in_sub_project__is_int=True,
        linkage__root__in_sub_project__int_gt=0,
    ),
    name='asp_sub_project_positive_integer',
)

Как только вы перейдете на Django 3.1, вы сможете передать RawSQL напрямую в CheckConstraint.check, если у него есть output_field = models.BooleanField().

RawSQL("""
   jsonb_typeof(linkage-> 'root' -> 'in_sub_project') is not distinct from 'number'
   and (linkage->'root'->>'in_sub_project')::numeric % 1 = 0
   and (linkage->'root'->>'in_sub_project')::numeric > 0
""",
    output_field=models.BooleanField()
)
person Simon Charette    schedule 26.05.2020
comment
› return sql % (lhs, lhs, rhs), параметры, левые и правые здесь не определены. Может помочь? - person Kim Stacks; 28.05.2020
comment
Вы можете взглянуть на мою вторую попытку в gist.github.com/simkimsia/60149d5ab4bad1817fc33315dc11b300 после того, как я попробуй сузить ошибки - person Kim Stacks; 28.05.2020
comment
Я скорректировал ответ, указав отсутствующие ссылки на имена переменных, и дважды включил параметры lhs. - person Simon Charette; 28.05.2020
comment
спасибо, но все еще не работает, я думаю, есть проблема со знаком %. пожалуйста, проверьте мою 3-ю попытку. Обратите внимание, что я уже разместил %% gist.github.com/simkimsia/ - person Kim Stacks; 28.05.2020
comment
Я награжу награду, даже если этот ответ еще не решил ее. Я отмечу это как правильное, когда оно решит. Спасибо - person Kim Stacks; 28.05.2020
comment
Скорректирован ответ с поиском, проверенным на Django 2.2 вместо псевдокода. - person Simon Charette; 28.05.2020
comment
Ух ты! Оно работает. Я вижу, что у вас 2950, ​​поэтому я дам вам еще 50 очков, чтобы помочь вам добраться до хорошего раунда 3k. - person Kim Stacks; 28.05.2020
comment
Эээ, немного странно, что сейчас минимум 200, и я не могу наградить сразу. Так что я думаю, вам придется подождать. ????????‍♂️ Кстати, вы можете помочь научить мужчину ловить рыбу? т.е. научите меня, как вы отлаживаете свое собственное ключевое преобразование, пока оно не заработает? Поскольку я использую ipdb, я все еще не могу перепроектировать, как вы это сделали. И я предвижу, что у меня будет больше таких ограничений, чтобы написать, и я не могу дождаться Django 3.2. Мы можем обсудить денежную компенсацию, если вы предпочитаете получать оплату в долларах, а не в баллах за обучение новичка Django. - person Kim Stacks; 28.05.2020
comment
Спасибо за ваше любезное предложение @KimStacks, но в данный момент я не берусь за внештатную работу и обычно предпочитаю тратить свое время на сам Django, когда это возможно. Я предлагаю вам взглянуть на youtube.com/watch?v=CGF-0csOjPw и просмотрите документацию docs.djangoproject.com/en/3.0/ howto/custom-lookups, чтобы узнать об этом подробнее. В конце концов, вы хотите, чтобы as_sql возвращал что-то, что вы могли бы передать cursor.execute, мне пришлось использовать mod вместо %, потому что последнее конфликтовало с форматированием строки. - person Simon Charette; 28.05.2020
comment
Я уважаю это. Спасибо, Саймон, тем не менее ???? - person Kim Stacks; 29.05.2020
comment
Присуждена награда. Спасибо - person Kim Stacks; 30.05.2020