Подтвердить что ты не робот

Имя уникального уникального_пользователя

У меня есть модель с unique_together для трех уникальных полей:

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"],
        ]

Теперь, если я попытаюсь сохранить экземпляр MyModel с существующей комбинацией csid + cid + uuid, я бы получил:

IntegrityError: (1062, "Duplicate entry '1-1-1' for key 'CSID'")

Это правильно. Но есть ли способ настроить это ключевое имя? (CSID в этом случае)

Другими словами, могу ли я указать имя ограничения, указанного в unique_together?

Насколько я понимаю, это не рассматривается в документации.

4b9b3361

Ответ 1

Изменение имени индекса в ./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;

например.

$ ./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;

Переопределение BaseDatabaseSchemaEditor._create_index_name

Решение, указанное @danihp, является неполным, оно работает только для обновлений полей (BaseDatabaseSchemaEditor._alter_field)

В sql я получаю переопределение _create_index_name:

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_model

на основе https://github.com/django/django/blob/master/django/db/backends/schema.py

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"

и это интересная часть в 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_model для использования _create_index_name для unique_together запретов.
  • изменить sql_create_table_unique шаблон, чтобы включить параметр name.

Вы также можете проверить возможное исправление этого билета:

https://code.djangoproject.com/ticket/24102

Ответ 2

Неплохо документировано, но в зависимости от того, используете ли вы Django 1.6 или 1.7, вы можете сделать это двумя способами:

В Django 1.6 вы можете переопределить unique_error_message, например::

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

    # ....

def unique_error_message(self, model_class, unique_check):
    if model_class == type(self) and unique_check == ("csid", "cid", "uuid"):
        return _('Your custom error')
    else:
        return super(MyModel, self).unique_error_message(model_class, unique_check)

Или в Django 1.7:

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"],
        ]
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s %(field_labels)s are not unique.",
            }
        }

Ответ 3

Ошибка целостности возникает из базы данных, но из 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'

Это означает, что вам нужно создать ограничение с желаемым именем в базе данных. Но есть django в миграциях, который называет ограничение. Посмотрите на _create_unique_sql:

def _create_unique_sql(self, model, columns):
    return self.sql_create_unique % {
        "table": self.quote_name(model._meta.db_table),
        "name": self.quote_name(self._create_index_name(model, columns, suffix="_uniq")),
        "columns": ", ".join(self.quote_name(column) for column in columns),
    }

Является _create_index_name, у которого есть алгоритм для ограничения имен:

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 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'

Вы должны переписать _create_index_name каким-то образом, чтобы изменить алгоритм. Кстати, возможно, написав свой собственный db-бэкэнд, содержащий mysql и перезаписывая _create_index_name в DatabaseSchemaEditor на schema.py (не проверено)

Ответ 4

Я считаю, что вы должны сделать это в своей базе данных;

MySQL:

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

Я верю, тогда скажу... для ключевого 'unique_index'

Ответ 5

Одно из решений - вы можете поймать 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' "

Теперь вы можете использовать это сообщение для отображения в качестве сообщения об ошибке.