在 docker 中使用 Elasticsearch 作為 django 的 search backend

目前手上的專案用了 django 作為網站的 Framework,而為了確保所有開發人員能夠在自己的開發機上用相同的環境進行開發,所以我們把整個 django 專案用 docker-compose 包裝成容器。

且為了未來專案正式上線時,可以放在 AWS 上,並盡量使用 AWS 的 managed service,所以選擇了 Elasticsearch 來作為 search backend。

這篇文章是把 Elasticsearch 加入 docker-compose 中的 django 專案的筆記。

第一步 – 加入 Elasticsearch

在專案的根目錄編輯 `docker-compose.yml`,加上以下的設定:

    elasticsearch:
        image: barnybug/elasticsearch:1.7.2
        container_name: elasticsearch
        environment:
            - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
        volumes:
            - ./esdata1:/data
            - ./eslog1:/logs
        expose:
            - "9200"
        ports:
            - 9200:9200

這邊要注意到的我們使用了 Elasticsearch 1.x.x。原因是 django 的搜尋套件 haystack 目前只支援 Elasticsearch 1.x.x。

第二步 – 安裝需要的 python 套件

因為我們把 django 包在 docker 裡面,這個 django 的 container 啟動時會讀取 requirements.txt 安裝必要的 python 套件,所以在裡面加上這次需要的套件:

pyelasticsearch==1.4
django-haystack==2.6.0

然後視狀況重新啟動整個專案的 containers

docker-compose up --build

到這裡請由輸入訊息確認您的 django、Database、Elasticsearch 都有跑起來

第三步 – 設定 django 的 settings.py

接下來我們要設定 haystack 這個套件來做 django 的搜尋功能,讓 haystack 去跟 Elasticsearch 要搜尋結果。在 `settings.py` 加上以下的設定:

INSTALLED_APPS = [
    'elasticsearch',
    'haystack',
] + INSTALLED_APPS

這樣寫的目的是讓 elasticsearch 和 hasystack 在原有的 apps 前面載入。

然後再加入

# Elasticsearch 連線設定
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://elasticsearch:9200/',
        'INDEX_NAME': 'products',
    }
}

# 預設 search tempalte 每頁顯示十行結果
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10

# 讓 hasystack 即時更新 search index
HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor"

第四步 – 設定 django 內的 Model、View、Templates

再來我們要告訴 haystack 要對哪些 Model 做搜尋,這邊我們會以 Product 這個 model 來做範例。首先新增 `product/search_indexes.py`,內容如下:

from haystack import indexes
from product.models import Product

# 因為是對 Product 這個 model 做設定,所以這個 class *必須* 命名為 ProductIndex
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
    # text 必須要有
    text = indexes.CharField(document=True, use_template=True)
    # 要搜尋 Product.product_name 和 Product.product_desc 這兩個欄位
    product_name = indexes.CharField(model_attr='product_name')
    product_desc = indexes.CharField(model_attr='product_desc')

    def get_model(self):
        return Product

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

新增 `templates/search/product/product_text.txt`,要注意的是這個檔案的檔名必須跟你的 model name 相符、路徑中的 product 請替換成您自己的 app name。此檔案內容如下:

{{ object.product_name }}
{{ object.product_desc }}

然後新增 `templates/search/search.html`

{% extends 'base.html' %}

{% block content %}
    <h2>Search</h2>

    <form method="get" action=".">
        <table>
            {{ form.as_table }}
            <tr>
                <td>&nbsp;</td>
                <td>
                    <input type="submit" value="Search">
                </td>
            </tr>
        </table>

        {% if query %}
            <h3>Results</h3>

            {% for result in page.object_list %}
                <p>
                    <a href="{{ result.object.product_name }}">{{ result.object.product_desc }}</a>
                </p>
            {% empty %}
                <p>No results found.</p>
            {% endfor %}

            {% if page.has_previous or page.has_next %}
                <div>
                    {% if page.has_previous %}<a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; Previous{% if page.has_previous %}</a>{% endif %}
                    |
                    {% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">{% endif %}Next &raquo;{% if page.has_next %}</a>{% endif %}
                </div>
            {% endif %}
        {% else %}
            {# Show some example queries to run, maybe query syntax, something else? #}
        {% endif %}
    </form>
{% endblock %}

修改 `urls.py`,加上下面這行:

     url(r'^search/', include('haystack.urls')),

再來 update search index:

docker-compose run web ./manage.py update_index

第五步 – 測試

測試方式會依照您的 django 專案有所不同。以我的專案來說,大致上是:

  1. 用任何方式新增物件,可以是用自己設計的新增頁面、也可以是 django 後台,甚至如果要用 shell 直接新增也可以
  2. 執行 docker-compose run web ./manage.py update_index,用 django 的 command 更新 search index
  3. 到 http://localhost:8000/search/ 以關鍵字搜尋看看

發佈留言

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料