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. 【原创】大数据基础之ETL vs ELT or DataWarehouse vs DataLake

    ETL ETL is an abbreviation of Extract, Transform and Load. In this process, an ETL tool extracts the ...

  2. 使用原生node.js搭建HTTP服务器,支持MP4视频、图片传输,支持下载rar文件

    前言 如何安装node.js,如何搭建一个简易的http服务器我这里就不再赘述了,不懂的同学可以先去学习一下.当然了,我写的也就属于简易版的增强版,大家有什么高见的欢迎提出,然后进入正题. 目录结构 ...

  3. 什么是NoSQL,为什么要使用NoSQL?

    详见: https://blog.csdn.net/a909301740/article/details/80149552 https://baike.so.com/doc/5569749-57849 ...

  4. redis-cluster集群总结

    Redis集群搭建 要想搭建一个最简单的Redis集群,那么至少需要6个节点:3个Master和3个Slave.为什么需要3个Master呢?如果你了解过Hadoop/Storm/Zookeeper这 ...

  5. Asp.Net Zero通用打印实现

    Asp.Net Zero是一款非常优秀的web框架,可以用来快速构建业务系统.框架满足了业务系统所需的大部分通用功能,但是系统必须的打印报表功能一直没有实现.下面给大家介绍如何在zero中集成打印功能 ...

  6. Nginx中虚拟主机配置

    一.Nginx中虚拟主机配置 1.基于域名的虚拟主机配置 1.修改宿主机的hosts文件(系统盘/windows/system32/driver/etc/HOSTS) linux : vim /etc ...

  7. 关于layui表格渲染templet解析单元格的问题

    原文链接:https://blog.csdn.net/wyp_comeon/article/details/81735951关于表格解析自定义单元格的解析参数请先详细查看官方文档:http://www ...

  8. zeus部署

    1.下载zeus 阿里在github上已经不维护zeus了,在网上找到一个别人贡献的 https://github.com/michael8335/zeus2 下载下来 通过shell rz命令上传到 ...

  9. Ruby2.0后版本的debug工具: byebug

    https://github.com/deivid-rodriguez/byebug/blob/master/GUIDE.md 安装: gem install byebug 使用: Rails: 直接 ...

  10. Echarts 饼状图 字体重叠问题

    原理:设置最小扇形的大小,把他撑起来 在 series 里 使用 minAngle: 38, //最小的扇区角度(0 ~ 360),用于防止某个值过小导致扇区太小影响交互 角度自己调好就可以了 个人笔 ...