python-3.xdjango-modelsdjango-3.0django-contenttypes

Django Python 3.x - OneToOneField override delete() with ContentTypes


Scenario: there is different service types, i.e. clothes_washing, room_cleaning and room_maintenance. Each one of these services have common fields like service_date for example.

Upon scenario I have model for each service and a Service model with common fields. the relation between Service model and each service model is OneToOneField.

I've tried to override the delete() function in Service model following this answer and It works for me but for only one service (like wheel in self.wheel.delete()). but if I want to delete upon the service type? how to achieve that approach?

my models.py:

class ClothesWashing(models.Model):
    # special fields for clothes_washing
    service = models.OneToOneField(Service, on_delete=models.DO_NOTHING, null=True)

class RoomCleaning(models.Model):
    # special fields for room_cleaning 
    service = models.OneToOneField(Service, on_delete=models.DO_NOTHING, null=True)

class Service(models.Model):
    # common fields for all services

    def delete(self, *args, **kwargs):
        #here I wanna "roomcleaning" attribute to be dynamic upon content type
        self.roomcleaning.delete()
        return super(self.__class__, self).delete(*args, **kwargs)


Solution

  • You can set the on_delete parameters to CASCADE:

    class ClothesWashing(models.Model):
        # special fields for clothes_washing
        service = models.OneToOneField(Service, on_delete=models.CASCADE, null=True)
    
    class RoomCleaning(models.Model):
        # special fields for room_cleaning 
        service = models.OneToOneField(Service, on_delete=models.CASCADE, null=True)

    The on_delete=… parameter [Django-doc] specifies what should happen when the item to which it refers is removed. So if Service is removed, and there is a ClothesWashing model that refers to it, then you can specify what to do.

    By using CASCADE [Django-doc], you will remove the related ClothesWashing object as well, or as specified in the documentation:

    Cascade deletes. Django emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey.

    It is better to implement it with this triggers, since methods like .delete() are not always called by the ORM when deleting in bulk. So Service.objects.all().delete() will delete all services, but will never call the .delete() method of your Service. By defining the triggers, you specify to Django what should happen with items that relate to it.

    In this specific case, you perhaps might want to work with model inheritance [Django-doc]. Django can implement some boilerplate logic itself (like OneToOneFields to the parent model, etc.).

    EDIT: If you want to delete the service if the given ClothesWashing, RoomCleaning, etc. are removed, you can override the .delete() method to delete that one too, you can for example make an abstract base class with:

    class ServiceBase(models.Model):
        # special fields for clothes_washing
        service = models.OneToOneField(Service, on_delete=models.CASCADE, null=True)
    
        def delete(self, *args, **kwargs):
            service = self.service
            super().delete(*args, **kwargs)
            self.service.delete()
    
        class Meta:
            abstract = True
    
    class ClothesWashing(ServiceBase):
        # …
        pass
    
    class RoomCleaning(ServiceBase):
        # …
        pass

    But likely if you use the ORM eventually some objects will not be removed, because of said ways to circumvent this.