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

Вернуться на верх