Как преобразовывать между моделями в 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()

Связанные ответы:

Вернуться на верх