1. 程式人生 > >flask中models設計

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