Мне нужно реализовать многоаспектный тип наследования от UML в Django ORM.У меня есть Contract
тип данных, который в зависимости от типа клиента (постоянного или бизнес-клиента) можно классифицировать как RegularContract
или BusinessContract
.Также контракт может иметь дату истечения срока действия или быть недействительным (не указывается, как долго он будет действителен), поэтому он также может иметь тип ExpiringContract
или NonExpiringContract
.Вот как выглядит концептуальная диаграмма:
И вот как я реализовал это: ![enter image description here](https://i.stack.imgur.com/SukrU.png)
models.py код:
class Contract(models.Model):
approval_date = models.DateTimeField(null=False)
def __getattr__(self, item):
if self.expiringcontract:
return getattr(self.expiringcontract, item)
elif self.nonexpiringcontract:
return getattr(self.nonexpiringcontract, item)
class ContractExpirationExtension(models.Model):
base = models.OneToOneField("website.Contract",
on_delete=models.CASCADE)
class Meta:
abstract = True
class ExpiringContract(ContractExpirationExtension):
termination_date = models.DateTimeField()
@property
def duration(self):
return self.termination_date - self.base.approval_date
class NonExpiringContract(ContractExpirationExtension):
@property
def duration(self):
return timedelta(days=100)
class ContractTypeExtension(models.Model):
base = models.OneToOneField("website.Contract", on_delete=models.CASCADE)
termination_delay = models.PositiveSmallIntegerField(default=30)
class Meta:
abstract = True
@classmethod
def create(cls, approval_date, contract_expiration_type, termination_delay, **kwargs):
type_extension = cls(termination_delay=termination_delay)
base = Contract(approval_date=approval_date)
expiration_type = contract_expiration_type(**kwargs)
expiration_type.base = base
type_extension.base = base
if contract_expiration_type.__name__ == ExpiringContract.__name__:
type_extension.base.expiringcontract = expiration_type
elif contract_expiration_type.__name__ == NonExpiringContract.__name__:
type_extension.base.nonexpiringcontract = expiration_type
return type_extension
def __getattr__(self, item):
if self.base:
return getattr(self.base,item)
class RegularContract(ContractTypeExtension):
termination_delay = models.PositiveSmallIntegerField(validators=[validate_term_delay_regular], blank=False)
class BusinessContract(ContractTypeExtension):
termination_delay = models.PositiveSmallIntegerField(validators=[validate_term_delay_business], blank=False)
Когда нам нужно создать новый экземпляр модели контракта, мы используем метод create()
из классов, которые наследуют ContractTypeExtension
абстрактный класс.В create()
методе я создаю Contract
базовый экземпляр и соответствующий истекающий или не истекающий экземпляр контракта на основе аргумента объекта класса, который я передаю create()
методу:
@classmethod
def create(cls, approval_date, contract_expiration_type, termination_delay, **kwargs):
type_extension = cls(termination_delay=termination_delay)
base = Contract(approval_date=approval_date)
expiration_type = contract_expiration_type(**kwargs)
expiration_type.base = base
type_extension.base = base
if contract_expiration_type.__name__ == ExpiringContract.__name__:
type_extension.base.expiringcontract = expiration_type
elif contract_expiration_type.__name__ == NonExpiringContract.__name__:
type_extension.base.nonexpiringcontract = expiration_type
return type_extension
Потому что мой экземпляр обычного или бизнес-объектаКонтракт содержит в себе другие экземпляры модели. Я не могу сохранить его без предварительного сохранения экземпляра base
и expiration_type
, поэтому я решил создать сигнал pre_save
, который будет делать именно это:
сигналов.py :
from django.db.models.signals import pre_save, pre_delete from django.dispatch import receiver
from .models import RegularContract, BusinessContract
@receiver(pre_save, sender=RegularContract)
@receiver(pre_save, sender=BusinessContract)
def pre_save_contract(sender, instance, *args,**kwargs):
print("Pre_save")
if not instance.id:
instance.base.save()
try:
instance.base.expiringcontract.save()
except (TypeError, ValueError):
instance.base.nonexpiringcontract.save()
Я зарегистрировал свой файл сигналов в __init__
приложения и в apps.py
config:
apps.py :
from django.apps import AppConfig
class WebsiteConfig(AppConfig):
name = 'website'
def ready(self):
import website.signals
веб-сайт .__ init __. Py :
default_app_config = 'website.apps.WebsiteConfig'
Чтобы проверить мой код, я написал простые тестовые случаи:
class BusinessContractTestCase(TestCase):
def setUp(self):
pass
def test_exprirating_creation(self):
approval_date = datetime.today()
termination_delay = 30
termination_date = approval_date+timedelta(days=720)
contract = BusinessContract.create(approval_date=approval_date, contract_expiration_type=ExpiringContract,
termination_delay=termination_delay,
termination_date=termination_date)
contract.save()
self.assertEqual(contract.termination_date.date(), ExpiringContract.objects.first().termination_date.date())
class RegularContractTestCase(TestCase):
def test_exprirating_creation(self):
approval_date = datetime.today()
termination_delay = 30
termination_date = approval_date + timedelta(days=720)
contract = RegularContract.create(approval_date=approval_date,
contract_expiration_type=ExpiringContract,
termination_delay=termination_delay,
termination_date=termination_date)
contract.save()
self.assertEqual(contract.termination_date.date(),
ExpiringContract.objects.first().termination_date.date())
Но при попытке запустить эти тесты они терпят неудачу, и я получаю эту ошибку:
Error
Traceback (most recent call last):
File "/home/ubuntu/workspace/webapp/website/tests.py", line 21, in test_exprirating_creation
contract.save()
File "/home/ubuntu/workspace/venv/lib/python3.5/site-packages/django/db/models/base.py", line 685, in save
"unsaved related object '%s'." % field.name
ValueError: save() prohibited to prevent data loss due to unsaved related object 'base'.
Так почему в моем коде не запускается сигнал pre_save
?