Django isn't creating db constraints for foreign keys with SQLite - django

I'm using Django 3.0.6
I'm adding ForeignKey and ManyToManyField to my models, but I've noticed that django creates the INDEX, but not the actual FOREIGN KEY constraints in the db.
I've tried to explicitly set db_constraint=True but as expected is useless, since it's True by default.
I've found so many answers explaining this, but only for very old Django versions, doing tricks for enabling it when it was otherwise disabled. Now instead it should just work out of the box. Couldn't find anything AT ALL regarding Django 3.
Code
class Token (models.Model):
owner = models.ForeignKey(Chiefdom, on_delete=models.CASCADE, db_constraint=True)
county = models.ManyToManyField(County, db_constraint=True)
amount = models.PositiveSmallIntegerField()
SQLite
CREATE TABLE IF NOT EXISTS Piece_token (
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
amount smallint unsigned NOT NULL,
owner_id integer NOT NULL
);
CREATE INDEX IF NOT EXISTS Piece_token_owner_id_d27c77f0 ON Piece_token (owner_id);
CREATE TABLE IF NOT EXISTS Piece_token_county (
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
token_id integer NOT NULL,
county_id integer NOT NULL
);
CREATE INDEX IF NOT EXISTS Piece_token_county_county_id_57802417 ON Piece_token_county (county_id);
CREATE INDEX IF NOT EXISTS Piece_token_county_token_id_e7798ae9 ON Piece_token_county (token_id);
CREATE UNIQUE INDEX IF NOT EXISTS Piece_token_county_token_id_county_id_b06b16cc_uniq ON Piece_token_county (token_id, county_id);

I have checked now with same version of Django and SQLite there are all foreign keys present
For example
SELECT * FROM pragma_foreign_key_list('auth_user_groups');
Note all foreign keys are deferred and checked from Django -> source

Related

Junction table referencing non existent entity

I have the following models in django
class WorkSession(models.Model):
pass
class Invoice(models.Model):
work_sessions = models.ManyToManyField(WorkSession, blank=True)
what I noticed is that when i do the following:
invoice = Invoice()
session = WorkSession(a=a, b=b)
invoiceo.work_sessions.set([session])
The invoice_worksession junction table gets populated with a relation, even though I haven't saved invoice yet.
Meaning that the invoices table, there's no row, but in the junction table, there's a row that references an invoice that doesn't exist yet.
Is this normal ?
Because this is causing an integrity error on fixture teardown since the invoice doesn't exist and yet, there's a refrence to
an invoice id in the junction table
EDIT
The following is a better explanation of what I'm trying to do and the problem itself
Here are my tables
CREATE TABLE "drscm_worksession" (
"id" char(32) NOT NULL,
"start_timestamp" integer NOT NULL,
"end_timestamp" integer NOT NULL,
PRIMARY KEY("id"),
);
CREATE TABLE "drscm_invoice" (
"id" char(32) NOT NULL,
PRIMARY KEY("id"),
);
and the junction table
CREATE TABLE "drscm_invoice_work_sessions" (
"id" integer NOT NULL,
"invoice_id" char(32) NOT NULL,
"worksession_id" char(32) NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("invoice_id") REFERENCES "drscm_invoice"("id") DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY("worksession_id") REFERENCES "drscm_worksession"("id") DEFERRABLE INITIALLY DEFERRED
);
The objective is to create an invoice in the database with work_sessions via an api call.
The data i would need to send over, using my api client is this:
{work_sessions: [uuid1, uuid2] }
so the code would be
url = "/invoices/"
ws1 = WorkSession().save()
ws2 = WorkSession().save()
data = {'work_sessions': [ws1.id, ws2.id] }
self.client.post(path=url, data=data)
This works PERFECTLY.
BUT,
I thought, I don't want to have to write these objects manually as they get bigger in the real test.
So the approach was to do this:
invoice = Invoice()
invoice.work_sessions.set([ws1, ws2])
data = InvoiceSerializer(instance=invoice).data
self.client.post(path=url, data=data)
This does create the invoice with the according sessions BUT throws an IntegrityError exception during the teardown phase of the test
Here's why it throws the exception:
Invoice() creates an instance of the object and that instance has an id, but the invoice is NOT in the database
invoice.work_sessions.set([ws1, ws2]) creates 2 rows in the drscm_invoice_work_sessions table (junction table), and the invoice_id column takes the id of the NOT CREATED invoice instance. ( Which is the bug here)
During teardown, it tries do delete the relations in the junction table --> looks for an invoice with id: invoice.id in the drscm_invoices table, but it doesn't exit.
--> Throws an exception
You need to save the objects first to the database, such that these have a primary key, so:
invoice = Invoice.objects.create()
session = WorkSession.objects.create(a=a, b=b)
invoice.work_sessions.add(session)

Django Related Models - magic behind the scenes

I was trying to find the answer in Django Documentation, but failed to do so.
Can anyone please explain how does Django "match" the objects of the related models?
eg. I have two models, and I am showing Django that they are related:
class Reporter(models.Model):
# ...
pass
class Article(models.Model):
reporter = models.ForeignKey(Reporter, related_name='report')
Then the magic happens and Django matches the two models, and adds _id field.
My question is:
How does Django know which objects of those two models are related?
Is it checking each and every field of those objects and sees if there is a match?
EDIT:
How does Django determine that a particular Reporter object is related to a particular Article object?
I understand that when it finds a match it adds the _id field, what I do not understand is based on what django "matches" two objects from different models.
To be more specific:
Let's say that there are two Reporter objects - r1 and r2.
There is one object in Article class - a1
How does django know that a1 is related to r1 and not to r2?
Thanks for your help!
It looks like you're not really SQL-savy, because there's really no "magic" involved and it's all basic relational model design.
Your above models translates to the canonical one to many SQL schema:
CREATE TABLE yourappname_reporter (
id int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id)
);
CREATE TABLE yourappname_article(
id int(11) NOT NULL AUTO_INCREMENT,
reporter_id int(11) NOT NULL,
PRIMARY KEY(id),
CONSTRAINT `reporter_id_refs_id_XXXX` FOREIGN KEY (`reporter_id`) REFERENCES `yourappname_reporter` (`id`)
);
As you can see, Django adds an 'id' primary key to your models (since you didn't explicitely defined one yourself), and the Article.reporter field translates to the reporter_id foreign key which references reporter.id. Here again Django uses the reporter.id primary key as foreign key reference as a (very sensible) default since you didn't explicitely told him to target another field.
Now when you create a new Article, you have to provide a Reporter instance, ie (assuming you have a reporter with id 1):
reporter = Reporter.objects.get(id=1)
article = Article.objects.create(reporter=reporter)
Then the ORM will issue the SQL query insert into yourappname_article (reporter_id) values (1), hence relating this new article row with this reporter row, and you can now get all articles from reporter 1 with select * from yourappname_article where reporter_id=1 (which is the query Django ORM will issue for Article.objects.filter(reporter_id=1))
To make a long story short: Django ORM is only a thin wrapper over your database, and you do have to know what a relational database is, how it works and how to properly use it (including how to properly design your db schema) if you expect to do anything good. If you don't even know what a foreign key is then by all means first learn about the relational model, proper relational design and basic SQL stuff.

django: how to create a column whose default value is now()?

I am using django model to create a table. And the table has a inserted_time column, and I am using plain sql to insert the data, so in the insert sql I don't want to care about this column since I expected database should auto fill now() to the column (using mysql). But how to create such column whose default value is now() in django model. I am using auto-now, but it doesn't work.
updated:
I created a model as:
class TestDaniel(models.Model):
inserted_time = models.DateTimeField(db_index = True,auto_now_add=True)
Then I checked the mysql database after migration, the definition of the table is:
CREATE TABLE `orajob_testdaniel` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`inserted_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `orajob_testdaniel_e69592ad` (`inserted_time`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
From the table definition, inserted_time doesn't have a default value, what I want is to have a default value of now() for column of inserted_time, So what I inserted data using SQL (not django model), it will auto populate that column as now()
Well, usage of default values for DateField is clearly declared in the Django documentation. Django DateField Documentation
According to the docs, you can do the following:
inserted_time = models.DateTimeField(auto_now_add=True)
This will set current datetime to this field. This value will be by default and will be set once you create an object. Warning: now, even if you set custom datetime to this field, it will be ignored!
It will not work if you create your object using plain SQL. Django should use its own ORM in order to set default values.

Django AutoField not returning new primary_key

We've got a small problem with a Django project we're working on and our postgresql database.
The project we're working on is a site/db conversion from a PHP site to a django site. So we used inspect db to generate the models from the current PHP backend.
It gave us this and we added the primary_key and unique equals True:
class Company(models.Model):
companyid = models.IntegerField(primary_key=True,unique=True)
...
...
That didn't seem to be working when we finally got to saving a new Company entry. It would return a not-null constraint error, so we migrated to an AutoField like below:
class Company(models.Model):
companyid = models.AutoField(primary_key=True)
...
...
This saves the Company entry fine but the problem is when we do
result = form.save()
We can't do
result.pk or result.companyid
to get the newly given Primary Key in the database (yet we can see that it has been given a proper companyid in the database.
We are at a loss for what is happening. Any ideas or answers would be greatly appreciated, thanks!
I just ran into the same thing, but during a django upgrade of a project with a lot of history. What a pain...
Anyway, the problem seems to result from the way django's postgresql backend gets the primary key for a newly created object: it uses pg_get_serial_sequence to resolve the sequence for a table's primary key. In my case, the id column wasn't created with a serial type, but rather with an integer, which means that my sequence isn't properly connected to the table.column.
The following is based on a table with the create statement, you'll have to adjust your table names, columns and sequence names according to your situation:
CREATE TABLE "mike_test" (
"id" integer NOT NULL PRIMARY KEY,
"somefield" varchar(30) NOT NULL UNIQUE
);
The solution if you're using postgresql 8.3 or later is pretty easy:
ALTER SEQUENCE mike_test_id_seq OWNED BY mike_test.id;
If you're using 8.1 though, things are a little muckier. I recreated my column with the following (simplest) case:
ALTER TABLE mike_test ADD COLUMN temp_id serial NOT NULL;
UPDATE mike_test SET temp_id = id;
ALTER TABLE mike_test DROP COLUMN id;
ALTER TABLE mike_test ADD COLUMN id serial NOT NULL PRIMARY KEY;
UPDATE mike_test SET id = temp_id;
ALTER TABLE mike_test DROP COLUMN temp_id;
SELECT setval('mike_test_id_seq', (SELECT MAX(id) FROM mike_test));
If your column is involved in any other constraints, you'll have even more fun with it.

How do I retroactively make AutoFields that don't break django-admin?

I created the models in a Django app using manage.py inspectdb on an
existing postgres database. This seemed to work except that all the
primary keys were described in the models as IntegerFields, which made
them editable in the admin panel, and had to be hand-entered based on
knowledge of the id of the previous record. I just learned about this
after some usage by the client, so I went back to change things like
blog_id = models.IntegerField(primary_key=True)
...to...
blog_id = models.AutoField(primary_key=True)
Now the id fields don't appear in the admin panel (good), but adding new rows
has become impossible (not good).
IntegrityError at /admin/franklins_app/blog/add/
duplicate key value violates unique constraint "blog_pkey"
What's the fix? (Bonus question: is it possible to capture the value that Django is trying to assign as the primary key for the new row?)
The sequence behind the serial field which is your primary key doesn't know about the manually entered records.
Find the maximum value of the primary key:
SELECT MAX(<primary_key>) FROM <your_table>;
Then set the next value of the underlying sequence to a number greater than that:
SELECT SETVAL('<primary_key_seq>', <max_value_in_primary_key_plus_something_for_safety>);
You'll find the name of the sequence (mentioned above as <primary_key_seq>) using:
SELECT pg_get_serial_sequence('<your_table_name>', '<primary_key_column_name');