1. 程式人生 > >Flask Web開發:用Select2實現類似知乎的標籤系統

Flask Web開發:用Select2實現類似知乎的標籤系統

知乎的標籤是這樣的

這裡寫圖片描述

用Select 2實現的是這樣的

這裡寫圖片描述

實現分類

Select2主要是用來美化分類表單,在使用之前要先實現分類的功能,簡單的可以一對多,難一點可以搞一個多對多

一對多關係的分類

如果只是想一篇文章設定一個分類,可以用一對多,“一”這邊是某個分類,然後“多”這一邊就是要新增到該分類下的文章,書中雖然沒有直接實現這樣的分類功能,但是已經實現了一對多的關係,可以參考著來實現,書中的是Post模型跟Comment模型的一對多,實現一對多關係的分類,可以將Post模型換成Category模型,Comment對應地換成Post,參考程式碼:

class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(32), unique=True, index=True,
        nullable=False)
    posts = db.relationship('Post', backref='category', lazy='dynamic')

    # 可以新增一個generate_fake函式,用來測試的時候生成假的分類
    @staticmethod
    def generate_fake(count=100):
        from sqlalchemy.exc import IntegrityError
        from random import seed, randint
        import forgery_py

        seed()
        for i in range(count):
            t = Category(name=forgery_py.lorem_ipsum.word())
            db.session.add(t)
            try:
                db.session.commit()
            except IntegrityError:
                db.session.rollback()

    def __repr__(self):
        return '<Category %r>' % self.name

# 寫完了Category類,相應地在Post類裡面改一下
class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.Text)
    body = db.Column(db.Text)
    body_html = db.Column(db.Text)
    summary = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    comments = db.relationship('Comment', backref='post', lazy='dynamic')
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))

    ...

多對多關係的分類

之所以會是多對多,是因為一篇文章可以有多個分類(或者標籤Tag),然後一個分類(或者標籤Tag)也有多篇文章,具體的實現在《Flask Web開發:基於Python的Web應用開發實戰學習筆記》第12章的第一小節就有講到,書中舉了一個學生註冊課程的例子,使用一個關聯表,裡面有兩個外來鍵,一個指向學生類Student另一個指向課程類Class,實現Post跟Category(或者是Tag)的多對多關係,可以參考Student跟Class類的關係,Post可以對應於Student,給Post新增一個分類,就相當於學生註冊一門課程,然後Category可以對應於Class,參考程式碼:

# 關聯表
post_tag_table = db.Table('post_tag_table',
    db.Column('post_id', db.Integer, db.ForeignKey('posts.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')))

# 這裡的程式碼叫Tag,如果想叫做分類改成Category就可以了
class Tag(db.Model):
    __tablename__ = 'tags'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(32), unique=True, index=True,
        nullable=False)

    # 測試用的程式碼,用來生成假的分類(或者標籤)
    @staticmethod
    def generate_fake(count=100):
        from sqlalchemy.exc import IntegrityError
        from random import seed, randint
        import forgery_py

        seed()
        for i in range(count):
            t = Tag(name=forgery_py.lorem_ipsum.word())
            db.session.add(t)
            try:
                db.session.commit()
            except IntegrityError:
                db.session.rollback()

    def __repr__(self):
        return '<Tag %r>' % self.name

class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.Text)
    body = db.Column(db.Text)
    body_html = db.Column(db.Text)
    summary = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    comments = db.relationship('Comment', backref='post', lazy='dynamic')
    is_about = db.Column(db.Boolean, default=False, index=True)
    # Post類裡面跟Tag的關係處理
    tags = db.relationship('Tag',
        secondary=post_tag_table,
        backref=db.backref('posts', lazy='dynamic'),
        lazy='dynamic')

實現了多對多之後,要想給文章新增分類,可以這樣:

post.tags.append(tag)
db.session.add(post)    # 別忘了提交到資料庫

要想刪除新增到文章裡面的分類,可以這樣:

post.tags.remove(tag)

在編輯文章的時候要注意,如果在編輯文章的時候不加判斷直接地新增,那會導致同一篇文章新增到同一個標籤裡面兩次甚至多次,這樣的話刪除的時候資料庫會出錯的,所以編輯文章的時候,需要在檢視函式裡面做一下判斷,如果某篇文章已經有了這個分類,那就要跳過不能把同一篇文章新增到同一個分類裡面多次

表單的處理

如果是一對多,表單的category(或者tag)屬性可以用SelectField,這個一次只能選擇一個值,如果是多對多表單的category(或者tag)可以用SelectMultipleField或者QuerySelectMultipleField,不管用哪個,在還沒有用Select2做處理之前,它看起來都是下面這樣的:

這裡寫圖片描述

按下Ctrl鍵的同時用滑鼠左鍵可以選擇多個值

用Select2美化表單

匯入檔案

到Select2的主頁:https://select2.github.io/,先下載Select2,然後往下拖到Getting started with Select2,如果英文可以的話,按照官網的來操作就可以了,按照首頁說明導完檔案,然後參考一下下面兩個地方里面的示例和說明就可以搞定了:

這裡寫圖片描述

在模版裡面匯入檔案,可以用CDN也可以用下載下來的檔案,解壓之後可以在dist資料夾找到那兩個檔案,不過在匯入那兩個檔案之前必須先匯入JQuery,下載下來的Select2資料夾裡面也有JQuery,不過是舊版本的,新的可以在這裡找到: http://jquery.com/download/ , 模版參考程式碼:

{% extends "admin/base.html" %}

{% block head %}
{{ super()}}
<link rel="stylesheet" href="{{ url_for('static', filename='css/select2.min.css') }}" />
{% endblock %}

{% block page_content %}
<div class="container">
<div class="row">
    <h1 class="text-center">新增文章</h1>
    <div class="editor">
        <form method="post" action="{{ url_for('.write_post') }}">
        {{ form.hidden_tag() }}
                    {{ form.title.label }} {{ form.title(class='form-control') }}
                    {{ form.tags.label }} {{ form.tags(class='form-control tags') }}
                    {{ form.body.label }} {{ form.body(id='mdeditor') }}
                    <input class="btn btn-default" type="submit" style="float: right; margin-top: 20px;" value="提交">
        </form>
    </div>
</div>
</div>
{% endblock %}

{% block scripts %}
{{ super() }}
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.1.1.js') }}"></script>
<script src="{{ url_for('static', filename='js/select2.min.js') }}"></script>
<script type="text/javascript">
    $('select').select2({});
</script>
{% endblock %}

匯入檔案之後的樣子

這裡寫圖片描述

其實預設的樣子是比較醜的,上面的是設定了Bootstrap主題之後的樣子,預設的是這樣的

這裡寫圖片描述

官網首頁有Bootstrap 3的主題,下載之後找到select2-bootstrap.css匯入再設定一下就可以了,

......
{% block head %}
{{ super()}}
<link rel="stylesheet" href="{{ url_for('static', filename='css/select2.min.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/select2-bootstrap.css') }}" />
{% endblock %}

{% block content %}
......
{% endblock %}

{% block scripts %}
{{ super() }}
......
{% endblock %}
......

Select 2相關的設定

主題設定

前面匯入了 Bootstrap 風格主題的CSS檔案,匯入之後還不能起作用,需要再JavaScript程式碼裡面設定一下:

<script type="text/javascript">
    $('#tags').select2({
        theme: 'bootstrap',
});
</script>

新增多語言支援

Select 2預設的提示語句預設是英文的,在dist\js\i18n下面找到zh-CN.js檔案,匯入再設定一下就可以支援中文了,匯入
<script src="{{ url_for('static', filename='js/zh-CN.js') }}" ></script>
設定只需要在前面的程式碼裡面新增這樣一句程式碼就可以了

<script type="text/javascript">
    $('#tags').select2({
        // 下面多條語句之間要有逗號
        theme: 'bootstrap',
        language: 'zh-CN',
});
</script>

其它設定

其它的一些設定基本不用匯入什麼檔案了,直接在JavaScript程式碼裡面新增就可以了

<script type="text/javascript">
$('#tags').select2({
        theme: 'bootstrap',
        placeholder: "新增標籤", // 設定選擇框裡面的提示性語句
        maximumSelectionLength: 5, // 設定最多可以選擇多少個
        allowClear: true, // 設定是否可以一次性清除掉所有標籤
        language: 'zh-CN'
});
</script>

SelectMultipleField和QuerySelectMultipleField

這兩個都可以提供多選,但是表單裡面使用SelectMultipleField的話,在檢視函式裡面處理多個標籤會比較麻煩,用QuerySelectMultipleField直接賦值就可以了,像這樣

def edit_post(id):
    post = Post.query.get_or_404(id)
    form = PostForm()
    if form.validate_on_submit():
        post.title = form.title.data
        post.body = form.body.data
        for tag in post.tags.all():
            post.tags.remove(tag)
        for tag in form.tags.data:
            post.tags.append(tag)
        db.session.add(post)
        db.session.commit()
        flash(u'文章內容已修改!', 'success')
        return redirect(url_for('.posts_manage'))
    form.title.data = post.title
    form.body.data = post.body
    form.tags.data = post.tags.all() // 用QuerySelectMultipleField的話這裡直接賦值給form.tags.data就可以了,用SelectMultipleField就不行了
    return render_template('admin/edit_post.html', form=form, post=post)

大概是因為QuerySelectMultipleField是有ORM支援的欄位(fields),它提供的多選框裡面返回的是資料庫物件,而SelectMultipleField沒有ORM支援,所以處理起來比較麻煩