Commit 864e54f8 by Renzo Lucioni

Add authoring organizations to programs in search results

Keeps serialized organizations in search and programs API responses consistent. ECOM-5285.
parent ec7b4469
# pylint: disable=abstract-method # pylint: disable=abstract-method
import json
from urllib.parse import urlencode from urllib.parse import urlencode
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
...@@ -51,11 +51,13 @@ PROGRAM_FACET_FIELD_OPTIONS = { ...@@ -51,11 +51,13 @@ PROGRAM_FACET_FIELD_OPTIONS = {
'status': {}, 'status': {},
} }
PROGRAM_SEARCH_FIELDS = ( BASE_PROGRAM_FIELDS = (
'text', 'uuid', 'title', 'subtitle', 'type', 'marketing_url', 'organizations', 'content_type', 'status', 'text', 'uuid', 'title', 'subtitle', 'type', 'marketing_url', 'content_type', 'status', 'card_image_url',
'card_image_url',
) )
PROGRAM_SEARCH_FIELDS = BASE_PROGRAM_FIELDS + ('authoring_organizations',)
PROGRAM_FACET_FIELDS = BASE_PROGRAM_FIELDS + ('organizations',)
def get_marketing_url_for_user(user, marketing_url): def get_marketing_url_for_user(user, marketing_url):
""" """
...@@ -541,6 +543,12 @@ class CourseRunFacetSerializer(BaseHaystackFacetSerializer): ...@@ -541,6 +543,12 @@ class CourseRunFacetSerializer(BaseHaystackFacetSerializer):
class ProgramSearchSerializer(HaystackSerializer): class ProgramSearchSerializer(HaystackSerializer):
authoring_organizations = serializers.SerializerMethodField()
def get_authoring_organizations(self, program):
organizations = program.organization_bodies
return [json.loads(organization) for organization in organizations]
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
field_options = PROGRAM_FACET_FIELD_OPTIONS field_options = PROGRAM_FACET_FIELD_OPTIONS
...@@ -555,7 +563,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer): ...@@ -555,7 +563,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
class Meta: class Meta:
field_aliases = COMMON_SEARCH_FIELD_ALIASES field_aliases = COMMON_SEARCH_FIELD_ALIASES
field_options = PROGRAM_FACET_FIELD_OPTIONS field_options = PROGRAM_FACET_FIELD_OPTIONS
fields = PROGRAM_SEARCH_FIELDS fields = PROGRAM_FACET_FIELDS
ignore_fields = COMMON_IGNORED_FIELDS ignore_fields = COMMON_IGNORED_FIELDS
index_classes = [ProgramIndex] index_classes = [ProgramIndex]
......
...@@ -18,7 +18,6 @@ from course_discovery.apps.core.models import User ...@@ -18,7 +18,6 @@ from course_discovery.apps.core.models import User
from course_discovery.apps.core.tests.factories import UserFactory 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.course_metadata.models import CourseRun, Program from course_discovery.apps.course_metadata.models import CourseRun, Program
from course_discovery.apps.course_metadata.search_indexes import OrganizationsMixin
from course_discovery.apps.course_metadata.tests.factories import ( from course_discovery.apps.course_metadata.tests.factories import (
CourseFactory, CourseRunFactory, SubjectFactory, PrerequisiteFactory, ImageFactory, VideoFactory, CourseFactory, CourseRunFactory, SubjectFactory, PrerequisiteFactory, ImageFactory, VideoFactory,
OrganizationFactory, PersonFactory, SeatFactory, ProgramFactory OrganizationFactory, PersonFactory, SeatFactory, ProgramFactory
...@@ -528,8 +527,9 @@ class ProgramSearchSerializerTests(TestCase): ...@@ -528,8 +527,9 @@ class ProgramSearchSerializerTests(TestCase):
program.authoring_organizations.add(authoring_organization) program.authoring_organizations.add(authoring_organization)
program.credit_backing_organizations.add(crediting_organization) program.credit_backing_organizations.add(crediting_organization)
program.save() program.save()
expected_organizations = [OrganizationsMixin.format_organization(org) for org in expected_organizations = [
(authoring_organization, crediting_organization)] OrganizationSerializer(org).data for org in (authoring_organization, crediting_organization)
]
# NOTE: This serializer expects SearchQuerySet results, so we run a search on the newly-created object # NOTE: This serializer expects SearchQuerySet results, so we run a search on the newly-created object
# to generate such a result. # to generate such a result.
...@@ -542,7 +542,7 @@ class ProgramSearchSerializerTests(TestCase): ...@@ -542,7 +542,7 @@ class ProgramSearchSerializerTests(TestCase):
'subtitle': program.subtitle, 'subtitle': program.subtitle,
'type': program.type.name, 'type': program.type.name,
'marketing_url': program.marketing_url, 'marketing_url': program.marketing_url,
'organizations': expected_organizations, 'authoring_organizations': expected_organizations,
'content_type': 'program', 'content_type': 'program',
'card_image_url': program.card_image_url, 'card_image_url': program.card_image_url,
'status': program.status, 'status': program.status,
......
import json
from haystack import indexes from haystack import indexes
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -5,10 +7,16 @@ from course_discovery.apps.course_metadata.models import Course, CourseRun, Prog ...@@ -5,10 +7,16 @@ from course_discovery.apps.course_metadata.models import Course, CourseRun, Prog
class OrganizationsMixin: class OrganizationsMixin:
@classmethod def format_organization(self, organization):
def format_organization(cls, organization):
return '{key}: {name}'.format(key=organization.key, name=organization.name) return '{key}: {name}'.format(key=organization.key, name=organization.name)
def format_organization_body(self, organization):
# Deferred to prevent a circular import:
# course_discovery.apps.api.serializers -> course_discovery.apps.course_metadata.search_indexes
from course_discovery.apps.api.serializers import OrganizationSerializer
return json.dumps(OrganizationSerializer(organization).data)
def prepare_organizations(self, obj): def prepare_organizations(self, obj):
return [self.format_organization(organization) for organization in obj.organizations.all()] return [self.format_organization(organization) for organization in obj.organizations.all()]
...@@ -117,6 +125,7 @@ class ProgramIndex(BaseIndex, indexes.Indexable, OrganizationsMixin): ...@@ -117,6 +125,7 @@ class ProgramIndex(BaseIndex, indexes.Indexable, OrganizationsMixin):
organizations = indexes.MultiValueField(faceted=True) organizations = indexes.MultiValueField(faceted=True)
authoring_organizations = indexes.MultiValueField(faceted=True) authoring_organizations = indexes.MultiValueField(faceted=True)
credit_backing_organizations = indexes.MultiValueField(faceted=True) credit_backing_organizations = indexes.MultiValueField(faceted=True)
organization_bodies = indexes.MultiValueField()
card_image_url = indexes.CharField(model_attr='card_image_url', null=True) card_image_url = indexes.CharField(model_attr='card_image_url', null=True)
status = indexes.CharField(model_attr='status', faceted=True) status = indexes.CharField(model_attr='status', faceted=True)
partner = indexes.CharField(model_attr='partner__short_code', null=True, faceted=True) partner = indexes.CharField(model_attr='partner__short_code', null=True, faceted=True)
...@@ -130,5 +139,9 @@ class ProgramIndex(BaseIndex, indexes.Indexable, OrganizationsMixin): ...@@ -130,5 +139,9 @@ class ProgramIndex(BaseIndex, indexes.Indexable, OrganizationsMixin):
def prepare_credit_backing_organizations(self, obj): def prepare_credit_backing_organizations(self, obj):
return [self.format_organization(organization) for organization in obj.credit_backing_organizations.all()] return [self.format_organization(organization) for organization in obj.credit_backing_organizations.all()]
def prepare_organization_bodies(self, obj):
organizations = obj.authoring_organizations.all() | obj.credit_backing_organizations.all()
return [self.format_organization_body(organization) for organization in organizations]
def prepare_marketing_url(self, obj): def prepare_marketing_url(self, obj):
return obj.marketing_url return obj.marketing_url
...@@ -80,7 +80,7 @@ disable = ...@@ -80,7 +80,7 @@ disable =
too-many-arguments, too-many-arguments,
too-many-locals, too-many-locals,
unused-wildcard-import, unused-wildcard-import,
duplicate-code,invalid-name,missing-docstring duplicate-code,invalid-name,missing-docstring,cyclic-import
[REPORTS] [REPORTS]
output-format = text output-format = text
...@@ -180,4 +180,4 @@ int-import-graph = ...@@ -180,4 +180,4 @@ int-import-graph =
[EXCEPTIONS] [EXCEPTIONS]
overgeneral-exceptions = Exception overgeneral-exceptions = Exception
# aa393e07e100b773853da38867c6a050ff9d6bfb # 5b6ec121b7c657d50f056591f315bd23c25d6a97
...@@ -5,7 +5,8 @@ ignore+= ,migrations, settings, wsgi.py ...@@ -5,7 +5,8 @@ ignore+= ,migrations, settings, wsgi.py
const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$ const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$
[MESSAGES CONTROL] [MESSAGES CONTROL]
DISABLE+= ,invalid-name,missing-docstring # cyclic-import is disabled because of https://github.com/PyCQA/pylint/issues/850.
DISABLE+= ,invalid-name,missing-docstring,cyclic-import
[TYPECHECK] [TYPECHECK]
ignored-classes+= ,WSGIRequest,UserFactory,CatalogFactory,responses ignored-classes+= ,WSGIRequest,UserFactory,CatalogFactory,responses
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