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.
Большое спасибо за ваш вклад!