SQLAlchemy关联对象创建报错:'str' object has no attribute '_sa_instance_state'问题排查与解决求助
你的问题根源很明确:你给ShiftStamps的activity关系字段传入了字符串值,而不是SQLAlchemy要求的Activities模型实例。虽然你修正了user字段传入实例的问题,但activity部分还是没处理,所以错误依然存在。
错误原因拆解
看你创建ShiftStamps的代码片段:
shiftstamp = ShiftStamps( user_id=founduser.id, user=founduser, start_time=..., end_time=..., activity=form.activity.data # 这里传的是字符串!比如"admin"或者"calling" )
form.activity.data是你从下拉框获取的字符串(对应Activities表的主键值),但ShiftStamps的activity字段是通过db.relationship定义的关系属性,它要求接收的是Activities类的实例,而不是字符串主键。这就是触发'str' object has no attribute '_sa_instance_state'错误的直接原因。
另外还有个小问题:你计算minutes的代码顺序错了,会导致类型错误,需要调整写法。
修复方案
步骤1:获取Activities实例
在创建ShiftStamps之前,根据表单提交的activity字符串查询对应的Activities实例:
found_activity = Activities.query.filter_by(activity=form.activity.data).first()
步骤2:修正ShiftStamps初始化代码
把activity=form.activity.data替换为activity=found_activity,同时修正minutes的计算逻辑:
shiftstamp = ShiftStamps( user=founduser, # 其实只传user或者user_id其中一个就行,SQLAlchemy会自动关联 start_time=datetime.combine(form.date.data, datetime.strptime(form.start_time.data, '%H:%M:%S').time()), end_time=datetime.combine(form.date.data, datetime.strptime(form.end_time.data, '%H:%M:%S').time()), activity=found_activity ) # 修正minutes计算:先算时间差的总秒数,再转成分钟 shiftstamp.minutes = int((shiftstamp.end_time - shiftstamp.start_time).total_seconds() / 60)
小贴士:其实你不需要同时传
user_id和user,只要传其中一个,SQLAlchemy会自动处理外键和关系的关联,传实例更符合ORM的使用习惯。
完整修改后的路由代码
@shift_route.route('/shift_add', methods=['GET', 'POST']) def shift_add(): form = ShiftStampForm() choiceMath = [(str(u.id), f"{u.first_name} {u.last_name}") for u in Users.query.order_by('first_name')] form.user.choices = choiceMath form.activity.choices = [a.activity for a in Activities.query.order_by('activity')] # 这里可以直接取activity,不用转str if form.validate_on_submit(): founduser = Users.query.filter_by(id=form.user.data).first() # 获取对应的Activities实例 found_activity = Activities.query.filter_by(activity=form.activity.data).first() shiftstamp = ShiftStamps( user=founduser, start_time=datetime.combine(form.date.data, datetime.strptime(form.start_time.data, '%H:%M:%S').time()), end_time=datetime.combine(form.date.data, datetime.strptime(form.end_time.data, '%H:%M:%S').time()), activity=found_activity ) # 正确计算分钟数 time_diff = shiftstamp.end_time - shiftstamp.start_time shiftstamp.minutes = int(time_diff.total_seconds() / 60) comparedShift = ShiftStamps.query.filter_by(user_id=shiftstamp.user_id, start_time=shiftstamp.start_time).first() if comparedShift: flash("This Shift Already Exists.", category='error') else: db.session.add(shiftstamp) db.session.commit() # 清空表单数据 form.user.data = '' form.date.data = '' form.start_time.data = '' form.end_time.data = '' form.activity.data = '' flash("Shift Added Successfully!", category='success') return redirect(url_for('views.home')) return render_template('/shift/shift_add.html', form=form)
额外说明
如果你不想每次都查询实例,也可以直接传入activity_id而不是activity,这样SQLAlchemy会自动通过外键关联关系,写法更简洁:
shiftstamp = ShiftStamps( user=founduser, start_time=..., end_time=..., activity_id=form.activity.data # 直接传字符串主键给外键字段 )
这种方式和传实例的效果是一样的,看你个人习惯选择即可。
内容的提问来源于stack exchange,提问作者jadside

