Why would I still get RestrictedError in Django when related records deleted?

I have the following code in a unit test:

        serum_sample.msruns.all().delete()
        print(f"THIS SHOULD BE 0: {serum_sample.msruns.count()}")
        serum_sample.delete()

I get out:

THIS SHOULD BE 0: 0

In the code, I've deleted the related records that have the RESTRICT relationship, but I still get this error:

django.db.models.deletion.RestrictedError: ("Cannot delete some instances of model 'Sample' because they are referenced through restricted foreign keys: 'MSRun.sample'.", {<MSRun: MS run of sample BAT-xz971 with Default by anonymous on 2021-04-29>, <MSRun: MS ...

The relation is:

class MSRun(HierCachedModel, MaintainedModel):

    ...

    sample = models.ForeignKey(
        to="DataRepo.Sample",
        on_delete=models.RESTRICT,
        related_name="msruns",
        help_text="The sample that was run on the mass spectrometer.",
    )

I thought that to be able to delete the sample, all I had to do was delete the MSRun records that link to it...

Why would this not be the case?

OK. I figured it out, though I still cannot explain the content of the error message. Of course, I hadn't supplied enough information for anyone else to be able to answer the question, because I couldn't determine the source of the problem. It turned out to be 2 foreign keys between the Animal and Sample tables:

class Animal(MaintainedModel, HierCachedModel, ElementLabel):

    ...

    last_serum_sample = models.ForeignKey(
        to="DataRepo.Sample",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="animals",
    )

class Sample(MaintainedModel, HierCachedModel):

    ...

    animal = models.ForeignKey(
        to="DataRepo.Animal",
        on_delete=models.CASCADE,
        null=False,
        related_name="samples",
    )

class MSRun(HierCachedModel, MaintainedModel):

    ...

    sample = models.ForeignKey(
        to="DataRepo.Sample",
        on_delete=models.RESTRICT,
        related_name="msruns",
    )

Previously, Animal.last_serum_sample was a @cached_function/@property, but in my current branch, I changed it into a @maintained_field_function named _last_serum_sample() that spits out a value to update the new model field last_serum_sample which, when the record's (or a hierarchically related record's) .save() or .delete was called, it would use the output of ``. Animal.save() is called to update the new _last_serum_sample() to update last_serum_sample. But it's old value was the sample that was deleted. So there were 2 things going on:

  1. During the update, it was trying to get a sample that was in the process of being deleted, which caused an exception.
  2. The delete of the sample record ultimately fails because there are 2 links to the record that were trying to govern how it is supposed to delete (MSRun.sample was set to RESTRICT and Animal.last_serum_sample was set to CASCADE). And one was doing a save during the other's delete operation.

So, I changed Animal.last_serum_sample.on_delete to SET_NULL instead of CASCADE:

class Animal(MaintainedModel, HierCachedModel, ElementLabel):

    ...

    last_serum_sample = models.ForeignKey(
        to="DataRepo.Sample",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="animals",
    )

and now my test passes.

I also added a catch of the exception when the deleted record's foreign key was attempted to be accessed.

I'm still in the debug process, so I might have more to resolve, but I was quite pleased to have figured out the contention.

Now, I'm left wondering why the problem presented as a RestrictedError that referenced links to that sample record from MSRun records that are in no way related to that sample and/or animal (the relationships are hierarchical: Animal->Sample->MSRun).

Regardless, it seem that triggering autoupdates of related records can run afoul when multiple different model records have conflicting on_delete behaviors.

Back to Top