2

I'm using django 1.8. What I need is to do case insensitive admin-search in multiple fields and allow the user to use the AND, OR and NOT operators and some how group words either with parentheses or quotes. Search Example:

cotton and (red or "dark blue")

I've already discovered django-advanced-filter and django-filter... They are filters! I also want to allow the user to type in the keys in the search box. I know that get_search_results allows us to override the search behaviour, but before I write a code for this, I want to ask is there a package that would do this for me? Note that I feel that making a custom search with haystack is pretty complex.

max
  • 9,708
  • 15
  • 89
  • 144
  • Custom search should be the way ahead if you're looking for real search functionality. Doing a search on DB is not really worth it in the long run. (Unless you decide to get hit by the slowness and only then decide to move to a real search). – zEro Mar 19 '16 at 23:01
  • so how would I do that for an admin view of a specific table? – max Mar 20 '16 at 05:51
  • Something like this: http://stackoverflow.com/questions/28512710/how-to-add-custom-search-box-in-django-admin but then of course you need to handle the way search is handled. – zEro Mar 20 '16 at 09:58
  • I already know about the search_fields which is the solution discussed there but that's not what I'm after. Thanks. – max Mar 21 '16 at 03:15

1 Answers1

4

This answer seems to work for me after performing the little edit mentioned in my comment. Yet, I have no idea whether this is the "correct" way of doing it.

Here is the updated code that works on django 1.8:

from django.contrib import admin
from django.db import models
from bookstore.models import Book
from django.contrib.admin.views.main import ChangeList
import operator

class MyChangeList(ChangeList):
    def __init__(self, *a):
        super(MyChangeList, self).__init__(*a)

    def get_queryset(self, request):
        print dir(self)
        # First, we collect all the declared list filters.
        (self.filter_specs, self.has_filters, remaining_lookup_params,
         use_distinct) = self.get_filters(request)

        # Then, we let every list filter modify the queryset to its liking.
        qs = self.root_queryset
        for filter_spec in self.filter_specs:
            new_qs = filter_spec.queryset(request, qs)
            if new_qs is not None:
                qs = new_qs

        try:
            # Finally, we apply the remaining lookup parameters from the query
            # string (i.e. those that haven't already been processed by the
            # filters).
            qs = qs.filter(**remaining_lookup_params)
        except (SuspiciousOperation, ImproperlyConfigured):
            # Allow certain types of errors to be re-raised as-is so that the
            # caller can treat them in a special way.
            raise
        except Exception, e:
            # Every other error is caught with a naked except, because we don't
            # have any other way of validating lookup parameters. They might be
            # invalid if the keyword arguments are incorrect, or if the values
            # are not in the correct type, so we might get FieldError,
            # ValueError, ValidationError, or ?.
            raise IncorrectLookupParameters(e)

        # Use select_related() if one of the list_display options is a field
        # with a relationship and the provided queryset doesn't already have
        # select_related defined.
        if not qs.query.select_related:
            if self.list_select_related:
                qs = qs.select_related()
            else:
                for field_name in self.list_display:
                    try:
                        field = self.lookup_opts.get_field(field_name)
                    except Exception as ex:# models.FieldDoesNotExist:
                        print ex
                        pass
                    else:
                        if isinstance(field.rel, models.ManyToOneRel):
                            qs = qs.select_related()
                            break

        # Set ordering.
        ordering = self.get_ordering(request, qs)
        qs = qs.order_by(*ordering)

        # Apply keyword searches.
        def construct_search(field_name):
            if field_name.startswith('^'):
                return "%s__istartswith" % field_name[1:]
            elif field_name.startswith('='):
                return "%s__iexact" % field_name[1:]
            elif field_name.startswith('@'):
                return "%s__search" % field_name[1:]
            else:
                return "%s__icontains" % field_name

        if self.search_fields and self.query:
            orm_lookups = [construct_search(str(search_field))
                           for search_field in self.search_fields]
            or_queries = []
            for bit in self.query.split():
                or_queries += [models.Q(**{orm_lookup: bit})
                              for orm_lookup in orm_lookups]
            if len(or_queries) > 0:
                qs = qs.filter(reduce(operator.or_, or_queries))
            if not use_distinct:
                for search_spec in orm_lookups:
                    if admin.utils.lookup_needs_distinct(self.lookup_opts, search_spec):
                        use_distinct = True
                        break

        if use_distinct:
            return qs.distinct()
        else:
            return qs


@admin.register(Book)
class AdminBookstore(admin.ModelAdmin):
    list_display = ('title', 'author', 'description')
    search_fields = ('title', 'author', 'description')
    def get_changelist(*a, **k):
        return MyChangeList
Community
  • 1
  • 1
Jarno
  • 6,243
  • 3
  • 42
  • 57
  • That is promising. It`s an old thread so I am trying to translate it to django 1.8 – max Apr 26 '16 at 21:13
  • Well actually I was referring to the answer of bahoo, but that one might work as well, though it didn't for me. – Jarno Apr 27 '16 at 08:41
  • This solution only does RO operation. For my project, I need to do combination of OR-AND. Would haystack solve this problem better? – max Apr 27 '16 at 15:48
  • 1
    I havn't worked with it, but it seems that in Haystack you can only use one boolean operator, i.e. you can't combine them as you desire. However, this looks promising for your purpose https://coderwall.com/p/wz_zdg/generating-searchqueryset-with-multiple-boolean-operators-in-haystack – Jarno Apr 27 '16 at 16:19