之前的部分实现了以下功能:基本的主页展示,登录登出系统,注册系统,撰写与编辑文章功能,用户个人页面的展示,分页功能,密码的修改,邮件认证身份。接下来的一个功能就是关注与被关注功能了。
经常逛微博或者其他社交类网站的人都知道,我们可以查看某个用户关注了多少人,分别是谁,或者这个用户的粉丝有多少,分别是谁。并且,如果我已经login的话,我可以去关注我还没有关注的人,或者取关我已经关注的人。
了解了需要达成的功能之后,接下来需要做的就是去实现它们。
在实现之前,必须要搞清楚实现这些功能的逻辑。
首先是第一个问题,也是最主要的一个问题,关注者与被关注者在数据库中都属于User,我们应该如何去建立这样一个多对多关系?
在这里可以举一个学生选课的例子。站在一个后台管理者的角度来看,我们有两组数据项目,一个是学生,一个是课程。我们一方面要知道某个学生选了哪些课,好来给学生的课表生成提供帮助。另一方面,我们要知道某门课有多少学生选择,来方便安排教师和教室。这就是一个多对多的典型例子。
这个问题困扰了大家许久,尤其是在学生与课程规模日益增多的背景下。于是乎某天有位兄弟灵光一现(当然不是我啦~),他想出了这样一个办法:何不把学生的id与他选的某门课的id绑定在一起,作为第三个数据库的信息。这样以来,当我们需要某个学生选的课程时,我们通过用学生id进行索引,就可以找到他所选的课程。反过来也是如此。
这个方法的表示如下图:
有所不同的是,关注者和被关注者都属于一个库,不过这并不影响。
首先,建立一个名为Follow的模型,其中包括follower_id和followed_id
以及timestamp(用来记时间,将用户排序),源代码如下:
class Follow(db.Model):
__tablename__='follows'
follower_id=db.Column(db.Integer,db.ForeignKey('users.id'),primary_key=True)
followed_id = db.Column(db.Integer,db.ForeignKey('users.id'),primary_key=True)
timestamp = db.Column(db.DateTime,default=datetime.utcnow)
Follow模型当中的两个id通过外键users.id与User模型建立关系,相对应的,User模型中也要建立相关的Column来匹配。以下是源码:
followed=db.relationship('Follow',foreign_keys[Follow.follower_id],backref=db.backref('follower',lazy='joined'),lazy='dynamic',cascade='all,delete-orphan')
followers = db.relationship('Follow',foreign_keys=[Follow.followed_id],backref=db.backref('followed',lazy='joined'),lazy='dynamic',cascade='all,delete-orphan')
由于代码太长,加上我又不知道咋排版。。这段代码放到编辑器里看起来有点丑。。
这里要对上面这段代码重点理解一下。首先要明确的是
第一个参数“Follow”指对应的模型的名称。foreign_key这里就有说法了,第一次看这里的时候其实很不理解,后来写博客的时候回顾了一下,说说自己的理解吧:
1.首先要知道 follower_id和followed_id分别代表了什么。follower_id 代表的是关注者的id,所谓关注者也就是关注别人的人,而这个被关注的人的id就是followed_id,也就是说 follower_id(关注了)followed_id
2.要明确一点,followed_id和follower_id在数据库中是在一行的,两者在数据库中是作为一条数据和timestamp绑定在一起的。
那么现在提问一个问题,假如我叫mike,我的id是5,我关注了susan,他的id是10,我还关注了kobe,他的id是8,现在我想在网页中查看mike关注了谁(mike has followed who?)也就是User库中的followed,我应该用哪条信息来检索Follow模型呢?
答案是follower_id。通过follwer_id=5,获得相应的Column,然后从中获取followed_id再返回User库通过id进行查询,就可以得到kobe和susan对象了。
3.通过2中的说明,我们就可以知道foreign_keys的意义了。因为followed_id和follower_id都通过外键users.id与User(users.id=followed_id或者users.id=follower_id)建立联系,所以我们要指定其中一个与User中的follower或者follwed建立联系,以完成区分,确保2者不会混淆。
4.lazy=joined。简单来说就是把followed和follower链接到一张表中,从而提高效率。因为链接到一张表中时,查询仅需要一次。而如过是select的话,我们需要再通过followed_id或者follower_id返回User模型中重新再查询一遍,这样就无故增加了操作量。
完成了模型的建立之后,我们就可以开始构思视图函数了。
整个关注与取关功能涉及到两个操作和两种状态。两个操作包括:1.关注 2.取消关注。两种状态包括:1.已关注(未关注) 2.被关注(未被关注)
整个逻辑关系如下:1.只能关注还未关注的人。2.只能取关已关注的人。
因此我们需要再models.py中对这四个兄弟进行定义,然后再编写视图函数。
关注操作:
def follow(self,user):
if not self.is_following(user):
fo = Follow(follower_id=self.id,followed_id=user.id)
db.session.add(fo)
取消关注操作:
def unfollow(self,user):
if self.is_following(user):
fo = self.followed.filter_by(followed_id=user.id)
db.session.delete(fo)
已关注状态:
def is_following(self,user):
return self.followed.filter_by(followed_id=user.id).first() is not None
已被关注状态:
def is_followed_by(self,user):
return self.follower.filter_by(follower_id=user.id).first() is not None
完成了这几项之后,我们就可着手去实现一些功能了。
1.关注功能
@main.route('/follow/<username>')
@login_required
@permission_required(Permission.FOLLOW)
def follow(username):
user = User.query.filter_by(username=username).first()
if user is None:
flash('用户不存在')
return redirect(url_for('main.index'))
if current_user.is_following(user):
flash('您已关注该用户')
return redirect(url_for('main.user',username=username))
current_user.follow(user)
flash('已关注')
return redirect(url_for('main.user',username=username))
2.取关功能
@main.route('/unfollow/<username>')
@login_required
@permission_required(Permission.FOLLOW)
def unfollow(username):
user = User.query.filter_by(username=username).first()
if user=None:
flash('用户不存在')
return redirect('main.index')
if not user.is_followed_by(current_user):
flash('您未关注该用户')
return redirect(url_for('main.user',username=username))
current_user.unfollow(user)
flash('已取消关注')
return redirect(url_for('main.index'))
3.显示粉丝
@main.route('/followers/<username>')
def follower(username):
user = User.query.filter_by(username=username).first()
if user is None:
flash('用户不存在')
return redirect(url_for('main.index'))
page = requests.args.get('page',1,type=int)
pagination = user.followers.paginate(page=page,per_page=5,error_out=False)
follows = [{'user':item.follower,'timestamp':item.timestamp} for item in pagination.items]
return render_template(('followers.html',user=user,endpoint='.followers',pagination=pagination,follows=follows))
4.显示关注者(与3同理)
同时再在user.html页面中建立4个标签或者按钮(关注,取关,显示粉丝/关注者),指向这四个视图函数。
这一章涉及到许多数据库的相关知识,对于我这样初涉it领域的noob来说,理解这些东西着实费了一番功夫。基础还是重要呀。。
欢迎各种指错和讨论,不过好像没人看。。