Furbeenator
Furbeenator

Reputation: 8285

Django: How do you delete child class object without deleting parent class object?

I have the following models (I left out def __unicode__(...) for clarity):

class Person(models.Model):
    first_name = models.CharField(max_length=64, null=True, blank=True)
    middle_name = models.CharField(max_length=32, null=True, blank=True)
    last_name = models.CharField(max_length=64, null=True, blank=True)

class MinorResident(Person):
    move_in_date = models.DateField(null=True)
    move_out_date = models.DateField(null=True)
    natural_child = models.NullBooleanField()

class OtherPerson(Person):
    associate_all_homes = models.BooleanField(default=False)

I have the following view method for using a MinorResident object to create an OtherPerson object, like:

def MinorToAdult(request, minor):
    p = Person.objects.get(id=minor.person_ptr_id)
    o = OtherPerson(p.id)
    o.__dict__.update(p.__dict__)
    o.save()
    return True

This all works great, but I still have a record in the minoresident table pointing to the person record with person_ptr_id. I also have a pointer record in the otherperson table with the same person_ptr_id pointing to the same person, and displaying all of the data as it was before the switch, but with an OtherPerson object instead of MinorResident object. So, I want to delete the MinorResident object, without deleting the parent class Person object. I suppose I can do something like:

p = Person.objects.get(id=minor.person_ptr_id)
o = OtherPerson()
o.__dict__.update(p.__dict__)
o.save()
minor.delete()
return True

But I would like to not have a new record in the Person table if I can help it, since it really isn't a new person, just a person whose an adult now. Maybe I can I do something like this? Or is there a better way to handle model transmutation?

p = Person.objects.get(id=minor.person_ptr_id)
o = OtherPerson(p.id)
o.__dict__.update(p.__dict__)
o.save()
minor.person_ptr_id = None
minor.delete()
return True

I looked at SO #3711191: django-deleting-object-keeping-parent, but I was hoping for an improved answer.

Upvotes: 5

Views: 5853

Answers (2)

Simon Charette
Simon Charette

Reputation: 5116

On Django 1.10.4+ you can use the keep_parents option:

minor.delete(keep_parents=True)

Else I suggest you use deletion.Collector with manual collection:

from django.db.models import deletion

collector = deletion.Collector(using=minor._state.db)
collector.add([minor])
collector.delete()

Upvotes: 8

Izz ad-Din Ruhulessin
Izz ad-Din Ruhulessin

Reputation: 6175

Option 1

Explicitly specify your parent_link fields and use an unmanaged model.

class MinorResident(Person):
    person = models.OneToOneField(
        Person,
        parent_link = True,
        primary_key = True,
        db_column = 'person_id'
    )
    move_in_date = models.DateField(null=True)
    move_out_date = models.DateField(null=True)
    natural_child = models.NullBooleanField()


class UnmanagedMinorResident(models.Model):
    person = models.OneToOneField(
        Person,
        primary_key = True,
        db_column = 'person_id'
    )
    move_in_date = models.DateField(null=True)
    move_out_date = models.DateField(null=True)
    natural_child = models.NullBooleanField()

    class Meta:
        managed = False
        db_table = MinorResident._meta.db_table

Now you can call UnmanagedMinorResident.delete() without deleting the parent row.

Option #2

Use a raw SQL query

from django.db import connection

minor = # MinorResident object
c = connection.cursor()
table = MinorResident._meta.db_table
column = MinorResident._meta.pk.column
# In this specific case it is safe to not escape.
sql = "DELETE FROM {0} WHERE {1}={2}".format(table, column, minor.pk)
c.execute(sql)

But you should probably change your data model and use the same table for both adults and minors. The properties you are storing in the MinorResident model do not belong there, they belong on the relationship between the MinorResident and the entity it is moving in/out from/to.

Upvotes: 4

Related Questions