Home > Enterprise >  How to sort a queryset by its Q objects order?
How to sort a queryset by its Q objects order?

Time:09-18

I've a simple searching form and view:

# 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 %}

If the word "polo" is the title of product 2 and it's also the description of the product 1, The problem is when I'm trying to search for that word, The product 1 is rendered in the first place in the template (because it's the first matching).

What I'm trying to do is to sort the queryset first by title then by description then by sub category, so the product 2 is rendered first (because the queryset is sorted by its Q objects order)

CodePudding user response:

You should use custom order_by

for more info about creating custom order, you can read this page and for Django model things this 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)

CodePudding user response:

@lucas-grugru give you already a perfect answer:

# views.py
def search(request):
    
    query= Q(publication_status='published')
    ordering = ''
    
    search_form = forms.SearchForm(request.GET)  # you dont need to check it get or post.
    if search_form.is_valid():
        search_dict = dict.fromkeys(search_keys, search_form.cleaned_data['q'])
        query &= Q(_connector=Q.OR, *search_dict)
        ordering = Сase(When({key:q, then=Value(idx)) for idx, (key, q) in enumerate(search_dict.items()))            
    
    products = models.Product.objects.filter(query)          
    context = {
        'products': products.order_by(ordering) if ordering else products,
        'search_form':search_form,
        }
    return render(request, 'search.html', context=context)

I made if ordering, because ordering is not a free operation. Each field you add to the ordering incurs a cost to your database. Each foreign key you add will implicitly include all of its default orderings as well.

don't forget, you can clean q in form:

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")
  • Related