Не удается сохранить связанные объекты в моделях Django, используя сигнал pre_save - PullRequest
0 голосов
/ 22 мая 2018

Мне нужно реализовать многоаспектный тип наследования от UML в Django ORM.У меня есть Contract тип данных, который в зависимости от типа клиента (постоянного или бизнес-клиента) можно классифицировать как RegularContract или BusinessContract.Также контракт может иметь дату истечения срока действия или быть недействительным (не указывается, как долго он будет действителен), поэтому он также может иметь тип ExpiringContract или NonExpiringContract.Вот как выглядит концептуальная диаграмма: enter image description here

И вот как я реализовал это: enter image description here

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?

1 Ответ

0 голосов
/ 23 мая 2018

После короткой отладки я понял свою проблему (спасибо Willem Van Onsem за указание этой детали с помощью pre_save.) Вот как я решил ее.Я слегка модифицировал create() метод.Вместо того, чтобы назначать base непосредственно для вновь созданного экземпляра и expiration_type для базы, я сохраняю их во временные переменные, которые позже я могу использовать в моем методе сигнала:

@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)
    type_extension.temp_base = base
    if contract_expiration_type.__name__ == ExpiringContract.__name__:
        type_extension.temp_expiringcontract = expiration_type
    elif contract_expiration_type.__name__ == NonExpiringContract.__name__:
        type_extension.base.temp_nonexpiringcontract = expiration_type
    return type_extension

Затем в signals.py in pre_save сигнал Я отдельно сохраняю базу из временной переменной и назначаю базе экземпляра и отдельно назначаю свой экземпляр контракта с истекающим / не истекающим сроком действия из временной переменной в базу и сохраняю его:

@receiver(pre_save, sender=RegularContract)
@receiver(pre_save, sender=BusinessContract)
def pre_save_contract(sender, instance, *args, **kwargs):
    print("Pre_save")
    instance.temp_base.save()
    instance.base = instance.temp_base
    if hasattr(instance,"temp_expiringcontract"):
        instance.base.expiringcontract = instance.temp_expiringcontract
        instance.base.expiringcontract.save()
    else:
        instance.base.nonexpiringcontract =    instance.temp_nonexpiringcontract
        instance.base.nonexpiringcontract.save()

Это, вероятно, не лучшее решение, но по крайней мере это работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...