Search a part of a word using django-elasticsearch-dsl-drf

I am building a Django application that contains search engine using elastic search by django_elasticsearch_dsl_drf library. I want to allow user to search for only letters or a part of a word and elastic search returns all words that contain these letters. For example if the user submitted ‘legal’ word the result will be all articles containing ‘legal, legally, illegal, illegally’. I have used nGram tokenizer to make this but it doesn’t work as when I submit the word no results are shown to me.

here is the most important part which is in documents.py:

from django_elasticsearch_dsl import Document, fields, Index
from django_elasticsearch_dsl.registries import registry
from django.conf import settings
from .models import *
from elasticsearch_dsl import analyzer, tokenizer

autocomplete_analyzer = analyzer('autocomplete_analyzer',
            tokenizer=tokenizer('trigram', 'nGram', min_gram=1, max_gram=20),
            filter=['lowercase']
        )

entry_index=Index('entries')

@registry.register_document
class EntryDocument(Document):
    title = fields.TextField(
        attr='title',
        fields={
            'raw': fields.TextField(required=True,analyzer=autocomplete_analyzer),
            'suggest': fields.CompletionField(),
            
        }
    )
    body = fields.TextField(
        attr='body',
        fields={
            'raw': fields.TextField(required=True,analyzer=autocomplete_analyzer),
            # 'suggest': fields.CompletionField()
        }
    )


    class Index:
        name = 'entries'
        settings = {
            "number_of_shards": 1,
            "number_of_replicas": 0,
            'max_ngram_diff': 20 
        }

    class Django:
        model = entry
        fields= []

for more details: models.py:

class entry(models.Model):
    title = models.CharField(max_length=600)
    body = models.TextField()

Serializers.py:

class EntryDocumentSerializer(DocumentSerializer):
    class Meta:
        document = EntryDocument

        fields = (
            'title',
            'body',
            'entryOrigin'
        )

views.py:

class EntryDocumentView(DocumentViewSet):
    document = EntryDocument
    serializer_class = EntryDocumentSerializer

    filter_backends = [
        DefaultOrderingFilterBackend,
      
        FilteringFilterBackend,
        SearchFilterBackend,
        SuggesterFilterBackend,
    ]

    search_fields = (
        'title',
        'body'
    )

    filter_fields = {
        'title': {
        'field' : 'title',
        'lookups': [
                LOOKUP_FILTER_RANGE,
                LOOKUP_QUERY_IN,
                LOOKUP_QUERY_GT,
                LOOKUP_QUERY_GTE,
                LOOKUP_QUERY_LT,
                LOOKUP_QUERY_LTE,
            ],

        },
        'body': {
        'field': 'body',
         'lookups': [
            LOOKUP_FILTER_RANGE,
            LOOKUP_QUERY_IN,
            LOOKUP_QUERY_GT,
            LOOKUP_QUERY_GTE,
            LOOKUP_QUERY_LT,
            LOOKUP_QUERY_LTE,
            ],

        },

        'entryOrigin': 'entryOrigin.id'
    }

    suggester_fields = {
         'title_suggest': {
            'field': 'title.suggest',
            'suggesters': [
                SUGGESTER_COMPLETION,
            ],
            'options': {
                'size': 20,  # Override default number of suggestions
                'skip_duplicates':True, # Whether duplicate suggestions should be filtered out.
            },
        },
        'body_suggest': {
            'field': 'body.suggest',
            'suggesters': [
                SUGGESTER_COMPLETION,
            ]}
    }

urls.py:

from django.contrib import admin
from django.urls import path,include
from rest_framework import routers
from thesearchable.views import EntryDocumentView
from rest_framework import routers
router = routers.SimpleRouter(trailing_slash=False)
router.register(r'entry-search', EntryDocumentView, basename='entry-search')


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('thesearchable.urls')),
   
]

urlpatterns += router.urls

So how can I search for only part of the word?

Try to use the match query with a fuzziness parameter in your search query.
Something like this should work:

from django_elasticsearch_dsl_drf.filter_backends import (
    DefaultOrderingFilterBackend,
    FilteringFilterBackend,
    SearchFilterBackend,
    SuggesterFilterBackend,
)
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet
from django_elasticsearch_dsl_drf.pagination import PageNumberPagination
from elasticsearch_dsl import Q
from .documents import EntryDocument

class EntryDocumentView(DocumentViewSet):
    document = EntryDocument
    serializer_class = EntryDocumentSerializer
    pagination_class = PageNumberPagination

    filter_backends = [
        DefaultOrderingFilterBackend,
        FilteringFilterBackend,
        SearchFilterBackend,
        SuggesterFilterBackend,
    ]

    search_fields = (
        'title',
        'body'
    )

    filter_fields = {
        'title': {
            'field': 'title.raw',
            'lookups': [
                LOOKUP_FILTER_RANGE,
                LOOKUP_QUERY_IN,
                LOOKUP_QUERY_GT,
                LOOKUP_QUERY_GTE,
                LOOKUP_QUERY_LT,
                LOOKUP_QUERY_LTE,
            ],
        },
        'body': {
            'field': 'body.raw',
            'lookups': [
                LOOKUP_FILTER_RANGE,
                LOOKUP_QUERY_IN,
                LOOKUP_QUERY_GT,
                LOOKUP_QUERY_GTE,
                LOOKUP_QUERY_LT,
                LOOKUP_QUERY_LTE,
            ],
        },
        'entryOrigin': 'entryOrigin.id'
    }

    suggester_fields = {
        'title_suggest': {
            'field': 'title.suggest',
            'suggesters': [
                SUGGESTER_COMPLETION,
            ],
            'options': {
                'size': 20,
                'skip_duplicates': True,
            },
        },
        'body_suggest': {
            'field': 'body.suggest',
            'suggesters': [
                SUGGESTER_COMPLETION,
            ]
        }
    }

    def get_queryset(self):
        qs = super().get_queryset()
        search_query = self.request.query_params.get('search', None)
        if search_query:
            query = Q("match", title={"query": search_query, "fuzziness": "AUTO"}) | \
                    Q("match", body={"query": search_query, "fuzziness": "AUTO"})
            qs = qs.query(query)
        return qs

I have implemented this code but when I wrote http://localhost:8000/entry-search?search=whic "which without h letter "in order to search for the entries containing the word ‘which’ nothing a appeared to me although there is an entry in the database that contains this word in its body

It’s possible that the fuzziness parameter is not set to a high enough value to match the partial word you’re searching for. I’m using the fuzziness parameter with the value of "AUTO", which tells Elasticsearch to automatically determine the appropriate fuzziness based on the length of the search term.
you can try this:

def get_queryset(self):
    qs = super().get_queryset()
    search_query = self.request.query_params.get('search', None)
    if search_query:
        query = Q("match", title={"query": search_query, "fuzziness": "2"}) | \
                Q("match", body={"query": search_query, "fuzziness": "2"})
        qs = qs.query(query)
    return qs

With this modification, a search for "whic" should match documents containing "which".