Как разделить слова, которые пользователь вставляет в поле ввода в приложении Django

У меня есть строка поиска, которая ищет в 2 моделях колонки title, body, short_description. Я использую базу данных MySQL. Сейчас я использую Q lookups, но есть некоторые ограничения поиска, которые я хотел бы "улучшить".

Один из них заключается в том, что Q-поиск находит результаты только по фразам, которые в точности совпадают с полем, поэтому, например, у меня есть заголовок why python is so amazing? и я должен написать why или python или python is, чтобы получить результаты. Что я хотел бы получить, так это расширить строку поиска, чтобы она работала следующим образом:

Пользователь вводит вопрос в строку поиска: python language и поиск разбивает каждое слово и возвращает все объекты, содержащие python или language. В конечном итоге результат возвращает объект с why python is so amazing?, независимо от того, что пользователь вставляет python language или amazing python.

Ниже я размещаю свой текущий код:

views.py

def search_items(request):
    query = request.GET.get('q')
    article_list= Article.objects.filter(title__icontains=query)
    qa_list = QA.objects.filter(title__icontains=query)

    if query is not None:
        lookups = Q(title__icontains=query) | Q(short_description__icontains=query) | Q(body__icontains=query)
        article_list= Article.objects.filter(lookups, status=1).distinct()
        qa_list = QA.objects.filter(lookups, status=1).distinct()

    context = {
        'query_name': query,
        'article_list': article_list,
        'qa_list': qa_list,
    }
    return render(request, 'search/search_items.html', context)

Я проверил это решение и это решение , но результаты не удовлетворительны, потому что когда я ставлю python language, чтобы найти объект с названием why python is so amazing, я не получаю никакого результата.

Вопрос

Буду признателен за любой совет, как добиться результата, при котором я получаю объекты со списком всех объектов на основе слов, которые пользователь вводит в поле ввода.

Я столкнулся с той же проблемой и решил ее, добавив пользовательский менеджер поиска в models.py, над моей моделью. Менеджер имеет два метода, один для поиска по одному слову, другой для поиска по нескольким словам. Строка запроса разбивается на список слов с помощью .split(), (см. представление) ниже.

models.py

class MyModelSearchManager(models.QuerySet):
    def search(self, query=None):
        qs = self
        if query is not None:
            or_lookup = (Q(some_field__icontains=query))
            qs = qs.filter(or_lookup).distinct()
        return qs

    def search_and(self, query=None):
        qs = self
        if query is not None:
            or_lookup = reduce(lambda x, y: x & y, [Q(some_field__icontains=word) for word in query])
            qs = qs.filter(or_lookup).distinct()
        return qs

class MyModelManager(models.Manager):
    def get_queryset(self):
        return MyModelSearchManager(self.model, using=self._db)

    def search(self, query=None):
        return self.get_queryset().search(query=query)

    def search_and(self, query=None):
        return self.get_queryset().search_and(query=query)

и, конечно же, объявите пользовательский менеджер под полями вашей модели:

objects = MyModelManager()

Тогда, на ваш взгляд, разделите строку поиска и сделайте различие между поиском по одному слову и поиском по нескольким словам:

class SearchView(ListView):
    template_name = 'my_app_templates/search_results.html'
    count = 0
    
    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['count'] = self.count or 0
        context['query'] = self.request.GET.get('q')
        return context

    def get_queryset(self):
        request = self.request
        query_list = request.GET.get('q', None).split()
        query_list_count = len(query_list)
        
        if query_list is not None:
            if query_list_count == 1:
                qs = MyModel.objects.search(query=query_list[0]).order_by('-date_added')
                self.count = len(qs)
            elif query_list_count > 1:
                qs = MyModel.objects.search_and(query=query_list).order_by('-date_added')
                self.count = len(qs)
            result_count = len(qs)
            create_search_record(self, request, query_list, query_list_count, result_count)
            return qs

Для многословных строк волшебство заключается в функции reduce, которая перебирает все ключевые слова по заданным полям модели. Большая заслуга в создании этого шаблона менеджера принадлежит Джастину Митчелу превосходная статья о поиске по нескольким моделям.

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