2

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
Amrou
  • 371
  • 1
  • 3
  • 18

1 Answers1

0

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)
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • What I'm testing is my view. So I'm creating the object via the test api client. So the invoice will be serialized and sent over as the body of the HTTP request. Unless my method is wrong – Amrou Apr 30 '22 at 10:04
  • And isn't this a bug ? The fact that the junction contains a reference to a non existing object ? Because the fact that I haven't the invoice itself was done on purpose. I was just trying to have a JSON with the necessary values to create my entity – Amrou Apr 30 '22 at 10:05
  • 1
    @Amrou: most databases with integrity constraints enabled, will not enable this no. So normally if you have a proper database, that will be rejected by the database. – Willem Van Onsem Apr 30 '22 at 10:06
  • @Amrou it's true, it looks weird and it's not documented well in the documentation. You should get an integrity error on `set` call, not later, right? – vinkomlacic Apr 30 '22 at 10:06
  • @vinkomlacic Exactly ! – Amrou Apr 30 '22 at 10:07
  • 2
    @vinkomlacic: this is not Django's responsibility: Django simply creates a query. The database should raise the integrity error. – Willem Van Onsem Apr 30 '22 at 10:07
  • @WillemVanOnsem how can I fix this then ? I tried it with postgres and sqlite and it's the same issue that occurs :/ – Amrou Apr 30 '22 at 10:08
  • @Amrou you 'll need to create objects first as suggested by WillemVanOnsem, then you can add or set the corresponding key in invoice table. – Sukhpreet Singh Apr 30 '22 at 11:18
  • I've updated my question @SukhpreetSingh, because I feel like I'm not explaining it good enough to understand what I'm trying to do. Sorry for that – Amrou Apr 30 '22 at 11:35