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. ubuntu 编译zbar 静态库

    wget http://downloads.sourceforge.net/project/zbar/zbar/0.10/zbar-0.10.tar.gz tar -zvxf zbar-0.10.ta ...

  2. linux mint ubuntu 安装virtualbox

    安装虚拟机:virtualbox 1.打开终端而且切换到root帐号,然后输入安装命令: apt-get install virtualbox 2.安装推荐的软件包:(必须安装这个包.不然看不到应用程 ...

  3. 帝国cms 权限操作

    <? if ($classid==5 || $classid==6 || $classid==7 || $classid==8 || $classid==9 || $classid==10 || ...

  4. docker 批量删除含有同样名字的images

    docker rmi --force $(docker images | grep doss-api | awk '{print $3}') docker rmi  $(docker images | ...

  5. 解决办法:Message: 对实体 "useUnicode" 的引用必须以 ';' 分隔符结尾

    Hibernate 5.3.1 INFO: HHH000206: hibernate.properties not foundException in thread "main" ...

  6. Oracle笔记(五) 单行函数

    虽然各个数据库都是支持SQL语句的,但是每一个数据库也有每一个数据库自己所支持的操作函数,这些就是单行函数,而如果要想进行数据库开发的话,除了要会使用SQL之外 ,就是要多学习函数. 单行函数主要分为 ...

  7. sourceforge.net

    https://sourceforge.net/ SourceForge.net,又称SF.net,是开源软件开发者进行开发管理的集中式场所. SourceForge.net由VA Software提 ...

  8. three.js之让物体动起来方式(二)移动物体

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. 通过maven命令将源代码编译成jar到本地仓库

    图: 4.2.3        采用maven命令编译成jar安装到本地maven库 在路径框输入cmd,执行命令: mvn clean install 图: 图2 成功后可以看到jar包

  10. 第一章 Vue介绍

    5 MVC和MVVM的关系图解 MVVM是前端视图层的分层开发思想,主要把每个页面,分层了M.V和VM.其中,VM是MVVM思想的核心,因为VM是M和V之间的调度者 6 Vue基本代码和MVVM之间对 ...