Skip to content
This repository has been archived by the owner on Jan 28, 2020. It is now read-only.

Commit

Permalink
Cleaned up form-based search code
Browse files Browse the repository at this point in the history
  • Loading branch information
George Schneeloch committed Sep 10, 2015
1 parent 24900b7 commit 3b856c7
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 435 deletions.
34 changes: 0 additions & 34 deletions learningresources/tests/test_forms.py

This file was deleted.

97 changes: 6 additions & 91 deletions rest/views.py
Expand Up @@ -24,8 +24,6 @@
from rest_framework.viewsets import GenericViewSet
from celery.states import FAILURE, SUCCESS, REVOKED
from celery.result import AsyncResult
from haystack.inputs import Exact
from haystack.query import SearchQuerySet
from statsd.defaults.django import statsd

from exporter.tasks import export_resources
Expand Down Expand Up @@ -63,9 +61,8 @@
ViewStaticAssetPermission,
)
from rest.util import CheckValidMemberParamMixin
from search.sorting import LoreSortingFields
from search.api import construct_queryset, make_facet_counts
from taxonomy.models import Vocabulary
from ui.views import get_vocabularies
from learningresources.models import (
Repository,
LearningResourceType,
Expand Down Expand Up @@ -800,94 +797,11 @@ class RepositorySearchList(GenericViewSet):
permission_classes = (ViewRepoPermission, IsAuthenticated)

def get_queryset(self):
repo_slug = self.kwargs['repo_slug']
query = self.request.GET.get('q', '')
queryset = SearchQuerySet()

selected_facets = self.request.GET.getlist('selected_facets')
kwargs = {}
for facet in selected_facets:
queryset = queryset.narrow(facet)

if query != "":
kwargs["content"] = query

queryset = queryset.filter(**kwargs)

repo_slug = self.kwargs['repo_slug']
queryset = queryset.filter(repository=Exact(repo_slug))

order_by = self.request.GET.get('sortby', '')
if order_by == "":
order_by = LoreSortingFields.DEFAULT_SORTING_FIELD
# default values in case of weird sorting options
order_by, _, order_direction = LoreSortingFields.get_sorting_option(
order_by)
order_by = "{0}{1}".format(order_direction, order_by)
queryset = queryset.order_by(
order_by, LoreSortingFields.BASE_SORTING_FIELD)

return queryset

def make_facet_counts(self, queryset): # pylint: disable=too-many-locals
"""
Facets on every facet available and provides a data structure
of facet count information ready for API use.
"""
def reformat(key, values, missing_count=None):
"""Convert tuples to dictionaries so we can use keys."""
reformatted = {
"facet": {
"key": str(key[0]),
"label": key[1]
},
"values": [
{
"label": value_label,
"key": str(value_key),
"count": count
} for value_key, value_label, count in values
]
}
if missing_count is not None:
reformatted["facet"]["missing_count"] = missing_count
return reformatted

for facet in ('course', 'run', 'resource_type'):
queryset = queryset.facet(facet)

repo_slug = self.kwargs['repo_slug']
vocabularies = Vocabulary.objects.filter(repository__slug=repo_slug)

for vocabulary in vocabularies:
queryset = queryset.facet(vocabulary.id)

facet_counts = queryset.facet_counts()
vocabs = get_vocabularies(facet_counts)

# return dictionary
ret_dict = {}

# process vocabularies
for key, values in vocabs.items():
missing_count = queryset.filter(
_missing_='{0}_exact'.format(key[0])).count()
ret_dict[key[0]] = reformat(
key, values, missing_count=missing_count)

# Reformat facet_counts to match term counts.
for key, label in (
("course", "Course"),
("run", "Run"),
("resource_type", "Item Type")
):
if 'fields' in facet_counts:
values = [(name, name, count) for name, count
in facet_counts['fields'][key]]
else:
values = []
ret_dict[key] = reformat((key, label), values)

return ret_dict
sortby = self.request.GET.get('sortby', '')
return construct_queryset(repo_slug, query, selected_facets, sortby)

@statsd.timer('lore.rest.repository_search_list')
def list(self, *args, **kwargs): # pylint: disable=unused-argument
Expand All @@ -896,7 +810,8 @@ def list(self, *args, **kwargs): # pylint: disable=unused-argument
an extra value for facet_counts.
"""
queryset = self.filter_queryset(self.get_queryset())
facet_counts = self.make_facet_counts(queryset)
repo_slug = self.kwargs['repo_slug']
facet_counts = make_facet_counts(repo_slug, queryset)

page = self.paginate_queryset(queryset)
if page is not None:
Expand Down
27 changes: 0 additions & 27 deletions search/__init__.py
@@ -1,27 +0,0 @@
"""
Import bits of the 'search' app which must be there when Django starts.
"""
from __future__ import unicode_literals

from haystack.query import SearchQuerySet

from search import signals
from taxonomy.models import Vocabulary


def get_sqs():
"""
Get custom SearchQuerySet for LORE.
Calling .facet() for every field must be done for the field
to be in the "facets" context variable provided by Haystack.
"""
sqs = SearchQuerySet()
# Add hard-coded facets.
for facet in ("course", "run", "resource_type"):
sqs = sqs.facet(facet)
# Add dynamic facets (from taxonomy). Certain characters cause problems,
# so use the primary key.
for vocabulary_id in Vocabulary.objects.all().values_list("id", flat=True):
sqs = sqs.facet(vocabulary_id)
return sqs
131 changes: 131 additions & 0 deletions search/api.py
@@ -0,0 +1,131 @@
"""
Functions for search functionality.
"""

from __future__ import unicode_literals

from haystack.inputs import Exact
from haystack.query import SearchQuerySet

from search.sorting import LoreSortingFields
from taxonomy.models import Vocabulary
from ui.views import get_vocabularies


def construct_queryset(repo_slug, query='', selected_facets=None, sortby=''):
"""
Create a SearchQuerySet given search parameters.
Args:
repo_slug (learningresources.models.Repository):
Slug for repository being searched.
query (unicode): If present, search phrase to use in queryset.
selected_facets (list or None):
If present, a list of facets to narrow the search with.
sortby (unicode): If present, order by this sorting option.
Returns:
haystack.query.SearchQuerySet: The queryset.
"""
if selected_facets is None:
selected_facets = []

queryset = SearchQuerySet()

kwargs = {}
for facet in selected_facets:
queryset = queryset.narrow(facet)

if query != "":
kwargs["content"] = query

queryset = queryset.filter(**kwargs)

queryset = queryset.filter(repository=Exact(repo_slug))

if sortby == "":
sortby = LoreSortingFields.DEFAULT_SORTING_FIELD
# default values in case of weird sorting options
sortby, _, order_direction = LoreSortingFields.get_sorting_option(
sortby)
sortby = "{0}{1}".format(order_direction, sortby)
queryset = queryset.order_by(
sortby, LoreSortingFields.BASE_SORTING_FIELD)

return queryset


def make_facet_counts(repo_slug, queryset):
"""
Facets on every facet available and provides a data structure
of facet count information ready for API use.
Args:
repo_slug (unicode): The slug for the repository.
queryset (haystack.query.SearchQuerySet):
The queryset to use for counting facets.
Returns:
dict:
A data structure with facet count information. It's structured
like this:
{"resource_type": {
"facet": {"key": "resource_type", "label": "Item Type"},
"values": [
{"count": 39, "key": "vertical", "label": "vertical"}
...
"""

def reformat(key, values, missing_count=None):
"""Convert tuples to dictionaries so we can use keys."""
reformatted = {
"facet": {
"key": str(key[0]),
"label": key[1]
},
"values": [
{
"label": value_label,
"key": str(value_key),
"count": count
} for value_key, value_label, count in values
]
}
if missing_count is not None:
reformatted["facet"]["missing_count"] = missing_count
return reformatted

for facet in ('course', 'run', 'resource_type'):
queryset = queryset.facet(facet)

vocabularies = Vocabulary.objects.filter(repository__slug=repo_slug)

for vocabulary in vocabularies:
queryset = queryset.facet(vocabulary.id)

facet_counts = queryset.facet_counts()
vocabs = get_vocabularies(facet_counts)

# return dictionary
ret_dict = {}

# process vocabularies
for key, values in vocabs.items():
missing_count = queryset.filter(
_missing_='{0}_exact'.format(key[0])).count()
ret_dict[key[0]] = reformat(
key, values, missing_count=missing_count)

# Reformat facet_counts to match term counts.
for key, label in (
("course", "Course"),
("run", "Run"),
("resource_type", "Item Type")
):
if 'fields' in facet_counts:
values = [(name, name, count) for name, count
in facet_counts['fields'][key]]
else:
values = []
ret_dict[key] = reformat((key, label), values)

return ret_dict
34 changes: 0 additions & 34 deletions search/forms.py

This file was deleted.

23 changes: 6 additions & 17 deletions search/tests/base.py
Expand Up @@ -3,7 +3,7 @@
from __future__ import unicode_literals

from learningresources.tests.base import LoreTestCase
from search.forms import SearchForm
from search.api import construct_queryset
from search.sorting import LoreSortingFields
from taxonomy.models import Term, Vocabulary

Expand All @@ -30,14 +30,8 @@ def search(self, query, sorting=LoreSortingFields.DEFAULT_SORTING_FIELD):
"""
Helper function to perform a search
"""
sort_order = LoreSortingFields.get_sorting_option(sorting)[2]
form = SearchForm(
data={"q": query},
repo_slug=self.repo.slug,
sortby=sorting,
sort_order=sort_order
)
return form.search()
return construct_queryset(
repo_slug=self.repo.slug, query=query, sortby=sorting)

def count_results(self, query):
"""Return count of matching indexed records."""
Expand All @@ -46,12 +40,7 @@ def count_results(self, query):
def count_faceted_results(self, vocab, term):
"""Return count of matching indexed records by facet."""
facet_query = "{0}_exact:{1}".format(vocab, term)
sorting = LoreSortingFields.DEFAULT_SORTING_FIELD
sort_order = LoreSortingFields.get_sorting_option(sorting)[2]
form = SearchForm(
selected_facets=[facet_query],
return construct_queryset(
repo_slug=self.repo.slug,
sortby=sorting,
sort_order=sort_order
)
return form.search().count()
selected_facets=[facet_query]
).count()

0 comments on commit 3b856c7

Please sign in to comment.