Commit 49038df3 by Anthony Mangano

add aggregation_key to search serializers

ECOM-6815
Adds the aggregation_key field to the search serializers so that it may
be used on the marketing site to filter duplicate courses from search
results.
parent ca1553b0
...@@ -53,6 +53,7 @@ COURSE_RUN_SEARCH_FIELDS = ( ...@@ -53,6 +53,7 @@ COURSE_RUN_SEARCH_FIELDS = (
'enrollment_end', 'pacing_type', 'language', 'transcript_languages', 'marketing_url', 'content_type', 'org', 'enrollment_end', 'pacing_type', 'language', 'transcript_languages', 'marketing_url', 'content_type', 'org',
'number', 'seat_types', 'image_url', 'type', 'level_type', 'availability', 'published', 'partner', 'program_types', 'number', 'seat_types', 'image_url', 'type', 'level_type', 'availability', 'published', 'partner', 'program_types',
'authoring_organization_uuids', 'subject_uuids', 'staff_uuids', 'mobile_available', 'logo_image_urls', 'authoring_organization_uuids', 'subject_uuids', 'staff_uuids', 'mobile_available', 'logo_image_urls',
'aggregation_key',
) )
PROGRAM_FACET_FIELD_OPTIONS = { PROGRAM_FACET_FIELD_OPTIONS = {
...@@ -66,8 +67,8 @@ BASE_PROGRAM_FIELDS = ( ...@@ -66,8 +67,8 @@ BASE_PROGRAM_FIELDS = (
'published', 'partner', 'published', 'partner',
) )
PROGRAM_SEARCH_FIELDS = BASE_PROGRAM_FIELDS + ('authoring_organizations', 'authoring_organization_uuids', PROGRAM_SEARCH_FIELDS = BASE_PROGRAM_FIELDS + ('aggregation_key', 'authoring_organizations',
'subject_uuids', 'staff_uuids',) 'authoring_organization_uuids', 'subject_uuids', 'staff_uuids',)
PROGRAM_FACET_FIELDS = BASE_PROGRAM_FIELDS + ('organizations',) PROGRAM_FACET_FIELDS = BASE_PROGRAM_FIELDS + ('organizations',)
PREFETCH_FIELDS = { PREFETCH_FIELDS = {
...@@ -980,7 +981,7 @@ class CourseSearchSerializer(HaystackSerializer): ...@@ -980,7 +981,7 @@ class CourseSearchSerializer(HaystackSerializer):
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
fields = ('key', 'title', 'short_description', 'full_description', 'text',) fields = ('key', 'title', 'short_description', 'full_description', 'text', 'aggregation_key',)
ignore_fields = COMMON_IGNORED_FIELDS ignore_fields = COMMON_IGNORED_FIELDS
index_classes = [CourseIndex] index_classes = [CourseIndex]
......
...@@ -13,7 +13,7 @@ from course_discovery.apps.api.fields import ImageField, StdImageSerializerField ...@@ -13,7 +13,7 @@ from course_discovery.apps.api.fields import ImageField, StdImageSerializerField
from course_discovery.apps.api.serializers import ( from course_discovery.apps.api.serializers import (
AffiliateWindowSerializer, CatalogSerializer, ContainedCourseRunsSerializer, ContainedCoursesSerializer, AffiliateWindowSerializer, CatalogSerializer, ContainedCourseRunsSerializer, ContainedCoursesSerializer,
CorporateEndorsementSerializer, CourseRunSearchSerializer, CourseRunSerializer, CourseRunWithProgramsSerializer, CorporateEndorsementSerializer, CourseRunSearchSerializer, CourseRunSerializer, CourseRunWithProgramsSerializer,
CourseSerializer, CourseWithProgramsSerializer, EndorsementSerializer, FAQSerializer, CourseSearchSerializer, CourseSerializer, CourseWithProgramsSerializer, EndorsementSerializer, FAQSerializer,
FlattenedCourseRunWithCourseSerializer, ImageSerializer, MinimalCourseRunSerializer, MinimalCourseSerializer, FlattenedCourseRunWithCourseSerializer, ImageSerializer, MinimalCourseRunSerializer, MinimalCourseSerializer,
MinimalOrganizationSerializer, MinimalProgramCourseSerializer, MinimalProgramSerializer, NestedProgramSerializer, MinimalOrganizationSerializer, MinimalProgramCourseSerializer, MinimalProgramSerializer, NestedProgramSerializer,
OrganizationSerializer, PersonSerializer, PositionSerializer, PrerequisiteSerializer, ProgramSearchSerializer, OrganizationSerializer, PersonSerializer, PositionSerializer, PrerequisiteSerializer, ProgramSearchSerializer,
...@@ -26,7 +26,7 @@ from course_discovery.apps.core.tests.factories import UserFactory ...@@ -26,7 +26,7 @@ from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.core.tests.helpers import make_image_file from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.core.tests.mixins import ElasticsearchTestMixin from course_discovery.apps.core.tests.mixins import ElasticsearchTestMixin
from course_discovery.apps.course_metadata.choices import CourseRunStatus, ProgramStatus from course_discovery.apps.course_metadata.choices import CourseRunStatus, ProgramStatus
from course_discovery.apps.course_metadata.models import CourseRun, Program from course_discovery.apps.course_metadata.models import Course, CourseRun, Program
from course_discovery.apps.course_metadata.tests.factories import ( from course_discovery.apps.course_metadata.tests.factories import (
CorporateEndorsementFactory, CourseFactory, CourseRunFactory, EndorsementFactory, ExpectedLearningItemFactory, CorporateEndorsementFactory, CourseFactory, CourseRunFactory, EndorsementFactory, ExpectedLearningItemFactory,
ImageFactory, JobOutlookItemFactory, OrganizationFactory, PersonFactory, PositionFactory, PrerequisiteFactory, ImageFactory, JobOutlookItemFactory, OrganizationFactory, PersonFactory, PositionFactory, PrerequisiteFactory,
...@@ -1168,6 +1168,28 @@ class AffiliateWindowSerializerTests(TestCase): ...@@ -1168,6 +1168,28 @@ class AffiliateWindowSerializerTests(TestCase):
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
class CourseSearchSerializerTests(TestCase):
def test_data(self):
course = CourseFactory()
serializer = self.serialize_course(course)
expected = {
'key': course.key,
'title': course.title,
'short_description': course.short_description,
'full_description': course.full_description,
'content_type': 'course',
'aggregation_key': 'course:{}'.format(course.key),
}
assert serializer.data == expected
def serialize_course(self, course):
""" Serializes the given `Course` as a search result. """
result = SearchQuerySet().models(Course).filter(key=course.key)[0]
serializer = CourseSearchSerializer(result)
return serializer
class CourseRunSearchSerializerTests(TestCase): class CourseRunSearchSerializerTests(TestCase):
def test_data(self): def test_data(self):
course_run = CourseRunFactory(transcript_languages=LanguageTag.objects.filter(code__in=['en-us', 'zh-cn']), course_run = CourseRunFactory(transcript_languages=LanguageTag.objects.filter(code__in=['en-us', 'zh-cn']),
...@@ -1204,9 +1226,10 @@ class CourseRunSearchSerializerTests(TestCase): ...@@ -1204,9 +1226,10 @@ class CourseRunSearchSerializerTests(TestCase):
'logo_image_urls': [org.logo_image_url for org in orgs], 'logo_image_urls': [org.logo_image_url for org in orgs],
'authoring_organization_uuids': get_uuids(course_run.authoring_organizations.all()), 'authoring_organization_uuids': get_uuids(course_run.authoring_organizations.all()),
'subject_uuids': get_uuids(course_run.subjects.all()), 'subject_uuids': get_uuids(course_run.subjects.all()),
'staff_uuids': get_uuids(course_run.staff.all()) 'staff_uuids': get_uuids(course_run.staff.all()),
'aggregation_key': 'courserun:{}'.format(course_run.course.key),
} }
self.assertDictEqual(serializer.data, expected) assert serializer.data == expected
def serialize_course_run(self, course_run): def serialize_course_run(self, course_run):
""" Serializes the given `CourseRun` as a search result. """ """ Serializes the given `CourseRun` as a search result. """
...@@ -1218,7 +1241,7 @@ class CourseRunSearchSerializerTests(TestCase): ...@@ -1218,7 +1241,7 @@ class CourseRunSearchSerializerTests(TestCase):
""" Verify a null `LevelType` is properly serialized as None. """ """ Verify a null `LevelType` is properly serialized as None. """
course_run = CourseRunFactory(course__level_type=None) course_run = CourseRunFactory(course__level_type=None)
serializer = self.serialize_course_run(course_run) serializer = self.serialize_course_run(course_run)
self.assertEqual(serializer.data['level_type'], None) assert serializer.data['level_type'] is None
class ProgramSearchSerializerTests(TestCase): class ProgramSearchSerializerTests(TestCase):
...@@ -1237,7 +1260,8 @@ class ProgramSearchSerializerTests(TestCase): ...@@ -1237,7 +1260,8 @@ class ProgramSearchSerializerTests(TestCase):
'partner': program.partner.short_code, 'partner': program.partner.short_code,
'authoring_organization_uuids': get_uuids(program.authoring_organizations.all()), 'authoring_organization_uuids': get_uuids(program.authoring_organizations.all()),
'subject_uuids': get_uuids([course.subjects for course in program.courses.all()]), 'subject_uuids': get_uuids([course.subjects for course in program.courses.all()]),
'staff_uuids': get_uuids([course.staff for course in list(program.course_runs)]) 'staff_uuids': get_uuids([course.staff for course in list(program.course_runs)]),
'aggregation_key': 'program:{}'.format(program.uuid),
} }
def test_data(self): def test_data(self):
...@@ -1251,7 +1275,7 @@ class ProgramSearchSerializerTests(TestCase): ...@@ -1251,7 +1275,7 @@ class ProgramSearchSerializerTests(TestCase):
serializer = ProgramSearchSerializer(result) serializer = ProgramSearchSerializer(result)
expected = self._create_expected_data(program) expected = self._create_expected_data(program)
self.assertDictEqual(serializer.data, expected) assert serializer.data == expected
def test_data_without_organizations(self): def test_data_without_organizations(self):
""" Verify the serializer serialized programs with no associated organizations. """ Verify the serializer serialized programs with no associated organizations.
...@@ -1262,7 +1286,7 @@ class ProgramSearchSerializerTests(TestCase): ...@@ -1262,7 +1286,7 @@ class ProgramSearchSerializerTests(TestCase):
serializer = ProgramSearchSerializer(result) serializer = ProgramSearchSerializer(result)
expected = self._create_expected_data(program) expected = self._create_expected_data(program)
self.assertDictEqual(serializer.data, expected) assert serializer.data == expected
class TypeaheadCourseRunSearchSerializerTests(TestCase): class TypeaheadCourseRunSearchSerializerTests(TestCase):
...@@ -1277,7 +1301,7 @@ class TypeaheadCourseRunSearchSerializerTests(TestCase): ...@@ -1277,7 +1301,7 @@ class TypeaheadCourseRunSearchSerializerTests(TestCase):
'orgs': [org.key for org in course_run.authoring_organizations.all()], 'orgs': [org.key for org in course_run.authoring_organizations.all()],
'marketing_url': course_run.marketing_url, 'marketing_url': course_run.marketing_url,
} }
self.assertDictEqual(serialized_course.data, expected) assert serialized_course.data == expected
def serialize_course_run(self, course_run): def serialize_course_run(self, course_run):
""" Serializes the given `CourseRun` as a typeahead result. """ """ Serializes the given `CourseRun` as a typeahead result. """
...@@ -1301,14 +1325,14 @@ class TypeaheadProgramSearchSerializerTests(TestCase): ...@@ -1301,14 +1325,14 @@ class TypeaheadProgramSearchSerializerTests(TestCase):
program = ProgramFactory(authoring_organizations=[authoring_organization]) program = ProgramFactory(authoring_organizations=[authoring_organization])
serialized_program = self.serialize_program(program) serialized_program = self.serialize_program(program)
expected = self._create_expected_data(program) expected = self._create_expected_data(program)
self.assertDictEqual(serialized_program.data, expected) assert serialized_program.data == expected
def test_data_multiple_authoring_organizations(self): def test_data_multiple_authoring_organizations(self):
authoring_organizations = OrganizationFactory.create_batch(3) authoring_organizations = OrganizationFactory.create_batch(3)
program = ProgramFactory(authoring_organizations=authoring_organizations) program = ProgramFactory(authoring_organizations=authoring_organizations)
serialized_program = self.serialize_program(program) serialized_program = self.serialize_program(program)
expected = [org.key for org in authoring_organizations] expected = [org.key for org in authoring_organizations]
self.assertEqual(serialized_program.data['orgs'], expected) assert serialized_program.data['orgs'] == expected
def serialize_program(self, program): def serialize_program(self, program):
""" Serializes the given `Program` as a typeahead result. """ """ Serializes the given `Program` as a typeahead result. """
......
...@@ -414,6 +414,23 @@ class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin ...@@ -414,6 +414,23 @@ class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin
expected = [self.serialize_course_run(course_run) for course_run in course_runs] expected = [self.serialize_course_run(course_run) for course_run in course_runs]
self.assertEqual(response.data['objects']['results'], expected) self.assertEqual(response.data['objects']['results'], expected)
def test_results_include_aggregation_key(self):
""" Verify the search results only include the aggregation_key for each document. """
course_run = CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Published)
program = ProgramFactory(partner=self.partner, status=ProgramStatus.Active)
response = self.get_response()
assert response.status_code == 200
response_data = json.loads(response.content.decode('utf-8'))
expected = sorted(
['courserun:{}'.format(course_run.course.key), 'program:{}'.format(program.uuid)]
)
actual = sorted(
[obj.get('aggregation_key') for obj in response_data['objects']['results']]
)
assert expected == actual
class TypeaheadSearchViewTests(DefaultPartnerMixin, TypeaheadSerializationMixin, LoginMixin, ElasticsearchTestMixin, class TypeaheadSearchViewTests(DefaultPartnerMixin, TypeaheadSerializationMixin, LoginMixin, ElasticsearchTestMixin,
SynonymTestMixin, APITestCase): SynonymTestMixin, APITestCase):
......
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