为何getattr会将布尔值转换为字符串?Django模型字段追踪疑问
嘿,我来帮你搞定这个Django模型字段追踪的问题!你的思路是对的——要追踪is_paid字段的变化,核心是记录字段的原始值,再在保存前和当前值对比。不过你当前的实现还有优化空间,我给你整理几种靠谱的方案:
方案一:手动实现(无需第三方库)
你当前在__init__里设置原始值的方式,在新建实例或从数据库加载实例时有效,但如果实例调用refresh_from_db()刷新后,原始值不会同步更新。我们可以把逻辑封装成方法,在合适的时机调用:
from django.db import models from django.utils.six import python_2_unicode_compatible @python_2_unicode_compatible class Invoice(models.Model): # 其他字段... is_paid = models.BooleanField(default=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__tracked_fields = ['is_paid'] # 初始化时记录原始值 self.set_original_values() def set_original_values(self): """批量设置追踪字段的原始值""" for field in self.__tracked_fields: setattr(self, f'__original_{field}', getattr(self, field)) def save(self, *args, **kwargs): # 保存前检查字段变化 for field in self.__tracked_fields: original_val = getattr(self, f'__original_{field}') current_val = getattr(self, field) if original_val != current_val: # 这里写你需要执行的逻辑,比如发通知、记日志 print(f"字段 {field} 从 {original_val} 变为 {current_val}") # 执行保存 super().save(*args, **kwargs) # 保存后重置原始值,确保下次修改时能正确追踪 self.set_original_values() def __str__(self): return f"Invoice #{self.pk}"
关键点说明:
set_original_values方法统一处理原始值的存储,方便复用和维护- 保存后重置原始值,避免后续修改时拿旧的原始值对比
- 兼容新建实例(此时原始值就是默认值)和已存在实例的场景
方案二:用Django信号实现(不修改模型save方法)
如果你不想重写模型的save方法,可以用Django的pre_save信号来监听保存事件:
from django.db.models.signals import pre_save from django.dispatch import receiver @receiver(pre_save, sender=Invoice) def track_invoice_is_paid_change(sender, instance, **kwargs): # 只追踪已存在的实例(排除新建场景) if instance.pk: # 从数据库获取原始实例 original_invoice = sender.objects.get(pk=instance.pk) if original_invoice.is_paid != instance.is_paid: # 处理字段变化逻辑 print(f"is_paid 从 {original_invoice.is_paid} 变为 {instance.is_paid}")
注意:
这种方式每次都会额外查询一次数据库获取原始实例,对性能敏感的场景需要权衡。
方案三:用第三方库简化(最优雅)
推荐使用django-model-utils的FieldTracker,它已经封装好了字段追踪的所有细节,不用自己手动管理原始值:
- 先安装库:
pip install django-model-utils
- 修改模型:
from django.db import models from model_utils import FieldTracker from django.utils.six import python_2_unicode_compatible @python_2_unicode_compatible class Invoice(models.Model): # 其他字段... is_paid = models.BooleanField(default=False) # 指定要追踪的字段 tracker = FieldTracker(fields=['is_paid']) def save(self, *args, **kwargs): if self.tracker.has_changed('is_paid'): original_val = self.tracker.previous('is_paid') current_val = self.is_paid print(f"is_paid 从 {original_val} 变为 {current_val}") super().save(*args, **kwargs)
优势:
- 不用自己处理原始值的存储和更新
- 支持批量追踪多个字段
- 提供
has_changed()、previous()等便捷方法
另外提一句:如果你用的是Django 3.0+,python_2_unicode_compatible装饰器已经不需要了,直接重写__str__方法就行。
内容的提问来源于stack exchange,提问作者Narnik Gamarnik
相关产品推荐
相关产品推荐

