S
S
skorpix2015-12-14 15:22:11
Django
skorpix, 2015-12-14 15:22:11

Haystack+Solr: How to set up faceted search so that it works using the "OR" method?

Good day to all:
I'm using the latest version of haystack (2.4.1) and solr version 4 (if necessary, I can find out the exact version, but it seems to me that this is not critical).
Set up a faceted search, this is how the index looks like

class SouvenirProductIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True)
    category_title = indexes.CharField(model_attr='base_product__subcategory__category__title', faceted=True)
    baseproduct_title = indexes.CharField(model_attr='base_product__title', faceted=True)
    subcategory_title = indexes.CharField(model_attr='base_product__subcategory__title', faceted=True)
    people_categories = indexes.MultiValueField(faceted=True)
    holidays = indexes.MultiValueField(faceted=True)
    colors = indexes.MultiValueField(faceted=True)
    suggestions = indexes.FacetCharField()

    def get_model(self):
        return SouvenirProduct

    def prepare_people_categories(self, obj):
        return [people.style for people in obj.people_categories.all()]

    def prepare_holidays(self, obj):
        return [holiday.slug for holiday in obj.holidays.all()]

    def prepare_colors(self, obj):
        colors = []
        for product_color in obj.productcolor_set.all():
            color = product_color.color
            if color.image:
                colors.append(color.image.url)
            else:
                colors.append(color.rgb)
        return colors

View:
class ProductSearchView(FacetedSearchView):
    template_name = 'search/search.html'
    form_class = FacetedNotEmptySearchForm
    facet_fields = ['people_categories', 'holidays', 'category_title', 'colors']

The form is inherited from FacetedSearchForm, changes don't affect my question.
As a result, my products are found by queries, facets are also displayed (categories, for example), but if I select several categories, then the search tries to find a product that would belong to all of these selected categories (AND method), but I need it to gave those goods that belong to at least one of the selected categories (OR method). How can I do that?
schema.xml file
<?xml version="1.0" ?>

<schema name="default" version="1.5">
  <types>
    <fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
    <fieldtype name="binary" class="solr.BinaryField"/>

    <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
    <fieldType name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="true"/>

    <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>

    <fieldType name="date" class="solr.TrieDateField" omitNorms="true" precisionStep="0" positionIncrementGap="0"/>
    <fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>

    <fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
    <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
    <fieldtype name="geohash" class="solr.GeoHashField"/>

    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="spell_ru" class="solr.TextField" positionIncrementGap="100" omitNorms="true">
      <analyzer>
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.HunspellStemFilterFactory" dictionary="ru_RU.dic" affix="ru_RU.aff" ignoreCase="true" />
      </analyzer>
    </fieldType>

    <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
      <analyzer>
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="ngram" class="solr.TextField" >
      <analyzer type="index">
        <tokenizer class="solr.KeywordTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.KeywordTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
      <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
      </analyzer>
    </fieldType>
  </types>

  <fields>
    <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
    <field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
    <field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
    <field name="_version_" type="long" indexed="true" stored ="true"/>

    <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true"/>
    <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>
    <dynamicField name="*_t"  type="spell_ru"    indexed="true"  stored="true"/>
    <dynamicField name="*_b"  type="boolean" indexed="true"  stored="true"/>
    <dynamicField name="*_f"  type="float"  indexed="true"  stored="true"/>
    <dynamicField name="*_d"  type="double" indexed="true"  stored="true"/>
    <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
    <dynamicField name="*_p" type="location" indexed="true" stored="true"/>
    <dynamicField name="*_coordinate"  type="tdouble" indexed="true"  stored="false"/>

    <field name="text" type="spell_ru" indexed="true" stored="true" multiValued="false" />
    <field name="holidays" type="spell_ru" indexed="true" stored="true" multiValued="true" />
    <field name="category_title" type="spell_ru" indexed="true" stored="true" multiValued="false" />
    <field name="category_title_exact" type="string" indexed="true" stored="true" multiValued="false" />
    <field name="subcategory_title" type="spell_ru" indexed="true" stored="true" multiValued="false" />
    <field name="subcategory_title_exact" type="string" indexed="true" stored="true" multiValued="false" />
    <field name="baseproduct_title" type="spell_ru" indexed="true" stored="true" multiValued="false" />
    <field name="baseproduct_title_exact" type="string" indexed="true" stored="true" multiValued="false" />
    <field name="people_categories" type="spell_ru" indexed="true" stored="true" multiValued="true" />
    <field name="suggestions" type="spell_ru" indexed="true" stored="true" multiValued="false" />
    <field name="colors" type="spell_ru" indexed="true" stored="true" multiValued="true" />
    <field name="colors_exact" type="string" indexed="true" stored="true" multiValued="true" />
    <field name="people_categories_exact" type="string" indexed="true" stored="true" multiValued="true" />
    <field name="holidays_exact" type="string" indexed="true" stored="true" multiValued="true" />
  </fields>
  <uniqueKey>id</uniqueKey>
  <defaultSearchField>text</defaultSearchField>
  <solrQueryParser defaultOperator="OR"/>
</schema>

Answer the question

In order to leave comments, you need to log in

1 answer(s)
S
skorpix, 2015-12-15
@skorpix

Solution (suddenly someone will come in handy) (I have also done here that if the search string is not set, then all results are displayed):

from collections import defaultdict

from haystack.forms import FacetedSearchForm


class FacetedNotEmptySearchForm(FacetedSearchForm):

    def no_query_found(self):
        return self.searchqueryset.all()

    @property
    def selected_multi_facets(self):
        selected_multi_facets = defaultdict(list)
        for facet_kv in self.selected_facets:
            if ":" not in facet_kv:
                continue
            field_name, value = facet_kv.split(':', 1)
            selected_multi_facets[field_name].append(value)
        return selected_multi_facets

    def search(self):
        sqs = super(FacetedSearchForm, self).search()
        for field, values in self.selected_multi_facets.items():
            if not values:
                continue
            clean_values = ['"%s"' % sqs.query.clean(val) for val in values]
            sqs = sqs.narrow(u'%s:(%s)' % (field, " OR ".join(clean_values)))
        return sqs

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question