通过Django创建数据库约束

时间:2018-04-20 09:09:47

标签: django postgresql check-constraints

我有一个看起来像这样的Django模型:

class Dummy(models.Model):
    ...
    system = models.CharField(max_length=16)

我希望system永远不会为空或包含空格。

我知道如何在Django中使用验证器。

但我会在数据库级别强制执行此操作。

为此创建数据库约束的最简单和类似django的方法是什么?

我使用PostgreSQL,不需要支持任何其他数据库。

4 个答案:

答案 0 :(得分:8)

第一期:通过Django

创建数据库约束

似乎django还没有这种能力。有一个9岁的开放ticket为它,但我不会屏住呼吸这个长期的事情。

您可以查看包django-db-constraints,通过该包可以在模型Meta中定义约束。我没有测试这个软件包,所以我不知道它真的有用。

# example using this package
class Meta:
    db_constraints = {
        'price_above_zero': 'check (price > 0)',
    }

第二个问题:字段system永远不应为空,也不应包含空格

现在我们需要在check语法中构建postgres约束来实现这一目标。我提出了这些选择:

  1. 删除空格后,检查system的长度是否不同。使用this answer中的提示,您可以尝试:

    # this check should only pass if `system` contains no
    # whitespaces (`\s` also detects new lines)
    check ( length(system) = length(regexp_replace(system, '\s', '', 'g')) )
    
  2. 检查空格数是否为0.为此我们可以regexp_matches

    # this check should only pass if `system` contains no
    # whitespaces (`\s` also detects new lines)
    check ( length(regexp_matches(system, '\s', 'g')) = 0 )
    

    请注意,length函数不能regexp_matches一起使用,因为后者会返回set of text[](数组),但我不能找到适当的函数来计算该集合的元素。

  3. 最后,将所有内容整合在一起,您的方法可能如下所示:

    class Dummy(models.Model):
        # this already sets NOT NULL to the field in the database
        system = models.CharField(max_length=16)
    
        class Meta:
            db_constraints = {
                'system_no_spaces': 'check ( length(system) > 0 AND length(system) = length(regexp_replace(system, "\s", "", "g")) )',
            }
    

    这会检查字段值是否为

    1. 不包含NULL(默认情况下CharField添加NOT NULL约束)
    2. 不为空(check的第一部分:length(system) > 0
    3. 没有空格(check的第二部分:替换空格后的长度相同)
    4. 让我知道这对你有用,或者这种方法有问题或缺点。

      编辑:从2.2版开始,Django拥有数据库级CheckConstraint

答案 1 :(得分:6)

您可以通过自定义django迁移添加CHECK约束。要检查字符串长度,您可以使用char_length函数和position来检查是否包含空格。

来自postgres docs(https://www.postgresql.org/docs/current/static/ddl-constraints.html)的引用:

  

检查约束是最通用的约束类型。它允许你   指定某列中的值必须满足布尔值   (真值)表达。

在迁移中运行任意sql RunSQL可以使用https://docs.djangoproject.com/en/2.0/ref/migration-operations/#runsql}操作:

  

允许在数据库上运行任意SQL - 对更多内容非常有用   Django不支持的数据库后端的高级功能   直接,就像部分索引一样。

创建空迁移:

python manage.py makemigrations --empty yourappname

添加sql以创建约束:

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
         migrations.RunSQL('ALTER TABLE appname_dummy ADD CONSTRAINT syslen '
                           'CHECK (char_length(trim(system)) > 1);',
                           'ALTER TABLE appname_dummy DROP CONSTRAINT syslen;'),
         migrations.RunSQL('ALTER TABLE appname_dummy ADD CONSTRAINT syswh '
                           'CHECK (position(' ' in trim(system)) = 0);',
                           'ALTER TABLE appname_dummy DROP CONSTRAINT syswh;')


    ]

运行迁移:

python manage.py migrate yourappname

答案 2 :(得分:1)

我修改答案以达到您的要求。

因此,如果您想运行数据库约束,请尝试以下方法:

import psycopg2
def your_validator():
    conn = psycopg2.connect("dbname=YOURDB user=YOURUSER")
    cursor = conn.cursor()
    query_result = cursor.execute("YOUR QUERY")
    if query_result is Null:
        # Do stuff
    else:
        # Other Stuff

然后使用pre_save信号。

models.py文件中添加

from django.db.models.signals import pre_save
class Dummy(models.Model):
...
    @staticmethod
    def pre_save(sender, instance, *args, **kwargs)
        # Of course, feel free to parse args in your def.
        your_validator()

答案 3 :(得分:1)

2019更新

Django 2.2添加了对database-level constrains的支持。新的CheckConstraintUniqueConstraint类可添加自定义数据库约束。使用Meta.constraints option将约束添加到模型中。

您的系统验证看起来像这样:

class Dummy(models.Model):
    ...
    system = models.CharField(max_length=16)

    class Meta:
        constraints = [
            CheckConstraint(
                check=~Q(system="") & ~Q(system__contains=" "),
                name="system_not_blank")
        ]