Flask 数据库orm踩坑_sa_instance_state

首先了解下
为了实现这个功能,SQLAlchemy 提供了 backref 和 back_populates 两个参数。

两个参数的效果完全一致,区别在于,backref 只需要在 Parent 类中声明 children,Child.parent 会被动态创建。

而 back_populates 必须在两个类中显式地使用 back_populates,更显繁琐
日常数据库查询中一对多和多对多可以满足日常所有查询,一对多反转可以多对一也可以多对多

from flask import  Flask
from flask_sqlalchemy import  SQLAlchemy
app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']='mysql://root:test123@127.0.0.1:3306/flask_sql_demo'
db=SQLAlchemy(app)
# 一对多的关系
class Role(db.Model):
    __tablename__='roles'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100),unique=True)
    users=db.relationship('User',uselist=False,back_populates='rolde')
class User(db.Model):
    __tablename__='users'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100))
    email=db.Column(db.String(100),nullable=True)
    passwd=db.Column(db.String(100),nullable=True)
    # 表示是外键,需要表明ID的形式来
    role_id=db.Column(db.INTEGER,db.ForeignKey('roles.id'))
    rolde = db.relationship('Role', uselist=False, back_populates='users')

class Parent(db.Model):
    __tablename__ = 'parent'
    id = db.Column(db.Integer, primary_key=True)
    childrens = db.relationship('Child', back_populates='parent')
    def __repr__(self):
        return '%s-%s'%(self.id,self.childrens)

class Child(db.Model):
    __tablename__ = 'child'
    id = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey('parent.id'))
    parent = db.relationship('Parent', back_populates='childrens')

    def __repr__(self):
        return '%s-%s' % (self.id, self.parent_id)

teacher_student=db.Table('teacher_student',db.metadata,
    db.Column('teacher_id',db.INTEGER,db.ForeignKey('teacher.id')),
    db.Column('student_id',db.INTEGER,db.ForeignKey('student.id'))
                      )

class Teacher(db.Model):
    __tablename__='teacher'
    id=db.Column(db.INTEGER,primary_key=True)
    subject=db.Column(db.String(100))
    students=db.relationship("Student",secondary=teacher_student,back_populates='teachers')

class Student(db.Model):
    __tablename__='student'
    id=db.Column(db.INTEGER,primary_key=True)
    sno=db.Column(db.INTEGER,unique=True)
    teachers=db.relationship("Teacher",secondary=teacher_student,back_populates='students')


if __name__=='__main__':
#     # 删除表
    db.drop_all()
#     # 创建表
    db.create_all()
#     # db.session.add(Role(name='admin'))
#     # db.session.commit()
#     app.run()

这个代码的说明

class Role(db.Model):
    __tablename__='roles'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100),unique=True)
    users=db.relationship('User',uselist=False,back_populates='rolde')
class User(db.Model):
    __tablename__='users'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100))
    email=db.Column(db.String(100),nullable=True)
    passwd=db.Column(db.String(100),nullable=True)
    # 表示是外键,需要表明ID的形式来
    role_id=db.Column(db.INTEGER,db.ForeignKey('roles.id'))
    rolde = db.relationship('Role', uselist=False, back_populates='users')

这两个表之间的关系是一对一的,因为使用了uselist=False
执行下面的python语句

r1=Role(**{'name':'管理员'})
db.session.add(r1)
db.session.commit()
u1=User(name='老李',role_id=1)
db.session.add(u1)
db.session.commit()
u1=User(name='老王',role_id=1)
db.session.add(u1)
db.session.commit()

在这里插入图片描述
在执行这个代码:

r1=Role.query.all()
print(r1)
for i in r1:
    print(type(i.users))

在这里插入图片描述
报错了,可以判断是上面的uselist导致
重新删除表在建表,此时表数据为空执行如下语句:

r1=Role(**{'name':'管理员'})
db.session.add(r1)
db.session.commit()
u1=User(name='老李',role_id=1)
db.session.add(u1)
db.session.commit()

在这里插入图片描述
在运行这个代码:

r1=Role.query.all()
print(r1)
for i in r1:
    print(type(i.users),i.users.name)

在这里插入图片描述
没有报错,能得到想要的结果,所以必须满足一比一条件才行,这个uselist只是为了严谨这样写,但部分情况可以不写,这样就不存在这种问题
python脚本里面类模型如果修改成这样:

class Role(db.Model):
    __tablename__='roles'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100),unique=True)
    users=db.relationship('User',back_populates='rolde')
class User(db.Model):
    __tablename__='users'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100))
    email=db.Column(db.String(100),nullable=True)
    passwd=db.Column(db.String(100),nullable=True)
    # 表示是外键,需要表明ID的形式来
    role_id=db.Column(db.INTEGER,db.ForeignKey('roles.id'))
    rolde = db.relationship('Role', back_populates='users')

执行drop_all在create_all,表重新建好

r1=Role(**{'name':'管理员'})
db.session.add(r1)
db.session.commit()
u1=User(name='老李',role_id=1)
db.session.add(u1)
db.session.commit()
u1=User(name='老王',role_id=1)
db.session.add(u1)
db.session.commit()

在这里插入图片描述
下面执行查询迭代

from collections import Iterable
from collections import Iterator
r1=Role.query.all()
print(r1)
for i in r1:
    print(type(i.users))
    print(isinstance(i.users,Iterable))
    print(isinstance(i.users,Iterator))
    print(i.users.__iter__())
    for j in i.users.__iter__():
        print(j,j.name)

在这里插入图片描述
没问题看到了想要的结果
多对多的需要一个关系表,别的和一对一一对多的没啥区别

stu1=Student.query.filter_by(sno=1)
print(stu1,type(stu1))
stu1=Student.query.filter_by(sno=1).all()
print(stu1,type(stu1))
stu2=Student.query.filter_by(sno=2)
print(stu2,type(stu2))
stu2=Student.query.filter_by(sno=2).all()
print(stu1,type(stu2))
stu3=Student.query.filter_by(sno=3)
print(stu3,type(stu3))
stu3=Student.query.filter_by(sno=3).all()
print(stu3,type(stu3))
stu4=Student.query.filter_by(sno=4)
print(stu4,type(stu4))
stu4=Student.query.filter_by(sno=4).all()
print(stu4,type(stu4))

teac1=Teacher.query.filter_by(id=1).first()
teac2=Teacher.query.filter_by(id=2).first()
teac3=Teacher.query.filter_by(id=3).first()
print(type(teac1),type(teac1.students),dir(teac1.students))
print(type(stu1),dir(stu1))
for i in teac1.students:
    print(type(i))
teac1.students.extend(stu2)
teac1.students.extend(stu1)
teac2.students.extend(stu2)
teac2.students.extend(stu4)
teac3.students.extend(stu2)
teac3.students.extend(stu1)
teac3.students.extend(stu3)
teac3.students.extend(stu4)
db.session.add_all([teac1,teac2,teac3])
db.session.commit()

运行结果

SELECT student.id AS student_id, student.sno AS student_sno 
FROM student 
WHERE student.sno = %s <class 'flask_sqlalchemy.BaseQuery'>
[<Student 1>] <class 'list'>
SELECT student.id AS student_id, student.sno AS student_sno 
FROM student 
WHERE student.sno = %s <class 'flask_sqlalchemy.BaseQuery'>
[<Student 1>] <class 'list'>
SELECT student.id AS student_id, student.sno AS student_sno 
FROM student 
WHERE student.sno = %s <class 'flask_sqlalchemy.BaseQuery'>
[<Student 3>] <class 'list'>
SELECT student.id AS student_id, student.sno AS student_sno 
FROM student 
WHERE student.sno = %s <class 'flask_sqlalchemy.BaseQuery'>
[<Student 4>] <class 'list'>
<class '__main__.Teacher'> <class 'sqlalchemy.orm.collections.InstrumentedList'> ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_sa_adapter', '_sa_appender', '_sa_converter', '_sa_instrumented', '_sa_iterator', '_sa_remover', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
<class 'list'> ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
<class '__main__.Student'>
<class '__main__.Student'>

注意里面使用的extend而不是append函数
因为使用append会报错
AttributeError: ‘list’ object has no attribute ‘_sa_instance_state’
关于extend 和append的区别自行查询,这里不做介绍
注意事项:
db.relationship(‘User’,back_populates=‘rolde’)
第一个参数User为model python中的名字,back_populates这个名字两个表要相互对应的上
在这里插入图片描述
图中标红的地方必须保持一致才行不然会报错
如果修改成下面代码:

class Teacher(db.Model):
    __tablename__='teacher'
    id=db.Column(db.INTEGER,primary_key=True)
    subject=db.Column(db.String(100))
    students=db.relationship("Student",secondary=teacher_student,back_populates='teachersdd')

class Student(db.Model):
    __tablename__='student'
    id=db.Column(db.INTEGER,primary_key=True)
    sno=db.Column(db.INTEGER,unique=True)
    teachers=db.relationship("Teacher",secondary=teacher_student,back_populates='studentsdd')

执行

t1=Teacher(subject='语文')
t2=Teacher(subject='数学')
t3=Teacher(subject='英语')
db.session.add_all([t1,t2,t3])
db.session.commit()
s1=Student(sno=1)
s2=Student(sno=2)
s3=Student(sno=3)
s4=Student(sno=4)
db.session.add_all([s1,s2,s3,s4])

在这里插入图片描述
所以必须保持一致才行
如果采用backref,相当与django中的relatename,直接访问这个属性就可以得到外键引用的对象
改写上面代码:

class Role(db.Model):
    __tablename__='roles'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100),unique=True)
    users=db.relationship('User',backref='rolde')
class User(db.Model):
    __tablename__='users'
    id=db.Column(db.INTEGER,primary_key=True)
    name=db.Column(db.String(100))
    email=db.Column(db.String(100),nullable=True)
    passwd=db.Column(db.String(100),nullable=True)
    # 表示是外键,需要表明ID的形式来
    role_id=db.Column(db.INTEGER,db.ForeignKey('roles.id'))
    # rolde = db.relationship('Role', back_populates='users')

执行

r1=Role(**{'name':'管理员'})
db.session.add(r1)
db.session.commit()
u1=User(name='老王',role_id=1)
db.session.add(u1)
db.session.commit()

执行

u1=User.query.filter_by(id=1).first()
print(type(u1.rolde))

在这里插入图片描述

  • 技术无止境
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值