下面回到首頁中,使用一個賬戶登錄,你肯定已經注意到了這里的內容:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

沒錯,現(xiàn)在都是寫死的一些固定信息,其中分享數(shù)量很容易就可以獲取,只需要修改首頁模板:

<p class="text-muted">我已經分享<span class="text-danger">{{ current_user.posts.count() }}</span>條心情</p>

這樣就可以顯示,但是關注和被關注顯然就不是這么簡單了,首先要思考一下,一個人可以關注多個用戶,而一個用戶也可以被多個人關注,所以,這很明顯是一個多對多的關系,而同時,無論是關注用戶還是被別人關注,顯然都是針對的用戶表,所以,這是一個典型的單表自關聯(lián)的多對多關系,而多對多就需要使用關聯(lián)表進行連接,下面創(chuàng)建一個關聯(lián)表(models/Follow.py):

from .. import dbfrom datetime import datetimeclass Follow(db.Model):
    __tablename__="follows"
    follorer_id=db.Column(db.Integer,db.ForeignKey("users.id"),primary_key=True)
    follored_id=db.Column(db.Integer,db.ForeignKey("users.id"),primary_key=True)
    createtime=db.Column(db.DateTime,default=datetime.utcnow)

然而這時候,SQLAlchemy框架是無法直接使用的,如果要使用這個關聯(lián)表,需要把它拆解為兩個標準的一對多關系(User.py):

 #關注我的followers = db.relationship("Follow",foreign_keys=[Follow.followed_id],backref=db.backref("followed",lazy='joined'),lazy="dynamic",cascade="all,delete-orphan")#我關注的followed = db.relationship("Follow", foreign_keys=[Follow.follower_id], backref=db.backref("follower", lazy='joined'), lazy="dynamic",cascade="all,delete-orphan")

看到這個,有必要解釋一下了:

  1. foreign_keys很明顯是表示外鍵,因為followers和followed都是與Follow表進行關聯(lián),為了消除歧義,必須使用foreign指定特定外鍵。

  2. backref的作用是回引Follow模型,即即可從用戶查詢Follow模型,也可直接查詢Follow所屬的用戶

  3. 第一個lazy,即lazy=joined,表示直接通過連接查詢來加載對象,即通過一條語句查出用戶和所有的followed過的用戶(假設followed字段),而假設把它設為select的話,則需要對每個followed的用戶進行一次查詢操作

  4. 第二個lazy,即lazy=dynamic,表示此操作返回的是一個查詢對象,而不是結果對象,可以簡單理解為一個半成品的sql語句,可以在其上添加查詢條件,返回使用條件之后的結果

  5. 這兩個lazy的作用都在一對多關系中的一的一側設定,即第一個在回引,即直接可以通過已關注的對象找到自己,第二個是在本身,即可以直接返回的已關注列表,并可進行篩選操作(followed字段)

  6. cascade表示主表字段發(fā)生變化的時候,外鍵關聯(lián)表的響應規(guī)則,all表示假設新增用戶后,自動更新所有的關系對象,all也為默認值,但在這個關系中,刪除用戶后顯然不能刪除所有與他關聯(lián)的用戶,包括他關注的和關注他的,所以使用delete-orphan的刪除選項,即只刪除關聯(lián)關系的對象,對于這個例子來說,也就是所有Follow對象

下面在為User表添加些與關注有關的輔助方法

#關注用戶def follow(self,user):
    if(not self.is_following(user)):
        f=Follow(follower=self,followed=user)
        db.session.add(f);#取消關注def unfollow(self,user):
    f=self.followed.filter_by(followed_id=user.id).first()    if f:
        db.session.delete(f);#我是否關注此用戶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.followers.filter_by(followed_id=user.id).first() is not None;

更新一下數(shù)據(jù)庫:

python manage.py db migrate -m "新增用戶關注功能"python manage.py db upgrade

現(xiàn)在就可以把首頁用戶頭像下方內容補充完整:

{% if current_user.is_authenticated %}
 <img src="http://on4ag3uf5.bkt.clouddn.com/{{current_user.headimg}}" alt="..." class="headimg img-thumbnail">
 <br><br>
 <p class="text-muted">我已經分享<span class="text-danger">{{ current_user.posts.count() }}</span>條心情</p>
 <p class="text-muted">我已經關注了<span class="text-danger">{{ current_user.followed.count() }}</span>名好友</p>
 <p class="text-muted">我已經被<span class="text-danger">{{ current_user.followers.count() }}</span>名好友關注</p>
 {%endif%}

刷新一下看看效果:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

功能正確實現(xiàn),但是貌似數(shù)據(jù)有點慘,下面我們來實現(xiàn)關注功能,其實到了現(xiàn)在這一步,關注功能已經非常的簡單,一個最簡單的實現(xiàn)方式,在用戶資料頁面新增一個關注按鈕,修改用戶資料頁:

 <p>
    {% if current_user.is_authenticated and current_user!=user %}
        {% if current_user.is_following(user) %}
            <button class="btn btn-primary" type="button">
            已關注 <a href="#" class="badge">取消</a>
            </button>
        {% else %}
            <a href="#" type="button" class="btn btn-primary">關注此用戶</a>
        {% endif %}
    {% endif %}
        <!--顯示用戶列表-->
        &nbsp;&nbsp;<a href="#">共有{{user.followers.count()}}人關注</a>
        &nbsp;&nbsp;<a href="#">共關注{{user.followed.count()}}人</a>
    {% if current_user.is_authenticated and current_user!=user %}
        {% if current_user.is_followed_by(user) %}
            <span class="label label-default">已關注我</span>
        {% endif %}
    {% endif %}
    </p>

可以看到,很多的超鏈接的href都為#,下面完善這些指向的視圖模型,首先是關注:

@main.route("/follow/<int:userid>",methods=["GET","POST"])@login_requireddef follow(userid):
    user=User.query.get_or_404(userid)    if(current_user.is_following(user)):
        flash("您不能重復關注用戶")        return redirect(url_for(".user",username=user.username))
    current_user.follow(user)
    flash("您已經成功關注用戶 %s" % user.username)    return redirect(url_for(".user", username=user.username))

接下來是取消關注,與關注幾乎一模一樣:

@main.route("/unfollow/<int:userid>",methods=["GET","POST"])@login_requireddef unfollow(userid):
    user = User.query.get_or_404(userid)    if (not current_user.is_following(user)):
        flash("您沒有關注此用戶")        return redirect(url_for(".user", username=user.username))
    current_user.unfollow(user)
    flash("您已經成功取關用戶 %s" % user.username)    return redirect(url_for(".user", username=user.username))

然后是兩個用戶列表,分別是我關注的用戶和關注我的用戶,這兩個列表除了title之外,幾乎一摸一樣,所以完全可以使用一個視圖模型:

@main.route("/<type>/<int:userid>",methods=["GET","POST"])def follow_list(type,userid):
    user = User.query.get_or_404(userid)
    follows= user.followers if "follewer" ==type else user.followed
    title=("關注%s用戶為:"%user.nickname ) if "follewer" ==type else ("%s關注的用戶為"%user.nickname)    return  render_template("follow_list.html",user=user,title=title,follows=follows)

這個視圖模型沒什么好說的,但需要注意兩點:

  1. 很容易可以看到,flask支持在路由中多個動態(tài)參數(shù)

  2. python中不支持三目表達式,但可以使用 a if 條件 else b來實現(xiàn)三目表達式的功能

而視圖模板可以簡單設置為如下:

{% extends "base.html" %}{% block title %}{{title}}{% endblock %}{% block main %}<style type="text/css">
    .media-object{        width: 64px;        height:64px;
    }</style><div class="container"><div class="row">
    <div>
      {% for follow in follows %}
        {% if type=="follower" %}
        {% set user=follow.follower %}
        {% else %}
        {% set user=follow.followed %}
        {% endif %}
         <div class="         {% if loop.index % 2 ==0 %}
            bg-warning         {% else %}
           bg-info         {% endif %}
        " style="padding: 3px;">
                  <div class="media">
                  <div class="media-left">
                    <a href="#">
                      <img class="media-object" src="http://on4ag3uf5.bkt.clouddn.com/{{user.headimg}}" alt="...">
                    </a>
                  </div>
                  <div class="media-body">
                    <h4 class="media-heading">{{user.nickname}}</h4>
                    {{follow.follower.remark[0,50]}}
                      <div>
                         關注時間:{{moment(follow.createtime).format('LL')}}
                          &nbsp;&nbsp;                          {% if type=="follower" and  current_user.id==user.id %}
                            <a href="{{url_for('main.unfollow',userid=user.id)}}" class="badge">取消關注</a>
                         {% endif %}
                      </div>
                  </div>
                </div>
              </div>
      {% endfor %}
     </div>
 </div></div>{% endblock %}

同樣也比較簡單,新的內容只有一點:

{% if type=="follower" %}
    {% set user=follow.follower %}{% else %}
    {% set user=follow.followed %}{% endif %}

set這個語句在jinja2中定義一個變量,對于這里來說,如果參數(shù)為follower,則user為follow對象的follower屬性,反之則為followed屬性。

另外,還需要注意一點,若當前登錄用戶為“我”,而“我”關注了此用戶,則可以取消,若對方關注了“我”,則是沒有辦法取消的,因為“我”是被關注對象。

最終的顯示效果如下:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

不懂美工的苦:(

最后,想象一下實際應用場景,在我進入這個輕博客,我首先想要看到的,一般來說,都是我關注的內容,而首頁,一般都基于一定的算法,比如熱點,熱度,時間等挖掘出來的內容,對于數(shù)據(jù)挖掘這塊不會涉及,所以首頁只是按時間倒敘即可,但是我關注的內容則需要單獨提煉出來,并且各個產品都有不同的展現(xiàn)方式,比如墻外的tumblr登陸用戶默認進入一個mine頁,展示的都是自己關注的內容,而現(xiàn)在這個輕博客的展示方式則相對更簡單,在首頁增加一個tab塊即可,但是實現(xiàn)方式則不是那么簡單,下面理一下步驟:

  1. 登錄用戶,一直userid

  2. 根據(jù)userid,可獲取所有已關注用戶

  3. 根據(jù)已關注用戶,查詢發(fā)布的posts

根據(jù)這些步驟,如果直接寫sql的話,非常簡單,我想只要對follow的邏輯理解了,任何一個入行的人都可以很輕松的寫出來:

SELECT posts.* FROM posts LEFT JOIN follows ON posts.author_id=follows.followed_id WHERE follows.follower_id=1

但這個用SQLAlchemy實現(xiàn)稍微有些麻煩,因為涉及了一些新的語法:

db.session.query(Post).select_from(Follow).filter_by(follower_id=self.id).join(Post,Follow.followed_id == Post.author_id)

語法不復雜,但與sql語句的書寫順序稍顯不同:

db.session.query(Post) \\查詢主表為Postselect_from(Follow)    \\關聯(lián)Followfilter_by(follower_id=self.id)  \\與之前普通查詢一樣,過濾語句,對應where條件join(Post,Follow.followed_id == Post.author_id) \\兩表聯(lián)結

為了操作方便,將此語句作為方法新增到user模型中:

class User(UserMixin,db.Model):
    ...    def followed_posts(user):
        return None if not user.is_administrator() else db.session.query(Post).select_from(Follow).filter_by(follower_id=user.id).join(Post,Follow.followed_id == Post.author_id)

而視圖模型則修改為:

@main.route("/",methods=["GET","POST"])def index():
    form=PostForm()    if form.validate_on_submit():
        post=Post(body=form.body.data,author_id=current_user.id)
        db.session.add(post);        return redirect(url_for(".index")) #跳回首頁
    posts=Post.query.order_by(Post.createtime.desc()).all()  #首頁顯示已有博文 按時間排序
    return render_template("index.html",form=form,posts=posts,follow_post=User.followed_posts(current_user))

在首頁模板中,全部post和已關注用戶的post除了post的list之外,其余的內容一模一樣,作為一個有bigger的碼農來說,當然不能復制粘貼了,這時候可以使用宏頁面("\templates_index_post_macros.html")

{% macro rander_posts(posts,moment) %}{% for post in posts %}
  <div class="bs-callout          {% if loop.index % 2 ==0 %}
           bs-callout-d          {% endif %}
          {% if loop.last %}
            bs-callout-last          {% endif %}" >
      <div class="row">
          <div class="col-sm-2 col-md-2">
                <!--使用測試域名-->
               <a class="text-left" href="{{url_for('main.user',username=post.author.username)}}">
                <img src="http://on4ag3uf5.bkt.clouddn.com/{{post.author.headimg}}" alt="...">
               </a>
          </div>
          <div class="col-sm-10 col-md-10">
           <div>
            <p>
               {% if post.body_html%}
                  {{post.body_html|safe}}
                {% else %}
               {{post.body}}
               {% endif %}
            </p>
            </div>
           <div>