Django中布尔值变更时通过Signal创建模型实例的问题排查
Django Signals 问题排查与修复
问题场景
定义了默认active=False的Initial_Model模型,需求是当用户点击按钮将active改为True时,自动创建ActiveSession实例,但执行后未创建实例且报错。
现有代码
初始模型
class Initial_Model(models.Model): active = models.BooleanField(default=False)
目标模型及信号函数
class ActiveSession(models.Model): start_date = models.DateTimeField(auto_now_add=True, blank=True, null=True) end_date = models.DateTimeField(auto_now_add=True, blank=True, null=True) @receiver(post_save, sender=Initial_Model) def create_session(sender, instance, **kwargs): if Initial_Model.active == True: ActiveSession.objects.create()
视图代码
def change_status(request, id): if request.is_ajax() and request.method == 'POST': initial_model = Initial_Model.objects.get(id=id) if request.POST.get('active') == 'true': initial_model.active = True initial_model.save() context = { 'initial_model': initial_model, 'id': initial_model.id, 'status': 'success', 'active': '' } html = render_to_string('app/name.html', context, request=request) start_session= timezone.now() return JsonResponse({'form': html})
报错信息
Traceback (most recent call last): File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner response = get_response(request) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "C:\Users\User\Desktop\Gym_App\my_gym\views.py", line 36, in change_status initial_model.save() File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\base.py", line 753, in save self.save_base(using=using, force_insert=force_insert, File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\base.py", line 777, in save_base pre_save.send( File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\dispatch\dispatcher.py", line 177, in send return [ File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\dispatch\dispatcher.py", line 178, in <listcomp> (receiver, receiver(signal=self, sender=sender, **named)) File "C:\Users\User\Desktop\Gym_App\my_gym\models.py", line 70, in create_session obj = sender.objects.get(instance.id) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\query.py", line 418, in get clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\query.py", line 942, in filter return self._filter_or_exclude(False, *args, **kwargs) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\query.py", line 962, in _filter_or_exclude clone._filter_or_exclude_inplace(negate, *args, **kwargs) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\query.py", line 969, in _filter_or_exclude_inplace self._query.add_q(Q(*args, **kwargs)) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\sql\query.py", line 1360, in add_q clause, _ = self._add_q(q_object, self.used_aliases) File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\sql\query.py", line 1379, in _add_q child_clause, needed_inner = self.build_filter( File "C:\Users\User\Desktop\Portfolio\venv\lib\site-packages\django\db\models\sql\query.py", line 1257, in build_filter arg, value = filter_expr TypeError: cannot unpack non-iterable int object
错误分析
- 信号函数字段访问错误:
Initial_Model.active访问的是模型类的字段定义,不是当前实例的字段值,应该用instance.active。 - 未判断状态切换场景:现有逻辑只要
active为True就创建实例,会导致每次保存(即使原本就是True)都重复创建ActiveSession。 get()方法调用错误:报错信息中obj = sender.objects.get(instance.id)是错误用法,get()需要传入关键字参数(如get(id=instance.id)),且信号中已经直接提供了instance,完全不需要重新查询。
修复方案
步骤1:修正信号逻辑,仅在状态从False变为True时创建实例
要实现状态切换判断,需要对比实例修改前后的active值。可以通过pre_save信号先保存旧值,再在post_save中判断:
from django.db.models.signals import pre_save, post_save from django.dispatch import receiver # 用pre_save保存旧的active值 @receiver(pre_save, sender=Initial_Model) def save_old_active(sender, instance, **kwargs): if instance.pk: # 实例已存在,保存旧的active值 instance._old_active = sender.objects.get(pk=instance.pk).active else: # 新实例,旧值为默认False instance._old_active = False @receiver(post_save, sender=Initial_Model) def create_session(sender, instance, created, **kwargs): # 仅当状态从False变为True时创建Session if not created and instance.active and not instance._old_active: ActiveSession.objects.create()
步骤2:修复get()方法错误(如果存在)
如果你的信号函数中确实有sender.objects.get(instance.id)这行代码,直接删除即可——信号中已经提供了当前操作的instance,无需重复查询。
步骤3:确保信号正确注册
在应用的apps.py中添加信号注册逻辑,确保Django启动时加载信号:
from django.apps import AppConfig class MyGymConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'my_gym' def ready(self): import my_gym.models.signals # 替换为你的信号所在路径
补充说明
created参数用于排除新创建的实例(新创建时active默认是False,不需要处理)。_old_active是自定义属性,用于存储修改前的active值,确保只在状态切换时触发创建逻辑。
内容的提问来源于stack exchange,提问作者A_K
相关产品推荐
相关产品推荐

