【问题标题】:Create DB Constraint via Django通过 Django 创建数据库约束
【发布时间】:2018-09-30 23:01:49
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

    标签: django postgresql check-constraints


    【解决方案1】:

    2019 年更新

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

    您的系统验证如下所示:

    from django.db import models
    from django.db.models.constraints import CheckConstraint
    from django.db.models.query_utils import Q
    
    
    class Dummy(models.Model):
        ...
        system = models.CharField(max_length=16)
    
        class Meta:
            constraints = [
                CheckConstraint(
                    check=~Q(system="") & ~Q(system__contains=" "),
                    name="system_not_blank")
            ]
    

    【讨论】:

    • 这个答案应该是解决方案,因为它使用内置的 Django 功能来实现预期的目标。
    【解决方案2】:

    第一期:通过Django创建数据库约束

    一) django 似乎还没有内置这种能力。有一个 9 岁的开放ticket,但我不会为已经发生这么长时间的事情屏住呼吸。

    编辑:从 2.2 版(2019 年 4 月)开始,Django 支持数据库级 check constraints

    B) 您可以查看包django-db-constraints,通过它您可以在模型Meta 中定义约束。我没有测试这个包,所以我不知道它到底有多大用处。

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

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

    现在我们需要在postgres 语法中构建check 约束来完成它。我想出了这些选项:

    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[](数组集),但我找不到合适的函数来计数现在该集合的元素。


    最后,将前两个问题结合起来,您的方法可能如下所示:

    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 的第二部分:替换空格后长度相同)

    让我知道这对您有何影响,或者这种方法是否存在问题或缺点。

    【讨论】:

    • 是的,您使用应用程序 django-db-constraints 的解决方案有效。这比使用 migrations.RunSQL(...) 手动编写迁移要好得多。
    • 该票现已修复,更改将很可能在 Django 2.2 中可用。
    【解决方案3】:

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

    引自 postgres 文档 (https://www.postgresql.org/docs/current/static/ddl-constraints.html):

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

    在migration中运行任意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
    

    【讨论】:

      【解决方案4】:

      我修改我的答案以满足您的要求。

      所以,如果你想运行一个 DB 约束试试这个:

      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()
      

      【讨论】:

      • 感谢您的帮助。您的解决方案在 Django 中进行验证。我想在数据库中进行验证。
      • 欢迎您@guettli。抱歉,我没有阅读 我知道如何在 Django 中使用验证器。
      • @guettli 我只是修改我的答案
      • 为什么要创建数据库连接?我的代码使用 Django,我想使用 django 方式来访问数据库连接/游标。我没有看到您创建了数据库约束。您在验证例程中执行 SQL。对我来说,这是不同的东西。
      猜你喜欢
      • 1970-01-01
      • 2011-12-24
      • 2020-09-16
      • 2017-12-07
      • 2018-04-16
      • 2022-01-11
      • 2021-11-16
      • 1970-01-01
      相关资源
      最近更新 更多