Skip to content

Commit

Permalink
Merge branch 'master' into jenkins/upgrade-python-requirements-537fcd4
Browse files Browse the repository at this point in the history
  • Loading branch information
AfaqShuaib09 authored Sep 19, 2024
2 parents e772934 + 599858f commit 5d30a98
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/migrations-check-mysql8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-20.04 ]
os: [ ubuntu-latest ]
python-version: [ 3.12 ]

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/requirements-upgrade.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:

jobs:
upgrade_requirements:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest

strategy:
matrix:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ def get_products(self, product_type, product_source):
subject_translations
)
elif product_type == 'degree':
queryset = Program.objects.marketable().exclude(degree__isnull=True).select_related('partner', 'type')
queryset = Program.objects.marketable().exclude(degree__isnull=True) \
.select_related('partner', 'type', 'primary_subject_override', 'language_override')

if product_source:
queryset = queryset.filter(product_source__slug=product_source)
Expand All @@ -115,7 +116,7 @@ def get_products(self, product_type, product_source):
).filter(Q(num_orgs__gt=0) & Q(card_image__isnull=False) & ~Q(card_image=''))

subject_translations = Prefetch(
'courses__subjects__translations',
'active_subjects__translations',
queryset=SubjectTranslation.objects.filter(language_code='es'),
to_attr='spanish_translations'
)
Expand Down Expand Up @@ -164,12 +165,12 @@ def get_transformed_data(self, product, product_type):
})
elif product_type == 'degree':
data.update({
"Subjects": ", ".join(subject.name for subject in product.subjects),
"Subjects": ", ".join(subject.name for subject in product.active_subjects),
"Subjects Spanish": ", ".join(
translation.name for subject in product.subjects
translation.name for subject in product.active_subjects
for translation in subject.spanish_translations
),
"Languages": ", ".join(language.code for language in product.languages),
"Languages": ", ".join(language.code for language in product.active_languages),
"Marketing Image": product.card_image.url if product.card_image else "",
})

Expand Down Expand Up @@ -198,7 +199,7 @@ def handle(self, *args, **options):
raise CommandError('No products found for the given criteria.')
products_count = products.count()

logger.info(f'Fetched {products_count} courses from the database')
logger.info(f'Fetched {products_count} {product_type}s from the database')
if output_csv:
with open(output_csv, 'w', newline='') as output_file:
output_writer = self.write_csv_header(output_file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
import factory
import mock
from django.core.management import CommandError, call_command
from django.db.models import Prefetch, prefetch_related_objects
from django.test import TestCase

from course_discovery.apps.course_metadata.choices import CourseRunStatus, ProgramStatus
from course_discovery.apps.course_metadata.management.commands.populate_product_catalog import Command
from course_discovery.apps.course_metadata.models import Course, CourseType, ProgramType
from course_discovery.apps.course_metadata.models import Course, CourseType, ProgramType, SubjectTranslation
from course_discovery.apps.course_metadata.tests.factories import (
CourseFactory, CourseRunFactory, CourseTypeFactory, DegreeFactory, OrganizationFactory, PartnerFactory,
ProgramTypeFactory, SeatFactory, SourceFactory
ProgramTypeFactory, SeatFactory, SourceFactory, SubjectFactory
)
from course_discovery.apps.ietf_language_tags.models import LanguageTag


class PopulateProductCatalogCommandTests(TestCase):
Expand Down Expand Up @@ -281,6 +283,48 @@ def test_populate_product_catalog_excludes_non_marketable_degrees(self):
self.assertEqual(len(matching_rows), 1,
f"Marketable degree '{marketable_degree.title}' should be in the CSV")

def test_populate_product_catalog_with_degrees_having_overrides(self):
"""
Test that the populate_product_catalog command includes the overridden subjects and languages for degrees.
"""
degree = DegreeFactory.create(
product_source=self.source,
partner=self.partner,
additional_metadata=None,
type=self.program_type,
status=ProgramStatus.Active,
marketing_slug="valid-marketing-slug",
title="Marketable Degree",
authoring_organizations=[self.organization],
card_image=factory.django.ImageField(),
primary_subject_override=SubjectFactory(name='Subject1'),
language_override=LanguageTag.objects.get(code='es'),
)

with NamedTemporaryFile() as output_csv:
call_command(
"populate_product_catalog",
product_type="degree",
output_csv=output_csv.name,
product_source="edx",
gspread_client_flag=False,
)

with open(output_csv.name, "r") as output_csv_file:
csv_reader = csv.DictReader(output_csv_file)
rows = list(csv_reader)

matching_rows = [
row for row in rows if row["UUID"] == str(degree.uuid.hex)
]
self.assertEqual(len(matching_rows), 1)

row = matching_rows[0]
self.assertEqual(row["UUID"], str(degree.uuid.hex))
self.assertEqual(row["Title"], degree.title)
self.assertIn(degree.primary_subject_override.name, row["Subjects"])
self.assertEqual(row["Languages"], degree.language_override.code)

@mock.patch(
"course_discovery.apps.course_metadata.management.commands.populate_product_catalog.Command.get_products"
)
Expand Down Expand Up @@ -363,6 +407,13 @@ def test_get_transformed_data_for_degree(self):
product = self.degrees[0]
command = Command()
product_authoring_orgs = product.authoring_organizations.all()
subject_translations = Prefetch(
"active_subjects__translations",
queryset=SubjectTranslation.objects.filter(language_code="es"),
to_attr="spanish_translations",
)
prefetch_related_objects([product], subject_translations)

transformed_prod_data = command.get_transformed_data(product, "degree")
assert transformed_prod_data == {
"UUID": str(product.uuid.hex),
Expand All @@ -372,10 +423,10 @@ def test_get_transformed_data_for_degree(self):
org.logo_image.url for org in product_authoring_orgs if org.logo_image
),
"Organizations Abbr": ", ".join(org.key for org in product_authoring_orgs),
"Languages": ", ".join(language.code for language in product.languages),
"Subjects": ", ".join(subject.name for subject in product.subjects),
"Languages": ", ".join(language.code for language in product.active_languages),
"Subjects": ", ".join(subject.name for subject in product.active_subjects),
"Subjects Spanish": ", ".join(
translation.name for subject in product.subjects
translation.name for subject in product.active_subjects
for translation in subject.spanish_translations
),
"Marketing URL": product.marketing_url,
Expand Down
29 changes: 29 additions & 0 deletions course_discovery/apps/course_metadata/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3518,6 +3518,16 @@ def course_run_statuses(self):
def languages(self):
return {course_run.language for course_run in self.course_runs if course_run.language is not None}

@property
def active_languages(self):
"""
:return: The list of languages; It gives preference to the language_override over the languages
extracted from the course runs.
"""
if self.language_override:
return {self.language_override}
return self.languages

@property
def transcript_languages(self):
languages = [course_run.transcript_languages.all() for course_run in self.course_runs]
Expand All @@ -3540,6 +3550,25 @@ def subjects(self):
common_others = [s for s, _ in Counter(course_subjects).most_common() if s not in common_primary]
return common_primary + common_others

@property
def active_subjects(self):
"""
:return: The list of subjects; the first subject should be the most common primary subjects of its courses,
other subjects should be collected and ranked by frequency among the courses.
Note: This method gives preference to the primary_subject_override over the primary subject of the courses.
"""
subjects = self.subjects

if self.primary_subject_override:
if self.primary_subject_override not in subjects:
subjects = [self.primary_subject_override] + subjects
else:
subjects = [self.primary_subject_override] + \
[subject for subject in subjects if subject != self.primary_subject_override]

return subjects

@property
def topics(self):
"""
Expand Down
59 changes: 59 additions & 0 deletions course_discovery/apps/course_metadata/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3519,6 +3519,65 @@ def test_program_duration_override(self):
self.program.program_duration_override = ''
assert self.program.program_duration_override is not None

def test_active_subjects_with_no_override(self):
"""
Test that active_subjects returns the subjects from the associated courses
when no primary_subject_override is set.
"""

subject1 = SubjectFactory.create(name='Subject 1')
subject2 = SubjectFactory.create(name='Subject 2')
course1 = CourseFactory.create(subjects=[subject1])
course2 = CourseFactory.create(subjects=[subject2])
program = ProgramFactory.create(primary_subject_override=None, courses=[course1, course2])

expected_subjects = [subject1, subject2]
self.assertEqual(program.active_subjects, expected_subjects)

def test_active_subjects_with_primary_subject_override(self):
"""
Test that active_subjects includes the primary_subject_override at the beginning
when it is set.
"""
primary_subject_override = SubjectFactory.create(name='Primary Subject')
other_subject = SubjectFactory.create(name='Other Subject')
course = CourseFactory.create(subjects=[other_subject])

program = ProgramFactory.create(primary_subject_override=primary_subject_override, courses=[course])

expected_subjects = [primary_subject_override, other_subject]
self.assertEqual(program.active_subjects, expected_subjects)

def test_active_languages_with_no_override(self):
"""
Test that active_languages returns the languages from the associated courses
when no language_override is set.
"""

language_en = LanguageTag.objects.create(code='en', name='English')
language_fr = LanguageTag.objects.get(code='fr')

course_run1 = CourseRunFactory.create(language=language_en)
course_run2 = CourseRunFactory.create(language=language_fr)

program = ProgramFactory.create(language_override=None, courses=[course_run1.course, course_run2.course])

expected_languages = {language_en, language_fr}
self.assertEqual(program.active_languages, expected_languages)

def test_active_languages_with_language_override(self):
"""
Test that active_languages returns the language_override when it is set.
"""

language_es = LanguageTag.objects.get(code='es')
language_de = LanguageTag.objects.get(code='de')
course_run = CourseRunFactory.create(language=language_de)
program = ProgramFactory.create(language_override=language_es, courses=[course_run.course])

expected_languages = {language_es}
self.assertEqual(program.active_languages, expected_languages)


class ProgramSubscriptionTests(TestCase):

Expand Down

0 comments on commit 5d30a98

Please sign in to comment.