Django搭建個人部落格:文章標籤功能

杜賽_dusai發表於2019-02-12

“標籤”是作者從文章中提取的核心詞彙,其他使用者可以通過標籤快速瞭解文章的關注點。每一篇文章的標籤可能都不一樣,並且還可能擁有多個標籤,這是與欄目功能不同的。

好在標籤功能也有優秀的三方庫:Django-taggit,省得自己動手設計了。快速開發就是這樣,能“借用”就不要自己重複勞動。

安裝及設定

首先在虛擬環境中安裝Django-taggit:

pip install django-taggit
複製程式碼

安裝成功後,修改專案設定以新增庫:

my_blog/settings.py

...
INSTALLED_APPS = [
    ...
    'taggit',
]
...
複製程式碼

修改文章模型

標籤是文章Model的屬性,因此需要修改文章模型。

需要注意的是標籤引用的不是內建欄位,而是庫中的TaggableManager,它是處理多對多關係的管理器:

article/models.py

...

# Django-taggit
from taggit.managers import TaggableManager

...
class ArticlePost(models.Model):
    ...

    # 文章標籤
    tags = TaggableManager(blank=True)

    ...
複製程式碼

然後記得資料遷移

帶標籤文章的發表

修改文章的表單類,讓其能夠提交標籤欄位:

article/forms.py

...
class ArticlePostForm(forms.ModelForm):
    class Meta:
        ...
        fields = ('title', 'body', 'tags')
複製程式碼

然後修改發表文章的檢視,儲存POST中的標籤:

article/views.py

...
def article_create(request):
    # 已有程式碼
    if request.method == "POST":
        article_post_form = ArticlePostForm(data=request.POST)
        if article_post_form.is_valid():
            new_article = article_post_form.save(commit=False)
            ...
            new_article.save()
            
            # 新增程式碼,儲存 tags 的多對多關係
            article_post_form.save_m2m()
            
            ...
複製程式碼

需要注意的是,如果提交的表單使用了commit=False選項,則必須呼叫save_m2m()才能正確的儲存標籤,就像普通的多對多關係一樣。

最後就是在發表文章的模板中新增標籤的表單項了:

templates/article/create.html

...
<!-- 提交文章的表單 -->
<form method="post" action=".">
    ...
    
    <!-- 文章標籤 -->
    <div class="form-group">
        <label for="tags">標籤</label>
        <input type="text" 
               class="form-control col-3" 
               id="tags" 
               name="tags"
        >
    </div>
    
    ...
</form>
...
複製程式碼

執行伺服器,就可以在發表頁面看到效果了:

Django搭建個人部落格:文章標籤功能

多個標籤最好用英文逗號進行分隔。中文逗號有的版本會報錯,乾脆就不要去使用了。

列表中顯示標籤

雖然儲存標籤的功能已經實現了,還得把它顯示出來才行。

顯示標籤最常用的位置是在文章列表中,方便使用者篩選感興趣的文章。

修改文章列表的模板,將標籤顯示出來:

templates/article/list.html

...
<!-- 欄目 -->
...

<!-- 標籤 -->
<span>
    {% for tag in article.tags.all %}
        <a href="#"
           class="badge badge-secondary" 
        >
            {{ tag }}
        </a>
    {% endfor %}
</span>

...
複製程式碼

連結中的class中是Bootstrap定義的徽章樣式

插入位置緊靠在欄目按鈕的後面。當然你想放到其他位置也是完全可以的。

重新整理列表頁面看看效果:

Django搭建個人部落格:文章標籤功能

標籤過濾

有時候使用者想搜尋帶有某一個標籤的所有文章,現在就來做這個功能。

與搜尋功能一樣,只需要調取資料時用filter()方法過濾結果就可以了。

修改<a>標籤中的href,使其帶有tag引數返回到View中:

templates/article/list.html

...

<!-- 標籤 -->
<span>
    {% for tag in article.tags.all %}
        <a href="{% url 'article:article_list' %}?tag={{ tag }}"
           class="badge badge-secondary" 
        >
            {{ tag }}
        </a>
    {% endfor %}
</span>

...
複製程式碼

然後在View中取得tag的值,並進行搜尋。

下面的程式碼將article_list()函式完整寫出來了(包括上一章末尾沒講的欄目查詢),方便讀者比對。

article/views.py

...
def article_list(request):
    # 從 url 中提取查詢引數
    search = request.GET.get('search')
    order = request.GET.get('order')
    column = request.GET.get('column')
    tag = request.GET.get('tag')

    # 初始化查詢集
    article_list = ArticlePost.objects.all()

    # 搜尋查詢集
    if search:
        article_list = article_list.filter(
            Q(title__icontains=search) |
            Q(body__icontains=search)
        )
    else:
        search = ''

    # 欄目查詢集
    if column is not None and column.isdigit():
        article_list = article_list.filter(column=column)

    # 標籤查詢集
    if tag and tag != 'None':
        article_list = article_list.filter(tags__name__in=[tag])

    # 查詢集排序
    if order == 'total_views':
        article_list = article_list.order_by('-total_views')

    paginator = Paginator(article_list, 3)
    page = request.GET.get('page')
    articles = paginator.get_page(page)
    
    # 需要傳遞給模板(templates)的物件
    context = {
        'articles': articles,
        'order': order,
        'search': search,
        'column': column,
        'tag': tag,
    }
    
    return render(request, 'article/list.html', context)

...
複製程式碼

注意Django-taggit中標籤過濾的寫法:filter(tags__name__in=[tag]),意思是在tags欄位中過濾nametag的資料條目。賦值的字串tag用方括號包起來。

之所以這樣寫是因為Django-taggit還支援多標籤的聯合查詢,比如:

Model.objects.filter(tags__name__in=["tag1", "tag2"])

為了實現帶引數的交叉查詢,還要將翻頁等位置的href修改一下:

templates/article/list.html

...

<!-- 所有類似地方加上 tag 引數,如排序、翻頁等 -->

<a href="{% url 'article:article_list' %}?search={{ search }}&column={{ column }}&tag={{ tag }}">
    最新
</a>

...

<a href="{% url 'article:article_list' %}?order=total_views&search={{ search }}&column={{ column }}&tag={{ tag }}">
    最熱
</a>

...

<a href="?page=1&order={{ order }}&search={{ search }}&column={{ column }}&tag={{ tag }}" class="btn btn-success">
    &laquo; 1
</a>

<!-- 上面3條是舉例,其他類似地方也請補充進去 -->
...
複製程式碼

標籤過濾功能就完成了。

Django-taggit更多的用法請閱讀官方文件:Django-taggit

總結

本章學習了使用Django-taggit來完成標籤功能。

在學習階段,你可以不借助他人的輪子,自己實現功能:瞎折騰對掌握基礎有很大幫助。

實際開發時,又分為兩種情況:

  • 淺層需求某項通用功能,開發完成後改動不大:此類功能建議儘量使用輪子,加快開發效率。人生苦短,能節約的時間,一秒鐘都不要浪費。
  • 需要大量定製化的功能,開發完成後需要頻繁改動:此類功能因為經常對底層程式碼進行改動,與其在別人的程式碼上修修補補,還不如自己從頭寫了。自己的程式碼不僅熟悉,而且都是為定製化而生的。

到底如何選擇,就根據你的喜歡進行斟酌了。


相關文章