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

flask中models设计的更多相关文章

  1. Flask 中的MTV架构之Models

    Flask 中的MTV架构之Models 1.Models(数据模型) 1.1 flask-sqlalchemy(数据库) ​ 说明:提供了大多数关系型数据库的支持,而且提供了ORM # 安装: pi ...

  2. flask框架----整合Flask中的目录结构

    一.SQLAlchemy-Utils 由于sqlalchemy中没有提供choice方法,所以借助SQLAlchemy-Utils组件提供的choice方法 import datetime from ...

  3. 整合Flask中的目录结构

    一.SQLAlchemy-Utils 由于sqlalchemy中没有提供choice方法,所以借助SQLAlchemy-Utils组件提供的choice方法 import datetime from ...

  4. Flask学习【第11篇】:整合Flask中的一些知识点

    SQLAlchemy-Utils 由于sqlalchemy中没有提供choice方法,所以借助SQLAlchemy-Utils组件提供的choice方法 import datetime from sq ...

  5. Flask系列(十一)整合Flask中的目录结构(sqlalchemy-utils)

    一.SQLAlchemy-Utils 由于sqlalchemy中没有提供choice方法,所以借助SQLAlchemy-Utils组件提供的choice方法 import datetime from ...

  6. Flask【第11篇】:整合Flask中的目录结构

    整合Flask中的目录结构 一.SQLAlchemy-Utils 由于sqlalchemy中没有提供choice方法,所以借助SQLAlchemy-Utils组件提供的choice方法 import ...

  7. Flask 中的 SQLAlchemy 使用教程

    Flask 是一个 python web micro framework.所谓微框架,主要是 flask 简洁与轻巧,自定义程度高.相比 django 更加轻量级. 之前一直折腾 django,得益于 ...

  8. flask中的蓝图与红图

    内容: 1.flask中的蓝图 2.flask子域名实现 3.flask中的红图 1.flask中的蓝图 一个大型项目中视图比较多,如果仅仅是写在app.py中不方便管理,蓝图就可以做到分功能分目录结 ...

  9. Flask中的ORM使用

    前言 ORM拓展 安装 数据库设置 使用 关系 单表操作 建表 应用表结构 CRUD 添加查找操作 更新操作 删除操作 一对多 多对多 总结 前言 最近几天接触了一下Flask,在惊叹于其简洁性的同时 ...

随机推荐

  1. ThreadLocal的原理与使用

    前言 在java web项目中,经常会使用到单例对象,从服务器启动那一时刻就实例化全局对象.然后会对某些全局对象的属性进行修改之类的操作,但是我们知道项目一般都是部署到tomcat.Jboss之类的服 ...

  2. SpringBoot整合Mybatis关于分页查询的方法

    最近公司在用到SpringBoot整合Mybatis时当web端页面数据增多时需要使用分页查询以方便来展示数据.本人对分页查询进行了一些步骤的总结,希望能够帮助到有需要的博友.如有更好的方式,也希望评 ...

  3. js div模拟水平滚动条

    这个也是我百度到的,但是忘记保存连接了,现在把代码贴上来,有需要的可以参考一下: <!DOCTYPE html> <html> <head> <meta ch ...

  4. Nginx作为代理服务之正反代理

    Nginx作为代理服务之正反代理 首先什么是代理,就跟明星的经纪人类似,比如作为苍老师经纪人的我,如果你们需要和苍老师拍小电影,可以跟我这个经纪人来商量比如价格啊,时间等相关信息,那么我就作为一个代理 ...

  5. 【Swift后台】环境安装

    macOS 在macOS上使用Vapor,需要Xcode 9.3或更高版本.Swift 4.1或更高版本.安装还需要Homebrew命令. 检查Swift版本: swift --version Vap ...

  6. 04-【servlet转发和重定向】

    转发: //forward(将 数据传给下一个资源(servlet,jsp ,html等 ,把请求和响应的数据和参数设置带过去 ) request.getRequestDispatcher(" ...

  7. VToRay C-S config

    Server config: { "inbounds": [{ "port": 20000, //Server Listening Port "pro ...

  8. CNN for NLP

    卷积神经网络在自然语言处理任务中的应用.参考链接:Understanding Convolutional Neural Networks for NLP(2015.11) Instead of ima ...

  9. Netty TCP 通信失败

    前段时间,在搞Netty TCP 通信,踩了一些坑,今天就在这篇总结一下 Netty通信失败原因 Netty TCP 通信失败的可能原因: 1.服务端或客户端,其中一端没有正常启动 2.是否在正确的位 ...

  10. XShell 假死

    使用vim时因为使用windows word带来的坏习惯经常喜欢ctrl+s ,而这个造成的结果就是xshell假死,解决办法是ctrl+q