• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Flask-sqlalchemy一对多,多对一,一对一,多对多

武飞扬头像
xuxuejian9824
帮助1

这里以作者和文章来演示一对多的关系:一个作者可以有多篇文章,但是一篇文章只能有一个作者。

配置项

首先,配置下数据库config.py

username = 'xxxx'
password = 'xxxx'
database = 'school'
hostname = 'localhost'
port = '3306'
uri = f'mysql pymysql://{username}:{password}@{hostname}:{port}/{database}'

SQLALCHEMY_DATABASE_URI  = uri
SQLALCHEMY_TRACK_MODIFICATIONS = False

在app.py文件中导入配置

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

接着创建模型类,建立python类到数据表的映射:

定义外键

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(100),nullable=False)
class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)
    user_id = db.Column(db.Integer,db.ForeignKey('user.id')) 

由于一个作者可以有多篇文章,所以外键应该设置在Article类中,这样每一篇文章的user_id字段都只会有一个值,因为只对应一个作者。

假设,在User类中存在外键字段article_id,那么一个作者的所有文章都需要存放在这一个字段中,但是外键只能存放单一数据(标量),所以外键的设置总是在“多”的这一侧定义。

定义关系属性

为什么需要关系属性,具体的原因我也不清楚,我想可能是从查询的角度来说,会更方便。

定义关系属性需要使用关系函数。关系属性在关系的出发侧定义,即一对多关系的“一”这一侧。一个作者拥有多篇文章,我们在User模型类中,定义一个叫articles的关系属性,用它可以表示每一个作者所对应的多篇文章。

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(100),nullable=False)

    articles = db.relationship('Article',backref=db.backref('user'))

articles 字段使用db.relationship()关系函数定义为关系属性,这个关系属性将返回多个记录。

  • relationship()函数的第一个参数为关系另一侧的模型名称,是python类的名称,不是数据表名称。
  • 第二个参数表示添加反向引用,会自动在另一侧,也就是Article模型中,建立一个关系属性,这个字段叫user,使用这个字段可以查找到文章所对应的用户。

现在user表中多了一个字段articles,但是它并不是数据库层面的,实际的表中并没有这个字段,可以认为只是一个查询接口。

接着创建数据表并插入数据:

 u1 = User(name='zs')
 a1 = Article(title='西游记',content='西游记是四大名著')
 a1.author = u1
 db.session.add(a1)
 db.session.commit()

创建了一个user表的记录,一个article表的记录,如何让他们建立联系呢?使用关系属性字段:

 a1.author = u1

直接将作者的实例对象u1赋值给文章实例对象a1的author字段,他们就会建立关系,文章表中的user_id字段就会指向那个作者。

article
学新通
user
学新通
现在用户zs拥有两篇文章,尝试使用关系属性查询。

u1 = User.query.filter_by(name='zs').first()
print(u1.articles)

输出user表的articles字段:

[<Article 1>, <Article 2>]

可以发现这个字段里面是两条记录,是article表中的两条记录,因为这两篇文章都是zs的文章,所以通过这个关系属性字段,可以获取到一个作者对应的所有文章。

反过来,关于反向引用,backref=db.backref('user'),它会在Article也建立一个关系属性,这个字段叫做user,可以通过这个字段获取到文章对应的作者。

比如:

#先找到文章
article1 = Article.query.filter_by(title='西游记').first()
#使用反向引用的字段,user,获取到这个文章对应的作者
print(article1.user)

输出:

<User 1>

双向的关系属性

上面在建立关系属性时,只是在User类中使用了关系属性:

articles = db.relationship('Article',backref=db.backref('user'))

这种方式,会隐式的在Article类中也建立一个关系属性user。我们可以使用back_populates参数显式的建立双向的关系属性。

这里仍然以作者author和文章article为例,一个作者可以有多篇文章,一篇文章只能有一个作者,建立显示的双向关系属性。

class User(db.Model):
    __tablename__ = 'author'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(100),nullable=False)

    #关系属性
    articles = db.relationship('Article',back_populates='authors')

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(200),nullable=False)
    content = db.Column(db.Text,nullable=False)
    auth_id = db.Column(db.Integer, db.ForeignKey('author.id'))

    #关系属性
    authors = db.relationship('User',back_populates='articles')
学新通

在一的一侧,User类中,建立了关系属性,articles,获取一个作者对应的多个文章记录。
在多的一侧,Article类中,建立了关系属性,authors,获取每一篇文章对应的一个作者记录。

使用back_populates参数连接对方,参数值需要设置为关系另一侧的关系属性名

使用关系属性添加数据
u1 = User(name='zs')
a1 = Article(title='西游记', content='西游记是四大名著')
a2 = Article(title='红楼梦', content='红楼梦是四大名著')

a1.authors = u1
a2.authors = u1
db.session.add_all([a1,a2])
db.session.commit()

实例化User类对象u1,Article类对象a1,a2。
然后使用Article类的关系属性字段,authors
将User类对象u1赋值给Article类对象的关系属性authors

或者反过来,使用用户的关系属性字段articles添加数据:

def insert():
    u1 = User(name='zs')
    a1 = Article(title='西游记',content='西游记是四大名著')
    a2 = Article(title='水浒传',content='水浒传是四大名著')

    u1.articles = [a1,a2]
    db.session.add(u1)
    db.session.commit()

这里的添加方式是:u1.articles = [a1,a2]

接着查询,使用关系属性字段就可以查询到了,这里只是演示了使用back_populates参数显示的建立双向的关系属性,之前使用的backref可以简化关系的定义,是一种隐式的双向关系的建立。

一对一

这里使用国家和首都演示一对一关系:每一个国家只有一个首都;反过来说,一个城市也只能作为一个国家的首都。

一对一关系实际上是通过建立双向关系的一对多关系的基础上转化而来的。

class Country(db.Model):
    __tablename__ = 'country'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(30),unique=True)
	capital = db.relationship('Capital',back_populates='country')


class Capital(db.Model):
    __tablename__ = 'capital'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30),unique=True)
    #外键在哪一个表中设置应该都可以
    country_id = db.Column(db.Integer,db.ForeignKey('country.id'))

    #设置关系属性
    country = db.relationship('Country',back_populates='capital')
学新通

首先上面是一个双向的关系属性,这时候表的关系和之前的一对多没有区别,现在我们需要给关系属性中加入一个参数uselist=False
”一“的一侧加入,就是在Country类模型中加入

 capital = db.relationship('Capital',back_populates='country',uselist=False)

加入这个参数以后,在使用Country.capital获取记录时,将限制只返回一条记录。
由于Capital中设置了外键country_id ,存储单一数据,一个记录的country_id 只会对应Country中的一个记录。
这时一对多关系就变成了一对一关系。

测试一对一:
首先插入数据,插入一个国家记录,一个城市记录,然后建立关系。

def insert():
    china = Country(name='中国')
    Beijing = Capital(name='北京')

    Beijing.country = china
    db.session.add(Beijing)
    db.session.commit()

这样,Country表中就有了
学新通
capital表中:
学新通
他们目前时一对一的关系,那么,假如现在新增一个城市,它的country_id指向中国,
这时候就变成了一个国家,对应两个城市,变成了一对多,但是我们定义的是一对一,这样可行吗?

def f():
    #首先拿到中国的这条记录
    china = Country.query.filter_by(name='中国')
    china = china.first()
    print(china)

    #新增城市,
    Guangzhou = Capital(name='广州')
    #建立广州与中国之间的关系
    Guangzhou.country = china
    db.session.add(Guangzhou)
    db.session.commit()
    print('success')

执行这个函数之后,数据表会有以下变化:
学新通
学新通

可以看到capital表中的北京这条记录的country_id值变成了NULL,这正是因为我们建立的关系时一对一的,不允许变成一对多,所以,会把之前的对应关系取消掉。假如删除了参数uselist=False,就可以建立一对多的关系了。

多对多

这里使用学生和老师来演示多对多关系:每个学生有多个老师,每个老师也可以有多个学生。
在一对多关系中,我们可以在”多“这一侧添加外键指向”一“这一侧,外键只能存储一个记录,但是在多对多关系中,每一个记录都可以与关系另一侧的多个记录建立关系,关系两侧的模型都需要存储一组外键。

在SQLAlchemy中,要想表示多对多关系,除了关系两侧的模型外,我们还需要创建一个关联表(association table)。关联表不存储数据,只用来存储关系两侧模型的外键对应关系,

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)

db = SQLAlchemy(app)

#关联表
association_table = db.Table('stu_tea_associ',#关联表的名称
    db.Column('student_id',db.Integer,db.ForeignKey('student.id')),#字段student_id,类型,关联的外键
    db.Column('teacher_id',db.Integer,db.ForeignKey('teacher.id')))#字段teacher_id,类型,关联的外键

#学生表
class Student(db.Model):
    __tablename__ = 'student'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(20))

    #建立关系属性
    teachers = db.relationship('Teacher',secondary=association_table,back_populates='students')

#教师表
class Teacher(db.Model):
    __tablename__ = 'teacher'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    name = db.Column(db.String(20))

    #建立关系属性
    students = db.relationship('Student', secondary=association_table, back_populates='teachers')
学新通

关联表使用db.Table类定义,传入的第一个参数是关联表的名称。我们在关联表中定义了两个字段student_idteacher_id,这两个字段作为外键,与student.idteacher.id两个字段关联起来。

另外,这里建立的关系属性是双向的关系属性,参数secondary='关联表名称'

接着,利用关系属性添加数据

def insert():
    stu1 = Student(name='小明')
    stu2 = Student(name='小红')

    tea1 = Teacher(name='张三')
    tea2 = Teacher(name='李四')

    #学生1的老师有多个
    #由于是多对多关系,所以会采用列表的形式进行外键的orm赋值
    stu1.teachers = [tea1,tea2]

    #学生2的老师有多个
    stu2.teachers = [tea1,tea2]

    db.session.add(stu1)
    db.session.add(stu2)
    db.session.commit()
学新通

调用关系属性赋值的时候,这里需要使用列表的形式添加。
student:
学新通
teacher:
学新通
关联表:association_table:
学新通

接着,使用关系属性查询数据;

def query():
    #先查询到小明这个同学
    stu1 = Student.query.filter_by(name='小明').first()
    #使用关系属性输出对应的老师
    print(stu1.teachers)

    #反过来,查找到老师,使用关系属性输出老师对应的所有学生
    tea1 = Teacher.query.filter_by(name='张三').first()
    print(tea1.students)

输出记录

[<Teacher 1>, <Teacher 2>]
[<Student 1>, <Student 2>]

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfieje
系列文章
更多 icon
同类精品
更多 icon
继续加载