Django搭建個人博客:簡單搜索博客文章

不管是最新文章列表也好、最熱文章列表也罷,都是把所有的文章數據全部展示給了用戶。

但是如果用戶只關心某些特定類型的文章,抽取全部數據就顯得既不方便、又不效率了。

因此,給用戶提供一個搜索功能,提供給用戶感興趣的幾篇文章,就大有用處了。

準備工作

邏輯

儘管細節不同,但是搜索和列表有很多類似的地方:它們都是先檢索出一些文章對象,並將其展示給用戶。上一章已經說過,代碼重複是萬惡之源,好的實踐必須把功能類似的模塊儘量複用起來。基於這個原則,我們打算繼續在原有的article_list()上添磚加瓦,讓其功能更加的強大。

隨着項目越來越龐大,又需要將功能複雜的模塊拆分成更簡單的多個模塊。目前我們還不用擔心這個問題。

更酷的是,我們希望搜索出來的文章也能夠按照時間、熱度等各種方式進行排序。因此需要構造一個新的參數search,能夠和之前的order參數進行聯合查詢。

GET還是POST?

用戶搜索內容時提交的文本,可以用GET請求提交,也可以用POST請求提交。根據實際的需要進行選擇。

因爲order是用GET提交的,並且翻頁是GET請求,因此選擇GET方式提交搜索文本,可以方便地和之前的模塊結合起來。

之前我們已經用過表單組件<form method="POST">,通過POST請求提交數據。表單組件同樣也可以提交GET請求,只要去掉method="POST"屬性就可以了。

Q對象

Model.objects.all()能夠返回表中的所有對象。

對應的,Model.objects.filter(**kwargs)可以返回與給定參數匹配的部分對象。

還有Model.objects.exclude(**kwargs)返回與給定參數不匹配的對象

如果想對多個參數進行查詢怎麼辦?比如同時查詢文章標題和正文內容。這時候就需要Q對象

視圖

那麼按照前面說好的,修改article_list()

article/views.py

...

# 引入 Q 對象
from django.db.models import Q

def article_list(request):
    search = request.GET.get('search')
    order = request.GET.get('order')
    # 用戶搜索邏輯
    if search:
        if order == 'total_views':
            # 用 Q對象 進行聯合搜索
            article_list = ArticlePost.objects.filter(
                Q(title__icontains=search) |
                Q(body__icontains=search)
            ).order_by('-total_views')
        else:
            article_list = ArticlePost.objects.filter(
                Q(title__icontains=search) |
                Q(body__icontains=search)
            )
    else:
        # 將 search 參數重置爲空
        search = ''
        if order == 'total_views':
            article_list = ArticlePost.objects.all().order_by('-total_views')
        else:
            article_list = ArticlePost.objects.all()

    paginator = Paginator(article_list, 3)
    page = request.GET.get('page')
    articles = paginator.get_page(page)
    
    # 增加 search 到 context
    context = { 'articles': articles, 'order': order, 'search': search }
    
    return render(request, 'article/list.html', context)

...

重點知識如下:

  • 新增參數search,存放需要搜索的文本。若search不爲空,則檢索特定文章對象。

  • 留意filterQ對象的用法。Q(title__icontains=search)意思是在模型的title字段查詢,icontains不區分大小寫的包含,中間用兩個下劃線隔開。search是需要查詢的文本。多個Q對象用管道符|隔開,就達到了聯合查詢的目的。

    icontains不區分大小寫,對應的contains區分大小寫

  • 爲什麼需要search = ''語句?如果用戶沒有搜索操作,則search = request.GET.get('search')會使得search = None,而這個值傳遞到模板中會錯誤地轉換成"None"字符串!等同於用戶在搜索“None”關鍵字,這明顯是錯誤的。

    完成本章內容後,可以刪除此語句看看效果

除此之外還有一點小的代碼優化工作:將需要重複用到order = request.GET.get('order')提取到頂部,讓模塊稍稍清爽一點。

模板

還是修改文章列表的模板文件。

需要修改的內容稍多,仔細一些不要看錯:

templates/article/list.html

...

<div class="container">
    <!-- 修改,麪包屑的href增加search參數 -->
    <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
            <li class="breadcrumb-item">
                <a href="{% url 'article:article_list' %}?search={{ search }}">
                    最新
                </a>
            </li>
            <li class="breadcrumb-item">
                <a href="{% url 'article:article_list' %}?order=total_views&search={{ search }}">
                    最熱
                </a>
            </li>
        </ol>
    </nav>

    <!-- 新增,搜索欄 -->
    <div class="row">
        <div class="col-auto mr-auto">
            <form class="form-inline" >
                <label class="sr-only">content</label>
                <input type="text" 
                    class="form-control mb-2 mr-sm-2" 
                    name="search" 
                    placeholder="搜索文章..." 
                    required
                >
            </form>
        </div>
    </div>

    <!-- 新增,搜索提示語 -->
    {% if search %}
        {% if articles %}
            <h4><span style="color: red">"{{ search }}"</span>的搜索結果如下:</h4>
            <hr>        
        {% else %}
            <h4>暫無<span style="color: red">"{{ search }}"</span>有關的文章。</h4>
            <hr>
        {% endif %}
    {% endif %}
        
            
...
    
<!-- 修改,頁碼href增加search參數 -->
<a href="?page=1&order={{ order }}&search={{ search }}" class="btn btn-success">
...
<a href="?page={{ articles.previous_page_number }}&order={{ order }}&search={{ search }}" class="btn btn-secondary">
...
<a href="?page={{ articles.next_page_number }}&order={{ order }}&search={{ search }}" class="btn btn-secondary">
...
<a href="?page={{ articles.paginator.num_pages }}&order={{ order }}&search={{ search }}"class="btn btn-success">

...
  • 麪包屑組件、頁碼組件都改動了href:增加了search參數
  • 新增搜索欄,以GET請求提交search參數;required屬性阻止用戶提交空白文本
  • 新增搜索提示語。好的UI必須讓用戶瞭解當前的狀態

Emmm…想想也不用改動其他東西了。

開始測試吧!

測試

還是打開文章列表頁面:

出現了搜索欄!並且翻頁、最熱等功能一切正常。

在搜索欄中輸入“PYTHON”,結果如下:

成功將標題或正文中含有"python"關鍵字的文章檢索出來了,並且是忽略大小寫的。點擊最熱可以讓檢索結果按瀏覽量排序,翻頁功能也正常工作。很好,達成了目標!

學到這裏的讀者應該感到自豪:你用了同一個url,集成了很多種功能,展示了不同的內容!這對新手來說其實並不容易做到。

這種方法有一個小缺點:有的時候url中會包含像search=''(空值)這樣無意義的字符串,強迫症簡直不能忍。所幸這無傷大雅,通常用戶並不會關心你的url是什麼樣子的,只要網頁美觀好用就行。

總結

本章完成了一個簡單的搜索功能,這對於個人博客來說應該夠用了。

更加複雜、深度定製的搜索可以藉助第三方模塊,如Haystack

另外筆者這樣實現搜索不一定是最優的。相信你已經掌握多種途徑來實現搜索功能了(POST請求?搜索專用視圖?另寫url?),盡情嘗試一番吧。

轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章