Python 自定义唯一密钥名

Python 自定义唯一密钥名,python,mysql,django,django-models,unique-constraint,Python,Mysql,Django,Django Models,Unique Constraint,我有一个定义了3个字段的模型,这3个字段在一起是唯一的: class MyModel(models.Model): clid = models.AutoField(primary_key=True, db_column='CLID') csid = models.IntegerField(db_column='CSID') cid = models.IntegerField(db_column='CID') uuid = models.CharField(max_

我有一个定义了3个字段的模型,这3个字段在一起是唯一的:

class MyModel(models.Model):
    clid = models.AutoField(primary_key=True, db_column='CLID')
    csid = models.IntegerField(db_column='CSID')
    cid = models.IntegerField(db_column='CID')
    uuid = models.CharField(max_length=96, db_column='UUID', blank=True)

    class Meta(models.Meta):
        unique_together = [
            ["csid", "cid", "uuid"],
        ]
现在,如果我尝试用现有的csid+cid+uuid组合保存一个
MyModel
实例,我会得到:

IntegrityError: (1062, "Duplicate entry '1-1-1' for key 'CSID'")
这是正确的。但是,有没有办法自定义密钥名称?(
CSID
在本例中)

换句话说,我是否可以为
unique\u中列出的约束一起提供名称


据我所知,文档中没有包括这一点。

我相信您必须在数据库中执行此操作

MySQL:

ALTER TABLE `votes` ADD UNIQUE `unique_index`(`user`, `email`, `address`);

我相信我会说。。。对于键“unique_index”

一种解决方案是,您可以在save()处捕获IntegrityError,然后根据需要生成自定义错误消息,如下所示

try:
    obj = MyModel()
    obj.csid=1
    obj.cid=1
    obj.uuid=1
    obj.save()

except IntegrityError:
    message = "IntegrityError: Duplicate entry '1-1-1' for key 'CSID', 'cid', 'uuid' "

现在您可以使用此消息显示为错误消息。

完整性错误是从数据库引发的,但来自django:

create table t ( a int, b int , c int);
alter table t add constraint u unique ( a,b,c);   <-- 'u'    
insert into t values ( 1,2,3);
insert into t values ( 1,2,3);

Duplicate entry '1-2-3' for key 'u'   <---- 'u'
是否创建索引名?谁拥有命名约束的算法

def _create_index_name(self, model, column_names, suffix=""):
    """
    Generates a unique name for an index/unique constraint.
    """
    # If there is just one column in the index, use a default algorithm from Django
    if len(column_names) == 1 and not suffix:
        return truncate_name(
            '%s_%s' % (model._meta.db_table, self._digest(column_names[0])),
            self.connection.ops.max_name_length()
        )
    # Else generate the name for the index using a different algorithm
    table_name = model._meta.db_table.replace('"', '').replace('.', '_')
    index_unique_name = '_%x' % abs(hash((table_name, ','.join(column_names))))
    max_length = self.connection.ops.max_name_length() or 200
    # If the index name is too long, truncate it
    index_name = ('%s_%s%s%s' % (
        table_name, column_names[0], index_unique_name, suffix,
    )).replace('"', '').replace('.', '_')
    if len(index_name) > max_length:
        part = ('_%s%s%s' % (column_names[0], index_unique_name, suffix))
        index_name = '%s%s' % (table_name[:(max_length - len(part))], part)
    # It shouldn't start with an underscore (Oracle hates this)
    if index_name[0] == "_":
        index_name = index_name[1:]
    # If it's STILL too long, just hash it down
    if len(index_name) > max_length:
        index_name = hashlib.md5(force_bytes(index_name)).hexdigest()[:max_length]
    # It can't start with a number on Oracle, so prepend D if we need to
    if index_name[0].isdigit():
        index_name = "D%s" % index_name[:-1]
    return index_name
对于当前django版本(1.7),复合唯一约束的约束名称如下所示:

>>> _create_index_name( 'people', [ 'c1', 'c2', 'c3'], '_uniq' )
'myapp_people_c1_d22a1efbe4793fd_uniq'

您应该以某种方式覆盖以更改算法。一种可能的方法是,在您的(未经测试的)数据库SchemaEditor上编写自己的db后端,并在其上覆盖
\u create\u index\u name
,但根据您使用的是Django 1.6还是1.7,有两种方法可以做到这一点:

:

:

更改
/manage.py sqlall
输出中的索引名。 您可以自己运行
/manage.py sqlall
,自己添加约束名称并手动应用,而不是
syncdb

$ ./manage.py sqlall test
BEGIN;
CREATE TABLE `test_mymodel` (
    `CLID` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `CSID` integer NOT NULL,
    `CID` integer NOT NULL,
    `UUID` varchar(96) NOT NULL,
    UNIQUE (`CSID`, `CID`, `UUID`)
)
;

COMMIT;
e、 g

重写
BaseDatabaseSchemaEditor.\u创建\u索引\u名称
@danihp指出的解决方案不完整,它只适用于字段更新(
BaseDatabaseSchemaEditor.\u alter\u field

通过重写
\u create\u index\u name
得到的sql是:

BEGIN;
CREATE TABLE "testapp_mymodel" (
    "CLID" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "CSID" integer NOT NULL,
    "CID" integer NOT NULL,
    "UUID" varchar(96) NOT NULL,
    UNIQUE ("CSID", "CID", "UUID")
)
;

COMMIT;
重写BaseDatabaseSchemaEditor.create\u模型 基于

这是
create_model
中感兴趣的部分:

    # Add any unique_togethers
    for fields in model._meta.unique_together:
        columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
        column_sqls.append(self.sql_create_table_unique % {
            "columns": ", ".join(self.quote_name(column) for column in columns),
        })
结论

你可以:

  • 覆盖
    create\u model
    以将
    \u create\u index\u name
    一起用于
    唯一的
    合同
  • 修改
    sql\u create\u table\u unique
    模板以包含
    name
    参数
您还可以检查此票证的可能修复:


感谢您的参与。这是一个选择。这更接近于我期望得到的答案。我会试试看,然后再回来找你。非常感谢。可能仅用于字段更新,
\u create\u index\u name
不用于
create\u model
中的
unique\u一起
。多亏了@dnozay,我们才有了真正的解决方案(+django ticket)。不过,我不得不说,我们仍然使用django 1.6.9(目前仍使用python2.6)。这意味着定制唯一约束名称()要困难得多。无论如何,回答得很好。非常感谢。只是好奇更改索引名的用例是什么?除非您直接处理数据库,否则我看不出这有什么关系,除了美观之外。@user193130这是一个很好的观点。我有一个相当奇怪的Django用例,其中我有一个没有任何外键关系的遗留mysql数据库,在整个应用程序中,我主要通过原始查询操作数据库(顺便说一句,这会让我很难受)。不过,在测试中,我使用了django ORM,使
models.py
尽可能接近真实的遗留数据库。在这里,我希望将唯一键约束命名为真实数据库中的唯一键约束。希望你能理解我现在的古怪:)啊,是的,这很有道理。处理遗留数据库听起来很麻烦,但我假设您正在制作尽可能接近真实数据库的
models.py
,以准备将其不仅用于测试,而且用于真实的应用程序代码,这样您就不必再处理原始查询了。希望你一切顺利猜测更改错误消息的所有答案都不适用于你lol@danihp请取消删除它-你花了很多时间和精力研究这个主题。不要担心赏金-我很确定还会有另一个赏金:)有趣的方法,设置错误消息,而不是设置约束名称!非常感谢。我喜欢这个解决方案不涉及SQL。这是一种直截了当的python风格的方法。也感谢您创建了一个票证。另一个问题是,我坚持使用1.6.9,因为我们仍然使用python2.6。就我所知,是1.6.9,你的意思是这样的吗?
$ ./manage.py sqlall test
BEGIN;
CREATE TABLE `test_mymodel` (
    `CLID` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `CSID` integer NOT NULL,
    `CID` integer NOT NULL,
    `UUID` varchar(96) NOT NULL,
    UNIQUE (`CSID`, `CID`, `UUID`)
)
;

COMMIT;
$ ./manage.py sqlall test
BEGIN;
CREATE TABLE `test_mymodel` (
    `CLID` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `CSID` integer NOT NULL,
    `CID` integer NOT NULL,
    `UUID` varchar(96) NOT NULL,
    UNIQUE constraint_name (`CSID`, `CID`, `UUID`)
)
;

COMMIT;
BEGIN;
CREATE TABLE "testapp_mymodel" (
    "CLID" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "CSID" integer NOT NULL,
    "CID" integer NOT NULL,
    "UUID" varchar(96) NOT NULL,
    UNIQUE ("CSID", "CID", "UUID")
)
;

COMMIT;
class BaseDatabaseSchemaEditor(object):
    # Overrideable SQL templates
    sql_create_table_unique = "UNIQUE (%(columns)s)"
    sql_create_unique = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s UNIQUE (%(columns)s)"
    sql_delete_unique = "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
    # Add any unique_togethers
    for fields in model._meta.unique_together:
        columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
        column_sqls.append(self.sql_create_table_unique % {
            "columns": ", ".join(self.quote_name(column) for column in columns),
        })