在使用 UUID 字段作为主字段的 Django 模型中添加非主键自动字段或“序列”字段

Posted

技术标签:

【中文标题】在使用 UUID 字段作为主字段的 Django 模型中添加非主键自动字段或“序列”字段【英文标题】:Adding a Non-Primary Key AutoField or a 'serial' field in a Django Model which uses a UUID field as a Primary Field 【发布时间】:2018-09-02 20:14:42 【问题描述】:

我正在构建一个以 UUIDField 作为主键的模型。但我的用例需要。也有一个自动增量字段。 Django 提供了一个 AutoField。但它需要 primary_key=True 在我的情况下这是我不想要的,因为我使用 UUIDField 作为 primary_key。

我尝试创建一个字段并将 db_type 设置为“serial”并添加一个迁移,该迁移会更改在 100000 处重新启动的序列。使用 Admin 将对象添加到数据库将始终将数字字段存储为空值。如果我删除 null=True。然后保存将失败,因为它需要数字字段的值。

如何在保持 UUIDField 作为主键的同时使数字字段递增?

fields.py

class SerialField(models.Field):
    description = _("BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE")

    empty_strings_allowed = False

    default_error_messages = 
        'invalid': _("'%(value)s' value must be an integer."),
    

    def __init__(self, *args, **kwargs):
        kwargs['blank'] = True
        super().__init__(*args, **kwargs)

    def db_type(self, connection):
        return 'serial'

models.py

from .fields import SerialField

class MyModel(models.Model):
    uuid = models.UUIDField(
        verbose_name=_("UUID Identifier"),
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_("Requried, PrimaryKey none-editable"),
        db_index=True,
    )
    number = SerialField(
        primary_key=False,
        editable=False,
        help_text=_("Auto Increment Number"),
        verbose_name=_("Number"),
        #null=True
    )

0002_auto_20180202.py 从 django.db 导入迁移

def forwards(apps, schema_editor):
    if schema_editor.connection.alias == 'default':
        return migrations.RunSQL(
            "ALTER SEQUENCE app_name_mymodel_number_seq RESTART WITH 100000"
        )


class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(forwards)
    ]

【问题讨论】:

我没有答案,但您可以在this ticket 中看到一些历史记录(尽管讨论主要是关于拥有多个AutoFields)。我同意这没有正确记录。 【参考方案1】:

为了举例,假设我们的表格名称是payouts

以下是支付模式:

class Payouts(models.Model):
    # seq_id = models.IntegerField() 
    # we will create seq_id auto-increment by raw SQL
    id = models.UUIDField(primary_key=True)

    class Meta:
        managed = True
        db_table = 'payouts'

您可以看到我们希望 id 以 uuid 作为主键。

按照以下说明进行操作:

第 1 步:为上述模型制作迁移文件。

python manage.py makemigrations

第 2 步:生成一个空迁移以执行 SQL 查询:

python manage.py makemigrations  --empty -n dont_delete_add_defaults_sql

步骤 3: 编辑 dont_delete_add_defaults_sql 如下:

from django.db import migrations    

class Migration(migrations.Migration):

    dependencies = [
        ('apiapp', '0001_initial'), #Replace 0001_initial with the name of file generated in step 1
    ]

    operations = [
        migrations.RunSQL("ALTER TABLE payouts ADD seq_id serial NOT NULL;"),
        migrations.RunSQL("DROP SEQUENCE IF EXISTS payouts_seq_id_seq CASCADE;"),
        migrations.RunSQL("create sequence payouts_seq_id_seq owned by payouts.seq_id;"),
        migrations.RunSQL("alter table payouts alter column seq_id set default nextval('payouts_seq_id_seq');"),
    ]

第 4 步:运行最终迁移

python manage.py migrate

【讨论】:

【参考方案2】:

也试过了。我的解决方法是改用原始 SQL。

迁移:

migrations.RunSQL(
    "CREATE SEQUENCE sequence_name START 100000",
    reverse_sql="DROP SEQUENCE IF EXISTS sequence_name",
    elidable=False,
),

型号:

def get_next_increment():
    with connection.cursor() as cursor:
        cursor.execute("SELECT nextval('sequence_name')")
        result = cursor.fetchone()
        return result[0]

class MyModel(models.Model):
    my_field = models.IntegerField(default=get_next_increment, editable=False, unique=True)

【讨论】:

以上是关于在使用 UUID 字段作为主字段的 Django 模型中添加非主键自动字段或“序列”字段的主要内容,如果未能解决你的问题,请参考以下文章

在 Django 中使用 2 个字段创建主键?

不能将 UUID 用作主键:Uuid:diesel::Expression 不满足 [重复]

将django RawQueryset中字段的值转换为不同的django字段类型

Django 模型 - 外键作为主键

以原子方式比较-交换 Django 中的模型字段

如何在 Django 中向模型添加临时字段?