Mou*_*Tio 5 django postgresql constraints django-models
我正在尝试使用 postgresql 数据库在 django 中为表模型设置约束。
我可以通过 postgresql 用这句话来做到这一点:
ALTER TABLE public.mytable ADD CONSTRAINT "myconstraint" UNIQUE(field1, field2) DEFERRABLE INITIALLY DEFERRED;
Run Code Online (Sandbox Code Playgroud)
但我想通过 django 模型来做。阅读 django 官方文档我没有发现任何相关的内容。
我需要这样的东西:
class Meta:
unique_together = (('field1', 'field2',), DEFERRABLE INITIALLY DEFERRED)
Run Code Online (Sandbox Code Playgroud)
有可能做这样的事情吗?
最近,Django 添加了对此功能的支持(请参阅票证)。从 Django 3.1 开始你可以这样写:
class UniqueConstraintDeferrable(models.Model):
name = models.CharField(max_length=255)
shelf = models.CharField(max_length=31)
class Meta:
required_db_features = {
'supports_deferrable_unique_constraints',
}
constraints = [
models.UniqueConstraint(
fields=['name'],
name='name_init_deferred_uniq',
deferrable=models.Deferrable.DEFERRED,
),
models.UniqueConstraint(
fields=['shelf'],
name='sheld_init_immediate_uniq',
deferrable=models.Deferrable.IMMEDIATE,
),
]
Run Code Online (Sandbox Code Playgroud)
我会通过一次迁移来做到这一点。首先以编程方式获取唯一约束名称,然后删除并重新添加(因为更改它似乎仅适用于 FK 约束,而不适用于唯一约束)。添加反向迁移也可以撤消此操作。
from django.db import migrations, connection
def _make_deferrable(apps, schema_editor):
"""
Change the unique constraint to be deferrable
"""
# Get the db name of the constraint
MyModel = apps.get_model('myapp', 'MyModel')
CONSTRAINT_NAME = schema_editor._constraint_names(MYModel,
['col1', 'col2'],
unique=True)[0]
TABLE_NAME = MyModel._meta.db_table
# Drop then re-add with deferrable as ALTER doesnt seem to work for unique constraints in psql
with schema_editor.connection.create_cursor() as curs:
curs.execute(
f'ALTER TABLE {TABLE_NAME} DROP CONSTRAINT "{CONSTRAINT_NAME}";'
)
curs.execute(
f'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT'
f' {CONSTRAINT_NAME}'
f' UNIQUE (col1, col2) DEFERRABLE INITIALLY DEFERRED;'
)
def _unmake_deferrable(apps, schema_editor):
"""
Reverse the unique constraint to be not deferrable
"""
# Get the db name of unique constraint
MyModel = apps.get_model('myapp', 'MyModel')
CONSTRAINT_NAME = schema_editor._constraint_names(MyModel,
['col1', 'col2'],
unique=True)[0]
TABLE_NAME = MyModel._meta.db_table
with schema_editor.connection.create_cursor() as curs:
curs.execute(
f'ALTER TABLE {TABLE_NAME} DROP CONSTRAINT "{CONSTRAINT_NAME}";'
)
curs.execute(
f'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT'
f' {CONSTRAINT_NAME}'
f' UNIQUE (col1, col2) NOT DEFERRABLE;'
)
class Migration(migrations.Migration):
dependencies = [
('myapp', '<previous_mig>'),
]
operations = [
migrations.RunPython(code=_make_deferrable, reverse_code=_unmake_deferrable)
]
Run Code Online (Sandbox Code Playgroud)
Django 不支持这一点。
您可以使用自定义 SQL 来完成此操作。在您的 中models.py,添加以下内容:
from django.db import connection
from django.db.models.signals import post_migrate
def after_migrate(sender, **kwargs):
cursor = connection.cursor()
cursor.execute('ALTER TABLE public.mytable ALTER CONSTRAINT '
'myconstraint DEFERRABLE INITIALLY DEFERRED')
post_migrate.connect(after_migrate)
Run Code Online (Sandbox Code Playgroud)
虽然我过去也做过这样的事情,但我发现这些年来我更喜欢让我的工作更简单并且独立于任何特定的 RDBMS。例如,您确实希望支持 SQLite,因为它使开发变得更加容易。通过对设计进行一点改变,您通常可以摆脱这些东西。
更新:我认为@fpghost的答案更好。我不知道我在想什么:-)