1. 程式人生 > >記錄一次完整的flask小型應用開發(5)-- 高階多對多關係

記錄一次完整的flask小型應用開發(5)-- 高階多對多關係

使用者之間互相follow

接下來,我們實現,讓使用者可以關注其他使用者,並且在首頁只顯示所關注使用者釋出的部落格文章。
多對多關係建立的解決辦法是新增第三張表,這個表為關聯表,多對多關係可以分解為原表和關聯表之間的兩個一對多關係。多對多關係仍然使用定義一對多關係的db.relationship()方法來定義,但是在多對多關係中,必須把secondary引數定義為關聯表,並且,多對多關係可以在任意一個類中定義,backref引數會處理好關係的另一側!關聯表就是一個簡單的表,不是模型。

那麼問題來了,學生和學生選課也是多對多關係,但是簡單的是這個例子中,關聯表連線的是學生和課程兩個明確的實體物件。
但是再想想我們的使用者互相關注的例子,只有使用者這一個實體。那麼這種關係就是自引用關係,即關係表的兩側都在同一個實體上面。

使用多對多關係的時候,往往需要儲存所聯絡的兩個實體之間的額外資訊,所以我們可以儲存使用者關注的時間,為了能夠在關係中處理自定義的資料,我們需要提升關聯表的地位,使其變成程式可訪問的模型。

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)

然後在user模型裡面定義關係:

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')

為了消除外來鍵之間的歧義,必須使用可選引數foreign_keys=來指定外來鍵

lazy引數: 這個lazy模式可以實現立即從聯結查詢中載入相關物件,比如某使用者關注了100位使用者,使用user.followed.all()會返回一個列表,包含了100個Follow例項,每一個例項的follower和followed回引屬性都指向相對應的使用者。當lazy設定為joined時,就一個一次性完成這些操作,如果是預設屬性select,那麼每一個屬性都需要單獨的查詢。lazy引數都在1的這一側,返回的結果是多的這一側中的記錄,使用dynamic的話,關係屬性不會直接返回記錄,而是返回查詢的物件。

cascade引數: 表示層疊選項,all表示除了delete-orphan之外的所有層疊選項,所以設為all,delete-orphan表示啟動所有的層疊選項,並且也刪除孤兒記錄。

接下來,我們在user模型中定義一些我們經常用到的方法:

# 從這裡開始是為了實現使用者關注功能的輔助方法
    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(follower_id=user.id).first() is not None

    def follow(self, user):
        if not self.is_following(user):
            # 這裡表示如果沒有關注那麼就關注他
            f = Follow(follower=self, followed=user)
            db.session.add(f)
            db.session.commit()

    def unfollow(self, user):
        f = self.followed.filter_by(followed_id=user.id).first()
        db.session.delete(f)
        db.session.commit()

接下來我們要在資料頁實現如下功能:

  • 未關注使用者,顯示follow按鈕
  • 已關注使用者,顯示unfollow按鈕
  • 顯示出關注者和被關注者的數量,以及列表
  • 相應的使用者頁面顯示follows you標識

所以我們首先修改資料頁的模板:

    {% if current_user.can(Permission.FOLLOW) and user != current_user %}
        {% if not current_user.is_following(user) %}
        <a href="{{ url_for('main.follow', username=user.username) }}" class="btn btn-primary">Follow</a>
        {% else %}
        <a href="{{ url_for('main.unfollow', username=user.username) }}" class="btn btn-default">UnFollow</a>
        {% endbif %}
    {% endif %}


    <a href="{{ url_for('main.followers', username=user.username) }}">
        Followers: <span class="badge">{{ user.followers.count() }}</span>
    </a>
    <a href="{{ url_for('main.following', username=user.username) }}">
        Following: <span class="badge">{{ user.followed.count() }}</span>
    </a>

    {% if current_user.is_authenticated() and user != current_user and user.is_following(current_user) %}
       | <span class="label label-default">He already followed you!</span>
    {% endif %}

這裡面有四個路由需要我們去實現,下面我們開始實現路由:

# 使用者在其他使用者的頁面點選follow,呼叫follow/username路由
@main.route('/follow/<username>')
@login_required
def follow(username):
    user = User.query.filter_by(username=username).first()
    if user is None:
        flash('invalid user')
        return redirect(url_for('main.index'))
    # 這裡其實不是很必要,因為如果已經關注了,頁面都不會顯示這個按鈕
    if current_user.is_following(user):
        flash('you already follow this user')
        return redirect(url_for('main.user', username=username))
    current_user.follow(user)
    flash('ok, you followed this user now')
    return redirect(url_for('main.user', username=username))


@main.route('/unfollow/<username>')
@login_required
def unfollow(username):
    user = User.query.filter_by(username=username).first()
    current_user.unfollow(user)
    flash('ok, you have cancled to follow this user')
    return redirect(url_for('main.user', username=username))


@main.route('/followers/<username>')
@login_required
def followers(username):
    flash('not finished')
    return redirect(url_for('main.user', username=username))


@main.route('/following/<username>')
@login_required
def following(username):
    flash('not finished')
    return redirect(url_for('main.user', username=username))