10

Is there some clever way how to perform delete in this situation?

class Bus(models.Model):  
    wheel = OneToOneField(Wheel)  

class Bike(models.Model):  
    wheel = OneToOneField(Wheel)  
    pedal = OneToOneField(Pedal)

class Car(models.Model):  
    wheel = OneToOneField(Wheel)  

class Wheel(models.Model):  
    somfields

car = Car()    
wheel = Wheel()  
wheel.save()
car.wheel = wheel
car.save()  
car.delete() # I want to delete also wheel (and also all stuff pointing via OneToOneField eg pedal)

Do I need to override delete methods of Car, Bike, Bus models or is there some better way? Other option is to create fields car, bike, bus on Wheel model, but it doesn't make much sense.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
ChRapO
  • 327
  • 5
  • 14

2 Answers2

14

Here is the thing, since Car links to Wheel, it is the dependent model in the relationship. Therefore when you delete a Wheel, it deletes all dependent models (including related Car rows). However when you delete a Car, since Wheel does not depend on Car, it is not removed.

In order to delete parent relations in Django, you can overwrite the Car's delete method:

class Car(models.Model):
    # ...

    def delete(self, *args, **kwargs):
        self.wheel.delete()
        return super(self.__class__, self).delete(*args, **kwargs)

Then when doing:

Car.objects.get(...).delete()

will also delete the Wheel.

Bryan
  • 6,682
  • 2
  • 17
  • 21
Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
  • 3
    Problem with overriding `delete()` method is that it will not be called if you do bulk delete operation as `Car.objects.filter().delete()` – Rohan May 18 '13 at 07:16
  • 3
    @Rohan yes you are right. For bulk delete operations aka. querysets as advised by django docs - https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.delete use django signals `post_delete` decorator. – Srikar Appalaraju May 18 '13 at 16:09
  • @Rohan To be specific an excerpt for the above django docs link - >> The delete() method does a bulk delete and does not call any delete() methods on your models. It does, however, emit the pre_delete and post_delete signals for all deleted objects (including cascaded deletions). – Srikar Appalaraju May 19 '13 at 05:41
11

Cascading delete is already provided by django, through on_delete attribute value CASCADE. It is also available for OneToOneField along with ForeignKey.

ForeignKey.on_delete

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.

However, in your models you are putting OneToOneField in another model, hence you are not seeing the expected behavior.

Change your models to:

class Car(models.Model):  
    somefields

class Wheel(models.Model):  
    somfields
    car = OneToOneField(Car)  

That is put OneToOneField in Wheel model instead of Car. Now when you delete Car model corresponding Wheel will also be deleted.

Problem with overriding delete() method is that it will not be called if you do bulk delete operation as Car.objects.filter().delete()

phoenix
  • 7,988
  • 6
  • 39
  • 45
Rohan
  • 52,392
  • 12
  • 90
  • 87
  • Yes, that's what I wanted to avoid to have car etc fields on the Wheel model. So for me the best solution looks like listen to pre_delete signal, find all OneToOneFields and manually delete them. – ChRapO May 18 '13 at 22:01
  • @ChRapO, How does that matter where its defined? You can have `parent_link=True` if you want reverse link. – Rohan May 19 '13 at 04:54