You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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'

问题原因与解决方案

核心原因

  1. Django实例的数据库刷新机制:post_save信号触发时,Django会将刚保存的实例从数据库重新加载,而__original_instance是内存临时属性,不会被持久化到数据库,刷新后的实例会丢失该属性,恢复为类定义的默认值None。
  2. 信号打印的误导性:post_save中打印instance.__original_instance时,可能实例还未完成数据库刷新,但调用get_revision_history_message时,实例已完成刷新,属性已丢失。
  3. 方法调用参数错误:post_save中调用get_revision_history_message时传入了created=True,但报错显示走到了word更新分支,说明实际调用时created可能为False或updated_field被设为word,此时__original_instance已变为None。

解决方案

  1. 存储必要字段而非完整实例:在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)
    
  2. 在方法内直接查询历史数据:不再依赖临时属性,在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)
    
  3. 修正方法调用参数:根据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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 23:14:54