Python Can';t使用pre_save信号在django模型中保存相关对象
我必须用Django ORM实现。我有Python Can';t使用pre_save信号在django模型中保存相关对象,python,django,python-3.x,Python,Django,Python 3.x,我必须用Django ORM实现。我有合同数据类型,根据客户类型(普通客户或业务客户)可分为常规合同或业务合同。此外,合同可以有到期日期,也可以不到期(未指定有效期),因此也可以是ExpiringContract或NonExpiringContract类型。这是概念图的外观: 我就是这样实现的: 型号.py代码: class Contract(models.Model): approval_date = models.DateTimeField(null=False) def
合同
数据类型,根据客户类型(普通客户或业务客户)可分为常规合同
或业务合同
。此外,合同可以有到期日期,也可以不到期(未指定有效期),因此也可以是ExpiringContract
或NonExpiringContract
类型。这是概念图的外观:
我就是这样实现的:
型号.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)
当我们需要创建新的契约模型实例时,我们使用继承了ContractTypeExtension
抽象类的类的create()
方法。在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\u type
实例,我无法保存它,因此我决定创建pre\u 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()
from django.apps import AppConfig
class WebsiteConfig(AppConfig):
name = 'website'
def ready(self):
import website.signals
default_app_config = 'website.apps.WebsiteConfig'
我在app的\uuuu init\uuuu
和apps.py
config中注册了我的信号文件:
apps.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()
from django.apps import AppConfig
class WebsiteConfig(AppConfig):
name = 'website'
def ready(self):
import website.signals
default_app_config = 'website.apps.WebsiteConfig'
网站。\uuuu init\uuuu.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()
from django.apps import AppConfig
class WebsiteConfig(AppConfig):
name = 'website'
def ready(self):
import website.signals
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
信号呢 经过短暂的调试,我已经理解了我的问题(感谢您在pre_save
中指出了这个细节),这就是我解决问题的方法。我稍微修改了create()
方法。我没有将base
直接分配给新创建的实例,也没有将expiration\u type
分配给base,而是将它们保存到临时变量中,以便以后在我的信号方法中使用:
@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
inpre_save
signal中,我分别将base从临时变量保存到实例的base,并将其分配到实例的base,将我的到期/非到期合同类型实例从临时变量分配到base并保存:
@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()
这可能不是最好的解决方案,但至少是可行的。在pre_save
级别,对象还没有主键,因此这就有点“鸡和蛋”的问题:您希望保存一个链接到尚未存储在数据库中的对象的对象。通常在这种情况下,最好执行post\u save
。