I'm using django-polymorphic
to model a simple relationship like this:
from django.db import models
from polymorphic.models import PolymorphicModel
class Base(PolymorphicModel):
attribute_base = models.BooleanField()
class DescendantA(Base):
attribute_a = models.BooleanField()
class DescendantB(Base):
attribute_b = models.BooleanField()
I would like to find a way to convert an instance of DescendantA
to an instance of DescendantB
while keeping the data of its Base
part (specifically the primary key). In my scenario, related models exist pointing to the instance. I would like to keep these relationships (and the instance's primary key) intact.
Setting the appropriate __class__
and polymorphic_ctype
attributes on the instance (see this answer) works mostly but predictably results in inconsistent data: The database row for DescendantA
will not be deleted and continues pointing to the Base
row.
Is there a clean(er) way to achieve this conversion? I'm inclined to just create a new instance and copy the original data but I hope there's a better way.
Related questions:
CodePudding user response:
So for what I understand by "converting" you really want to persist data that way in your database. Then you do have to create and save a new instance of DescendantB
copying the data over from your DescendantA
and then deleting the former as they are two different tables. If they were proxy models though they would share the same table and then all you would have to do would be changing the polymorphic_ctype
.
What you could try to do is copying over the attributes from the DescendantA
instance using __dict__
but that relies of course that both models always share the same fields:
from copy import deepcopy
descendant_a = DescendantA.objects.first() # the one you are copying over
descendant_b_kwargs = deepcopy(descendant_a.__dict__) # creates a dict copying the attributes over from descendant_a
descendant_b_kwargs.pop('_state')
descendant_b_kwargs.pop('base_ptr_id') # remove also the pointer to the parent
descendant_b = DescendantB.objects.create(**descendant_b_kwargs)
descendant_b.delete()
CodePudding user response:
I found a way that works pretty well but uses private parts of Django's API and so might break in the future. I wrote some tests that will alert me if this happens.
The gist of my solution is
- the creation of a new child instance using
instance.save_base(raw=True)
which won't save the parent model (and is not part of the public API). - the deletion of the former child instance using
instance.delete(keep_parents=True)
which won't delete the parent model. - setting the correct
polymorphic_ctype
attribute.
from django.contrib.contenttypes.models import ContentType
# Setup
a = DescendantA.objects.create()
# Create new child instance
b = DescendantB(base_ptr=a)
b.save_base(raw=True)
# Delete the other child instance
a.delete(keep_parents=True)
# Change class and point to correct content type
a.__class__ = DescendantB
a.polymorphic_ctype = ContentType.objects.get_for_model(DescendantB)
a.save()
Related answers: