Skip to content

Adapting this backend for use with Django Oscar 4.x #1

@em-ex

Description

@em-ex

Hello,
First of all I'd like thank you for publishing this project!

To be completely honest I've very little experience with Haystack but I'm working on a e-commerce site built on Django Oscar framework which for a while used Haystack for search. Since Oscar was updated, its entire catalogue functionality got rewritten to use Haystack too. Introducing solutions like Elasticsearch/OpenSearch or Solr to a small store seemed like an overkill so it was suggested to me in the Oscar community to take a look at this Postgres backend instead.

Recently I've been trying to make it work with Oscar but encountered a few issues, particularly with how the backend handles BooleanField and MultiValueField in the context of Oscar's catalogue app.

After building schema, migrating, and updating index (I'm using the default Oscar's search indexes) I was getting empty catalogue pages. Initial/base query was returning all the products but somewhere along the way the query got down to 0. While debugging I noticed that the query that was being generated upon visiting category pages looked like this:

<SQ: AND (is_public__content=true AND structure__in=['standalone', 'parent'] AND category__in=[13410])>

Two issues with it:

  • is_public__contentalways returned 0 product, however if omitted
  • category__in threw a TypeError: 'int' object is not iterable, and even if PKs were strings (category__in=['13410',]) it still returned no product

I tinkered with the queries in debug console for a while. Manually altering the SearchQuerySet to something like SearchQuerySet().filter_and(is_public__exact=True, structure__in=["standalone", "parent"], category__in=[{"13410"}]) brought me to a query which looked like this:

<SQ: AND (category__in=[{'13410'}] AND is_public__exact=True AND structure__in=['standalone', 'parent'])>

This way it finally started to render products from the given category. However it didn't return products that belonged to multiple categories.

After more experiments I landed on a monkey patch that allowed me to use your backend with Oscar's out-of-the-box views:

diff --git a/__init__.orig.py b/__init__.py
index d04645e..53a3270 100755
--- a/__init__.orig.py
+++ b/__init__.py
@@ -743,10 +743,26 @@ class ORMSearchQuery(BaseSearchQuery):
                 return index.fields[field].is_multivalued
         return False
 
+    def _is_boolean(self, field):
+        ui = connections["default"].get_unified_index()
+        for index in ui.get_indexes().values():
+            if field in index.fields and index.fields[field].field_type == 'boolean':
+                return True
+        return False
+
     def build_query_fragment(self, field, filter_type, value):
         if hasattr(value, "prepare"):
             value = value.prepare(self)
 
+        # monkey patch to check for boolean field
+        if self._is_boolean(field) and filter_type in (None, 'content'):
+            if isinstance(value, str):
+                if value.lower() == "true":
+                    value = True
+                elif value.lower() == "false":
+                    value = False
+            return Q(**{f"{field}__exact": value})
+
         if filter_type == "content":
             if field == "content":
                 self.content_search_text = value
@@ -772,7 +788,11 @@ class ORMSearchQuery(BaseSearchQuery):
             value = (value[0], value[1])
 
         # For MultiValueField (ArrayField), use array containment.
-        if filter_type == "exact" and self._is_multivalued(field):
+        if self._is_multivalued(field):
+            if filter_type == "in":
+                return Q(**{f"{field}__overlap": list(value)})
+            if filter_type == "exact":
+                return Q(**{f"{field}__contains": [value]})
             return Q(**{f"{field}__contains": [value]})
 
         # contains/startswith/endswith → case-insensitive Django lookups

Main question is: am I missing something/doing something wrong?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions