应用项目首页目前按时间降序显示数据库中的所有文章,本节我们将在首页添加2个导航显示卡,分别显示所有文章以及所关注用户的文章。(在User类的初始化方法中,将用户关注自身,在Follower中也可以看到用户自己发表的文章)。
一. 使用数据库联结查询所关注用户的所有文章
显示所关注用户发布的所有文章,第一步显然是要获取这些用户,然后获取各用户的文章,再按一定顺序排列,写入一个列表。但实际上由于这种方式的伸缩性不好,我们不回去使用它。随着数据不断变大,生成这个列表的工作量也不断增长,而且分页等操作也无法高效完成。这是一个常见的问题,称之为"N+1"问题,比如我们关注了N个用户,则获取关注用户的所有文章则需要发起N+1次查询。
高效获取博客文章,而不管数据库有多大,最好的方法是在一次查询中完成所有操作。我们使用联结来完成以上操作,联结操作用到2个或多个表,在其中查找满足条件的记录组合,再把记录组合插入一张临时表中。这个临时表就是联结查询的结果。在User模型中新增followed_posts方法属性:
class User(UserMixin, db.Model):
...
@property
def followed_posts(self):
return Post.query.join(Follow, Post.author_id == Follow.followed_id).filter(Follow.follower_id == self.id)
注:因为Post和Follow之间并没有定义外键,使用join联结时必须指定关联的条件。这里的联结为内联结,也可以形象地表述为取二者的交集;
二. 在首页显示所关注用户的文章
我们将决定显示所有博客文章还是只显示所关注用户文章的选项存储在名为show_followed的cookie中,下面我们修改index视图函数:
app/main/views.py:显示所有博客文章还是只显示关注用户的文章
@main.route('/', methods=['POST', 'GET'])
def index():
form = PostForm()
if current_user.can(Permission.WRITE) and form.validate_on_submit():
post = Post(body=form.body.data, author=current_user._get_current_object())
db.session.add(post)
db.session.commit()
return redirect(url_for('.index'))
page = request.args.get('page', 1, type=int)
# posts = Post.query.order_by(Post.timestamp.desc()).all()
# pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], error_out=False)
show_followed = False
if current_user.is_authenticated:
show_followed = bool(request.cookies.get('show_followed', ''))
if show_followed:
query = current_user.followed_posts
else:
query = Post.query
pagination = query.order_by(Post.timestamp.desc()).paginate(
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], error_out=False)
posts = pagination.items
return render_template('index.html', form=form, posts=posts, pagination=pagination, show_followed=show_followed)
显示所有用户文章时使用顶级查询Post.query,显示关注用户文章时使用新属性User.followed_posts。show_followed cookie在如下2个新路由中设定:
@main.route('/all')
@login_required
def show_all():
resp = make_response(redirect(url_for('.index')))
resp.set_cookie('show_followed', '', max_age=30*24*60*60) # 30天
return resp
@main.route('/followed')
@login_required
def show_followed():
resp = make_response(redirect(url_for('.index')))
resp.set_cookie('show_followed', '1', max_age=30*24*60*60) # 30天
return resp
cookie只能在响应对象对象中设置,因此这2个路由不能依赖Flask,要使用make_response()方法创建响应对象。set_cookieI()函数前2个值分别为cookie名称和值,可选的max_age参数设置cookie的过期时间,单位为秒。如果不指定max_age参数,浏览器关闭后cookie就会过期。
最后,在模板中添加2个导航选项卡,分别调用/all和/followed路由,并在会话中设置正确的值。
app/templates/index.html {% include '_posts.html' %}改为:
<div class="post-tabs">
<ul class="nav nav-tabs">
<li {% if not show_followed %}class="active"{% endif %}>
<a href="{{ url_for('.show_all') }}">ALL</a>
</li>
<li {% if show_followed %}class="active"{% endif %}>
<a href="{{ url_for('.show_followed') }}">Followers</a>
</li>
</ul>
{% include '_posts.html' %}
</div>
三. 把用户设为自己的关注者
为了在所关注用户的文章列表中看到用户自己发布的文章,我们在User的__init__()方法中,使用self.follow(self)把用户设为自己的关注者。如果数据库中已存在一些用户,而且没有关注自己,正确的办法是添加一个函数,更新现有用户。创建函数更新数据库这一技术经常用来更新已部署的应用:
class User(UserMinxin, db.Model):
@staticmethod
def add_self_follows():
for user in User.query.all():
if not user.is_following(user):
user.follow(user)
db.session.commit()
执行: