Django模型方法无法访问信号赋值的__original_instance属性
问题描述
定义了Django模型SrdRevisionWording,其中__original_instance是自定义非数据库属性,用于存储历史实例。在pre_save信号中为该属性赋值后,信号内打印显示已成功获取实例对象;post_save信号中也能正常打印该属性,但调用模型的get_revision_history_message方法时,出现AttributeError,提示__original_instance为None,无法访问其word属性。
模型代码
class SrdRevisionWording(ModelDiff): srd_revision = models.ForeignKey(SrdRevision, on_delete=models.CASCADE) word = models.CharField(max_length=50, null=False) designation = models.CharField(max_length=200, null=False) is_list = models.BooleanField(null=False, default=False) parent_element = models.IntegerField(null=True) # 上一SRD版本中Wording元素的ID __original_instance = None with_signal = True def get_revision_history_message(self, created=False, deleted=False, updated_field=None, check_history=False): if created: return 'Word "{}" added'.format(self.word) if deleted: return 'Word "{}" deleted'.format(self.word) match updated_field: case 'word': return 'Word {} changed to {}'.format(self.__original_instance.word, self.word) case 'designation' | 'is_list': return 'Word {} modified'.format(self.word)
pre_save信号代码
@receiver(pre_save, sender=SrdRevisionDocument) @receiver(pre_save, sender=SrdRevisionWording) def model_pre_save_handler(sender, instance, **kwargs): print('pre_save') if instance.srd_revision.revision != 'A': try: previous_srd_revision_state = sender.objects.get(pk=instance.parent_element) except sender.DoesNotExist: instance.__original_instance = None else: instance.__original_instance = previous_srd_revision_state print(instance.__original_instance)
post_save信号代码
@receiver(post_save, sender=SrdRevisionDocument) @receiver(post_save, sender=SrdRevisionWording) def handle_revision_history_element_changed(sender, instance, **kwargs): print('signal') print(instance.__original_instance) if instance.with_signal is not False: message = instance.get_revision_history_message(created=True, check_history=True)
报错信息
return 'Word {} changed to {}'.format(self.__original_instance.word, self.word) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'word'
问题原因与解决方案
核心原因
- Django实例的数据库刷新机制:
post_save信号触发时,Django会将刚保存的实例从数据库重新加载,而__original_instance是内存临时属性,不会被持久化到数据库,刷新后的实例会丢失该属性,恢复为类定义的默认值None。 - 信号打印的误导性:
post_save中打印instance.__original_instance时,可能实例还未完成数据库刷新,但调用get_revision_history_message时,实例已完成刷新,属性已丢失。 - 方法调用参数错误:
post_save中调用get_revision_history_message时传入了created=True,但报错显示走到了word更新分支,说明实际调用时created可能为False或updated_field被设为word,此时__original_instance已变为None。
解决方案
存储必要字段而非完整实例:在
pre_save中只存储需要的历史字段值,避免依赖完整实例:@receiver(pre_save, sender=SrdRevisionDocument) @receiver(pre_save, sender=SrdRevisionWording) def model_pre_save_handler(sender, instance, **kwargs): print('pre_save') if instance.srd_revision.revision != 'A': try: previous_srd_revision_state = sender.objects.get(pk=instance.parent_element) instance._original_word = previous_srd_revision_state.word except sender.DoesNotExist: instance._original_word = None print(instance._original_word)对应修改
get_revision_history_message:case 'word': return 'Word {} changed to {}'.format(self._original_word, self.word)在方法内直接查询历史数据:不再依赖临时属性,在
get_revision_history_message内部查询历史实例:def get_revision_history_message(self, created=False, deleted=False, updated_field=None, check_history=False): if created: return 'Word "{}" added'.format(self.word) if deleted: return 'Word "{}" deleted'.format(self.word) original_word = self.word if updated_field == 'word' and self.parent_element is not None: try: original_instance = self.__class__.objects.get(pk=self.parent_element) original_word = original_instance.word except self.__class__.DoesNotExist: pass match updated_field: case 'word': return 'Word {} changed to {}'.format(original_word, self.word) case 'designation' | 'is_list': return 'Word {} modified'.format(self.word)修正方法调用参数:根据
post_save的kwargs传递正确参数:def handle_revision_history_element_changed(sender, instance, **kwargs): print('signal') if instance.with_signal is not False: message = instance.get_revision_history_message( created=kwargs.get('created', False), updated_field='word' # 根据实际业务逻辑传递正确字段 )
内容的提问来源于stack exchange,提问作者Atro_TLT
相关产品推荐
相关产品推荐

