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

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

错误分析

  1. 信号函数字段访问错误:Initial_Model.active访问的是模型类的字段定义,不是当前实例的字段值,应该用instance.active。
  2. 未判断状态切换场景:现有逻辑只要active为True就创建实例,会导致每次保存(即使原本就是True)都重复创建ActiveSession。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 19:31:02