Commit ae40c8c2 by Anthony Mangano

add custom endpoint that returns distinct hit and facet counts

ECOM-6815
parent a4495712
......@@ -313,7 +313,7 @@ class CourseRunSearchViewSetTests(DefaultPartnerMixin, SerializationMixin, Login
@ddt.ddt
class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin, ElasticsearchTestMixin,
class AggregateSearchViewSetTests(DefaultPartnerMixin, SerializationMixin, LoginMixin, ElasticsearchTestMixin,
SynonymTestMixin, APITestCase):
path = reverse('api:v1:search-all-facets')
......
# pylint: disable=abstract-method
from drf_haystack.fields import FacetDictField, FacetListField
from drf_haystack.serializers import FacetFieldSerializer
from rest_framework import serializers
from rest_framework.fields import DictField
from course_discovery.apps.api.serializers import AggregateFacetSearchSerializer, QueryFacetFieldSerializer
class DistinctCountsAggregateFacetSearchSerializer(AggregateFacetSearchSerializer):
""" Custom AggregateFacetSearchSerializer which includes distinct hit and facet counts."""
def get_fields(self):
""" Return the field_mapping needed for serializing the data."""
# Re-implement the logic from the superclass methods, but make sure to handle field and query facets properly.
# https://github.com/edx/course-discovery/blob/master/course_discovery/apps/api/serializers.py#L950
# https://github.com/inonit/drf-haystack/blob/master/drf_haystack/serializers.py#L373
field_data = self.instance.pop('fields', {})
query_data = self.format_query_facet_data(self.instance.pop('queries', {}))
field_mapping = super(DistinctCountsAggregateFacetSearchSerializer, self).get_fields()
field_mapping['fields'] = FacetDictField(
child=FacetListField(child=DistinctCountsFacetFieldSerializer(field_data), required=False)
)
field_mapping['queries'] = DictField(
query_data, child=DistinctCountsQueryFacetFieldSerializer(), required=False
)
if self.serialize_objects:
field_mapping.move_to_end('objects')
self.instance['fields'] = field_data
self.instance['queries'] = query_data
return field_mapping
def get_objects(self, instance):
""" Return the objects that should be serialized along with the facets."""
data = super(DistinctCountsAggregateFacetSearchSerializer, self).get_objects(instance)
data['distinct_count'] = self.context['objects'].distinct_count()
return data
def format_query_facet_data(self, query_facet_counts):
""" Format and return the query facet data so that it may be properly serialized."""
# Re-implement the logic from the superclass method, but make sure to handle changes to the raw query facet
# data and extract the distinct counts.
# https://github.com/edx/course-discovery/blob/master/course_discovery/apps/api/serializers.py#L966
query_data = {}
for field, options in getattr(self.Meta, 'field_queries', {}).items(): # pylint: disable=no-member
# The query facet data is expected to be formatted as a dictionary with fields mapping to a two-tuple
# containing count and distinct count.
count, distinct_count = query_facet_counts.get(field, (0, 0))
if count:
query_data[field] = {
'field': field,
'options': options,
'count': count,
'distinct_count': distinct_count,
}
return query_data
class DistinctCountsFacetFieldSerializer(FacetFieldSerializer):
""" Custom FacetFieldSerializer which includes distinct counts."""
distinct_count = serializers.SerializerMethodField()
def get_distinct_count(self, instance):
""" Return the distinct count for this facet."""
# The instance is expected to be formatted as a three tuple containing the field name, normal count and
# distinct count. This is consistent with the superclass implementation here:
# https://github.com/inonit/drf-haystack/blob/master/drf_haystack/serializers.py#L321
count = instance[2]
return serializers.IntegerField(read_only=True).to_representation(count)
class DistinctCountsQueryFacetFieldSerializer(QueryFacetFieldSerializer):
""" Custom QueryFacetFieldSerializer which includes distinct counts."""
distinct_count = serializers.SerializerMethodField()
def get_distinct_count(self, instance):
""" Return the distinct count for this facet."""
count = instance['distinct_count']
return serializers.IntegerField(read_only=True).to_representation(count)
from django.conf.urls import include, url
urlpatterns = [
url(r'^v1/', include('course_discovery.apps.edx_catalog_extensions.api.v1.urls', namespace='v1')),
]
from django.conf.urls import url
from course_discovery.apps.edx_catalog_extensions.api.v1.views import DistinctCountsAggregateSearchViewSet
# Only expose the facets action of this view
distinct_facets_action = DistinctCountsAggregateSearchViewSet.as_view({'get': 'facets'})
urlpatterns = [
url(r'^search/all/facets', distinct_facets_action, name='search-all-facets')
]
from course_discovery.apps.api.v1.views.search import AggregateSearchViewSet
from course_discovery.apps.edx_catalog_extensions.api.serializers import DistinctCountsAggregateFacetSearchSerializer
from course_discovery.apps.edx_haystack_extensions.distinct_counts.query import DistinctCountsSearchQuerySet
class DistinctCountsAggregateSearchViewSet(AggregateSearchViewSet):
""" Provides a facets action that can include distinct hit and facet counts in the response."""
# Custom serializer that includes distinct hit and facet counts.
facet_serializer_class = DistinctCountsAggregateFacetSearchSerializer
def get_queryset(self, *args, **kwargs):
""" Return the base Queryset to use to build up the search query."""
queryset = super(DistinctCountsAggregateSearchViewSet, self).get_queryset(*args, **kwargs)
return DistinctCountsSearchQuerySet.from_queryset(queryset).with_distinct_counts('aggregation_key')
from django.conf.urls import include, url
urlpatterns = [
url(r'^api/', include('course_discovery.apps.edx_catalog_extensions.api.urls', namespace='api')),
]
......@@ -50,6 +50,12 @@ urlpatterns = auth_urlpatterns + [
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
]
# Add the catalog extension urls if edx_catalog_extensions is installed.
if 'course_discovery.apps.edx_catalog_extensions' in settings.INSTALLED_APPS:
urlpatterns.append(
url(r'^extensions/', include('course_discovery.apps.edx_catalog_extensions.urls', namespace='extensions'))
)
if settings.DEBUG: # pragma: no cover
# We need this url pattern to serve user uploaded assets according to
# https://docs.djangoproject.com/en/1.10/howto/static-files/#serving-files-uploaded-by-a-user-during-development
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment