iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0

Day 26 Django 來找查,實作搜尋功能

假設網站中有琳瑯滿目的商品,那要讓使用者快速找到商品就一定需要搜尋功能,馬上就來試試改造完的多租戶架構搜尋引擎吧!

定義商品索引

在之前的『裝上引擎,Django 的移動城堡』我們已經定義過了商品索引,但還缺少了 ID 欄位與商品圖片的巢狀欄位。

調整商品索引

首先要為之前的索引加上 id 欄位,id 欄位將會在使用 Django CBV 的 ListView 時,能直接取用 Django Elasticsearch DSL 的 search 返回結果,若沒有 id 會無法正常取用。

# products/documents.py

# ...

@registry.register_document
class ProductDocument(Document):
    
    # ...
    
    class Django:
        model = Product
        # we removed the category field from here
        fields = [
            'id',   # add id field
            'name',
            'description',
            'price',
            'created',
            'modified',
        ]

    class Index:
        name = "product"

@registry.register_document
class ProductCategoryDocument(Document):

    class Django:
        model = ProductCategory
        fields = [
            'id',  # add id field
            'name',
            'name_en',
            'description',
            'description_en',
            'created',
            'modified',
            'image'
        ]

    class Index:
        name = "productcategory"

新增 ProductImageDocument 商品圖片索引,而因為商品會有多張商品圖片,所以要在 ProductDocument 新增 product_image_set 為巢狀欄位與新增 ProductImageDocument 索引。

# products/documents.py

# ...

from products.models import Product, ProductCategory, ProductImage

# ...

@registry.register_document
class ProductDocument(Document):
    
    # ...

    product_image_set = fields.NestedField(properties={
        'name': fields.TextField(),
        'image': fields.FileField(),
        'order': fields.IntegerField(),
    })

    # ...
        
@registry.register_document
class ProductImageDocument(Document):

    class Django:
        model = ProductImage
        fields = [
            'id',
            'name',
            'image',
            'order'
        ]

    class Index:
        name = "productimage"  

使用指令更新索引

# 租戶 example01
docker exec --workdir /opt/app/web example_tenant_web \
    python3.10 manage.py search_index --populate --schema example01

...

Indexing 3 'ProductCategory' objects 
Indexing 2 'Product' objects 
Indexing 6 'ProductImage' objects

# 租戶 example02
docker exec --workdir /opt/app/web example_tenant_web \
    python3.10 manage.py search_index --populate --schema example02

...

Indexing 1 'ProductCategory' objects 
Indexing 2 'Product' objects 
Indexing 1 'ProductImage' objects 

資料展示

{
    "_index": "product",
    "_type": "_doc",
    "_id": "example01-2",
    "_score": 1,
    "_routing": "example01",
    "_source": {
        "category": {
            "name": "家電3C",
            "name_en": "Appliance 3C",
            "description": "走在科技尖端",
            "description_en": "on the cutting edge of technology",
            "created": "2022-09-13T20:11:59.723669+08:06",
            "modified": "2022-10-06T22:01:08.914940+08:06",
            "image": "/media/example01/productcategory_media/pexels-tyler-lastovich-6991221664214284.jpg"
        },
        "product_image_set": [
            {
                "name": "圖片1",
                "image": "/media/example01/productimage_media/%E5%9C%96%E7%89%8711664206061.jpg",
                "order": 1
            },
            {
                "name": "圖片2",
                "image": "/media/example01/productimage_media/%E5%9C%96%E7%89%8721664206061.jpg",
                "order": 2
            },
            {
                "name": "圖片3",
                "image": "/media/example01/productimage_media/%E5%9C%96%E7%89%8731664206061.jpg",
                "order": 3
            }
        ],
        "id": 2,
        "name": "義式咖啡機",
        "description": "咖啡機是一種沖煮咖啡機具的總稱。以沖煮方式來分類,有滴濾式咖啡機(俗稱美式咖啡機)跟義式咖啡機兩種,滴濾咖啡機因構造簡單,以滴濾方法沖調咖啡,機身細小,操作容易,較為一般家庭採用。義式咖啡機以加壓後的熱水沖調濃縮咖啡,因為需要裝設鍋爐,體積較大,價錢也較昂貴,通常用於咖啡廳等營業場所。但隨著濃縮咖啡的流行,針對家庭用戶所設計的小型化義式咖啡機也漸漸普及。",
        "price": 9990,
        "created": "2022-09-13T22:18:40.622796+08:06",
        "modified": "2022-09-27T01:33:13.109226+08:06"
    }
}

使用搜尋引擎顯示商品列表

調整商品視圖

在視圖匯入 ProductDocument 索引後,使用 search() 建立物件,並用 query 函數查詢 routing 為當前 schema 名稱的所有資料並返回。

# products/views.py

# ...

from products.documents import ProductDocument

# ...

class ProductListView(ListView):
    model = Product
    template_name = 'products/list.html'
    context_object_name = 'items'
    paginate_by = 10

    def get_queryset(self):
        results = ProductDocument.search().query("term", **{"_routing": connection.schema_name})
        return results

調整商品列表頁面

使用 Django Elasticsearch DSL 的返回值會與透過 ORM 返回的巢狀關聯欄位有一些差異,這裡我們要修改取得商品圖片的模板語法:

<!-- products/templates/products/list.html -->

<!-- ... -->

<div class="product-thumb">
    {% if item.product_image_set.0.image %}
    <img class="img-responsive" src="{{ item.product_image_set.0.image }}" alt="product-img" />
    {% endif %}

<!-- ... -->

<div class="modal-image">
    {% if item.product_image_set.0.image %}
    <img class="img-responsive" src="{{ item.product_image_set.0.image }}" alt="product-img" />
    {% endif %}

頁面展示

使用搜尋引擎顯示的內容已成功與原先使用 ORM 的方式相同。

https://ithelp.ithome.com.tw/upload/images/20221008/20151656kHTq3oebsb.png

實作查詢功能

調整商品列表視圖

我們要在商品列表視圖接收使用者搜尋的查詢詞 q 參數,並儲存到 self.query_string 變數。

在 get_queryset 函數查詢索引時,使用 self.query_string 尋找 name 欄位,若無則返回全部資料,最後將結果儲存至 context 的 query_string 中 。

查詢結果筆數則儲存至 count 變數與 context 的 count 中。

最後將 context 傳遞給 template。

# products/views.py

# ...

class ProductListView(ListView):
    model = Product
    template_name = 'products/list.html'
    context_object_name = 'items'
    paginate_by = 10

    def get(self, request, *args, **kwargs):
        
        self.query_string = request.GET.get('q', '')
        self.object_list = self.get_queryset()
            
        context = self.get_context_data()
        if self.query_string:
            count = self.object_list.count()
            context['query_string'] = self.query_string
            context['count'] = count
        else:
            context['query_string'] = ''
            context['count'] = 0
        return self.render_to_response(context)

    def get_queryset(self):
        results = ProductDocument.search().query("term", **{"_routing": connection.schema_name})
        if self.query_string:
            results = results.query("match", name=self.query_string)
        return results

調整商品列表頁面

若有接收到 query_string 則顯示查詢詞與的查詢結果筆數。

<!-- products/templates/products/list.html -->

<!-- ... -->

{% extends "base.html" %}
{% load static %}

{% block content %}
<section class="page-header">
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <div class="content">
                    <h1 class="page-name">
                        {% if query_string %}
                          {{query_string}} 的搜尋結果為 {{ count }} 筆
                        {% else %}
                            全部商品
                        {% endif%}
                    </h1>
                    <ol class="breadcrumb">
                        <li><a href="{% url 'products:home' %}">首頁</a></li>
                        <li class="active"><a href="{% url 'products:list' %}">商品列表</a></li>
                    </ol>
                </div>
            </div>
        </div>
    </div>
</section>

調整基底頁面

更新原先的 Search 區塊,使用 GET 方法將 q 參數傳遞至商品列表頁面

<!-- products/templates/base.html -->

<!-- ... -->

<!-- Search -->
<li class="dropdown search dropdown-slide">
    <a href="#!" class="dropdown-toggle" data-toggle="dropdown" data-hover="dropdown"><i
            class="tf-ion-ios-search-strong"></i> Search</a>
    <ul class="dropdown-menu search-dropdown">
        <li>
            <form method="GET" action="{% url 'products:list' %}" 
                onsubmit="this.action = this.action + this.search.value;alert(this.action); this.submit();">
                <input type="search" name="q" class="form-control" placeholder="Search..."/>
            </form>
        </li>
    </ul>
</li>
<!-- / Search -->

到這裡搜尋功能就大功告成了!

搜尋功能展示:

在右上方的搜尋區塊輸入查詢詞

https://ithelp.ithome.com.tw/upload/images/20221008/20151656Y0gx19KIIB.png

按下 enter 送出後,進入查詢結果頁面

https://ithelp.ithome.com.tw/upload/images/20221008/20151656UJYuieLlGd.png

完美!

多租戶搜尋引擎的介紹到這裡告一段落了,主要是帶大家了解與在多租戶架構下使用搜尋引擎的方式,搜尋引擎本身能為網站做的遠遠不止這裡示範的查詢功能,剩下就請大家自行研究囉~

下一回我們要回到 Django 的管理介面『合法克隆,複製你的 Django 租戶』。


上一篇
Day 25 改造引擎,Django 多租戶下的搜尋引擎
下一篇
Day 27 合法克隆,複製你的 Django 租戶
系列文
全能住宅改造王,Django 多租戶架構的應用 —— 實作一個電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言