- # Figure out if we were searching for a token matching some word in theme name.
- themes = frag.tags.filter(category='theme')
- themes_hit = set()
- if self.query_terms is not None:
- for i in range(0, len(f[self.OTHER]['themes'])):
- tms = f[self.OTHER]['themes'][i].split(r' +') + f[self.OTHER]['themes_pl'][i].split(' ')
- tms = map(unicode.lower, tms)
- for qt in self.query_terms:
- if qt in tms:
- themes_hit.add(f[self.OTHER]['themes'][i])
- break
-
- def theme_by_name(n):
- th = filter(lambda t: t.name == n, themes)
- if th:
- return th[0]
- else:
- return None
- themes_hit = filter(lambda a: a is not None, map(theme_by_name, themes_hit))
-
- m = {'score': f[self.SCORE],
- 'fragment': frag,
- 'section_number': f[self.POSITION][self.POSITION_INDEX] + 1,
- 'themes': themes,
- 'themes_hit': themes_hit
- }
- m.update(f[self.OTHER])
- hits.append(m)
-
- hits.sort(lambda a, b: cmp(a['score'], b['score']), reverse=True)
-
- self._processed_hits = hits
-
- return hits
-
- @staticmethod
- def aggregate(*result_lists):
- books = {}
- for rl in result_lists:
- for r in rl:
- if r.book_id in books:
- books[r.book_id].merge(r)
- else:
- books[r.book_id] = r
- return books.values()
-
- def __cmp__(self, other):
- c = cmp(self.score, other.score)
- if c == 0:
- # this is inverted, because earlier date is better
- return cmp(other.published_date, self.published_date)
- else:
- return c
-
- def __len__(self):
- return len(self.hits)
-
- def snippet_pos(self, idx=0):
- return self.hits[idx]['snippets_pos']
-
- def snippet_revision(self, idx=0):
- try:
- return self.hits[idx]['snippets_revision']
- except (IndexError, KeyError):
- return None
-
-
-class Search(SolrIndex):
- """
- Search facilities.
- """
- def __init__(self, default_field="text"):
- super(Search, self).__init__(mode='r')
-
- def make_term_query(self, query, field='text', modal=operator.or_):
- """
- Returns term queries joined by boolean query.
- modal - applies to boolean query
- fuzzy - should the query by fuzzy.
- """
- if query is None:
- query = ''
- q = self.index.Q()
- q = reduce(modal, map(lambda s: self.index.Q(**{field: s}), query.split(r" ")), q)
-
- return q
-
- def search_phrase(self, searched, field='text', book=False,
- filters=None,
- snippets=False):
- if filters is None:
- filters = []
- if book:
- filters.append(self.index.Q(is_book=True))
-
- q = self.index.query(**{field: searched})
- q = self.apply_filters(q, filters).field_limit(score=True, all_fields=True)
- res = q.paginate(rows=100).execute()
- return [SearchResult(found, how_found=u'search_phrase') for found in res]
-
- def search_some(self, searched, fields, book=True,
- filters=None, snippets=True, query_terms=None):
- assert isinstance(fields, list)
- if filters is None:
- filters = []
- if book:
- filters.append(self.index.Q(is_book=True))
-
- query = self.index.Q()
-
- for fld in fields:
- query = self.index.Q(query | self.make_term_query(searched, fld))
-
- query = self.index.query(query)
- query = self.apply_filters(query, filters).field_limit(score=True, all_fields=True)
- res = query.execute()
- return [SearchResult(found, how_found='search_some', query_terms=query_terms) for found in res]
-
- def search_everywhere(self, searched, query_terms=None):
- """
- Tries to use search terms to match different fields of book (or its parts).
- E.g. one word can be an author survey, another be a part of the title, and the rest
- are some words from third chapter.
- """
- books = []
- # content only query : themes x content
- q = self.make_term_query(searched, 'text')
- q_themes = self.make_term_query(searched, 'themes_pl')
-
- query = self.index.query(q).query(q_themes).field_limit(score=True, all_fields=True)
- res = query.execute()
-
- for found in res:
- books.append(SearchResult(found, how_found='search_everywhere_themesXcontent', query_terms=query_terms))
-
- # query themes/content x author/title/tags
- in_content = self.index.Q()
- in_meta = self.index.Q()
-
- for fld in ['themes_pl', 'text']:
- in_content |= self.make_term_query(searched, field=fld)
-
- for fld in ['tags', 'authors', 'title']:
- in_meta |= self.make_term_query(searched, field=fld)
-
- q = in_content & in_meta
- res = self.index.query(q).field_limit(score=True, all_fields=True).execute()
-
- for found in res:
- books.append(SearchResult(found, how_found='search_everywhere', query_terms=query_terms))
-
- return books
-
- def get_snippets(self, searchresult, query, field='text', num=1):
- """
- Returns a snippet for found scoreDoc.
- """
- maxnum = len(searchresult)
- if num is None or num < 0 or num > maxnum:
- num = maxnum
- book_id = searchresult.book_id
- revision = searchresult.snippet_revision()
- snippets = Snippets(book_id, revision=revision)
- snips = [None] * maxnum
- try:
- snippets.open()
- idx = 0
- while idx < maxnum and num > 0:
- position, length = searchresult.snippet_pos(idx)
- if position is None or length is None:
- continue
- text = snippets.get((int(position),
- int(length)))
- snip = self.index.highlight(text=text, field=field, q=query)
- if snip not in snips:
- snips[idx] = snip
- if snip:
- num -= 1
- idx += 1
-
- except IOError, e:
- book = catalogue.models.Book.objects.filter(id=book_id)
- if not book:
- log.error("Book does not exist for book id = %d" % book_id)
- elif not book.get().children.exists():
- log.error("Cannot open snippet file for book id = %d [rev=%s], %s" % (book_id, revision, e))
- return []
- finally:
- snippets.close()
-
- # remove verse end markers..
- snips = map(lambda s: s and s.replace("/\n", "\n"), snips)
-
- searchresult.snippets = snips
-
- return snips
-
- def hint_tags(self, query, pdcounter=True, prefix=True):
- """
- Return auto-complete hints for tags
- using prefix search.
- """
- q = self.index.Q()
- query = query.strip()
- for field in ['tag_name', 'tag_name_pl']:
- if prefix:
- q |= self.index.Q(**{field: query + "*"})
- else:
- q |= self.make_term_query(query, field=field)
- qu = self.index.query(q)
-
- return self.search_tags(qu, pdcounter=pdcounter)
-
- def search_tags(self, query, filters=None, pdcounter=False):
- """
- Search for Tag objects using query.
- """
- if not filters:
- filters = []
- if not pdcounter:
- filters.append(~self.index.Q(is_pdcounter=True))
- res = self.apply_filters(query, filters).execute()
-
- tags = []
- pd_tags = []
-
- for doc in res:
- is_pdcounter = doc.get('is_pdcounter', False)
- category = doc.get('tag_category')
- try:
- if is_pdcounter:
- if category == 'pd_author':
- tag = PDCounterAuthor.objects.get(id=doc.get('tag_id'))
- elif category == 'pd_book':
- tag = PDCounterBook.objects.get(id=doc.get('tag_id'))
- tag.category = 'pd_book' # make it look more lik a tag.
- else:
- # WTF
- print ("Warning. cannot get pdcounter tag_id=%d from db; cat=%s" % (
- int(doc.get('tag_id')), category)).encode('utf-8')
- pd_tags.append(tag)
- else:
- tag = catalogue.models.Tag.objects.get(id=doc.get("tag_id"))
- tags.append(tag)
-
- except catalogue.models.Tag.DoesNotExist:
- pass
- except PDCounterAuthor.DoesNotExist:
- pass
- except PDCounterBook.DoesNotExist:
- pass
-
- tags_slugs = set(map(lambda t: t.slug, tags))
- tags = tags + filter(lambda t: t.slug not in tags_slugs, pd_tags)