[Django]-Django: ForeignKey on_delete SET_DEFAULT behavior

7👍

It depends on your business requirements. Should every Item always point to a valid Policy? And what’s the requirement from business point-of-view when a Policy gets deleted? Should the Items pointing to it also be deleted? We don’t know your requirements, so it’s difficult to answer your question. These are your options from a technical perspective:

  • Set on_delete=CASCADE if you want the Item to be deleted when the Policy is deleted
  • Set on_delete=PROTECT if you don’t want to allow any Policy to be deleted if there’s still any Item pointing to it. In that case you’ll have to try: policy.delete(); except ProtectedError: ... in your code to handle that situation.
  • Set on_delete=SET_DEFAULT if you know your default policy will not be deleted (you could override the delete method on Policy to avoid deleting the default policy).
  • Set on_delete=SET_NULL if an Item can have no Policy. That may be valid in certain business scenarios. But in this case you must also have null=True.

1👍

I reproduced your scenario with Django 4.1 and sqlite, and I got the exception you waited for!

But just to provide more customizability, especially in handling exceptions, the following code works with the mentioned environment:

from django.db import models
import django.db


def SET_DEFAULT_AND_PREVENT_DELETE_DEFAULT(collector, field, sub_objs, using):
    """
    ``sub_objs`` is the Queryset of Items that are affected by this delete.
    Hence, none of them should have referenced the default Policy (Otherwise,
    the default Policy is going to be deleted!).
    Also, if we are deleting an Item, there should be a default Policy to
    be set as the new Policy of Item.
    """
    try:
        default_policy = Policy.objects.get(name='default')
    except Policy.DoesNotExist:
        raise django.db.InternalError("You should have default policy before "
                                      "deleting a referenced policy")
    for item in sub_objs:
        if item.policy == default_policy:
            raise django.db.InternalError("You cannot delete default policy "
                                          "when there are items referencing it")

    collector.add_field_update(field, default_policy, sub_objs)


class Policy(models.Model):
    name = models.SlugField(max_length=256, blank=False, unique=True)


class Item(models.Model):
    policy = models.ForeignKey('Policy', on_delete=SET_DEFAULT_AND_PREVENT_DELETE_DEFAULT)

Leave a comment