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支援,所以處理起來比較麻煩