Как преобразовывать между моделями в django-polymorphic?
Я использую django-polymorphic
для моделирования простых отношений следующим образом:
from polymorphic.models import PolymorphicModel
class Base(PolymorphicModel):
pass
class DescendantA(Base):
pass
class DescendantB(Base):
pass
Я хотел бы найти способ преобразования экземпляра DescendantA
в экземпляр DescendantB
, сохраняя данные его Base
части (в частности, первичный ключ).
Установка соответствующих атрибутов __class__
и polymorphic_ctype
на экземпляре (см. этот ответ) в основном работает, но предсказуемо приводит к несогласованным данным: Строка базы данных для DescendantA
не удаляется и продолжает указывать на строку Base
.
Есть ли чистый(ый) способ достичь этого преобразования? Я склоняюсь к тому, чтобы просто создать новый экземпляр и скопировать исходные данные, но я надеюсь, что есть лучший способ.
Так как я понимаю под "преобразованием" вы действительно хотите сохранить данные таким образом в своей базе данных. Тогда вам придется создать и сохранить новый экземпляр DescendantB
, скопировав данные из DescendantA
, а затем удалить первый, поскольку это две разные таблицы. Если бы это были прокси-модели, они бы использовали одну и ту же таблицу, и тогда все, что вам нужно было бы сделать, это изменить polymorphic_ctype
.
Можно попробовать скопировать атрибуты из экземпляра DescendantA
, используя __dict__
, но это, конечно, зависит от того, что обе модели всегда имеют одинаковые поля:
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()
Я нашел способ, который работает довольно хорошо, но использует частные части API Django и поэтому может сломаться в будущем. Я написал несколько тестов, которые предупредят меня, если это произойдет.
Суть моего решения такова
- создание нового дочернего экземпляра с помощью
instance.save_base(raw=True)
, который не сохраняет родительскую модель (и является не частью публичного API). - удаление бывшего дочернего экземпляра с помощью
instance.delete(keep_parents=True)
, которое не удаляет родительскую модель. - установка правильного
polymorphic_ctype
атрибута.
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()
Связанные ответы: