Integrity Error on DRF on fixture teardown

181 views Asked by At

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 :/

1

There are 1 answers

5
Sukhpreet Singh On

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.