Search code examples
pythondjangodjango-autocomplete-light

Force django-autocomplete=light to return 'best' matches first


I have a long list of items I am searching through with a django-autocomplete-light field, with the autocomplete defined as such:

class OccupationAutocomplete(autocomplete_light.AutocompleteModelBase):
    model = models.Occupation
    search_fields=['title',]

Some job titles are alphabetically quite far through the list, like for example:

"teacher"

which come after other 'less ideal titles', like:

"Agricultural teacher", "Architecture teacher", "Building teacher", etc...

What I would like is for the "best" match, either closest or just match that starts with the text of the search, so if someone searches for "teach", they get "teacher", as it starts with the same letters, and then other less accurate matches after.

I've tried setting search_fields with a preferred order

    search_fields=['^title','title',]

but an analysis of the autocomplete code shows that the terms are all munged into one query before being returned.

How would I go about ordering this list in a more appropriate way?


Solution

  • In the end I had to implement a new class, this just accepts a list of dictionaries in order of 'preferred weight' and then returns results based on that.

    class OrderedAutocomplete(autocomplete_light.AutocompleteModelBase):
        ordered_search_fields = []
    
        def choices_for_request(self):
            """
            Return a queryset based on :py:attr:`choices` using options
            :py:attr:`split_words`, :py:attr:`search_fields` and
            :py:attr:`limit_choices`.
            """
            assert self.choices is not None, 'choices should be a queryset'
            q = self.request.GET.get('q', '')
            exclude = self.request.GET.getlist('exclude')
    
            base_split_words = self.split_words
    
            _choices = []
            for weight,opt in enumerate(self.ordered_search_fields):
                if len(_choices) >= self.limit_choices:
                    break
    
                search_fields = opt['search_fields']
                self.split_words = opt['split_words'] or base_split_words
    
                conditions = self._choices_for_request_conditions(q,
                        search_fields)
                choices = self.order_choices(self.choices.filter(conditions).exclude(pk__in=exclude))
                if choices.count():
                    _choices += list(choices)[0:self.limit_choices]
    
            return _choices[0:self.limit_choices]
    

    This can then be instantiated using:

    class OccupationAutocomplete(OrderedAutocomplete):
        model = models.Occupation
    
        ordered_search_fields = [
            {'search_fields': ['^title'], 'split_words':False},
            {'search_fields': ['title'], 'split_words':True},
            ]
    autocomplete_light.register(OccupationAutocomplete)