sebap123
sebap123

Reputation: 2685

Error when saving model for the second time in Django

I have following models structure:

class Parent(models.Model):
     fieldA = models.TextField()
     fieldB = models.TextField()

class Child(Parent):
     fieldC = models.CharField()

I noticed some unexpected behavior in following code snippet:

child = Child(fieldA = 'Text fieldA', fieldB = 'Text fieldB', fieldC = 'Text fieldC')
child.full_clean()
child.save()

self.assertEqual(Child.objects.count(), 1)
child.delete()
self.assertEqual(Child.objects.count(), 0)

child.full_clean()
child.save()

Not getting into why I add the second child.save(), assertions are passed, but when I want to save it with this second error it is failing with ValueError:

ValueError: save() prohibited to prevent data loss due to unsaved related object 'parent_ptr'

At the same time I don't see any such error with following code:

parent = Parent(fieldA = 'Text fieldA', fieldB = 'Text fieldB')
parent.full_clean()
parent.save()

self.assertEqual(Parent.objects.count(), 1)
parent.delete()
self.assertEqual(Parent.objects.count(), 0)

parent.full_clean()
parent.save()

Why is that happening? Is someone able to tell me how I am supposed to fix the first snippet?

Upvotes: 0

Views: 98

Answers (1)

mattbasta
mattbasta

Reputation: 13709

What's happening is that you're taking advantage of a feature called "multi-table inheritance".

https://docs.djangoproject.com/en/3.0/topics/db/models/#multi-table-inheritance

What this means is that instead of Child representing a table with three fields, it only has two fields: fieldC and a foreign key (OneToOneField) to a record in Parent.

When you delete the Child, it deletes both the Child and Parent rows. When you try to save the instance of Child again, the value of the parent_ptr OneToOneField still contains the ID of the old Parent row, which points to something that doesn't exist anymore.

You probably want to use abstract base classes instead:

https://docs.djangoproject.com/en/3.0/topics/db/models/#abstract-base-classes

class Base(models.Model):
     fieldA = models.TextField()
     fieldB = models.TextField()
     class Meta:
        abstract = True


class Parent(Base):
    pass

class Child(Base):
     fieldC = models.CharField()

Here, there is no link between Parent and Child: the table that Child represents has three fields, and the table that Parent represents has two fields.

Upvotes: 2

Related Questions