Django does not honor ON DELETE CASCADE - python

This is my model:
class Subscriber(models.Model):
...
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, null=True)
...
This is the generated SQL, according to sqlmigrate (and to manual inspection of the database):
ALTER TABLE `myapp_subscriber` ADD CONSTRAINT `myapp_subscriber_tenant_id_b52815ee_fk_myapp_tenant_id` FOREIGN KEY (`tenant_id`) REFERENCES `myapp_tenant` (`id`);
I was expecting something like this:
CREATE TABLE child (
id INT,
parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE
) ENGINE=INNODB;
With the ON DELETE CASCADE.
MySql (MariaDB actually) complains when I delete:
SQL Error (1451): Cannot delete or update a parent row: a foreign key constraint fails
Which makes sense since there is no ON DELETE CASCADE clause.
Why is Django 2.1.5 not honoring the ON DELETE CASCADE clause?

From the docs:
on_delete doesn’t create a SQL constraint in the database. Support for
database-level cascade options may be implemented later
It will perform the cascade in Django itself, so if you delete a Tenant object using Django delete() your Subscriber object will also be deleted. But not if you do it in SQL.

Related

Django. Create composite primary key

I want create composite promary key like {id, project_id}. I remove old tables(all). when i do:
python manage.py makemigrations
I have a mistake:
AssertionError: Model mdm.Group can't have more than one AutoField.
change my model:
id = models.AutoField(db_index=True, primary_key=False)
and add composite primary key as
constraints = [
models.UniqueConstraint(
fields=['id', 'project_id'], name='unique_group_project'
)
]
From docs:
By default, Django gives each model the following field:
id = models.AutoField(primary_key=True)
This is an auto-incrementing primary key.
If you’d like to specify a custom primary key, just specify primary_key=True on one of your fields. If Django sees you’ve explicitly set Field.primary_key, it won’t add the automatic id column.
Each model requires exactly one field to have primary_key=True (either explicitly declared or automatically added).
I just don't understand the problem. If I add AutoField, It's necessarily must PK. How I can resolve problem with Autofield id and composite PK (id, project_id)?

Django ManyToMany through with multiple databases

TLTR: Django does not include database names in SQL queries, can I somehow force it to do this or is there a workaround?
The long version:
I have two legacy MySQL databases (Note: I have no influence on the DB layout) for which I'm creating a readonly API using DRF on Django 1.11 and python 3.6
I'm working around the referential integrity limitation of MyISAM DBs by using the SpanningForeignKey field suggested here: https://stackoverflow.com/a/32078727/7933618
I'm trying to connect a table from DB1 to a table from DB2 via a ManyToMany through table on DB1. That's the query Django is creating:
SELECT "table_b"."id" FROM "table_b" INNER JOIN "throughtable" ON ("table_b"."id" = "throughtable"."b_id") WHERE "throughtable"."b_id" = 12345
Which of course gives me an Error "Table 'DB2.throughtable' doesn't exist" because throughtable is on DB1 and I have no idea how to force Django to prefix the tables with the DB name. The query should be:
SELECT table_b.id FROM DB2.table_b INNER JOIN DB1.throughtable ON (table_b.id = throughtable.b_id) WHERE throughtable.b_id = 12345
Models for app1 db1_app/models.py: (DB1)
class TableA(models.Model):
id = models.AutoField(primary_key=True)
# some other fields
relations = models.ManyToManyField(TableB, through='Throughtable')
class Throughtable(models.Model):
id = models.AutoField(primary_key=True)
a_id = models.ForeignKey(TableA, to_field='id')
b_id = SpanningForeignKey(TableB, db_constraint=False, to_field='id')
Models for app2 db2_app/models.py: (DB2)
class TableB(models.Model):
id = models.AutoField(primary_key=True)
# some other fields
Database router:
def db_for_read(self, model, **hints):
if model._meta.app_label == 'db1_app':
return 'DB1'
if model._meta.app_label == 'db2_app':
return 'DB2'
return None
Can I force Django to include the database name in the query? Or is there any workaround for this?
A solution exists for Django 1.6+ (including 1.11) for MySQL and sqlite backends, by option ForeignKey.db_constraint=False and explicit Meta.db_table. If the database name and table name are quoted by ' ` ' (for MySQL) or by ' " ' (for other db), e.g. db_table = '"db2"."table2"'). Then it is not quoted more and the dot is out of quoted. Valid queries are compiled by Django ORM. A better similar solution is db_table = 'db2"."table2' (that allows not only joins but it is also by one issue nearer to cross db constraint migration)
db2_name = settings.DATABASES['db2']['NAME']
class Table1(models.Model):
fk = models.ForeignKey('Table2', on_delete=models.DO_NOTHING, db_constraint=False)
class Table2(models.Model):
name = models.CharField(max_length=10)
....
class Meta:
db_table = '`%s`.`table2`' % db2_name # for MySQL
# db_table = '"db2"."table2"' # for all other backends
managed = False
Query set:
>>> qs = Table2.objects.all()
>>> str(qs.query)
'SELECT "DB2"."table2"."id" FROM DB2"."table2"'
>>> qs = Table1.objects.filter(fk__name='B')
>>> str(qs.query)
SELECT "app_table1"."id"
FROM "app_table1"
INNER JOIN "db2"."app_table2" ON ( "app_table1"."fk_id" = "db2"."app_table2"."id" )
WHERE "db2"."app_table2"."b" = 'B'
That query parsing is supported by all db backends in Django, however other necessary steps must be discussed individually by backends. I'm trying to answer more generally because I found a similar important question.
The option 'db_constraint' is necessary for migrations, because Django can not create the reference integrity constraint
ADD foreign key table1(fk_id) REFERENCES db2.table2(id),
but it can be created manually for MySQL.
A question for particular backends is if another database can be connected to the default at run-time and if a cross database foreign key is supported. These models are also writable. The indirectly connected database should be used as a legacy database with managed=False (because only one table django_migrations for migrations tracking is created only in the directly connected database. This table should describe only tables in the same database.) Indexes for foreign keys can however be created automatically on the managed side if the database system supports such indexes.
Sqlite3: It has to be attached to another default sqlite3 database at run-time (answer SQLite - How do you join tables from different databases), at best by the signal connection_created:
from django.db.backends.signals import connection_created
def signal_handler(sender, connection, **kwargs):
if connection.alias == 'default' and connection.vendor == 'sqlite':
cur = connection.cursor()
cur.execute("attach '%s' as db2" % db2_name)
# cur.execute("PRAGMA foreign_keys = ON") # optional
connection_created.connect(signal_handler)
Then it doesn't need a database router of course and a normal django...ForeignKey can be used with db_constraint=False. An advantage is that "db_table" is not necessary if the table names are unique between databases.
In MySQL foreign keys between different databases are easy. All commands like SELECT, INSERT, DELETE support any database names without attaching them previously.
This question was about legacy databases. I have however some interesting results also with migrations.
I have a similar setup with PostgreSQL. Utilizing search_path to make cross-schema references possible in Django (schema in postgres = database in mysql). Unfortunately, seems like MySQL doesn't have such a mechanism.
However, you might try your luck creating views for it. Make views in one databases that references other databases, use it to select data. I think it's the best option since you want your data read-only anyway.
It's however not a perfect solution, executing raw queries might be more useful in some cases.
UPD: Providing mode details about my setup with PostgreSQL (as requested by bounty later). I couldn't find anything like search_path in MySQL documentation.
Quick intro
PostgreSQL has Schemas. They are synonymous to MySQL databases. So if you are MySQL user, imaginatively replace word "schema" with word "database". Requests can join tables between schemas, create foreign keys, etc... Each user (role) has a search_path:
This variable [search_path] specifies the order in which schemas are searched when
an object (table, data type, function, etc.) is referenced by a simple
name with no schema specified.
Special attention on "no schema specified", because that's exactly what Django does.
Example: Legacy databases
Let's say we got coupe legacy schemas, and since we are not allowed to modify them, we also want one new schema to store the NM relation in it.
old1 is the first legacy schema, it has old1_table (which is also the model name, for convenience sake)
old2 is the second legacy schema, it has old2_table
django_schema is a new one, it will store the required NM relation
All we need to do is:
alter role django_user set search_path = django_schema, old1, old2;
This is it. Yes, that simple. Django has no names of the schemas ("databases") specified anywhere. Django actually has no idea what is going on, everything is managed by PostgreSQL behind the scenes. Since django_schema is first in the list, new tables will be created there. So the following code ->
class Throughtable(models.Model):
a_id = models.ForeignKey('old1_table', ...)
b_id = models.ForeignKey('old2_table', ...)
-> will result in a migration that creates table throughtable that references old1_table and old2_table.
Problems: if you happened to have several tables with same names, you will either need to rename them or still trick Django into using a dot inside of table names.
Django does have the ability to work with multiple databases. See https://docs.djangoproject.com/en/1.11/topics/db/multi-db/.
You can also use raw SQL queries in Django. See https://docs.djangoproject.com/en/1.11/topics/db/sql/.

SQLAlchemy fails to delete object with foreign key relationship [duplicate]

My User model has a relationship to the Address model. I've specified that the relationship should cascade the delete operation. However, when I query and delete a user, I get an error that the address row is still referenced. How do I delete the user and the addresses?
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
addresses = db.relationship('Address', cascade='all,delete', backref='user')
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
db.session.query(User).filter(User.my_id==1).delete()
IntegrityError: (IntegrityError) update or delete on table "user" violates foreign key constraint "addresses_user_id_fkey" on table "address"
DETAIL: Key (my_id)=(1) is still referenced from table "address".
'DELETE FROM "user" WHERE "user".id = %(id_1)s' {'id_1': 1}
You have the following...
db.session.query(User).filter(User.my_id==1).delete()
Note that after "filter", you are still returned a Query object. Therefore, when you call delete(), you are calling delete() on the Query object (not the User object). This means you are doing a bulk delete (albeit probably with just a single row being deleted)
The documentation for the Query.delete() method that you are using says...
The method does not offer in-Python cascading of relationships - it is
assumed that ON DELETE CASCADE/SET NULL/etc. is configured for any
foreign key references which require it, otherwise the database may
emit an integrity violation if foreign key references are being
enforced.
As it says, running delete in this manner will ignore the Python cascade rules that you've set up. You probably wanted to do something like..
user = db.session.query(User).filter(User.my_id==1).first()
db.session.delete(user)
Otherwise, you may wish to look at setting up the cascade for your database as well.

Django Polymoprhic

I want to implement models using inheritance and I've found this package django-polymorphic. But I was reading about inheritance in django models and almost on every page I found they recommend using abstract = True in parent model. Which will duplicate fields for subclasses, resultsing in making queries faster.
I've done some testing and found out that this library doesn't use use abstract varaible:
class Parent(PolymorphicModel):
parent_field = models.TextField()
class Child(Parent):
child_field = models.TextField()
This results in:
Parent table:
| app_parent| CREATE TABLE `app_parent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_field` longtext NOT NULL,
`polymorphic_ctype_id` int(11),
PRIMARY KEY (`id`),
KEY `app_polymorphic_ctype_id_a7b8d4c7_fk_django_content_type_id` (`polymorphic_ctype_id`),
CONSTRAINT `app_polymorphic_ctype_id_a7b8d4c7_fk_django_content_type_id` FOREIGN KEY (`polymorphic_ctype_id`) REFERENCES `django_content_type` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
Child table:
| app_child | CREATE TABLE `app_child` (
`parent_ptr_id` int(11) NOT NULL,
`child_field` varchar(20) NOT NULL,
PRIMARY KEY (`parent_ptr_id`),
CONSTRAINT `no_parent_ptr_id_079ccc0e_fk_app_parent_id` FOREIGN KEY (`parent_ptr_id`) REFERENCES `app_arent` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
Should I use my own classes which uses abstract field or should i stick with this?
Do you have a need to be able to query parent table?
Parent.objects.all()
If yes, then most probably you will need to use the multi-table inheritance with abstract=False.
Using model inheritance with abstract=False you get more complicated database schema, with more database relations. Creating a child instance will take 2 inserts instead of 1 (parent and child table). Querying child data will require table join. So this method certainly has got it's shortcomings. But when you want to query for common columns data, it's best supported way in django.
Django polymorphic builds on top of standard django model inheritance, by adding extra column polymorphic_ctype which allows to identify subclass having only a parent object.
There are various ways that you can use to achieve similar results using abstract=True. But often it results in more complicated querying code.
If using abstract=True bellow is 2 examples how you can query common-data of all childs.
Chaining multiple queries
def query_all_childs(**kwargs):
return chain(
Child1.objects.filter(**kwargs)
Child2.objects.filter(**kwargs)
)
Using database views
Manually create a database view that combines several tables (this could be done by attaching sql code to post-migrate signal):
create database view myapp_commonchild
select 'child1' as type, a, b from child1
union all
select 'child2' as type, a, b from child2
Create a concrete-parent model with managed=False. This flag tells django to ignore the table in database migrations (because we have manually created database view for that).
class Parent(models.Model):
a = CharField()
b = CharField()
class CommonChild(Parent):
type = models.CharField()
class Meta:
managed = False
class Child1(Parent):
pass
class Child2(Parent):
pass
Now you can query CommonChild.objects.all() and access common fields of child classes.
Speaking of performance, I don't know how big your tables are or how heavy reads/writes are, but most probably using abstract=False will not impact your performance in a noticeable way.

how to update model in django by adding new primary key field?

How to replace default primary key in Django model with custom primary key field?
I have a model with no primary key defined at first since django automatically adds an id field by default as primary field.
#models.py
from django.db import models
class Event(models.Model):
title = models.CharField(max_length=50, unique=True)
description = models.CharField(max_length=150)
I added some objects into it from django shell.
>>e = Event('meeting', 'Contents about meeting')
>>e.save()
>>e = Event('party', 'Contents about party')
>>e.save()
Then I require to add custom character field as primary into this model.
class Event(models.Model):
event-id = models.CharField(max_length=50, primary_key=True)
...
Running makemigrations:
$ python manage.py makemigrations
You are trying to add a non-nullable field 'event-id' to event without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and `django.utils.timezone modules` are available, so you can do e.g. timezone.now()
>>> 'meetings'
Migrations for 'blog':
0002_auto_20141201_0301.py:
- Remove field id from event
- Add field event-id to event
But while running migrate it threw an error:
.virtualenvs/env/local/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 485, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: UNIQUE constraint failed: blog_event__new.event-id
In my experience (using Django 1.8.* here), I've seen similar situations when trying to update the PK field for models that already exist, have a Foreign Key relationship to another model, and have associated data in the back-end table.
You didn't specify if this model is being used in a FK relation, but it seems this is the case.
In this case, the error message you're getting is because the data that already exists needs to be made consistent with the changes you're requesting --i.e. a new field will be the PK. This implies that the current PK must be dropped for django to 'replace' them. (Django only supports a single PK field per model, as per docs[1].)
Providing a default value that matches currently existing data in the related table should work.
For example:
class Organization(models.Model):
# assume former PK field no longer here; name is the new PK
name = models.CharField(primary_key=True)
class Product(models.Model):
name = models.CharField(primary_key=True)
organization = models.ForeignKey(Organization)
If you're updating the Organization model and products already exist, then existing product rows must be updated to refer to a valid Organization PK value. During the migration, you'd want to choose one of the existing Organization PKs (e.g. 'R&D') to update the existing products.
[1] https://docs.djangoproject.com/en/1.8/topics/db/models/#automatic-primary-key-fields
Django has already established an auto incrementing integer id as primary key in your backend as and when u made the previous model.
When u were trying to run the new model , An attempt was made to recreate a new primary key column that failed.
Another reason is,When u made the field,Django was expecting a unique value be explicitly defined for each new row which it couldn't found,hence the reason.
As told in previous answer you can re-create the migration and then try doing it again.It should work.. cheers :-)
The problem is that you made the field unique, then attempted to use the same value for all the rows in the table. I'm not sure if there's a way to programmatically provide the key, but you could do the following:
Delete the migration
Remove the primary_key attribute from the field
Make a new migration
Apply it
Fill in the value for all your rows
Add the primary_key attribute to the field
Make a new migration
Apply it
It's bruteforce-ish, but should work well enough.
Best of luck!

Categories