Как вручную сгенерировать фикстуры для полиморфных моделей Django?

У меня есть несколько Django Polymorphic моделей:

import uuid
from django.db import models
from polymorphic.models import PolymorphicModel

class Fruit(PolymorphicModel):
    class Meta:
        abstract = True

class Apple(Fruit):
    variety=models.CharField(max_length=30,primary_key=True)

class Grape(Fruit):
    id=models.UUIDField(primary_key=True, default=uuid.uuid4)
    colour=models.CharField(max_length=30)

Тогда я могу создать несколько приспособлений:

[
    {"model": "test_polymorphic.apple", "pk": "bramley", "fields": {}},
    {"model": "test_polymorphic.apple", "pk": "granny smith", "fields": {}},
    {"model": "test_polymorphic.grape", "pk": "00000000-0000-4000-8000-000000000000", "fields": { "colour": "red"} },
    {"model": "test_polymorphic.grape", "pk": "00000000-0000-4000-8000-000000000001", "fields": { "colour": "green"} }
]

и с помощью python -m django loaddata fixture_name загрузите его в базу данных, что "кажется" успешным.

Тогда, если я использую:

from test_polymorphic import models
models.Apple.objects.all()

При этом возникает ошибка:

PolymorphicTypeUndefined: The model Apple#bramley does not have a `polymorphic_ctype_id` value defined.
If you created models outside polymorphic, e.g. through an import or migration, make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass.

Использование loaddata обходит метод save() модели, поэтому типы содержимого по умолчанию не устанавливаются в моделях.

Я смог найти соответствующие типы содержимого, используя:

from django.contrib.contenttypes.models import ContentType
for model_name in ("apple", "grape"):
    print(
      model_name,
      ContentType.objects.get(app_label="test_polymorphic", model=model_name).id,
    )

Что выводит:

apple: 3
grape: 2

и затем измените крепление на:

[
  {
    "model": "test_polymorphic.apple",
    "pk": "bramley",
    "fields": { "polymorphic_ctype_id": 3 }
  },
  {
    "model": "test_polymorphic.apple",
    "pk": "granny smith",
    "fields": { "polymorphic_ctype_id": 3 }
  },
  {
    "model": "test_polymorphic.grape",
    "pk": "00000000-0000-4000-8000-000000000000",
    "fields": { "colour": "red", "polymorphic_ctype_id": 2 }
  },
  {
    "model": "test_polymorphic.grape",
    "pk": "00000000-0000-4000-8000-000000000001",
    "fields": { "colour": "green", "polymorphic_ctype_id": 2 }
  }
]

Включение полиморфного типа содержимого в поля, после чего приспособление работает. Однако это значение фактически является магическим числом, и я не уверен, что оно не изменится, если:

  • Приложение установлено рядом с другими Django-приложениями, которые уже генерируют типы контента; или
  • Если в приложение добавлены другие модели;
  • и т.д.

Вопрос:

Могу ли я полагаться на то, что polymorphic_ctype_id будет фиксированным значением, и если нет, то как мне установить полиморфный тип содержимого по умолчанию в модели при загрузке приспособлений (если использование loaddata обходит метод save в модели и я не могу быть уверен в том, какими будут значения id типов содержимого)?

В документации Django Polymorphic - "Migrating existing models to polymorphic" показано, как перенести неполиморфные модели в полиморфные:

Для заполнения значения модели можно использовать следующий код на языке Python:

from django.contrib.contenttypes.models import ContentType
from myapp.models import MyModel

new_ct = ContentType.objects.get_for_model(MyModel)
MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)

Они приводят пример включения этой функции в миграцию, но ее также можно запустить вручную после использования loaddata для загрузки приспособления, в котором значения не установлены.

Если необходимо обновить несколько моделей, то можно написать вспомогательную функцию:

from django.contrib.contenttypes.models import ContentType

def set_polymorphic_ctype(model):
     app_label = model._meta.app_label
     name = model._meta.model_name
     ctype = ContentType.objects.get(app_label=app_label, model=name)
     return model.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=ctype)

и вызовите его для каждой из моделей:

set_polymorphic_ctype(models.Apple)
set_polymorphic_ctype(models.Grape)

Если вы загружаете приспособления в TestCase, то я не нашел метода обратного вызова, который выполнялся бы после загрузки приспособлений, но вы можете вызвать вспомогательную функцию во время настройки теста.

class FruitTestCase(TestCase):

    fixtures = [ "fixture_name" ]

    def setUp(self):
        set_polymorphic_ctype(models.Apple)
        set_polymorphic_ctype(models.Grape)

    def test_something(self):
        ...
Вернуться на верх