🎉
DjangoとAWS OpenSearchを接続する
概要
DjangoとAWS OpenSearchを接続する方法に関するメモです。以下の記事が参考になりました。
ただし、上記の記事はElasticsearchを対象にした設定のため、OpenSearchに応じた変更が必要です。
変更点
以下のElasticsearch Setupの部分から、OpenSearchに応じた変更が必要でした。
具体的には、以下の2つのライブラリが必要でした。
(env)$ pip install opensearch-py
(env)$ pip install django-opensearch-dsl
その後は、django_elasticsearch_dsl
となっている箇所をdjango-opensearch-dsl
に、elasticsearch_dsl
をopensearchpy
に書き換えることで、記事の通りに進めることができました。
例えば、以下のような形です。
blog/documents.py
# blog/documents.py
from django.contrib.auth.models import User
from django_opensearch_dsl import Document, fields # opensearchに変更
from django_opensearch_dsl.registries import registry # opensearchに変更
from blog.models import Category, Article
@registry.register_document
class UserDocument(Document):
class Index:
name = 'users'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
}
class Django:
model = User
fields = [
'id',
'first_name',
'last_name',
'username',
]
@registry.register_document
class CategoryDocument(Document):
id = fields.IntegerField()
class Index:
name = 'categories'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
}
class Django:
model = Category
fields = [
'name',
'description',
]
@registry.register_document
class ArticleDocument(Document):
author = fields.ObjectField(properties={
'id': fields.IntegerField(),
'first_name': fields.TextField(),
'last_name': fields.TextField(),
'username': fields.TextField(),
})
categories = fields.ObjectField(properties={
'id': fields.IntegerField(),
'name': fields.TextField(),
'description': fields.TextField(),
})
type = fields.TextField(attr='type_to_string')
class Index:
name = 'articles'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
}
class Django:
model = Article
fields = [
'title',
'content',
'created_datetime',
'updated_datetime',
]
Populate Elasticsearch
Elasticsearchを対象にした上記の記事では、以下のコマンドが紹介されています。
python manage.py search_index --rebuild
一方、OpenSearchの場合は、以下のコマンドが必要でした。
インデックスの作成
python manage.py opensearch index create
The following indices will be created:
- users.
- categories.
- articles.
Continue ? [y]es [n]o : y
Creating index 'users'... OK
Creating index 'categories'... OK
Creating index 'articles'... OK
ドキュメントの登録
python3 manage.py opensearch document index
The following documents will be indexed:
- 5 User.
- 3 Category.
- 5 Article.
Continue ? [y]es [n]o : y
Indexing 5 User: OK
Indexing 3 Category: OK
Indexing 5 Article: OK
5 User successfully indexed, 0 errors:
3 Category successfully indexed, 0 errors:
5 Article successfully indexed, 0 errors:
インデックスのリビルド
python manage.py opensearch index rebuild
その他:analyzerとfieldsの追加
以下の部分に記載されているField Classesを試します。
以下の例では、username
について、html_strip
というanalyzerと、Keywordフィールドを設定しています。
blog/documents.py
# blog/documents.py
from django.contrib.auth.models import User
from django_opensearch_dsl import Document, fields
from django_opensearch_dsl.registries import registry
from blog.models import Category, Article
from opensearchpy import analyzer, tokenizer
html_strip = analyzer(
'html_strip',
tokenizer="standard",
filter=["lowercase", "stop", "snowball"],
char_filter=["html_strip"]
)
@registry.register_document
class UserDocument(Document):
username = fields.TextField(
analyzer=html_strip,
fields={'raw': fields.KeywordField()}
)
class Index:
name = 'users'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
}
class Django:
model = User
fields = [
'id',
'first_name',
'last_name',
# 'username',
]
上記の結果、以下のようなマッピングがOpenSearchに登録されました。
{
"users" : {
"mappings" : {
"properties" : {
"first_name" : {
"type" : "text"
},
"id" : {
"type" : "integer"
},
"last_name" : {
"type" : "text"
},
"username" : {
"type" : "text",
"fields" : {
"raw" : {
"type" : "keyword"
}
},
"analyzer" : "html_strip"
}
}
}
}
username.raw
を使用することで、ソートやアグリゲーションが可能となります。以下がviewsの例です。-
を与えることで、降順になるようです。
search/views.py
# search/views.py
import abc
from django.http import HttpResponse
from opensearchpy import Q
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.views import APIView
from blog.documents import ArticleDocument, UserDocument, CategoryDocument
from blog.serializers import ArticleSerializer, UserSerializer, CategorySerializer
class PaginatedOpenSearchAPIView(APIView, LimitOffsetPagination):
serializer_class = None
document_class = None
@abc.abstractmethod
def generate_q_expression(self, query):
"""This method should be overridden
and return a Q() expression."""
def get(self, request, query):
try:
q = self.generate_q_expression(query)
search = self.document_class.search().query(q).sort(
"-username.raw"
)
response = search.execute()
print(
f'*** Found {response.hits.total.value} hit(s) for query: "{query}"')
results = self.paginate_queryset(response, request, view=self)
serializer = self.serializer_class(results, many=True)
return self.get_paginated_response(serializer.data)
except Exception as e:
print(e)
return HttpResponse(e, status=500)
まとめ
DjangoとAWS OpenSearchの接続にあたり、参考になりましたら幸いです。
Discussion