Deleting auto created model which is under PROTECTED ForeignKey
Asked Answered
K

3

7
class Basket:
    name = models.CharField(max_length=50, blank=True, null=True)

class Apple:
    name = models.CharField(max_length=50, blank=True, null=True)
    basket = models.ForeignKey(Basket, on_delete=models.PROTECT)

...

myapple = new Apple(name="my")
myapple.save()

...

auto_created_basket = myapple.basket
myapple.basket = existing_basket
auto_created_basket.delete()

I try to swap out the auto_created_basket to another one, but I get an error when I try to delete it.

"Cannot delete some instances of model 'Basket' because they are referenced through a protected foreign key: 'Apple.basket'", [<Apple: My apple>])

Kook answered 11/8, 2015 at 18:57 Comment(2)
This is because the foreign key in apple references something in basket so, if you delete basket then that variable in apple will be confused.Host
first: myapple = new Apple(name="my") will throw a syntax error. Remove new. Second: you should specify in which basket the Apple should be inserted. Apple(name='my', basket=b) where b is an actual basket instance.Thumbstall
K
2

I hate to answer my question, but my example was too over simplified. The answers are very good points.

In the real product there are post_save signals involved, responsible for the creation of the auto_created_basket for example.

The problem is that when I say myapple.basket = existing_basket Django's layer is fine, but the DB is still holding the reference to older relation. The solution in my case was to move the auto_created_basket.delete() after I save myapple once more.

Kook answered 12/8, 2015 at 4:51 Comment(0)
T
7

In your Apple model the basket field is a foreing key

basket = models.ForeignKey(Basket, on_delete=models.PROTECT)

whose on_delete attribute value specifically states to protect the apples by preventing the deletion of the basket, i.e. the basket cannot be deleted as long as an apple is still in it.

As the official docs say

When an object referenced by a ForeignKey is deleted, Django by default emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey.

and

the PROTECT parameter prevents deletion of the referenced object by raising ProtectedError

So, the easiest step should be to remove the on_delete parameter and go with the default behaviour

basket = models.ForeignKey(Basket)

However, please have a look at all the possible parameters to a ForeignKey model field and choose the combination that suits the requirements of your application/scenario.

UPDATE:

Recent Django versions require on_delete. Just don't remove it and add the parameter you want (like on_delete=models.CASCADE).

Thumbstall answered 11/8, 2015 at 19:9 Comment(1)
Just a quick comment since this answer ranks pretty high on google and contains a logic error: The on_delete does not specify that the Apple should not be deleted when the Basket is deleted: Instead it specifies that the Basket cannot be deleted as long as an Apple is still in it. Your official documentation quote states that as well, just the explanation above contradicts it :-) - great answer otherwiseKoloski
T
2

You can try moving apples from auto_created_basket into existing_basket before deleting the basket first:

>>> auto_created_basket.apple_set.update(basket=existing_basket)
>>> auto_created_basket.delete()

or

>>> myapple.basket = existing_basket
>>> myapple.save()
>>> auto_created_basket.delete()

Alternatively, if you want to collect apples that are from deleted baskets in a single basket, you can assign a function to on_delete property as follows:

def get_sentinel_basket():
    basket, created = Basket.objects.get_or_create(name='DELETED')
    return basket

--

basket = models.ForeignKey(Basket, on_delete=models.SET(get_sentinel_basket))

so when a basket is deleted, the .basket attribute of apples in that basket will automatically be set to Basket(name='DELETED').

Truant answered 11/8, 2015 at 19:58 Comment(0)
K
2

I hate to answer my question, but my example was too over simplified. The answers are very good points.

In the real product there are post_save signals involved, responsible for the creation of the auto_created_basket for example.

The problem is that when I say myapple.basket = existing_basket Django's layer is fine, but the DB is still holding the reference to older relation. The solution in my case was to move the auto_created_basket.delete() after I save myapple once more.

Kook answered 12/8, 2015 at 4:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.