• flask中models设计


    1. 自关联

    class Comment(db.Model):
        __tablename__ = 'albumy_comment'
        id = db.Column(db.Integer, primary_key=True)
        body = db.Column(db.Text)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
        flag = db.Column(db.Integer, default=0)
    
        replied_id = db.Column(db.Integer, db.ForeignKey('albumy_comment.id'))
        user_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'))
        photo_id = db.Column(db.Integer, db.ForeignKey('albumy_photo.id'))
    
        photo = db.relationship('Photo', back_populates='comments')
        user = db.relationship('User', back_populates='comments')
        replies = db.relationship('Comment', back_populates='replied', cascade='all')  # 一  我下面所有给我的评论
        replied = db.relationship('Comment', back_populates='replies', remote_side=[id])  # 多 我对哪条评论进行的评论

    以评论表为例,评论下又可以有针对该评论的回复,因此在表中增加 replied_id 外键字段,指向该表的主键id。

    在设置关系属性时,需要再多的一方设置remote_side=[id]。

    2. 第三张表中的多个外键字段执行同一个表中的同一个字段

    class Follow(db.Model):
        __tablename__ = 'albumy_follow'
        follower_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'), primary_key=True)
        followed_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'), primary_key=True)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow)
    
        follower = db.relationship('User', foreign_keys=[follower_id], back_populates='following', lazy='joined')
        followed = db.relationship('User', foreign_keys=[followed_id], back_populates='followers', lazy='joined')
    
    
    class User(UserMixin, db.Model):
        __tablename__ = 'albumy_user'
        id = db.Column(db.INTEGER, primary_key=True)
        # 资料
        username = db.Column(db.String(20), unique=True, index=True)
        email = db.Column(db.String(254), unique=True, index=True)
        password_hash = db.Column(db.String(128))
        name = db.Column(db.String(30))
        website = db.Column(db.String(255))
        bio = db.Column(db.String(120))
        location = db.Column(db.String(50))
        member_since = db.Column(db.DateTime, default=datetime.utcnow)
        avatar_s = db.Column(db.String(64))
        avatar_m = db.Column(db.String(64))
        avatar_l = db.Column(db.String(64))
        avatar_raw = db.Column(db.String(64))
        receive_comment_notification = db.Column(db.Boolean, default=True)
        receive_follow_notification = db.Column(db.Boolean, default=True)
        receive_collect_notification = db.Column(db.Boolean, default=True)
        show_collections = db.Column(db.Boolean, default=True)
        role_id = db.Column(db.Integer, db.ForeignKey('albumy_role.id'))
        role = db.relationship('Role', back_populates='users')
        photos = db.relationship('Photo', back_populates='user', cascade='all')
        collections = db.relationship('Collect', back_populates='collector', cascade='all')  # 如:都收藏了那些图片
        comments = db.relationship('Comment', back_populates='user', cascade='all')
        following = db.relationship('Follow', foreign_keys=[Follow.follower_id], back_populates='follower',
                                    lazy='dynamic', cascade='all')  # 都关注了哪些用户
        followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], back_populates='followed',
                                    lazy='dynamic', cascade='all')  # 都被哪些用户
        notifications = db.relationship('Notification', back_populates='receiver', cascade='all')
        # 用户状态
        confirmed = db.Column(db.Boolean, default=False)
        locked = db.Column(db.Boolean, default=False)
        active = db.Column(db.Boolean, default=True)

    以 Follow表(关注表)与user表为例,follow表中记录着关注者id 与被关注着id,这两个外键字段都指向user表中的id。

    因为在Follow模型中,两个字段定义的外键是指向同一个表的同一个字段(user.id)的。而当我们需要在Follow模型上建立反向属性时,SQLAlchemy没法知道哪个外键对应哪个反向属性,所以我们需要在关系函数中使用foreign_keys参数来明确对应的字段。
    Follow表:

    follower = db.relationship('User', foreign_keys=[follower_id], back_populates='following', lazy='joined')
    followed = db.relationship('User', foreign_keys=[followed_id], back_populates='followers', lazy='joined')

    User表:

    following = db.relationship('Follow', foreign_keys=[Follow.follower_id], back_populates='follower',
                                    lazy='dynamic', cascade='all')  # 都关注了哪些用户
    followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], back_populates='followed',
                                    lazy='dynamic', cascade='all')  # 都被哪些用户

    3. 使用关联表表示多对多关系

    class Role(db.Model):
        __tablename__ = 'albumy_role'
        id = db.Column(db.Integer, primary_key=True)
        code = db.Column(db.String(21), unique=True)
        name = db.Column(db.String(21), unique=True)
        desc = db.Column(db.String(64), nullable=True)
        users = db.relationship('User', back_populates='role')
        permissions = db.relationship('Permission', secondary='albumy_roles_permissions', back_populates='roles')
    
    
    class Permission(db.Model):
        __tablename__ = 'albumy_permission'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(21), unique=True)
        desc = db.Column(db.String(64), nullable=True)
        roles = db.relationship('Role', secondary='albumy_roles_permissions', back_populates='permissions')
    
    
    roles_permissions = db.Table(
        'albumy_roles_permissions',
        db.Column('id', db.Integer, primary_key=True),
        db.Column('role_id', db.Integer, db.ForeignKey('albumy_role.id')),
        db.Column('permission_id', db.Integer, db.ForeignKey('albumy_permission.id'))
    )

    1. 使用关联表很方便,唯一的缺点是只能用来表示关系,不能用来存储数据。
    2. 当使用关联表时,SQLAlchemy会帮助我们操作关系,所以对关系某一侧调用关系属性会直接返回关系另一侧的对应记录。但是使用关联模型时,我们则需要手动操作关系。

    4. 使用关联模型表示多对多关系

    class Photo(db.Model):
        __tablename__ = 'albumy_photo'
        id = db.Column(db.Integer, primary_key=True)
        description = db.Column(db.String(500))
        filename = db.Column(db.String(64))
        filename_s = db.Column(db.String(64))
        filename_m = db.Column(db.String(64))
        flag = db.Column(db.Integer, default=0)  # 举报次数
        can_comment = db.Column(db.Boolean, default=True)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow)
        user_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'))
        user = db.relationship(User, back_populates='photos')
        tags = db.relationship('Tag', back_populates='photos', secondary='albumy_photos_tags', cascade='all')
    
        collectors = db.relationship('Collect', back_populates='collected', cascade='all')  # 如:被收藏的数量
        comments = db.relationship('Comment', back_populates='photo', cascade='all')
    # 关联模型
    class Collect(db.Model):
        __tablename__ = 'albumy_collect'
        # id = db.Column(db.Integer, primary_key=True)
        collector_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'), primary_key=True)  # 收藏者id
        collected_id = db.Column(db.Integer, db.ForeignKey('albumy_photo.id'), primary_key=True)  # 被收藏图片id
        timestamp = db.Column(db.DateTime, default=datetime.utcnow)
        collector = db.relationship('User', back_populates='collections', lazy='joined')
        collected = db.relationship('Photo', back_populates='collectors', lazy='joined')
        # __table_args__ = (
        #     db.UniqueConstraint('collector_id', 'collected_id', name='u_collector_id_collected_id'),
        #     # db.Index('ix_user_post_user_id_insert_time', 'user_id', 'insert_time'),
        # )

    当使用关联表时,SQLAlchemy会帮助我们操作关系,所以对关系某一侧调用关系属性会直接返回关系另一侧的对应记录。但是使用关联模型时,我们则需要手动操作关系。具体的表现是,我们在Photo和User模型中定义的关系属性返回的不再是关系另一侧的记录,而是存储对应关系的中间人——Collect记录。在Collect记录中添加的标量关系属性collector和collected,分别表示收藏者和被收藏图片,指向对应的User和Photo记录,我们需要进一步调用这两个关系属性,才可以获取关系另一侧的记录。

    from flask_login import UserMixin
    from datetime import datetime
    from werkzeug.security import generate_password_hash, check_password_hash
    from flask_avatars import Identicon
    from flask import current_app
    import os
    
    from albumy.extensions import db
    
    
    class Follow(db.Model):
        __tablename__ = 'albumy_follow'
        follower_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'), primary_key=True)
        followed_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'), primary_key=True)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow)
    
        follower = db.relationship('User', foreign_keys=[follower_id], back_populates='following', lazy='joined')
        followed = db.relationship('User', foreign_keys=[followed_id], back_populates='followers', lazy='joined')
    
    
    class User(UserMixin, db.Model):
        __tablename__ = 'albumy_user'
        id = db.Column(db.INTEGER, primary_key=True)
        # 资料
        username = db.Column(db.String(20), unique=True, index=True)
        email = db.Column(db.String(254), unique=True, index=True)
        password_hash = db.Column(db.String(128))
        name = db.Column(db.String(30))
        website = db.Column(db.String(255))
        bio = db.Column(db.String(120))
        location = db.Column(db.String(50))
        member_since = db.Column(db.DateTime, default=datetime.utcnow)
        avatar_s = db.Column(db.String(64))
        avatar_m = db.Column(db.String(64))
        avatar_l = db.Column(db.String(64))
        avatar_raw = db.Column(db.String(64))
        receive_comment_notification = db.Column(db.Boolean, default=True)
        receive_follow_notification = db.Column(db.Boolean, default=True)
        receive_collect_notification = db.Column(db.Boolean, default=True)
        show_collections = db.Column(db.Boolean, default=True)
        role_id = db.Column(db.Integer, db.ForeignKey('albumy_role.id'))
        role = db.relationship('Role', back_populates='users')
        photos = db.relationship('Photo', back_populates='user', cascade='all')
        collections = db.relationship('Collect', back_populates='collector', cascade='all')  # 如:都收藏了那些图片
        comments = db.relationship('Comment', back_populates='user', cascade='all')
        following = db.relationship('Follow', foreign_keys=[Follow.follower_id], back_populates='follower',
                                    lazy='dynamic', cascade='all')  # 都关注了哪些用户
        followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], back_populates='followed',
                                    lazy='dynamic', cascade='all')  # 都被哪些用户
        notifications = db.relationship('Notification', back_populates='receiver', cascade='all')
        # 用户状态
        confirmed = db.Column(db.Boolean, default=False)
        locked = db.Column(db.Boolean, default=False)
        active = db.Column(db.Boolean, default=True)
    
        def __init__(self, **kwargs):
            super(User, self).__init__(**kwargs)
            self.generate_avatar()
            self.set_role()
            self.follow(self)
    
        def generate_avatar(self):
            """生成用户头像"""
            avatar = Identicon()
            filenames = avatar.generate(text=self.username)
            self.avatar_s = filenames[0]
            self.avatar_m = filenames[1]
            self.avatar_l = filenames[2]
            db.session.commit()
    
        def set_role(self):
            """为用户设置角色,默认为user"""
            if self.role is None:
                role = Role.query.filter_by(code='user').first()
                self.role = role
                db.session.commit()
    
        def set_password(self, pwd):
            """设置加密密码"""
            self.password_hash = generate_password_hash(pwd)
    
        def check_password(self, pwd):
            """检验密码正确性"""
            return check_password_hash(self.password_hash, pwd)
    
        def is_admin(self):
            """判断用户是否具有管理员的角色"""
            return self.role.code == 'administrator'
    
        def can(self, permission_name):
            """判断用户是否具有某个权限"""
            permission = Permission.query.filter_by(name=permission_name).first()
            return permission is not None and self.role is not None and permission in self.role.permissions
    
        def collect(self, photo):
            """
            收藏图片
            :param photo: 图片对象
            :return:
            """
            if not self.is_collecting(photo):
                collect = Collect(collector=self, collected=photo)
                db.session.add(collect)
                db.session.commit()
    
        def uncollect(self, photo):
            """
            取消图片收藏
            :param photo: 图片对象
            :return:
            """
            collect = Collect.query.with_parent(self).filter_by(collected_id=photo.id).first()
            if collect:
                db.session.delete(collect)
                db.session.commit()
    
        def is_collecting(self, photo):
            """
            是否收藏图片
            :return: 图片对象
            """
            collect = Collect.query.with_parent(self).filter_by(collected_id=photo.id).first()
            if collect:
                return True
            else:
                return False
    
        def follow(self, user):
            """
            关注用户
            :param user: user对象
            :return:
            """
            if not self.is_following(user):
                follow = Follow(follower=self, followed=user)
                db.session.add(follow)
                db.session.commit()
    
        def unfollow(self, user):
            """
            取消关注
            :param user: user对象
            :return:
            """
            follow = self.following.filter_by(followed_id=user.id).first()
            if follow:
                db.session.delete(follow)
                db.session.commit()
    
        def is_following(self, user):
            """
            是否关注用户
            :param user: user对象
            :return:
            """
            if user.id is None:
                return False
            return self.following.filter_by(followed_id=user.id).first() is not None
    
        def is_followed_by(self, user):
            """
            用户是否被关注
            :param user: user对象
            :return:
            """
            return self.followers.filter_by(follower_id=user.id).first() is not None
    
        def lock(self):
            self.locked = True
            self.role = Role.query.filter_by(name='Locked').first()
            db.session.commit()
    
        def unlock(self):
            self.locked = False
            self.role = Role.query.filter_by(name='User').first()
            db.session.commit()
    
        @property
        def is_active(self):
            return self.active
    
        def block(self):
            self.active = False
            db.session.commit()
    
        def unblock(self):
            self.active = True
            db.session.commit()
    
    
    @db.event.listens_for(User, 'after_delete', named=True)
    def delete_avatars(**kwargs):
        target = kwargs['target']
        for filename in [target.avatar_s, target.avatar_m, target.avatar_l, target.avatar_raw]:
            if filename is not None:  # avatar_raw may be None
                path = os.path.join(current_app.config['AVATARS_SAVE_PATH'], filename)
                if os.path.exists(path):  # not every filename map a unique file
                    os.remove(path)
    
    
    class Role(db.Model):
        __tablename__ = 'albumy_role'
        id = db.Column(db.Integer, primary_key=True)
        code = db.Column(db.String(21), unique=True)
        name = db.Column(db.String(21), unique=True)
        desc = db.Column(db.String(64), nullable=True)
        users = db.relationship('User', back_populates='role')
        permissions = db.relationship('Permission', secondary='albumy_roles_permissions', back_populates='roles')
    
    
    class Permission(db.Model):
        __tablename__ = 'albumy_permission'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(21), unique=True)
        desc = db.Column(db.String(64), nullable=True)
        roles = db.relationship('Role', secondary='albumy_roles_permissions', back_populates='permissions')
    
    
    roles_permissions = db.Table(
        'albumy_roles_permissions',
        db.Column('id', db.Integer, primary_key=True),
        db.Column('role_id', db.Integer, db.ForeignKey('albumy_role.id')),
        db.Column('permission_id', db.Integer, db.ForeignKey('albumy_permission.id'))
    )
    
    
    class Photo(db.Model):
        __tablename__ = 'albumy_photo'
        id = db.Column(db.Integer, primary_key=True)
        description = db.Column(db.String(500))
        filename = db.Column(db.String(64))
        filename_s = db.Column(db.String(64))
        filename_m = db.Column(db.String(64))
        flag = db.Column(db.Integer, default=0)  # 举报次数
        can_comment = db.Column(db.Boolean, default=True)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow)
        user_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'))
        user = db.relationship(User, back_populates='photos')
        tags = db.relationship('Tag', back_populates='photos', secondary='albumy_photos_tags', cascade='all')
    
        collectors = db.relationship('Collect', back_populates='collected', cascade='all')  # 如:被收藏的数量
        comments = db.relationship('Comment', back_populates='photo', cascade='all')
    
    
    # 为Photo创建一个数据库事件监听函数
    @db.event.listens_for(Photo, 'after_delete', named=True)
    def delete_photo_file(**kwargs):
        """删除photo对象时, 删除对应的文件"""
        """
            kwargs = 
                {'connection': <sqlalchemy.engine.base.Connection object at 0x0000025B138A7978>, 
                 'mapper': <Mapper at 0x25b134fb2b0; Photo>, 
                 'target': <Photo 8>
                }
            如果不加named=True, 需要传三个位置参数
        """
        target = kwargs['target']  # <Photo 8>
        for filename in [target.filename, target.filename_s, target.filename_m]:
            if filename is not None:
                path = os.path.join(current_app.config['ALBUMY_UPLOAD_PATH'], filename)
                if os.path.exists(path):
                    os.remove(path)
    
    
    class Tag(db.Model):
        __tablename__ = 'albumy_tag'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(12))
        desc = db.Column(db.String(64))
        photos = db.relationship(Photo, back_populates='tags', secondary='albumy_photos_tags')
    
    
    photos_tags = db.Table(
        'albumy_photos_tags',
        db.Column('id', db.Integer, primary_key=True),
        db.Column('photo_id', db.Integer, db.ForeignKey('albumy_photo.id')),
        db.Column('tag_id', db.Integer, db.ForeignKey('albumy_tag.id')),
    )
    
    
    # 关联模型
    class Collect(db.Model):
        __tablename__ = 'albumy_collect'
        # id = db.Column(db.Integer, primary_key=True)
        collector_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'), primary_key=True)  # 收藏者id
        collected_id = db.Column(db.Integer, db.ForeignKey('albumy_photo.id'), primary_key=True)  # 被收藏图片id
        timestamp = db.Column(db.DateTime, default=datetime.utcnow)
        collector = db.relationship('User', back_populates='collections', lazy='joined')
        collected = db.relationship('Photo', back_populates='collectors', lazy='joined')
        # __table_args__ = (
        #     db.UniqueConstraint('collector_id', 'collected_id', name='u_collector_id_collected_id'),
        #     # db.Index('ix_user_post_user_id_insert_time', 'user_id', 'insert_time'),
        # )
    
    
    class Comment(db.Model):
        __tablename__ = 'albumy_comment'
        id = db.Column(db.Integer, primary_key=True)
        body = db.Column(db.Text)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
        flag = db.Column(db.Integer, default=0)
    
        replied_id = db.Column(db.Integer, db.ForeignKey('albumy_comment.id'))
        user_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'))
        photo_id = db.Column(db.Integer, db.ForeignKey('albumy_photo.id'))
    
        photo = db.relationship('Photo', back_populates='comments')
        user = db.relationship('User', back_populates='comments')
        replies = db.relationship('Comment', back_populates='replied', cascade='all')  #
        replied = db.relationship('Comment', back_populates='replies', remote_side=[id])  #
    
    
    class Notification(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        message = db.Column(db.Text)
        is_read = db.Column(db.Boolean, default=False)
        timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
        receiver_id = db.Column(db.Integer, db.ForeignKey('albumy_user.id'))
        receiver = db.relationship(User, back_populates='notifications')
    完整models







  • 相关阅读:
    投行风云:FO的酸甜苦辣【转】
    周末排毒
    LeetCode | Single Number II【转】
    Adding supplementary tables and figures in LaTeX【转】
    Algorithm | Tree traversal
    Leetcode | Linked List Cycle I && II
    Network | UDP checksum
    Ubuntu下将现有的文件打包成deb包
    Base64编码加密
    requestAnimationFrame 的原理与优势
  • 原文地址:https://www.cnblogs.com/yuqiangli0616/p/10266908.html
Copyright © 2020-2023  润新知