• 用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)


    目录

    前文列表

    用 Flask 来写个轻博客 (1) — 创建项目
    用 Flask 来写个轻博客 (2) — Hello World!
    用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
    用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
    用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
    用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)

    扩展阅读

    SQLAlchemy_定义(一对一/一对多/多对多)关系
    Openstack_SQLAlchemy_一对多关系表的多表插入实现

    前期准备

    在实现多对多之前,我们还需要先增加一个评论(Comment) models class,而且 Comment 是 Post 的关系是 one to many。

    • 首先依旧是在 models.py 中定义模型类:
    class Post(db.Model):
        """Represents Proected posts."""
    
        __tablename__ = 'posts'
        id = db.Column(db.String(45), primary_key=True)
        title = db.Column(db.String(255))
        text = db.Column(db.Text())
        publish_date = db.Column(db.DateTime)
        # Set the foreign key for Post
        user_id = db.Column(db.String(45), db.ForeignKey('users.id'))
        # Establish contact with Comment's ForeignKey: post_id
        comments = db.relationship(
            'Comment',
            backref='posts',
            lazy='dynamic')
    
        def __init__(self, title):
            self.title = title
    
        def __repr__(self):
            return "<Model Post `{}`>".format(self.title)
    
    
    class Comment(db.Model):
        """Represents Proected comments."""
    
        __tablename__ = 'comments'
        id = db.Column(db.String(45), primary_key=True)
        name = db.Column(db.String(255))
        text = db.Column(db.Text())
        date = db.Column(db.DateTime())
        post_id = db.Column(db.String(45), db.ForeignKey('posts.id'))
    
        def __init__(self, name):
            self.name = name
    
        def __repr__(self):
            return '<Model Comment `{}`>'.format(self.name)
    • 然后要记住在 manage.py 中返回 Comment
    def make_shell_context():
        """Create a python CLI.
    
        return: Default import object
        type: `Dict`
        """
        return dict(app=main.app,
                    db=models.db,
                    User=models.User,
                    Post=models.Post,
                    Comment=models.Comment)
    • 最后在 manager shell 验证是否成功导入了 Comment
    (env) [root@flask-dev JmilkFan-s-Blog]# python manage.py shell
    >>> Comment
    <class 'models.Comment'>

    多对多

    如果我们有两个 models,它们之间是相互引用的,而且彼此都可以相互引用对方的多个对象。这就是所谓 many to many 的关系类型。

    多对多关系会在两个类之间增加一个关联表。 这个关联的表在 relationship() 方法中通过 secondary 参数来表示。通常的,这个表会通过 MetaData 对象来与声明基类关联, 所以这个 ForeignKey 指令会使用链接来定位到远程的表:

    posts_tags = db.Table('posts_tags',
        db.Column('post_id', db.String(45), db.ForeignKey('posts.id')),
        db.Column('tag_id', db.String(45), db.ForeignKey('tags.id')))
    
    
    class Post(db.Model):
        """Represents Proected posts."""
    
        __tablename__ = 'posts'
        id = db.Column(db.String(45), primary_key=True)
        title = db.Column(db.String(255))
        text = db.Column(db.Text())
        publish_date = db.Column(db.DateTime)
        # Set the foreign key for Post
        user_id = db.Column(db.String(45), db.ForeignKey('users.id'))
        # Establish contact with Comment's ForeignKey: post_id
        comments = db.relationship(
            'Comment',
            backref='posts',
            lazy='dynamic')
        # many to many: posts <==> tags
        tags = db.relationship(
            'Tag',
            secondary=posts_tags,
            backref=db.backref('posts', lazy='dynamic'))
    
        def __init__(self, title):
            self.title = title
    
        def __repr__(self):
            return "<Model Post `{}`>".format(self.title)
    
    
    class Tag(db.Model):
        """Represents Proected tags."""
    
        __tablename__ = 'tags'
        id = db.Column(db.String(45), primary_key=True)
        name = db.Column(db.String(255))
    
        def __init__(self, name):
            self.name = name
    
        def __repr__(self):
            return "<Model Tag `{}`>".format(self.name)
    • many to many 的关系仍然是由 db.relationship() 来定义
    • seconddary(次级):会告知 SQLAlchemy 该 many to many 的关联保存在 posts_tags 表中
    • backref:声明表之间的关系是双向,帮助手册 help(db.backref)。需要注意的是:在 one to many 中的 backref 是一个普通的对象,而在 many to many 中的 backref 是一个 List 对象。

    NOTE 1:实际上 db.Table 对象对数据库的操作比 db.Model 更底层一些。后者是基于前者来提供的一种对象化包装,表示数据库中的一条记录。 posts_tags 表对象之所以使用 db.Table 不使用 db.Model 来定义,是因为我们不需要对 posts_tags (self.name)进行直接的操作(不需要对象化),posts_tags 代表了两张表之间的关联,会由数据库自身来进行处理。

    NOTE 2posts_tags 的声明定义最好在 Post 和 Tag 之前。

    NOTE 3: 没添加一个 models class 都要记得在 manage.py 中导入并返回,方便之后的调试,这里就不作重复了。

    • 同步数据库
    (env) [root@flask-dev JmilkFan-s-Blog]# python manage.py shell
    >>> db.create_all()

    使用样例

    >>> posts = db.session.query(Post).all()
    >>> posts
    [<Model Post `Second Post`>, <Model Post `First Post`>]
    >>> post_one = posts[1]
    >>> post_two = posts[0]
    
    # 实例化 3 个 Tag 的对象
    >>> from uuid import uuid4
    >>> tag_one = Tag('JmilkFan')
    >>> tag_one.id = str(uuid4())
    >>> tag_two = Tag('FanGuiju')
    >>> tag_two.id = str(uuid4())
    >>> tag_three = Tag('Flask')
    >>> tag_three.id = str(uuid4())
    
    # 将 Tag 的实例化对象赋值给 Post 实例化对象的 tags 属性
    # 即指定 Tag 和 Post 之间的关联状态
    # post_one 对应一个 tag
    # post_two 对应三个 tags
    # tag_one/tag_three 对应一个 post
    # tag_two 对象两个 posts
    >>> post_one.tags
    []
    >>> post_one.tags = [tag_two]
    >>> post_two.tags = [tag_one, tag_two, tag_three]

    NOTE: 此时的数据库中还是只有原来就已经存在的两条 posts 记录,但是还没有 tags 记录。这是因为在刚刚实例化的 Tag 对象还没有被提交,所以不会被写入到数据库中。

    mysql> select * from posts;
    +--------------------------------------+-------------+------+--------------+--------------------------------------+
    | id                                   | title       | text | publish_date | user_id                              |
    +--------------------------------------+-------------+------+--------------+--------------------------------------+
    | 0722c107-296f-4ac5-8133-48f3470d85ca | Second Post | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
    | 140078fe-c53b-4226-ad47-33734793e47e | First Post  | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
    +--------------------------------------+-------------+------+--------------+--------------------------------------+
    2 rows in set (0.00 sec)
    
    mysql> select * from tags;
    Empty set (0.00 sec)

    NOTE:再次提交 post_one/post_two 对象。post_one/post_two 对象相应的记录本就已经存在于数据库中了为什么要重新提交呢?
    这是因为 post_one/post_two 都被指定了新的关联属性 tags,所以提交 post_one/post_two 不仅仅是更新 posts 的引用,更重要的是将新创建的 3 个 tags 对象写入到数据库中,同时也是将 posts 和 tags 的映射关系写入到 posts_tags 表中。

    >>> db.session.add(post_one)
    >>> db.session.add(post_two)
    >>> db.session.commit()

    再次查看数据库:

    mysql> select * from tags;
    +--------------------------------------+----------+
    | id                                   | name     |
    +--------------------------------------+----------+
    | 22c46fa9-6899-4851-b95b-cc6267b68c7c | FanGuiju |
    | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e | JmilkFan |
    | 76b38f31-6a23-4acd-b252-fc580e46d186 | Flask    |
    +--------------------------------------+----------+
    3 rows in set (0.00 sec)
    
    mysql> select * from posts_tags;
    +--------------------------------------+--------------------------------------+
    | post_id                              | tag_id                               |
    +--------------------------------------+--------------------------------------+
    | 140078fe-c53b-4226-ad47-33734793e47e | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
    | 0722c107-296f-4ac5-8133-48f3470d85ca | 76b38f31-6a23-4acd-b252-fc580e46d186 |
    | 0722c107-296f-4ac5-8133-48f3470d85ca | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
    | 0722c107-296f-4ac5-8133-48f3470d85ca | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
    +--------------------------------------+--------------------------------------+
    4 rows in set (0.00 sec)

    NOTE:在上面说过了 many to many 的 backref 是一个 List 对象,所以我们还可以反过来为 tags 添加一个 posts 对象(引用)。

    >>> tag_one.posts.all()
    [<Model Post `Second Post`>]
    >>> tag_one.posts.append(post_one)
    >>> tag_one.posts.all()
    [<Model Post `Second Post`>, <Model Post `First Post`>]
    >>> post_one.tags
    [<Model Tag `FanGuiju`>, <Model Tag `JmilkFan`>]
    # 因为修改了 tag_one 的 posts 属性(添加了 post_one 的引用),所以需要重新提交 tag_one 才会被写入到数据库。
    >>> db.session.add(tag_one)
    >>> db.session.commit()

    再次查看数据库的记录:

    mysql> select * from tags;
    +--------------------------------------+----------+
    | id                                   | name     |
    +--------------------------------------+----------+
    | 22c46fa9-6899-4851-b95b-cc6267b68c7c | FanGuiju |
    | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e | JmilkFan |
    | 76b38f31-6a23-4acd-b252-fc580e46d186 | Flask    |
    +--------------------------------------+----------+
    3 rows in set (0.00 sec)
    
    mysql> select * from posts;
    +--------------------------------------+-------------+------+--------------+--------------------------------------+
    | id                                   | title       | text | publish_date | user_id                              |
    +--------------------------------------+-------------+------+--------------+--------------------------------------+
    | 0722c107-296f-4ac5-8133-48f3470d85ca | Second Post | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
    | 140078fe-c53b-4226-ad47-33734793e47e | First Post  | NULL | NULL         | ad7fd192-89d8-4b53-af96-fceb1f91070f |
    +--------------------------------------+-------------+------+--------------+--------------------------------------+
    2 rows in set (0.00 sec)
    
    mysql> select * from posts_tags;
    +--------------------------------------+--------------------------------------+
    | post_id                              | tag_id                               |
    +--------------------------------------+--------------------------------------+
    | 140078fe-c53b-4226-ad47-33734793e47e | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
    | 0722c107-296f-4ac5-8133-48f3470d85ca | 76b38f31-6a23-4acd-b252-fc580e46d186 |
    | 0722c107-296f-4ac5-8133-48f3470d85ca | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
    | 0722c107-296f-4ac5-8133-48f3470d85ca | 22c46fa9-6899-4851-b95b-cc6267b68c7c |
    | 140078fe-c53b-4226-ad47-33734793e47e | 2a3f5c3f-1e1b-4d4a-89c6-62ea74fed75e |
    +--------------------------------------+--------------------------------------+
    5 rows in set (0.00 sec)

    NOTE: 从 posts_tags 的记录中可以看出 posts 和 tags 之间的多对多关系。

    获取文章 First Post 下有哪些标签:

    >>> db.session.query(Post).filter_by(title='First Post').first().tags
    [<Model Tag `JmilkFan`>, <Model Tag `FanGuiju`>]

    获取标签 JmilkFan 下有哪些文章

    >>> db.session.query(Tag).filter_by(name='JmilkFan').first().posts.all()
    [<Model Post `First Post`>, <Model Post `Second Post`>]

    NOTE:再次说明一下,在定义 models 间关系时使用的 backref 参数,指定了加载关联对象的方式(这里使用了动态方式),所以加载 Tag 的关联对象 Post 时,返回的是 sqlalchemy.orm.dynamic.AppenderBaseQuery object 而不是全部的关联对象。

    那么为什么反之却是直接返回全部的关联对象呢?
    这是因为我们是在 Post 中使用了 backref 对象,所以对于两者的关系而言,backref 指的是 Tag。

    >>> db.session.query(Post).filter_by(title='First Post').first().tags
    [<Model Tag `JmilkFan`>, <Model Tag `FanGuiju`>]
    >>> db.session.query(Tag).filter_by(name='JmilkFan').first().posts
    <sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x38ff850>

    一直在使用的 session

    session 是连接到数据库的一个桥梁,实际上 session 具有更多的功能,例如:事务
    事务:是对数据库进行操作即集合,在我们 commit 的时候,实际事务帮我们实现了一系列有效的数据库操作。
    例如:刚刚我们在 commit 一个 post_one/post_two 前,明明没有 commit tag_one/tag_two/tag_three,为什么数据库中还会写入这三条记录呢?这些都是由事务去帮我们进行的隐式的数据库操作。如果没有事务我们就需要按部就班一步步的完成对数据库的写入,这样的效率是非常低的。除此之外 SQLAlchemy 的 session 还会提供很多有用的功能,感兴趣的话可以继续挖掘,这里就不多做介绍了。

  • 相关阅读:
    [Clojure] 包管理器leiningen配置国内镜像仓库
    [Haskell] 为什么列表操作++很昂贵?
    js判断除了空格换行之外是否为空
    iOS上架之隐私信息访问权限(uni-app)
    vue之动态绑定class
    this
    uni-app 上传图片之压缩图片上传
    uniapp无痛刷新token
    jQuery 发送跨域请求(jsonp)
    Document
  • 原文地址:https://www.cnblogs.com/jmilkfan-fanguiju/p/7532282.html
Copyright © 2020-2023  润新知