From 3295e16774208830372e2ee6d2de01f233fb52e8 Mon Sep 17 00:00:00 2001 From: Hamza Shafique Date: Thu, 29 Aug 2024 15:01:38 +0500 Subject: [PATCH] feat: product_type = degree in management command for SFMC Course Catalog POC' --- .../commands/populate_product_catalog.py | 68 ++++++++++++++----- .../tests/test_populate_product_catalog.py | 68 ++++++++++++++++++- 2 files changed, 117 insertions(+), 19 deletions(-) diff --git a/course_discovery/apps/course_metadata/management/commands/populate_product_catalog.py b/course_discovery/apps/course_metadata/management/commands/populate_product_catalog.py index 4da0a651a22..1ca03c7f002 100644 --- a/course_discovery/apps/course_metadata/management/commands/populate_product_catalog.py +++ b/course_discovery/apps/course_metadata/management/commands/populate_product_catalog.py @@ -7,7 +7,7 @@ from django.db.models import Prefetch from course_discovery.apps.course_metadata.gspread_client import GspreadClient -from course_discovery.apps.course_metadata.models import Course, CourseType, SubjectTranslation +from course_discovery.apps.course_metadata.models import Course, CourseType, SubjectTranslation, Program logger = logging.getLogger(__name__) @@ -73,7 +73,7 @@ def get_products(self, product_type, product_source): 'verified', 'spoc-verified-audit' ] - if (product_type := product_type.lower()) in ['executive_education', 'bootcamp', 'ocm_course']: + if product_type in ['executive_education', 'bootcamp', 'ocm_course']: queryset = Course.objects.available() if product_type == 'ocm_course': @@ -100,6 +100,26 @@ def get_products(self, product_type, product_source): 'subjects', subject_translations ) + elif product_type == 'degree': + queryset = Program.objects.marketable() \ + .exclude(degree__isnull=True) \ + .select_related('partner', 'type') + + if product_source: + queryset = queryset.filter(product_source__slug=product_source) + + subject_translations = Prefetch( + 'courses__subjects__translations', + queryset=SubjectTranslation.objects.filter(language_code='es'), + to_attr='spanish_translations' + ) + + return queryset.prefetch_related( + 'authoring_organizations', + 'courses__subjects', + 'courses__course_runs', + subject_translations, + ) else: # Return empty queryset if invalid product type specified return Course.objects.none() @@ -112,28 +132,43 @@ def write_csv_header(self, output_csv): writer.writeheader() return writer - def get_transformed_data(self, product): + def get_transformed_data(self, product, product_type): """ Transforms the product data for product's catalog """ authoring_orgs = product.authoring_organizations.all() - return { + + common_data = { "UUID": str(product.uuid), "Title": product.title, "Organizations Name": ", ".join(org.name for org in authoring_orgs), - "Organizations Logo": ", ".join( - org.logo_image.url for org in authoring_orgs if org.logo_image - ), + "Organizations Logo": ", ".join(org.logo_image.url for org in authoring_orgs if org.logo_image), "Organizations Abbr": ", ".join(org.key for org in authoring_orgs), - "Languages": product.languages_codes, - "Subjects": ", ".join(subject.name for subject in product.subjects.all()), - "Subjects Spanish": ", ".join( - translation.name for subject in product.subjects.all() - for translation in subject.spanish_translations - ), "Marketing URL": product.marketing_url, - "Marketing Image": (product.image.url if product.image else ""), } + if product_type in ['executive_education', 'bootcamp', 'ocm_course']: + common_data.update({ + "Subjects": ", ".join(subject.name for subject in product.subjects.all()), + "Subjects Spanish": ", ".join( + translation.name for subject in product.subjects.all() + for translation in subject.spanish_translations + ), + "Languages": product.languages_codes, + "Marketing Image": product.image.url if product.image else "", + }) + elif product_type == 'degree': + common_data.update({ + "Subjects": ", ".join(subject.name for subject in product.subjects), + "Subjects Spanish": ", ".join( + translation.name for subject in product.subjects + for translation in subject.spanish_translations + ), + "Languages": ", ".join(language.code for language in product.languages), + "Marketing Image": product.card_image.url if product.card_image else "", + }) + + return common_data + def handle(self, *args, **options): product_type = options.get('product_type') @@ -152,6 +187,7 @@ def handle(self, *args, **options): gspread_client = GspreadClient() try: + product_type = product_type.lower() products = self.get_products(product_type, product_source) if not products.exists(): raise CommandError('No products found for the given criteria.') @@ -163,7 +199,7 @@ def handle(self, *args, **options): output_writer = self.write_csv_header(output_file) for product in products: try: - output_writer.writerow(self.get_transformed_data(product)) + output_writer.writerow(self.get_transformed_data(product, product_type)) except Exception as e: # pylint: disable=broad-exception-caught logger.error(f"Error writing product {product.uuid} to CSV: {str(e)}") continue @@ -171,7 +207,7 @@ def handle(self, *args, **options): logger.info(f'Populated {products_count} {product_type}s to {output_csv}') elif gspread_client_flag: - csv_data = [self.get_transformed_data(product) for product in products] + csv_data = [self.get_transformed_data(product, product_type) for product in products] gspread_client.write_data( PRODUCT_CATALOG_CONFIG, self.CATALOG_CSV_HEADERS, diff --git a/course_discovery/apps/course_metadata/management/commands/tests/test_populate_product_catalog.py b/course_discovery/apps/course_metadata/management/commands/tests/test_populate_product_catalog.py index 95463a385c7..3262d8c5c09 100644 --- a/course_discovery/apps/course_metadata/management/commands/tests/test_populate_product_catalog.py +++ b/course_discovery/apps/course_metadata/management/commands/tests/test_populate_product_catalog.py @@ -10,9 +10,10 @@ from course_discovery.apps.course_metadata.choices import CourseRunStatus from course_discovery.apps.course_metadata.management.commands.populate_product_catalog import Command -from course_discovery.apps.course_metadata.models import Course, CourseType +from course_discovery.apps.course_metadata.models import Course, CourseType, ProgramType from course_discovery.apps.course_metadata.tests.factories import ( - CourseFactory, CourseRunFactory, CourseTypeFactory, PartnerFactory, SeatFactory, SourceFactory + CourseFactory, CourseRunFactory, CourseTypeFactory, PartnerFactory, SeatFactory, SourceFactory, + ProgramTypeFactory, DegreeFactory ) @@ -37,6 +38,14 @@ def setUp(self): self.course_run_2 = CourseRunFactory.create_batch( 2, course=Course.objects.all()[1] ) + self.program_type = ProgramTypeFactory.create(slug=ProgramType.MICROMASTERS) + self.degrees = DegreeFactory.create_batch( + 2, + product_source=self.source, + partner=self.partner, + additional_metadata=None, + type=self.program_type, + ) def test_populate_product_catalog(self): """ @@ -65,6 +74,33 @@ def test_populate_product_catalog(self): self.assertIn("Marketing URL", row) self.assertIn("Marketing Image", row) + def test_populate_product_catalog_for_degree(self): + """ + Test populate_product_catalog command for degree product type and verify data has been populated successfully + """ + with NamedTemporaryFile(mode="w", delete=False) 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) + for row in csv_reader: + self.assertIn("UUID", row) + self.assertIn("Title", row) + self.assertIn("Organizations Name", row) + self.assertIn("Organizations Logo", row) + self.assertIn("Organizations Abbr", row) + self.assertIn("Languages", row) + self.assertIn("Subjects", row) + self.assertIn("Subjects Spanish", row) + self.assertIn("Marketing URL", row) + self.assertIn("Marketing Image", row) + @mock.patch( "course_discovery.apps.course_metadata.management.commands.populate_product_catalog.Command.get_products" ) @@ -114,7 +150,7 @@ def test_get_transformed_data(self): product = self.courses[0] command = Command() product_authoring_orgs = product.authoring_organizations.all() - transformed_prod_data = command.get_transformed_data(product) + transformed_prod_data = command.get_transformed_data(product, "ocm_course") assert transformed_prod_data == { "UUID": str(product.uuid), "Title": product.title, @@ -140,6 +176,32 @@ def test_get_transformed_data(self): "Marketing Image": (product.image.url if product.image else ""), } + def test_get_transformed_data_for_degree(self): + """ + Verify get_transformed_data method is working correctly for degree + """ + product = self.degrees[0] + command = Command() + product_authoring_orgs = product.authoring_organizations.all() + transformed_prod_data = command.get_transformed_data(product, "degree") + assert transformed_prod_data == { + "UUID": str(product.uuid), + "Title": product.title, + "Organizations Name": ", ".join(org.name for org in product_authoring_orgs), + "Organizations Logo": ", ".join( + 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), + "Subjects Spanish": ", ".join( + translation.name for subject in product.subjects + for translation in subject.spanish_translations + ), + "Marketing URL": product.marketing_url, + "Marketing Image": product.card_image.url if product.card_image else "", + } + @mock.patch('course_discovery.apps.course_metadata.management.commands.populate_product_catalog.GspreadClient') @mock.patch( 'course_discovery.apps.course_metadata.management.commands.populate_product_catalog.Command.get_products'