Django: Сериализация пользовательского поля модели с отношением

Context

Я разрабатываю систему контроля версий, подобную git, основанную на django, на базе данных postgres.

В моей концепции у меня есть следующие структуры данных:

  • AbstractDataModel
    У меня есть различные типы моделей данных, которые основаны на этом типе AbstractDataModel, который предоставляет необходимые метаданные для управления версионированием. Один экземпляр AbstractDataModel похож на один файл внутри репозитория git.

  • AbstractDataBaseline
    В этой модели данных я хочу хранить базовые линии для всех экземпляров данных. Как один коммит в git. По определенной причине одна версия в этой системе состоит из ProjectBaseline, которая указывает на несколько базовых линий, таких как TypeABaseline, TypeBBaseline, TypeCBaseline, ... и каждая базовая линия указывает на несколько экземпляров данных.

Моя реализация:

# app1/models/abstract_data_model.py
from django.db import models

class AbstractDataModel(models.Model):
    class Meta:
        abstract = True
        constraints = [
            models.UniqueConstraint(fields=['object_id', 'version'], name='...')
        ]

    object_id = models.IntegerField()
    version = models.CharField(max_length=64)

object_id следует использовать для отслеживания экземпляра между несколькими версиями, а version - это просто увеличивающееся число, разделенное точками, чтобы представить упрощенное разветвление.

e.g.

v1 -> v2 -> v3 -> v4 -> v5 -> ...
              \-> v3.1 -> v3.2 -> 3.3 -> ...
# app1/models/abstract_data_baseline.py
from django.db import models

class AbstractDataBaseline(models.Model):
    class Meta:
        abstract = True

    version = models.CharField(max_length=64)

Проект, внутри этого "VCS" может быть структурирован следующим образом:

ProjectBaseline (ProjectBaseline ->AbstractDataBaseline)
--> TypeABaseline (TypeABaseline -> AbstractDataBaseline)
----> TypeA_Item1 (TypeA -> AbstractDataModel)
----> TypeA_Item2 (TypeA -> AbstractDataModel)
----> TypeA_Item3 (TypeA -> AbstractDataModel)
----> TypeA_Item4 (TypeA -> AbstractDataModel)
--> TypeBBaseline (TypeBBaseline -> AbstractDataBaseline)
----> TypeB_Item1 (TypeB -> AbstractDataModel)
----> TypeB_Item2 (TypeB -> AbstractDataModel)
----> TypeB_Item3 (TypeB -> AbstractDataModel)
--> TypeCBaseline (TypeCBaseline -> AbstractDataBaseline)
----> TypeC_Item1 (TypeC -> AbstractDataModel)
----> TypeC_Item2 (TypeC -> AbstractDataModel)
----> TypeC_Item3 (TypeC -> AbstractDataModel)

Выпуск

Теперь я хочу иметь отношения между элементами. Например, TypeB должен иметь атрибут, указывающий на TypeA. Это похоже на ForeignKey, но ForeignKeys указывают на уникальный PrimaryKey. Но в моем случае я хочу управлять ссылками через поле object_id, потому что я не хочу изменять каждую ссылку и обновлять первичные ключи, если целевые объекты получат новую версию в следующем базовом уровне.

Для решения этой проблемы я реализовал пользовательское поле:

# app1/models/data_ref_field.py
...
class DataRefField(models.IntegerField):

    def __init__(self, target, *args, **kwargs):
        self.target_model = target
        super().__init__(*args, **kwargs)

    def contribute_to_class(self, cls, name, private_only=False):
        super(DataRefField, self).contribute_to_class(cls, name)
        setattr(cls, self.name, DataRefFieldForwardProxy(self.name, self.target_model))

        lazy_related_operation(self.contribute_to_related_class, cls, self.target_model)

    def contribute_to_related_class(self, cls, related):
        related_field_name = f"{cls._meta.model_name}_refset"
        setattr(related, related_field_name, DataRefFieldReverseProxy(self, related_field_name))

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs["target"] = self.target_model
        return name, path, args, kwargs

DataRefFieldForwardProxy и DataRefFieldReverseProxy используются для сбора нужных экземпляров данных из базы данных.

Для проверки я вручную изменил свою локальную базу данных и опробовал ее, и, похоже, она работает так, как задумано.

Но теперь мне нужны соответствующие модульные тесты для автоматического тестирования и проверки этой функции, поэтому я написал несколько фикстур для создания данных для тестирования:

- model: app1.typea
  pk: 1
  fields:
    version: 1
    object_id: 2
    ...

- model: app2.typeb
  pk: 1
  fields:
    version: 1
    object_id: 1
    my_ref_typea: 2   # ref to object_id 2 of typea

# baselines as needed ...

Если я запускаю свой тесткейс с этим приспособлением, я получаю следующее исключение:

...
TypeError: Problem installing fixture '...': Tried to update field app2.typeb.my_ref_typea with a model instance <TypeA: TypeA object(1)>. Use a value compatible with DataRefField.

Итак, проблема с (де)сериализацией этого поля DataRefField, я полагаю.

Но как я могу решить эту проблему? Я просто хочу записать целое число object_id=2 в поле db.

Большое спасибо за ваш вклад!

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