Elasticsearch выполняет поиск только по одному полю
У меня есть приложение на drf, и я добавил elasticsearch(django_elasticsearch_dsl) для поиска. Но я столкнулся с проблемой, когда я хочу выполнить поиск по двум или более полям, используя эластичный поиск только по одному полю.
Я пытаюсь выполнить поиск как по "названию", так и по "описанию", но поиск выполняется только в поле "название".
Модель Django
class Product(TimestampMixin, HistoricalModel, models.Model):
class Status(models.TextChoices):
NEW = 'new', _('New')
USED = 'used', _('Used')
class PublicationStatus(models.TextChoices):
ACTIVE = 'active', _('Active')
INACTIVE = 'inactive', _('Inactive')
DRAFT = 'draft', _('Draft') # Очікуючі
REJECTED = 'rejected', _('Rejected') # Відхилені
SOLD = 'sold', _('Sold')
DELETED = 'deleted', _('Deleted')
title = models.CharField(_('Product name'), max_length=255)
description = models.TextField(
verbose_name=_('Product description'),
validators=[
MinLengthValidator(settings.MIN_LENGTH_DESCRIPTION, message=_('Product description is too short')),
MaxLengthValidator(settings.MAX_LENGTH_DESCRIPTION, message=_('Product description is too long')),
],
)
...
Документ
@registry.register_document
class ProductDocument(Document):
title = fields.TextField(analyzer="multilang_analyzer")
description = fields.TextField(analyzer="multilang_analyzer")
class Index:
name = "products"
settings = {
'number_of_shards': 1,
'number_of_replicas': 1,
'analysis': {
'analyzer': {
'multilang_analyzer': {
'type': 'custom',
'tokenizer': 'standard',
'char_filter': ['html_strip'],
'filter': [
'lowercase',
'russian_stop',
'ukrainian_stop',
'english_stop',
'russian_stemmer',
'english_stemmer'
]
}
},
'filter': {
'russian_stop': {'type': 'stop', 'stopwords': '_russian_'},
'ukrainian_stop': {'type': 'stop', 'stopwords': '_ukrainian_'},
'english_stop': {'type': 'stop', 'stopwords': '_english_'},
'russian_stemmer': {'type': 'stemmer', 'language': 'russian'},
'english_stemmer': {'type': 'stemmer', 'language': 'english'}
}
}
}
class Django:
model = Product
fields = ['title', 'description']
просмотр
class SearchProductView(ListAPIView):
serializer_class=ProductSerializer
def get_queryset(self):
q = self.request.GET.get("query", "")
if q:
search = (ProductDocument.search()
.query('multi-match', query=q, fields=["title^2", "description"]))
response = search.execute()
return response
сериализатор
class ProductSerializer(serializers.ModelSerializer):
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), write_only=True, required=True)
prices = PriceSerializer(many=True, required=False, read_only=True)
amount = serializers.DecimalField(
max_digits=13,
decimal_places=2,
min_value=MIN_PRICE,
write_only=True,
)
currency = serializers.PrimaryKeyRelatedField(queryset=Currency.objects.all(), write_only=True)
images = ProductImageSerializer(many=True, source='product_images', read_only=True)
uploaded_images = serializers.ListField(
child=serializers.ImageField(use_url=True, allow_null=True),
write_only=True,
required=False,
allow_empty=True,
)
video = ProductVideoSerializer(source='product_videos', read_only=True, many=True)
upload_video = serializers.FileField(write_only=True, required=False)
seller = serializers.SerializerMethodField(read_only=True)
status = serializers.ChoiceField(choices=Product.Status.choices)
Например, у меня есть экземпляр с этими данными
{"**id**": 4,
"**title**": "Нові стоїки 52 тижні для наповненого життя",
"description": "Книга ''Нові стоїки. 52 тижні для наповненого життя''. Прочитана 1 раз, стан нової. Можна забрати у Львові на Зеленій.\r\n\r\nКнижка про філософію стоїцизму — вчення, ... .",
...
curl -X ВЫВОДИТ "http://elasticsearch:9200/products/_search?красиво"
"_index" : "products",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"title" : "Нові стоїки 52 тижні для наповненого життя",
"description" : "Книга ''Нові стоїки. 52 тижні для наповненого життя''. Прочитана 1 раз, стан нової. Можна забрати у Львові на Зеленій.\r\n\r\nКнижка про філософію стоїцизму — вчення, яке сьогодні набуває все більше популярності. Античні стоїки уміли те..."
И я хочу найти по слову "Нига" (которое является первым в списке), но elastic выполняет поиск только в title, где это слово отсутствует, и я получаю
{
"count": 0,
"next": null,
"previous": null,
"results": []
}
Например, я ищу по Нові (это первое в названии) и
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 4,
"slug": "novi-stoiki-52-tizhni-dlia-napovnenogo-zhittia4",
"title": "Нові стоїки 52 тижні для наповненого життя",
"status": "Used",
...
Даже если я добавлю description поле в сериализатор, ничего не изменится
Проблема, вероятно, заключается в том, как вы интерпретируете ответ на поиск в DRF и, возможно, как индексируются поля или запрашиваются запросы в Elasticsearch.
search.execute() возвращает объект ответа Elasticsearch DSL, а не набор запросов Django, поэтому ListAPIView DRF не знает, как его сериализовать.
Попробуйте преобразовать response в список реальных объектов Django (экземпляров продукта):
from django.db.models import Q
class SearchProductView(ListAPIView):
serializer_class = ProductSerializer
def get_queryset(self):
q = self.request.GET.get("query", "")
if q:
search = (
ProductDocument.search()
.query('multi-match', query=q, fields=["title^2", "description"])
)
response = search.execute()
# Get list of matching IDs
ids = [hit.meta.id for hit in response]
return Product.objects.filter(id__in=ids)
return Product.objects.none()
Также убедитесь, что вы действительно обновили индекс после добавления поля описания, выполнив:
python manage.py search_index --rebuild
Вы добавили поле описания позже? На самом деле ваш код выглядит неплохо.
python manage.py search_index --delete -f
python manage.py search_index --create
python manage.py search_index --populate