diff --git a/credentials/apps/verifiable_credentials/composition/schemas.py b/credentials/apps/verifiable_credentials/composition/schemas.py index ade36631c..837e5bcdf 100644 --- a/credentials/apps/verifiable_credentials/composition/schemas.py +++ b/credentials/apps/verifiable_credentials/composition/schemas.py @@ -4,6 +4,8 @@ from rest_framework import serializers +from ..constants import CredentialsType + class EducationalOccupationalProgramSchema(serializers.Serializer): # pylint: disable=abstract-method """ @@ -20,6 +22,21 @@ class Meta: read_only_fields = "__all__" +class EducationalOccupationalCourseSchema(serializers.Serializer): # pylint: disable=abstract-method + """ + Defines Open edX Course. + """ + + TYPE = "Course" + + id = serializers.CharField(default=TYPE, help_text="https://schema.org/Course") + name = serializers.CharField(source="course.title") + courseCode = serializers.CharField(source="user_credential.credential.course_id") + + class Meta: + read_only_fields = "__all__" + + class EducationalOccupationalCredentialSchema(serializers.Serializer): # pylint: disable=abstract-method """ Defines Open edX user credential. @@ -30,7 +47,19 @@ class EducationalOccupationalCredentialSchema(serializers.Serializer): # pylint id = serializers.CharField(default=TYPE, help_text="https://schema.org/EducationalOccupationalCredential") name = serializers.CharField(source="user_credential.credential.title") description = serializers.CharField(source="user_credential.uuid") - program = EducationalOccupationalProgramSchema(source="*") + + def to_representation(self, instance): + """ + Dynamically add fields based on the type. + """ + representation = super().to_representation(instance) + + if instance.user_credential.credential_content_type.model == CredentialsType.PROGRAM: + representation["program"] = EducationalOccupationalProgramSchema(instance).data + elif instance.user_credential.credential_content_type.model == CredentialsType.COURSE: + representation["course"] = EducationalOccupationalCourseSchema(instance).data + + return representation class Meta: read_only_fields = "__all__" diff --git a/credentials/apps/verifiable_credentials/composition/tests/test_schemas.py b/credentials/apps/verifiable_credentials/composition/tests/test_schemas.py new file mode 100644 index 000000000..6bf828b12 --- /dev/null +++ b/credentials/apps/verifiable_credentials/composition/tests/test_schemas.py @@ -0,0 +1,83 @@ +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from credentials.apps.catalog.tests.factories import ( + CourseFactory, + CourseRunFactory, + OrganizationFactory, + ProgramFactory, +) +from credentials.apps.core.tests.factories import UserFactory +from credentials.apps.core.tests.mixins import SiteMixin +from credentials.apps.credentials.tests.factories import ( + CourseCertificateFactory, + ProgramCertificateFactory, + UserCredentialFactory, +) +from credentials.apps.verifiable_credentials.composition.schemas import EducationalOccupationalCredentialSchema +from credentials.apps.verifiable_credentials.issuance.tests.factories import IssuanceLineFactory + + +class EducationalOccupationalCredentialSchemaTests(SiteMixin, TestCase): + def setUp(self): + super().setUp() + self.user = UserFactory() + self.orgs = [OrganizationFactory.create(name=name, site=self.site) for name in ["TestOrg1", "TestOrg2"]] + self.course = CourseFactory.create(site=self.site) + self.course_runs = CourseRunFactory.create_batch(2, course=self.course) + self.program = ProgramFactory( + title="TestProgram1", + course_runs=self.course_runs, + authoring_organizations=self.orgs, + site=self.site, + ) + self.course_certs = [ + CourseCertificateFactory.create( + course_id=course_run.key, + course_run=course_run, + site=self.site, + ) + for course_run in self.course_runs + ] + self.program_cert = ProgramCertificateFactory.create( + program=self.program, program_uuid=self.program.uuid, site=self.site + ) + self.course_credential_content_type = ContentType.objects.get( + app_label="credentials", model="coursecertificate" + ) + self.program_credential_content_type = ContentType.objects.get( + app_label="credentials", model="programcertificate" + ) + self.course_user_credential = UserCredentialFactory.create( + username=self.user.username, + credential_content_type=self.course_credential_content_type, + credential=self.course_certs[0], + ) + self.program_user_credential = UserCredentialFactory.create( + username=self.user.username, + credential_content_type=self.program_credential_content_type, + credential=self.program_cert, + ) + self.program_issuance_line = IssuanceLineFactory(user_credential=self.program_user_credential, subject_id="did:key:test") + self.course_issuance_line = IssuanceLineFactory(user_credential=self.course_user_credential, subject_id="did:key:test") + + + def test_to_representation_program(self): + data = EducationalOccupationalCredentialSchema(self.program_issuance_line).data + + assert data["id"] == "EducationalOccupationalCredential" + assert data["name"] == self.program_cert.title + assert data["description"] == str(self.program_user_credential.uuid) + assert data["program"]["id"] == "EducationalOccupationalProgram" + assert data["program"]["name"] == self.program.title + assert data["program"]["description"] == str(self.program.uuid) + + def test_to_representation_course(self): + data = EducationalOccupationalCredentialSchema(self.course_issuance_line).data + + assert data["id"] == "EducationalOccupationalCredential" + assert data["name"] == self.course_certs[0].title + assert data["description"] == str(self.course_user_credential.uuid) + assert data["course"]["id"] == "Course" + assert data["course"]["name"] == self.course.title + assert data["course"]["courseCode"] == self.course_certs[0].course_id diff --git a/credentials/apps/verifiable_credentials/constants.py b/credentials/apps/verifiable_credentials/constants.py new file mode 100644 index 000000000..f58987890 --- /dev/null +++ b/credentials/apps/verifiable_credentials/constants.py @@ -0,0 +1,6 @@ +class CredentialsType: + """ + Enum to define the type of credentials. + """ + PROGRAM = "programcertificate" + COURSE = "coursecertificate" diff --git a/credentials/apps/verifiable_credentials/issuance/models.py b/credentials/apps/verifiable_credentials/issuance/models.py index aaea75bb2..2082a3b7b 100644 --- a/credentials/apps/verifiable_credentials/issuance/models.py +++ b/credentials/apps/verifiable_credentials/issuance/models.py @@ -12,12 +12,14 @@ from django.utils.translation import gettext_lazy as _ from django_extensions.db.models import TimeStampedModel +from credentials.apps.catalog.models import Course from credentials.apps.credentials.models import UserCredential from credentials.apps.verifiable_credentials.utils import capitalize_first from ..composition.utils import get_data_model, get_data_models from ..settings import vc_settings from ..storages.utils import get_storage +from ..constants import CredentialsType User = get_user_model() @@ -106,8 +108,8 @@ def credential_verbose_type(self): Map internal credential types to verbose labels (source models do not provide those). """ contenttype_to_verbose_name = { - "programcertificate": _("program certificate"), - "coursecertificate": _("course certificate"), + CredentialsType.PROGRAM: _("program certificate"), + CredentialsType.COURSE: _("course certificate"), } return contenttype_to_verbose_name.get(self.credential_content_type) @@ -120,10 +122,10 @@ def credential_name(self): return credential_title contenttype_to_name = { - "programcertificate": _("program certificate for passing a program {program_title}").format( + CredentialsType.PROGRAM: _("program certificate for passing a program {program_title}").format( program_title=getattr(self.program, "title", "") ), - "coursecertificate": self.credential_verbose_type, + CredentialsType.COURSE: self.credential_verbose_type, } return capitalize_first(contenttype_to_name.get(self.credential_content_type)) @@ -132,48 +134,58 @@ def credential_description(self): """ Verifiable credential achievement description resolution. """ - effort_portion = ( - _(", with total {hours_of_effort} Hours of effort required to complete it").format( - hours_of_effort=self.program.total_hours_of_effort + if self.credential_content_type == CredentialsType.PROGRAM: + effort_portion = ( + _(", with total {hours_of_effort} Hours of effort required to complete it").format( + hours_of_effort=self.program.total_hours_of_effort + ) + if self.program.total_hours_of_effort + else "" ) - if self.program.total_hours_of_effort - else "" - ) - program_certificate_description = _( - "{credential_type} is granted on program {program_title} completion offered by {organizations}, in collaboration with {platform_name}. The {program_title} program includes {course_count} course(s){effort_info}." # pylint: disable=line-too-long - ).format( - credential_type=self.credential_verbose_type, - program_title=self.program.title, - organizations=", ".join(list(self.program.authoring_organizations.values_list("name", flat=True))), - platform_name=self.platform_name, - course_count=self.program.course_runs.count(), - effort_info=effort_portion, - ) - type_to_description = { - "programcertificate": program_certificate_description, - "coursecertificate": "", - } - return capitalize_first(type_to_description.get(self.credential_content_type)) + description = _( + "{credential_type} is granted on program {program_title} completion offered by {organizations}, in collaboration with {platform_name}. The {program_title} program includes {course_count} course(s){effort_info}." # pylint: disable=line-too-long + ).format( + credential_type=self.credential_verbose_type, + program_title=self.program.title, + organizations=", ".join(list(self.program.authoring_organizations.values_list("name", flat=True))), + platform_name=self.platform_name, + course_count=self.program.course_runs.count(), + effort_info=effort_portion, + ) + elif self.credential_content_type == CredentialsType.COURSE: + description = _("{credential_type} is granted on course {course_title} completion offered by {organization}, in collaboration with {platform_name}").format( + credential_type=self.credential_verbose_type, + course_title=getattr(self.course, "title", ""), + platform_name=self.platform_name, + organization=self.user_credential.credential.course_key.org, + ) + return capitalize_first(description) @property def credential_narrative(self): """ Verifiable credential achievement criteria narrative. """ - program_certificate_narrative = _( - "{recipient_fullname} successfully completed all courses and received passing grades for a Professional Certificate in {program_title} a program offered by {organizations}, in collaboration with {platform_name}." # pylint: disable=line-too-long - ).format( - recipient_fullname=self.subject_fullname or _("recipient"), - program_title=self.program.title, - organizations=", ".join(list(self.program.authoring_organizations.values_list("name", flat=True))), - platform_name=self.platform_name, - ) - type_to_narrative = { - "programcertificate": program_certificate_narrative, - "coursecertificate": "", - } - return capitalize_first(type_to_narrative.get(self.credential_content_type)) + if self.credential_content_type == CredentialsType.PROGRAM: + narrative = _( + "{recipient_fullname} successfully completed all courses and received passing grades for a Professional Certificate in {program_title} a program offered by {organizations}, in collaboration with {platform_name}." # pylint: disable=line-too-long + ).format( + recipient_fullname=self.subject_fullname or _("recipient"), + program_title=self.program.title, + organizations=", ".join(list(self.program.authoring_organizations.values_list("name", flat=True))), + platform_name=self.platform_name, + ) + elif self.credential_content_type == CredentialsType.COURSE: + narrative = _( + "{recipient_fullname} successfully completed a course and received a passing grade for a Course Certificate in {course_title} a course offered by {organization}, in collaboration with {platform_name}. " # pylint: disable=line-too-long + ).format( + recipient_fullname=self.subject_fullname or _("recipient"), + course_title=getattr(self.course, "title", ""), + organization=self.user_credential.credential.course_key.org, + platform_name=self.platform_name, + ) + return capitalize_first(narrative) @property def credential_content_type(self): @@ -183,6 +195,11 @@ def credential_content_type(self): def program(self): return getattr(self.user_credential.credential, "program", None) + @property + def course(self): + course_id = getattr(self.user_credential.credential, "course_id", None) + return Course.objects.filter(course_runs__key=course_id).first() + @property def platform_name(self): if not (site_configuration := getattr(self.user_credential.credential.site, "siteconfiguration", "")): diff --git a/credentials/apps/verifiable_credentials/rest_api/v1/tests/test_views.py b/credentials/apps/verifiable_credentials/rest_api/v1/tests/test_views.py index 27cff56ba..51cd387df 100644 --- a/credentials/apps/verifiable_credentials/rest_api/v1/tests/test_views.py +++ b/credentials/apps/verifiable_credentials/rest_api/v1/tests/test_views.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType from django.test import TestCase from django.urls import reverse +from ddt import ddt, data, unpack from rest_framework import status from credentials.apps.catalog.tests.factories import ( @@ -22,12 +23,13 @@ from credentials.apps.verifiable_credentials.issuance import IssuanceException from credentials.apps.verifiable_credentials.issuance.tests.factories import IssuanceLineFactory from credentials.apps.verifiable_credentials.storages.learner_credential_wallet import LCWallet -from credentials.apps.verifiable_credentials.utils import get_user_program_credentials_data +from credentials.apps.verifiable_credentials.utils import get_user_credentials_data JSON_CONTENT_TYPE = "application/json" +@ddt class ProgramCredentialsViewTests(SiteMixin, TestCase): def setUp(self): super().setUp() @@ -73,22 +75,43 @@ def setUp(self): def test_deny_unauthenticated_user(self): self.client.logout() - response = self.client.get("/verifiable_credentials/api/v1/program_credentials/") + response = self.client.get("/verifiable_credentials/api/v1/credentials/") self.assertEqual(response.status_code, 401) def test_allow_authenticated_user(self): """Verify the endpoint requires an authenticated user.""" self.client.logout() self.client.login(username=self.user.username, password=USER_PASSWORD) - response = self.client.get("/verifiable_credentials/api/v1/program_credentials/") + response = self.client.get("/verifiable_credentials/api/v1/credentials/") self.assertEqual(response.status_code, 200) - def test_get(self): + def test_get_without_query_params(self): self.client.login(username=self.user.username, password=USER_PASSWORD) - response = self.client.get("/verifiable_credentials/api/v1/program_credentials/") + response = self.client.get("/verifiable_credentials/api/v1/credentials/") self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["program_credentials"], get_user_program_credentials_data(self.user.username)) + self.assertEqual(response.data["program_credentials"], get_user_credentials_data(self.user.username, "programcertificate")) + self.assertEqual(response.data["course_credentials"], get_user_credentials_data(self.user.username, "coursecertificate")) + + @data( + ("programcertificate", {"program_credentials": "programcertificate"}, ["course_credentials"]), + ("coursecertificate", {"course_credentials": "coursecertificate"}, ["program_credentials"]), + ("programcertificate,coursecertificate", + {"program_credentials": "programcertificate", "course_credentials": "coursecertificate"}, []) + ) + @unpack + def test_get_with_query_params(self, types, expected_data, not_in_keys): + self.client.login(username=self.user.username, password=USER_PASSWORD) + response = self.client.get(f"/verifiable_credentials/api/v1/credentials/?types={types}") + self.assertEqual(response.status_code, 200) + + for key, expected_value in expected_data.items(): + self.assertEqual( + response.data[key], + get_user_credentials_data(self.user.username, expected_value) + ) + for key in not_in_keys: + self.assertNotIn(key, response.data) class InitIssuanceViewTestCase(SiteMixin, TestCase): url_path = reverse("verifiable_credentials:api:v1:credentials-init") diff --git a/credentials/apps/verifiable_credentials/rest_api/v1/urls.py b/credentials/apps/verifiable_credentials/rest_api/v1/urls.py index d531359bf..103577134 100644 --- a/credentials/apps/verifiable_credentials/rest_api/v1/urls.py +++ b/credentials/apps/verifiable_credentials/rest_api/v1/urls.py @@ -9,7 +9,7 @@ router = routers.DefaultRouter() -router.register(r"program_credentials", views.ProgramCredentialsViewSet, basename="program_credentials") +router.register(r"credentials", views.CredentialsViewSet, basename="credentials") urlpatterns = [ path(r"credentials/init/", views.InitIssuanceView.as_view(), name="credentials-init"), diff --git a/credentials/apps/verifiable_credentials/rest_api/v1/views.py b/credentials/apps/verifiable_credentials/rest_api/v1/views.py index cc0442c49..32dbb2b84 100644 --- a/credentials/apps/verifiable_credentials/rest_api/v1/views.py +++ b/credentials/apps/verifiable_credentials/rest_api/v1/views.py @@ -25,7 +25,7 @@ from credentials.apps.verifiable_credentials.storages.utils import get_available_storages, get_storage from credentials.apps.verifiable_credentials.utils import ( generate_base64_qr_code, - get_user_program_credentials_data, + get_user_credentials_data, is_valid_uuid, ) @@ -35,7 +35,7 @@ User = get_user_model() -class ProgramCredentialsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): +class CredentialsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): authentication_classes = ( JwtAuthentication, SessionAuthentication, @@ -43,17 +43,33 @@ class ProgramCredentialsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): permission_classes = (IsAuthenticated,) + CREDENTIAL_TYPES_MAP = { + "programcertificate": "program_credentials", + "coursecertificate": "course_credentials", + } + def list(self, request, *args, **kwargs): """ - List data for all the user's issued program credentials. - GET: /verifiable_credentials/api/v1/program_credentials/ + List data for all the user's issued credentials. + GET: /verifiable_credentials/api/v1/credentials?types=coursecertificate,programcertificate Arguments: request: A request to control data returned in endpoint response Returns: response(dict): Information about the user's program credentials """ - program_credentials = get_user_program_credentials_data(request.user.username) - return Response({"program_credentials": program_credentials}) + types = self.request.query_params.get('types') + response = {} + + if types: + types = types.split(',') + else: + types = self.CREDENTIAL_TYPES_MAP.keys() + + for type in types: + if type in self.CREDENTIAL_TYPES_MAP: + response[self.CREDENTIAL_TYPES_MAP[type]] = get_user_credentials_data(request.user.username, type) + + return Response(response) class InitIssuanceView(APIView): diff --git a/credentials/apps/verifiable_credentials/tests/test_utils.py b/credentials/apps/verifiable_credentials/tests/test_utils.py index 8eb42fe43..4cb7f46ff 100644 --- a/credentials/apps/verifiable_credentials/tests/test_utils.py +++ b/credentials/apps/verifiable_credentials/tests/test_utils.py @@ -20,7 +20,7 @@ from credentials.apps.verifiable_credentials.utils import ( capitalize_first, generate_base64_qr_code, - get_user_program_credentials_data, + get_user_credentials_data, ) @@ -73,25 +73,25 @@ def setUp(self): def test_get_user_program_credentials_data_not_completed(self): self.program_user_credential.delete() - result = get_user_program_credentials_data(self.user.username) + result = get_user_credentials_data(self.user.username, "programcertificate") assert result == [] def test_get_user_program_credentials_data_zero_programs(self): self.program_cert.delete() self.program.delete() self.program_user_credential.delete() - result = get_user_program_credentials_data(self.user.username) + result = get_user_credentials_data(self.user.username, "programcertificate") assert result == [] def test_get_user_program_credentials_data_one_program(self): - result = get_user_program_credentials_data(self.user.username) + result = get_user_credentials_data(self.user.username, "programcertificate") assert result[0]["uuid"] == str(self.program_user_credential.uuid).replace("-", "") assert result[0]["status"] == self.program_user_credential.status assert result[0]["username"] == self.program_user_credential.username assert result[0]["download_url"] == self.program_user_credential.download_url assert result[0]["credential_id"] == self.program_user_credential.credential_id - assert result[0]["program_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "") - assert result[0]["program_title"] == self.program_user_credential.credential.program.title + assert result[0]["credential_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "") + assert result[0]["credential_title"] == self.program_user_credential.credential.program.title def test_get_user_program_credentials_data_multiple_programs(self): self.program2 = ProgramFactory( @@ -108,23 +108,61 @@ def test_get_user_program_credentials_data_multiple_programs(self): credential_content_type=self.program_credential_content_type, credential=self.program_cert2, ) - result = get_user_program_credentials_data(self.user.username) + result = get_user_credentials_data(self.user.username, "programcertificate") assert result[0]["uuid"] == str(self.program_user_credential.uuid).replace("-", "") assert result[0]["status"] == self.program_user_credential.status assert result[0]["username"] == self.program_user_credential.username assert result[0]["download_url"] == self.program_user_credential.download_url assert result[0]["credential_id"] == self.program_user_credential.credential_id - assert result[0]["program_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "") - assert result[0]["program_title"] == self.program_user_credential.credential.program.title + assert result[0]["credential_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "") + assert result[0]["credential_title"] == self.program_user_credential.credential.program.title assert result[1]["uuid"] == str(self.program_user_credential2.uuid).replace("-", "") assert result[1]["status"] == self.program_user_credential2.status assert result[1]["username"] == self.program_user_credential2.username assert result[1]["download_url"] == self.program_user_credential2.download_url assert result[1]["credential_id"] == self.program_user_credential2.credential_id - assert result[1]["program_uuid"] == str(self.program_user_credential2.credential.program_uuid).replace("-", "") - assert result[1]["program_title"] == self.program_user_credential2.credential.program.title + assert result[1]["credential_uuid"] == str(self.program_user_credential2.credential.program_uuid).replace("-", "") + assert result[1]["credential_title"] == self.program_user_credential2.credential.program.title + def test_get_user_course_credentials_data_zero_courses(self): + self.course_user_credentials[0].delete() + self.course_user_credentials[1].delete() + result = get_user_credentials_data(self.user.username, "coursecertificate") + assert result == [] + + def test_get_user_course_credentials_data_one_course(self): + self.course_user_credentials[1].delete() + result = get_user_credentials_data(self.user.username, "coursecertificate") + assert result[0]["uuid"] == str(self.course_user_credentials[0].uuid).replace("-", "") + assert result[0]["status"] == self.course_user_credentials[0].status + assert result[0]["username"] == self.course_user_credentials[0].username + assert result[0]["download_url"] == self.course_user_credentials[0].download_url + assert result[0]["credential_id"] == self.course_user_credentials[0].credential_id + assert result[0]["credential_uuid"] == self.course_user_credentials[0].credential.course_id + assert result[0]["credential_title"] == self.course_user_credentials[0].credential.title + + def test_get_user_course_credentials_data_multiple_courses(self): + result = get_user_credentials_data(self.user.username, "coursecertificate") + assert result[0]["uuid"] == str(self.course_user_credentials[0].uuid).replace("-", "") + assert result[0]["status"] == self.course_user_credentials[0].status + assert result[0]["username"] == self.course_user_credentials[0].username + assert result[0]["download_url"] == self.course_user_credentials[0].download_url + assert result[0]["credential_id"] == self.course_user_credentials[0].credential_id + assert result[0]["credential_uuid"] == self.course_user_credentials[0].credential.course_id + assert result[0]["credential_title"] == self.course_user_credentials[0].credential.title + + assert result[1]["uuid"] == str(self.course_user_credentials[1].uuid).replace("-", "") + assert result[1]["status"] == self.course_user_credentials[1].status + assert result[1]["username"] == self.course_user_credentials[1].username + assert result[1]["download_url"] == self.course_user_credentials[1].download_url + assert result[1]["credential_id"] == self.course_user_credentials[1].credential_id + assert result[1]["credential_uuid"] == self.course_user_credentials[1].credential.course_id + assert result[1]["credential_title"] == self.course_user_credentials[1].credential.title + + def test_non_existing_content_type(self): + result = get_user_credentials_data(self.user.username, "non_existing_content_type") + assert result == [] class TestGenerateBase64QRCode(TestCase): def test_correct_output_format(self): diff --git a/credentials/apps/verifiable_credentials/utils.py b/credentials/apps/verifiable_credentials/utils.py index 11428aef3..55add7f66 100644 --- a/credentials/apps/verifiable_credentials/utils.py +++ b/credentials/apps/verifiable_credentials/utils.py @@ -9,37 +9,53 @@ from credentials.apps.credentials.data import UserCredentialStatus -def get_user_program_credentials_data(username): +def get_user_credentials_data(username, model): """ Translates a list of UserCredentials (for programs) into context data. Arguments: request_username(str): Username for whom we are getting UserCredential objects for + model(str): The model for content type (programcertificate | coursecertificate) Returns: list(dict): A list of dictionaries, each dictionary containing information for a credential that the user awarded """ - program_cert_content_type = ContentType.objects.get(app_label="credentials", model="programcertificate") - program_credentials = get_user_credentials_by_content_type( - username, [program_cert_content_type], UserCredentialStatus.AWARDED.value + try: + credential_cert_content_type = ContentType.objects.get(app_label="credentials", model=model) + except ContentType.DoesNotExist: + return [] + + credentials = get_user_credentials_by_content_type( + username, [credential_cert_content_type], UserCredentialStatus.AWARDED.value ) - return [ - { + + data = [] + for credential in credentials: + if model == "programcertificate": + credential_uuid = credential.credential.program_uuid.hex + credential_title = credential.credential.program.title + credential_org = ", ".join( + credential.credential.program.authoring_organizations.values_list("name", flat=True) + ) + elif model == "coursecertificate": + credential_uuid = credential.credential.course_id + credential_title = credential.credential.title + credential_org = credential.credential.course_key.org + + data.append({ "uuid": credential.uuid.hex, "status": credential.status, "username": credential.username, "download_url": credential.download_url, "credential_id": credential.credential_id, - "program_uuid": credential.credential.program_uuid.hex, - "program_title": credential.credential.program.title, - "program_org": ", ".join( - credential.credential.program.authoring_organizations.values_list("name", flat=True) - ), + "credential_uuid": credential_uuid , + "credential_title": credential_title, + "credential_org": credential_org, "modified_date": credential.modified.date().isoformat(), - } - for credential in program_credentials - ] + }) + + return data def generate_base64_qr_code(text):