'Integrity Error on DRF on fixture teardown

I have these 2 models in Django:

class Invoice(models.Model):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    owner = models.ForeignKey(to="User", on_delete=models.CASCADE)
    client = models.ForeignKey(to=Client, on_delete=models.CASCADE)
    project = models.ForeignKey(to=Project, on_delete=models.CASCADE)
    work_sessions = models.ManyToManyField  (WorkSession)
    fixed_travels = models.ManyToManyField(FixedTravel)
    hourly_travels = models.ManyToManyField(HourlyTravel)

class WorkSession(models.Model):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    start_timestamp = models.IntegerField(editable=True, null=False, blank=False)
    end_timestamp = models.IntegerField(editable=True, null=False, blank=False)
    project = models.ForeignKey(to=Project, on_delete=models.CASCADE)
    owner = models.ForeignKey(
        to="User", related_name="work_sessions", on_delete=models.CASCADE
    )

I'm in the middle of an APITestCase in Django Rest Framework where I create 2 work sessions and one invoice and assign those sessions to the invoice

Now on the fixture teardown, this exception gets thrown

raise IntegrityError(
django.db.utils.IntegrityError: The row in table 'drscm_invoice_work_sessions' with primary key '1' has an invalid foreign key: drscm_invoice_work_sessions.invoice_id contains a value '13ba348db35746c1b7f56884efc6249a' that does not have a corresponding value in drscm_invoice.id.

What I want is for WorkSession to exist even though an Invoice gets deleted And it's not a ManyToOne relationship that I'm looking for :/

What am I doing wrong here ?

EDIT

I've tried to update it to use a through model like this:

class Invoice(models.Model):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    owner = models.ForeignKey(to="User", on_delete=models.CASCADE)
    client = models.ForeignKey(to=Client, on_delete=models.CASCADE)
    project = models.ForeignKey(to=Project, on_delete=models.CASCADE)
    work_sessions = models.ManyToManyField(
        WorkSession,
        through="InvoiceWorkSession",
        through_fields=("invoice", "work_session"),
        blank=True,
    )
    fixed_travels = models.ManyToManyField(
        FixedTravel,
        through="InvoiceWorkSession",
        through_fields=("invoice", "fixed_travel"),
        blank=True,
    )
    hourly_travels = models.ManyToManyField(
        HourlyTravel,
        through="InvoiceWorkSession",
        through_fields=("invoice", "hourly_travel"),
        blank=True,
    )

    def __str__(self):
        return f"{self.id}"

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.client = self.project.client
        self.owner = self.project.client.owner

        super(Invoice, self).save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields,
        )


class InvoiceWorkSession(models.Model):

    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, null=True)
    work_session = models.ForeignKey(WorkSession, on_delete=models.CASCADE, null=True)
    fixed_travel = models.ForeignKey(FixedTravel, on_delete=models.CASCADE, null=True)
    hourly_travel = models.ForeignKey(HourlyTravel, on_delete=models.CASCADE, null=True)

and the issue still persists :/



Solution 1:[1]

If a work session is only to be linked with one invoice only modify your models as below

class Invoice(models.Model):
  ....
  work_sessions  = models.OneToOne(to=WorkSession, on_delete=models.SET_NULL, null=True, blank=True, related_name="inv_workssessions")

or to attach multiple invoices to worksession use ForeignKey relationship similary.

on_delete=models.CASCADE deletes the all the related objects when the parent object is deleted. on_delete=models.SET_NULL will just remove the foreign key relationship and preserve the row.

You can also check other available option here in official documentation.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sukhpreet Singh