Django Rest Framework: how to arrange / sort a search / filter query?

I am building an API with Django Rest Framework and I would like to have a function that allows users to search for a query. At the moment, http://127.0.0.1:8000/api/v1/species/?name=human

it gives:

{
    count: 3,
    next: null,
    previous: null,
    results: [
        {
            id: 1,
            name: "Humanoid",
            characters: [
                {
                    id: 46,
                    name: "Doctor Princess"
                }
            ]
        },
        {
            id: 3,
            name: "Inhuman (overtime)",
            characters: [

            ]
        },
        {
            id: 4,
            name: "Human",
            characters: [
                {
                    id: 47,
                    name: "Abraham Lincoln"
                }
            ]
        }
    ]
}

      

This is pretty close to what I want, but not quite there. I would like the first object inside to results

be the one that was id

of 4, since the field is name

most relevant to the search term (? Name = human). (I don't care about the ordering of the others.) It seems to be currently sorting the results in ascending order id

. Does anyone know a good way to handle this? Thank!

Here is my api folder views.py

class SpeciesFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(name="name", lookup_type=("icontains"))
    class Meta:
        model = Species
        fields = ['name']

class SpeciesViewSet(viewsets.ModelViewSet):
    queryset = Species.objects.all()
    serializer_class = SpeciesSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    # search_fields = ('name',)
    filter_class = SpeciesFilter

      

+3


source to share


2 answers


You want to sort the search result by relevance , in your case name: "Human"

should be the best result since it exactly matches the query word.

If this is just to solve the problem, you can use a raw SQL query to achieve your goal, e.g .:

# NOT TESTED, sql expression may vary based on which database you are using
queryset = Species.objects.raw("select * from species where lower(name) like '%human%' order by char_length(name) desc limit 20")

      



This query will find the entire record containing "human" (ignore cases) and sort the result by the length of the desc name field. which name: "Human"

will be the first element to be displayed.


FYI, database query is usually not the best approach for this kind of thing, you should check out the djang-haystack project which helps you build a search engine from a django project quickly and easily.

+3


source


I agree with @piglei on django-haystack, but I think sorting by the length of the field value is a terrible idea and there is no need to resort to writing SQL to do this. The best way would be something like:

 Species.objects.all().extra(order_by=['char_length(name)'])  # PostgreSQl

      

Still awful, even how to fix it quickly. Unless you really want to install django-haystack, a slightly less scary approach is to sort your results with python:

from difflib import SequenceMatcher

species = Species.objects.all()

species = sorted(species,
                 lambda s: SequenceMatcher(None, needle.lower(), s.name.lower()).quick_ratio(),
                 reverse=True)

      

I have not tested this code, so let me know if it doesn't work and also if you need help integrating it into DRF.



The reason this is still scary is that the difflib search algorithm is different from the one used for database searches, so you can never get results that are more relevant using difflib than some of those who __icontains

can find. More on this here: Is there a way to filter the django queryset based on string similarity (a la python difflib)?

Edit:

While trying to find an example of why sorting by the length of a field value is a terrible idea, I actually managed to convince myself that it could be a less terrible idea when used with __icontains

. I'm going to leave the answer as though it might be useful or interesting to someone. Example:

needle = 'apple'
haystack = ['apple', 'apples', 'apple computers', 'apples are nice']  # Sorted by value length

      

0


source







All Articles