Упорядочивание Django QuerySet по месту появления термина в результатах

Я пытаюсь реализовать автозаполнение для поля ввода с помощью Django и jQuery (пользователь ищет еду в меню). Проблема заключается в том, чтобы понять, как упорядочить результаты.

Если, например, пользователь набирает "Рыба", я хочу, чтобы автозаполнение было упорядочено следующим образом:

  • "Рыба и чипсы"
  • "Жареная рыба"
  • "Жареная рыба"
  • "Жареный сом"

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

Мой набор запросов в настоящее время извлекается в Django с помощью

views.py
def load_menu(request):
    term = request.GET['term'] # the search term typed by user
    menu = Menu.objects.filter(item__icontains=term).order_by('item', 'id') # item = name of food
    
    # constructing JSON response
    data = [] 
    for item in menu:
        item = {
            'label': item.item,
            'value': item.id,
        }
        data.append(item)
    return JsonResponse({'data': data})  # received by jQuery function to display autocomplete menu

но это упорядочивает результаты только по алфавиту, чего я не хочу.

Как я могу сделать заказ, как указано в примере выше?

Вы достигаете этого, добавляя вспомогательное поле в набор запросов. Таким образом, вы можете включить свою специфическую логику в это поле и использовать ее в предложении order by. Возьмем следующий пример: order_by_position добавляется с помощью annotate и case when и присваивается 1, 2, 3 на основе следующих условий: 1 если элемент начинается с термина, 2 если элемент содержит термин, и 3, если элемент не содержит термин. Наконец, запрос упорядочивается по 'order_by_position', 'item', что гарантирует, что он сначала упорядочен по определенному порядку в order_by_position, а затем в алфавитном порядке на item.

from django.db.models import Case, Value, When, CharField

def load_menu(request):
    term = request.GET['term']  # the search term typed by user
    menu = Menu.objects.annotate(
        order_by_position=Case(
            When(item__startswith=term, then=Value(1)),
            When(item__contains=term, then=Value(2)),
            default=Value(3),
            output_field=CharField(),
        )
    ).filter(item__icontains=term).order_by('order_by_position', 'item')

    # constructing JSON response
    data = []
    for item in menu:
        item_data = {
            'label': item.item,
            'value': item.id,
        }
        data.append(item_data)
    return JsonResponse({'data': data})

Чтобы пойти еще дальше и быть более конкретным, вы можете также рассмотреть индекс совпадающего термина:

    from django.db.models import CharField, Value, Func, PositionField

class StartsWith(Func):
    function = 'POSITION'
    arity = 2
    output_field = PositionField()

def load_menu(request):
    term = request.GET['term']  # the search term typed by user
    menu = Menu.objects.annotate(
        start_pos=StartsWith(F('item'), Value(term)),
    ).filter(item__icontains=term).order_by('start_pos', 'item')

    # constructing JSON response
    data = []
    for item in menu:
        item_data = {
            'label': item.item,
            'value': item.id,
        }
        data.append(item_data)
    return JsonResponse({'data': data})
Вернуться на верх