[Django]-Custom the `on_delete` param function in Django model fields

6👍

The choices for on_delete can be found in django/db/models/deletion.py

For example, models.SET_NULL is implemented as:

def SET_NULL(collector, field, sub_objs, using):
    collector.add_field_update(field, None, sub_objs)

And models.CASCADE (which is slightly more complicated) is implemented as:

def CASCADE(collector, field, sub_objs, using):
    collector.collect(sub_objs, source=field.remote_field.model,
                      source_attr=field.name, nullable=field.null)
    if field.null and not connections[using].features.can_defer_constraint_checks:
        collector.add_field_update(field, None, sub_objs)

So, if you figure out what those arguments are then you should be able to define your own function to pass to the on_delete argument for model fields. collector is most likely an instance of Collector (defined in the same file, not sure what it’s for exactly), field is most likely the model field being deleted, sub_objs is likely instances that relate to the object by that field, and using denotes the database being used.

There are alternatives for custom logic for deletions too, incase overriding the on_delete may be a bit overkill for you.

The post_delete and pre_delete allows you define some custom logic to run before or after an instance is deleted.

from django.db.models.signals import post_save
def delete_ipv4manage(sender, instance, using):
    print('{instance} was deleted'.format(instance=str(instance)))

post_delete.connect(delete_ipv4manage, sender=IPv4Manage)

And lastly you can override the delete() method of the Model/Queryset, however be aware of caveats with bulk deletes using this method:

Overridden model methods are not called on bulk operations

Note that the delete() method for an object is not necessarily called when deleting objects in bulk using a QuerySet or as a result of a cascading delete. To ensure customized delete logic gets executed, you can use pre_delete and/or post_delete signals.

5👍

Another useful solution is to use the models.SET() where you can pass a function (deleted_guest in the example below)

guest = models.ForeignKey(‘Guest’, on_delete=models.SET(deleted_guest))

and the function deleted_guest is

DELETED_GUEST_EMAIL = 'deleted-guest@introtravel.com'
def deleted_guest():
    """ used for setting the guest field of a booking when guest is deleted """
    from intro.models import Guest
    from django.conf import settings
    deleted_guest, created = Guest.objects.get_or_create(
        first_name='Deleted',
        last_name='Guest',
        country=settings.COUNTRIES_FIRST[0],
        email=DELETED_GUEST_EMAIL, 
        gender='M')
    return deleted_guest

You can’t send any parameters and you have to be careful with circular imports. In my case I am just setting a filler record, so the parent model has a predefined guest to represent one that has been deleted. With the new GDPR rules we gotta be able to delete guest information.

1👍

CASCADE and PROTECT etc are in fact functions, so you should be able to inject your own logic there. However, it will take a certain amount of inspection of the code to figure out exactly how to get the effect you’re looking for.

Depending what you want to do it might be relatively easy, for example the PROTECT function just raises an exception:

def PROTECT(collector, field, sub_objs, using):
    raise ProtectedError(
        "Cannot delete some instances of model '%s' because they are "
        "referenced through a protected foreign key: '%s.%s'" % (
            field.remote_field.model.__name__, sub_objs[0].__class__.__name__, field.name
        ),
        sub_objs
    )

However if you want something more complex you’d have to understand what the collector is doing, which is certainly discoverable.

See the source for django.db.models.deletion to get started.

0👍

There is nothing stopping you from adding your own logic. However, you need to consider multiple factors including compatibility with the database that you are using.

For most use cases, the out of the box logic is good enough if your database design is correct. Please check out your available options here https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey.on_delete.

Leave a comment