Custom unique_together key name - python

I have a model with a unique_together defined for 3 fields to be 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"],
]
Now, if I attempt to save a MyModel instance with an existing csid+cid+uuid combination, I would get:
IntegrityError: (1062, "Duplicate entry '1-1-1' for key 'CSID'")
Which is correct. But, is there a way to customize that key name? (CSID in this case)
In other words, can I provide a name for a constraint listed in unique_together?
As far as I understand, this is not covered in the documentation.

Its not well documented, but depending on if you are using Django 1.6 or 1.7 there are two ways you can do this:
In Django 1.6 you can override the unique_error_message, like so:
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)
Or in 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's %(field_labels)s are not unique.",
}
}

Changing index name in ./manage.py sqlall output.
You could run ./manage.py sqlall yourself and add in the constraint name yourself and apply manually instead of 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.
$ ./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;
Overriding BaseDatabaseSchemaEditor._create_index_name
The solution pointed out by #danihp is incomplete, it only works for field updates (BaseDatabaseSchemaEditor._alter_field)
The sql I get by overriding _create_index_name is:
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;
Overriding BaseDatabaseSchemaEditor.create_model
based on 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"
and this is the piece in create_model that is of interest:
# 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),
})
Conclusion
You could:
override create_model to use _create_index_name for unique_together contraints.
modify sql_create_table_unique template to include a name parameter.
You may also be able to check a possible fix on this ticket:
https://code.djangoproject.com/ticket/24102

Integrity error is raised from database but from 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'
That means that you need to create constraint with desired name in database. But is django in migrations who names constraint. Look into _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),
}
Is _create_index_name who has the algorithm to names constraints:
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
For the current django version (1.7) the constraint name for a composite unique constraint looks like:
>>> _create_index_name( 'people', [ 'c1', 'c2', 'c3'], '_uniq' )
'myapp_people_c1_d22a1efbe4793fd_uniq'
You should overwrite _create_index_name in some way to change algorithm. A way, maybe, writing your own db backend inhering from mysql and overwriting _create_index_name in your DatabaseSchemaEditor on your schema.py (not tested)

I believe you have to do that in your Database;
MySQL:
ALTER TABLE `votes` ADD UNIQUE `unique_index`(`user`, `email`, `address`);
I believe would then say ... for key 'unique_index'

One solution is you can catch the IntegrityError at save(), and then make custom error message as you want as below.
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' "
Now you can use this message to display as error message.

Related

Django allows model object with empty fields to be saved

Below is the model named 'Dataset' containing three fields name, desc and data_file.
class Dataset(models.Model):
name = models.CharField(max_length=256)
desc = models.TextField()
data_file = models.FileField(upload_to='datasets/')
I created a model object with python command.
>>> d = Dataset()
>>> d.save()
>>> d.name, d.desc, d.data_file
('', '', <FieldFile: None>)
Django allowed this object to be saved. Even when blank = False is the default for every field.
How can I may sure that dataset objects cannot be created with these three fields empty ?
Below is the sqlite3 schema:
CREATE TABLE IF NOT EXISTS "datasets_dataset"(
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" varchar(256) NOT NULL,
"data_file" varchar(100) NOT NULL,
"desc" text NOT NULL
);

How to get access to SQLAlchemy automap?

I am using SQLAlchemy automap. When I described structure Declarative I have got backref property:
The above configuration establishes a collection of Address objects on User called User.addresses.
But now with automap my code is like next:
engine = create_engine('sqlite:///sql_test.db', echo=True)
Session = sessionmaker(bind=engine)
sess = Session()
Base = automap_base()
Base.prepare(engine, reflect=True)
User = Base.classes.Users
addresses = Base.classes.addresses
answer = sess.query(User).filter(User.id==1).first()
print('type:', type(answer)) # will print: class User
But how I can get access to addresses here? I tried: answer.addresses and so one, but it is not working.
Users:
CREATE TABLE "Users"(
"id" Integer PRIMARY KEY,
"name" Text,
CONSTRAINT "unique_id" UNIQUE ( "id" ) )
Addresses:
CREATE TABLE "addresses"(
"id" Integer PRIMARY KEY,
"email" Text,
"user_id" Integer,
CONSTRAINT "lnk_Users_addresses" FOREIGN KEY ( "user_id" ) REFERENCES "Users"( "id" ),
CONSTRAINT "unique_id" UNIQUE ( "id" ) )
The default naming scheme for collection relationships is:
return referred_cls.__name__.lower() + "_collection"
So given that you have a model class addresses, then your relationship should be
User.addresses_collection
If you wish to alter this behaviour, pass your own implementation as the name_for_collection_relationship= keyword argument to AutomapBase.prepare().

Django inspectdb 'unique_together' refers to the non-existent field (2015)

I'm using Django with MySQL and having a problem after using 'inspectdb' command to create my models.py file.
DDL:
CREATE TABLE YDB_Collects (
COriginal_Data_Type_ID VARCHAR(16) NOT NULL,
CTask_Name VARCHAR(16) NOT NULL,
PRIMARY KEY (COriginal_Data_Type_ID, CTask_Name),
INDEX FK_COLLECTS_TASK (CTask_Name),
CONSTRAINT FK_COLLECTS_ORIGINAL_DATA_TYPE FOREIGN KEY (COriginal_Data_Type_ID) REFERENCES YDB_Original_Data_Type (Original_Data_Type_ID),
CONSTRAINT FK_COLLECTS_TASK FOREIGN KEY (CTask_Name) REFERENCES YDB_Task (Task_Name)
)
As you can see, COriginal_Data_Type_ID and CTask_Name are foreign keys, as well as the composite primary key.
For this DDL, Django's 'inspectdb' command gave this model:
class YdbCollects(models.Model):
coriginal_data_type = models.ForeignKey('YdbOriginalDataType', db_column='COriginal_Data_Type_ID') # Field name made lowercase.
ctask_name = models.ForeignKey('YdbTask', db_column='CTask_Name') # Field name made lowercase.
class Meta:
managed = False
db_table = 'ydb_collects'
unique_together = (('COriginal_Data_Type_ID', 'CTask_Name'),)
But when I run 'makemigrations' command, it gives me the error message:
'unique_together' refers to the non-existent field 'COriginal_Data_Type_ID' and 'CTask_Name'
When I change:
unique_together = (('COriginal_Data_Type_ID', 'CTask_Name'),)
into:
unique_together = (('coriginal_data_type', 'ctask_name'),)
then yeah, it goes OK. But is this correct way to go? Seems like the code has different schema from my DDL. Original foreign key I defined was ID of data type, not data type itself.
Did I do something wrong here? And where are my COriginal_Data_Type_ID and CTask_Name fields?
This has been fixed in Django 1.10 and later (and backported to Django 1.8.8 and 1.9).
It was a Django bug and the fix is here: django/django#2cb50f9
It involved this change in django/core/management/commands/inspectdb.py:
# tup = '(' + ', '.join("'%s'" % c for c in columns) + ')' # Change this
tup = '(' + ', '.join("'%s'" % column_to_field_name[c] for c in columns) + ')' # to this
unique_together.append(tup)

Inserting into MySQL table using peewee raises "unknown column" exception

I have the following script:
from peewee import *
db = MySQLDatabase('database', user='root')
class BaseModel(Model):
class Meta:
database = db
class Locations(BaseModel):
location_id = PrimaryKeyField()
location_name = CharField()
class Units(BaseModel):
unit_id = PrimaryKeyField()
unit_num = IntegerField()
location_id = ForeignKeyField(Locations, related_name='units')
db.connect()
for location in Locations.select():
for pod_num in range (1, 9):
unit = Units.create(unit_num=pod_num, location_id=location.location_id)
table locations has few rows, table units is empty. When I try to start it, I keep getting exception:
(1054, "Unknown column 'location_id_id' in 'field list'")
What am I doing wrong?
Here is part of SQL script for creating table:
CREATE TABLE IF NOT EXISTS `database`.`units` (
`unit_id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`unit_num` TINYINT UNSIGNED NOT NULL ,
`location_id` INT UNSIGNED NOT NULL ,
PRIMARY KEY (`unit_id`) ,
UNIQUE INDEX `ID_UNIQUE` (`unit_id` ASC) ,
INDEX `location_idx` (`location_id` ASC) ,
CONSTRAINT `location_id`
FOREIGN KEY (`location_id` )
REFERENCES `database`.`locations` (`location_id` )
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB;
Thank you in advance!
If you want to explicitly specify a column, use db_column:
class Units(BaseModel):
unit_id = PrimaryKeyField()
unit_num = IntegerField()
location_id = ForeignKeyField(Locations, db_column='location_id', related_name='units')
This is documented: http://peewee.readthedocs.org/en/latest/peewee/models.html#field-types-table

Base entity with concrete inheritance

I want to have a base entity with a field deleted which marks a deleted record. And i have 2 subclasses, each of them to have their own table with all own columns:
from elixir import *
from sqlalchemy import create_engine
class Catalog(Entity):
using_options(inheritance='concrete')
deleted = Boolean
class Contact(Catalog):
using_options(inheritance='concrete')
name = Field(String(60))
class Location(Catalog):
using_options(inheritance='concrete')
name = Field(String(100))
setup_all()
metadata.bind = create_engine('sqlite:///', echo=True)
metadata.create_all()
And the result:
CREATE TABLE __main___catalog (
id INTEGER NOT NULL,
PRIMARY KEY (id)
)
CREATE TABLE __main___contact (
id INTEGER NOT NULL,
name VARCHAR(60),
PRIMARY KEY (id)
)
CREATE TABLE __main___location (
id INTEGER NOT NULL,
name VARCHAR(100),
PRIMARY KEY (id)
)
Questions:
How to avoid creation of a table for the base entity? - solved: using_options(abstract = True)
Why field deleted is not in the created tables? - this solved - i forgot to put it inside a Field
I want to avoid typing in each subclass using_options(inheritance='concrete') but still have "concrete inheritance". Is there a way to make it default for all subclasses?
This works:
class Catalog(Entity):
deleted = Field(Boolean)
using_options(abstract = True, inheritance = 'concrete')
class Contact(Catalog):
name = Field(String(60))
class Location(Catalog):
name = Field(String(100))
and creates the following tables:
CREATE TABLE __main___contact (
id INTEGER NOT NULL,
deleted BOOLEAN,
name VARCHAR(60),
PRIMARY KEY (id),
CHECK (deleted IN (0, 1))
)
CREATE TABLE __main___location (
id INTEGER NOT NULL,
deleted BOOLEAN,
name VARCHAR(100),
PRIMARY KEY (id),
CHECK (deleted IN (0, 1))
)

Categories

Resources