在这节我们实现的功能比较复杂,就是实现用户"关注"和"取消关注"的功能。

一个用户可以关注多个其他的用户,一个用户也可以被其他多个用户所关注,这样看的话,在数据库中显然是多对多的关系。但是这有一个问题。我们想要表示用户关注其他用户,因为我们只有用户。我们应该使用什么作为多对多关系的第二个表(实体)?这种关系的第二个表也是用户。我们现在来建这张表,表名是followers。在这张表中我们只设置了两个字段(follower_id 和 followered_id),这两个字段作为外键都关联到user表中。下面我们来开始逐步实现。

一、建立数据模型

1.添加followers表(model.py)

followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)

注:注意我们并没有像对 users 和 posts 一样把它声明为一个模式。因为这是一个辅助表,我们使用 flask-sqlalchemy 中的低级的 APIs 来创建没有使用关联模式。

2.在user表中定义一个多对多的关系

class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
nickname = db.Column(db.String(64), unique = True)
email = db.Column(db.String(120), unique = True)
posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
followed = db.relationship('User',
secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic')

关系的设置不是很简单,需要一些解释。像我们在前面章节设置一对多关系一样,我们使用了 db.relationship 函数来定义关系。我们将连接 User 实例到其它 User 实例,换一种通俗的话来说,在这种关系下连接的一对用户,左边的用户是关注着右边的用户。因为我们定义左边的用户为 followed,当我们从左边用户查询这种关系的时候,我们将会得到被关注用户的列表。让我们一个一个来解释下 db.relationship() 中的所有参数:

(1)‘User’ 是这种关系中的右边的表(实体)(左边的表/实体是父类)。因为定义一个自我指向的关系,我们在两边使用同样的类。

(2)secondary 指明了用于这种关系的辅助表。

(3)primaryjoin 表示辅助表中连接左边实体(发起关注的用户)的条件。注意因为 followers 表不是一个模式,获得字段名的语法有些怪异。

(4)secondaryjoin 表示辅助表中连接右边实体(被关注的用户)的条件。

(5)backref 定义这种关系将如何从右边实体进行访问。当我们做出一个名为 followed 的查询的时候,将会返回所有跟左边实体联系的右边的用户。当我们做出一个名为 followers 的查询的时候,将会返回一个所有跟右边联系的左边的用户。lazy 指明了查询的模式。dynamic 模式表示直到有特定的请求才会运行查询,这是对性能有很好的考虑。

(6)lazy 是与 backref 中的同样名称的参数作用是类似的,但是这个是应用于常规查询。

运行迁移脚本:python db_migrate.py

二、添加和移除关注者

为了使得代码具有可重用性,我们将会在 User 模型中实现 follow 和 unfollow 函数,而不是在视图函数中。这种方式不仅可以让这个功能应用于真实的应用也能在单元测试中测试。原则上,从视图函数中移除应用程序的逻辑到数据模型中是一种好的方式。你们必须要保证视图函数尽可能简单,因为它能难被自动化测试。

我们在model.py 的User模型中加入:

    def follow(self, user):
if not self.is_following(user):
self.followed.append(user)
return self def unfollow(self, user):
if self.is_following(user):
self.followed.remove(user)
return self def is_following(self, user):
return self.followed.filter(followers.c.followed_id == user.id).count() > 0

上面这些方法是很简单了,多亏了 sqlalchemy 在底层做了很多的工作。我们只是从 followed 关系中添加或者移除了表项,sqlalchemy 为我们管理辅助表。

follow 和 unfollow 方法是定义成当它们成功的话返回一个对象或者失败的时候返回 None。当返回一个对象的时候,这个对象必须被添加到数据库并且提交。

is_following 方法在一行代码中做了很多。我们做了一个 followed 关系查询,这个查询返回所有当前用户作为关注者的 (follower, followed) 对。

三、测试

下面我们在测试模块中测试一下:(test.py)

    def test_follow(self):
from model import User
u1 = User(nickname='john', email='john@example.com')
u2 = User(nickname='susan', email='susan@example.com')
db.session.add(u1)
db.session.add(u2)
db.session.commit()
assert u1.unfollow(u2) == None
u = u1.follow(u2)
db.session.add(u)
db.session.commit()
assert u1.follow(u2) == None
assert u1.is_following(u2)
assert u1.followed.count() == 1
assert u1.followed.first().nickname == 'susan'
assert u2.followers.count() == 1
assert u2.followers.first().nickname == 'john'
u = u1.unfollow(u2)
assert u != None
db.session.add(u)
db.session.commit()
assert u1.is_following(u2) == False
assert u1.followed.count() == 0
assert u2.followers.count() == 0

四、数据库查询

我们的数据库模型已经能够支持大部分我们列出来的需求。我们缺少的实际上是最难的。我们的首页将会显示登录用户所有关注者撰写的 blog,因为我们需要一个返回这些 blog 的查询。

最明了的解决方式就是查询给定的关注者用户的列表,这也是我们目前可以做到的。接着对每一个返回的用户去查询他的或者她的 blog。一旦我们完成所有的查询工作,我们把它们整合到一个列表中然后排序。听起来不错?实际上不是。

这种方法其实问题很大。当一个用户拥有上千个关注者的话会发生些什么?我们需要执行上千次甚至更多的数据库查询,并且在内存中我们需要维持一个数据量很大的 blog 的列表,接着还要排序。不知道这些做完,要花上多久的时间?

这种收集以及排序的工作需要在其它的地方完成,我们只要使用结果就行。这类的工作其实就是关系型数据库擅长。数据库有索引,因此允许以一种高效地方式去查询以及排序。

所以我们真正想要的是要拿出一个单一的数据库查询,表示我们想要得到什么样的信息,然后我们让数据库弄清楚什么是最有效的方式来为我们获取数据。

下面这种查询可以实现上述的要求,这个单行的代码又被我们添加到 User 模型(文件model.py):

    def followed_posts(self):
return Post.query.join(followers, (followers.c.followed_id == Post.user_id)).filter(
followers.c.follower_id == self.id).order_by(Post.timestamp.desc())

下面我们来一一解释一下这个方法做了哪些工作:

1.连接

为了理解一个连接操作做了什么,让我们看看例子。假设我们有一个如下内容的 User 表:

只为了简化例子,表里面还有一些额外的字段没有显示。

比如说,我们的 followers 辅助表中表示用户 “john” 关注着 用户 “susan” 以及 “david”,用户 “susan” 关注着 “mary” 以及 用户 “mary” 关注着 “david”。表示上述的数据是这样的:

最后,我们的 Post 表中,每一个用户有一篇 blog:

这里再次申明为了使得例子显得简单,我们忽略了一些字段。

下面是我们的查询的连接部分的,独立于其余的查询:

Post.query.join(followers,
(followers.c.followed_id == Post.user_id))

在 Post 表中调用了 join 操作。这里有两个参数,第一个是其它的表,我们的 followers 表。第二参数就是连接的条件。

连接操作所做的就是创建一个数据来自于 Post 和 followers 表的临时新的表,根据给定条件进行整合。

在这个例子中,我们要 followers 表中的字段 followed_id 与 Post 表中的字段 user_id 相匹配。

为了演示整合的过程,我们从 Post 表中取出所有记录,从 followers 表中取出符合条件的记录插入在后边。如果没有匹配的话,Post 表中的记录就会被移除。

我们例子中这个临时表的连接的结果如下:

注:Post 表中的 user_id=1 记录被移除了,因为在 followers 表中没有 followed_id=1 的记录。

2.过滤

连接操作给我们被某人关注的用户的 blog 的列表,但是没有指出谁是关注者。我们仅仅对这个列表的子集感兴趣,我们只需要被某一特定用户关注的用户的 blog 列表。因此我们过滤这个表格,查询的过滤操作是:

filter(followers.c.follower_id == self.id)

注意查询是在我们目标用户的内容中执行,因为这是 User 类的一个方法,self.id 就是我们感兴趣的用户的 id。因此在我们的例子中,如果我们感兴趣的用户的 id 是 id=1,那么我们会得到另一个临时表:

3.排序

order_by(Post.timestamp.desc())

在这里,我们要说的结果应该按照 timestamp 字段按降序排列,这样的第一个结果将是最近的 blog。

这里还有一个小问题需要我们改善我们的查询操作。当用户阅读他们关注者的 blog 的时候,他们可能也想看到自己的 blog。因此最好把用户自己的 blog 也包含进查询结果中。

其实这不需要做任何改变。我们只需要把自己添加为自己的关注者。

五、成为自己的关注者

在 after_login 中处理 OpenID 的时候就设置自己成为自己的关注者(microblog.py)

 @oid.after_login
def after_login(resp):
if resp.email is None or resp.email == "":
flash('Invalid login. Please try again.')
return redirect(url_for('login'))
user = model.User.query.filter_by(email=resp.email).first()
if user is None:
nickname = resp.nickname
if nickname is None or nickname == "":
nickname = resp.email.split('@')[0]
nickname = model.User.make_unique_nickname(nickname)
user = model.User(nickname=nickname, email=resp.email)
db.session.add(user)
db.session.commit()
db.session.add(user.follow(user))
db.session.commit()
remember_me = False
if 'remember_me' in session:
remember_me = session['remember_me']
session.pop('remember_me', None)
login_user(user, remember = remember_me)
return redirect( url_for('index'))

六、关注以及取消关注的连接

1.定义关注及其取消关注用户的视图函数(microblog.py)

@app.route('/follow/<nickname>')
@login_required
def follow(nickname):
from model import User
user = User.query.filter_by(nickname=nickname).first()
if user is None:
flash('User %s not found.' % nickname)
return redirect(url_for('index'))
if user == g.user:
flash('You can\'t follow yourself!')
return redirect(url_for('user', nickname=nickname))
u = g.user.follow(user)
if u is None:
flash('Cannot follow ' + nickname + '.')
return redirect(url_for('user', nickname=nickname))
db.session.add(u)
db.session.commit()
flash('You are now following ' + nickname + '!')
return redirect(url_for('user', nickname=nickname)) @app.route('/unfollow/<nickname>')
@login_required
def unfollow(nickname):
from model import User
user = User.query.filter_by(nickname=nickname).first()
if user is None:
flash('User %s not found.' % nickname)
return redirect(url_for('index'))
if user == g.user:
flash('You can\'t unfollow yourself!')
return redirect(url_for('user', nickname=nickname))
u = g.user.unfollow(user)
if u is None:
flash('Cannot unfollow ' + nickname + '.')
return redirect(url_for('user', nickname=nickname))
db.session.add(u)
db.session.commit()
flash('You have stopped following ' + nickname + '.')
return redirect(url_for('user', nickname=nickname))
if __name__ == '__main__':
app.debug=True
app.run()

2.修改模板user.html

 {<!-- extend base layout -->
{% extends "base.html" %} {% block content %}
<table>
<tr valign="top">
<td><img src="{{user.avatar(128)}}"></td>
<td>
<h1>User: {{user.nickname}}</h1>
{% if user.about_me %}<p>{{user.about_me}}</p>{% endif %}
{% if user.last_seen %}<p><i>Last seen on: {{user.last_seen}}</i></p>{% endif %}
<p>{{user.followers.count()}} followers |
{% if user.id == g.user.id %}
<a href="{{url_for('edit')}}">Edit your profile</a>
{% elif not g.user.is_following(user) %}
<a href="{{url_for('follow', nickname = user.nickname)}}">Follow</a>
{% else %}
<a href="{{url_for('unfollow', nickname = user.nickname)}}">Unfollow</a>
{% endif %}
</p>
</td>
</tr>
</table>
<hr>
{% for post in posts %}
{% include 'post.html' %}
{% endfor %}
{% endblock %}

在编辑一行上,我们会显示关注者的用户数目,后面可能会跟随三种可能的链接:

(1)如果用户属于登录状态,“编辑” 链接会显示。

(2)否则,如果用户不是关注者,“关注” 链接会显示

(3)否则,一个 “取消关注” 将会显示。

flask_关注者,联系人和好友的更多相关文章

  1. Flask学习之八 关注、联系人和好友

    英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-viii-followers-contacts-and- ...

  2. flask_关注者

    表的模型实现 class Follow(db.Model): __tablename__ = 'follows' follower_id = db.Column(db.Integer,db.Forei ...

  3. mapreduce 查找共同好友

    A:B,C,D,F,E,O B:A,C,E,K C:F,A,D,I D:A,E,F,L E:B,C,D,M,L F:A,B,C,D,E,O,M G:A,C,D,E,F H:A,C,D,E,O I:A, ...

  4. python通过人脸识别全面分析好友,一起看透你的“朋友圈”

    微信:一个提供即时通讯服务的应用程序,更是一种生活方式,超过数十亿的使用者,越来越多的人选择使用它来沟通交流. 不知从何时起,我们的生活离不开微信,每天睁开眼的第一件事就是打开微信,关注着朋友圈里好友 ...

  5. python 使用wxpy实现获取微信好友列表 头像 群成员

    最近在学习 python 突然想要试试能不能把微信里面的微信群和好友取出来 结果百度了一下 找到了 wxpy 这怎么能不试一下呢 用到 wxpy.threading.os.time 四个库 第一步 判 ...

  6. python flask大型项目目录

    Hello World 作者背景 应用程序简介 要求 安装 Flask 在 Flask 中的 “Hello, World” 下一步? 模板 回顾 为什么我们需要模板 模板从天而降 模板中控制语句 模板 ...

  7. 微信jsApI及微信分享对应在手机浏览器的调用总结。

    摘录自别人的博客: 第一篇:微信内置浏览器的JsAPI(WeixinJSBridge续) 之前有写过几篇关于微信内置浏览器(WebView)中特有的Javascript API(Javascript ...

  8. 微信禁用右上角的分享按钮,WeixinJSBridge API以及隐藏分享的子按钮等菜单项

    <!--禁用微信分享按钮--> <script> function onBridgeReady() { WeixinJSBridge.call('hideOptionMenu' ...

  9. 《Spark大数据处理:技术、应用与性能优化 》

    基本信息 作者: 高彦杰 丛书名:大数据技术丛书 出版社:机械工业出版社 ISBN:9787111483861 上架时间:2014-11-5 出版日期:2014 年11月 开本:16开 页码:255 ...

随机推荐

  1. 经典的Hello World VFP前端调后端C# Webservice

    1.按我设想的三层架构中,VFP是完全可以做为前端UI的,我们可以划分如下三层结构: 图片:三层架构图.jpg[设为封面] [删除] 其实大家看图,都明白大致意思,但是要明白各层数据是怎么流动的,却要 ...

  2. IDEA工具使用说明

    IDEA使用说明 1.安装 2.开始界面 1)create New Project (新建项目) 2)Import Project  (导入项目) 3)Open (打开已有的项目) 4)Check o ...

  3. eclipse或者myeclipse安装svn报错”unable to load default svn client”

    是svn版本低了的问题 subeclipse下载,直接百度site1.X                  X为你需要的版本 解压site1.X 将此窗口先放到一边 在eclipse的安装目录下的dr ...

  4. Excel表格解析

    //add by yangwenpei WGCW-144 使用Excel表格导入纸票记录 20161212 start /** * @param fileInputStream * @param co ...

  5. Python—变量

    1.在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量 2.访问限制: class内部属性可以被外部 ...

  6. python 连接redis工具类

    #!/usr/bin/python # coding=utf-8 __author__ = 'shuangjiang' import redis import sys default_encoding ...

  7. 通过RGB灯输出七色

    本文由博主原创,如有不对之处请指明,转载请说明出处. /********************************* 代码功能:输出模拟信号,控制RGB灯的颜色 使用函数: pinMode(引脚 ...

  8. orcl 中decode的妙用

    在不认识这个函数之前,我们要对同一张表或者同一查询语句块中的某一个字段进行计算分类统计时,要用到很多函数以及外连接.用这一函数往往可以解决很多复杂的东西. 网上的一段具有代表性的decode用法,不用 ...

  9. Bimmap 成像用bitblt 缩放问题

    BitBlt不能变尺寸,但是StrectchBlt可以变尺寸,在使用StrectchBlt时要注意,首先要设置一下SetStretchBltMode,通常设成HALFTONE,这样缩放时就不会失真. ...

  10. R语言画图,根据正负值画不同颜色,并且画水平线或者垂直线

    col=ifelse(x<0, "blue", "red") #如果x值为负值,用蓝色表示,反之,用红色表示 abline(v=0,col="g ...