Как отсортировать набор queryset по порядку следования Q-объектов?

У меня есть простая форма поиска и представление:

# forms.py
class SearchForm(forms.Form):
    q = forms.CharField(label='Search', max_length=1024, required=False)


# views.py
def search(request):
    
    search_form = forms.SearchForm()
    q = ''
    
    if request.method == 'GET':
        search_form = forms.SearchForm(request.GET)
        
        if search_form.is_valid():
            q = search_form.cleaned_data['q']
            
    products = models.Product.objects.filter(publication_status='published')
    
    if q.strip() != '': # If there's something to search for which not whitespaces
        products = products.filter(
            Q(title__contains=q) |
            Q(description__contains=q) |
            Q(main__name__contains=q) |
            Q(sub__contains=q)
            )

    context = {
        'products': products,
        'search_form':search_form,
        }
    return render(request, 'search.html', context=context)



# search.html
{% if products %}
<h3>Products:</h3>

<ul>
  {% for p in products %}
    <li><ul>
      <li><a href="{% url 'product' p.pk %}">{{ p }}</a></li>
      <li>{{ p.description }}</li>
      <li><a href="{% url 'main' p.main.pk %}">{{ p.main }}</a></li>
      <li><a href="{% url 'sub' p.main.pk p.sub %}">{{ p.sub|verbose }}</a></li>
    </ul></li><br>
  {% endfor %}
</ul>
{% else %}
<h3>No products found matching your search!</h3>
{% endif %}

Если слово "поло" является названием товара 2, а также описанием товара 1, Проблема в том, что когда я пытаюсь найти это слово, продукт 1 отображается на первом месте в шаблоне (потому что это первое соответствие).

Я пытаюсь отсортировать набор запросов сначала по названию, затем по описанию, затем по подкатегории, так что продукт 2 отображается первым (поскольку набор запросов отсортирован по порядку объектов Q)

Вам следует использовать пользовательский order_by

Для получения дополнительной информации о создании пользовательского заказа, вы можете прочитать эту страницу а для моделей Django это page

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


custom_order=Case(
        When(title__contains=q, then=Value(1)),
        When(description__contains=q, then=Value(2)),
        When(main__name__contains=q, then=Value(3)),
        When(sub__contains=q, then=Value(4)))
        
products.filter(
            Q(title__contains=q) |
            Q(description__contains=q) |
            Q(main__name__contains=q) |
            Q(sub__contains=q)
            ).order_by(custom_order)

@lucas-grugru уже дал вам отличный ответ:

# views.py
def search(request):
    
    search_form = forms.SearchForm()
    query= Q(publication_status='published')
    search_keys = ('title__contains', 'description__contains', 'main__name__contains', 'sub__contains')
    if request.method == 'GET':
        search_form = forms.SearchForm(request.GET)
        
        if search_form.is_valid():
            query &= Q(_connector=Q.OR, dict.fromkeys(search_keys, search_form.cleaned_data['q']))
            
    context = {
        'products': models.Product.objects.filter(query).order_by(key.removesuffix('_contains') for key in search_keys),
        'search_form':search_form,
        }
    return render(request, 'search.html', context=context)

не забывайте, вы можете очистить q в форме:

class SearchForm(forms.Form):
    q = forms.CharField(label='Search', max_length=1024, required=False)

    def clean_q(self):
        q = (self.cleaned_data.get('q') or '').strip()
        if q:
            return q
        raise ValidationError("Nothing to search")
Вернуться на верх