python - set title matplotlib
Nombre Ășnico de clave personalizada (5)
Tengo un modelo con un unique_together
definido para que 3 campos sean únicos juntos:
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"],
]
Ahora, si intento guardar una instancia de MyModel
con una combinación existente de csid + cid + uuid, obtendría:
IntegrityError: (1062, "Duplicate entry ''1-1-1'' for key ''CSID''")
Cual es correcta. Pero, ¿hay una manera de personalizar ese nombre de clave? ( CSID
en este caso)
En otras palabras, ¿puedo proporcionar un nombre para una restricción enumerada en unique_together
?
Por lo que entiendo, esto no está cubierto en la documentación.
Cambiar el nombre del índice en ./manage.py sqlall
output.
Puede ejecutar ./manage.py sqlall
usted mismo y agregar el nombre de la restricción usted mismo y aplicar manualmente en lugar de 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;
p.ej
$ ./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;
Anulando BaseDatabaseSchemaEditor._create_index_name
La solución señalada por @danihp está incompleta, solo funciona para actualizaciones de campo ( BaseDatabaseSchemaEditor._alter_field
)
El sql que obtengo al reemplazar _create_index_name
es:
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;
Anulando BaseDatabaseSchemaEditor.create_model
basado en 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"
y esta es la pieza en create_model
que es de interé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),
})
Conclusión
Tú podrías:
- reemplaza a
create_model
para usar_create_index_name
para lasunique_together
deunique_together
. - modifique la plantilla
sql_create_table_unique
para incluir un parámetro dename
.
También puede comprobar una posible solución en este ticket:
Creo que tienes que hacer eso en tu base de datos;
MySQL:
ALTER TABLE `votes` ADD UNIQUE `unique_index`(`user`, `email`, `address`);
Creo que entonces diría ... para clave ''unique_index''
El error de integridad se genera desde la base de datos pero desde 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''
Eso significa que necesita crear una restricción con el nombre deseado en la base de datos. Pero es django en las migraciones quien restringe los nombres. Mira en _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),
}
Es _create_index_name quien tiene el algoritmo para nombrar restricciones:
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
Para la versión actual de django (1.7), el nombre de la restricción para una restricción única compuesta se ve así:
>>> _create_index_name( ''people'', [ ''c1'', ''c2'', ''c3''], ''_uniq'' )
''myapp_people_c1_d22a1efbe4793fd_uniq''
Debería sobrescribir _create_index_name
de alguna manera para cambiar el algoritmo. Una forma, tal vez, escribiendo su propio backend de DatabaseSchemaEditor
de DatabaseSchemaEditor
_create_index_name
de mysql y sobrescribiendo _create_index_name
en su DatabaseSchemaEditor
en su schema.py (no probado)
No está bien documentado, pero dependiendo de si está utilizando Django 1.6 o 1.7, hay dos formas de hacerlo:
En Django 1.6, puedes anular el unique_error_message
, de esta forma :
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)
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''s %(field_labels)s are not unique.",
}
}
Una solución es que puede capturar IntegrityError en save () y luego hacer un mensaje de error personalizado como desee, como se muestra a continuación.
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'' "
Ahora puede usar este mensaje para mostrarlo como mensaje de error.