Junction table referencing non existent entity

70 views Asked by At

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:

  1. Invoice() creates an instance of the object and that instance has an id, but the invoice is NOT in the database
  2. 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)
  3. 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
1

There are 1 answers

9
willeM_ Van Onsem On

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)