diff --git a/platform_plugin_elm_credentials/api/credential_builder.py b/platform_plugin_elm_credentials/api/credential_builder.py index bec60e2..26fb6ab 100644 --- a/platform_plugin_elm_credentials/api/credential_builder.py +++ b/platform_plugin_elm_credentials/api/credential_builder.py @@ -22,7 +22,7 @@ ProvenBy, SpecifiedBy, ) -from platform_plugin_elm_credentials.api.utils import get_fullname, to_iso_format +from platform_plugin_elm_credentials.api.utils import as_lang_string, get_fullname, to_iso_format class CredentialBuilder: @@ -124,9 +124,9 @@ def title(self) -> dict: Returns: str: The title of the credential. """ - return { - "en": f"Course certificate for passing {self.course_block.display_name} course" - } + return as_lang_string( + f"Course certificate for passing {self.course_block.display_name} course" + ) @property def grade(self) -> Grade: @@ -137,7 +137,7 @@ def grade(self) -> Grade: Grade: The grade of the credential. """ note = str(round(float(self.certificate.grade) * 100, 2)) - return Grade(note_literal={"en": note}) + return Grade(note_literal=as_lang_string(note)) @property def issuer(self) -> Issuer: @@ -150,8 +150,8 @@ def issuer(self) -> Issuer: issuer_id = self.credential_settings.get("issuer_id") return Issuer( id=issuer_id or str(uuid4()), - alt_label={"en": self.course_block.org}, - legal_name={"en": self.course_block.org}, + alt_label=as_lang_string(self.course_block.org), + legal_name=as_lang_string(self.course_block.org), ) def get_maped_language(self) -> str: @@ -187,12 +187,12 @@ def build(self) -> dict: given_name, family_name = get_fullname(self.full_name) awarding_body = AwardingBody( - alt_label={"en": self.course_block.org}, - legal_name={"en": self.course_block.org}, - location=Location(address=Address(country_code=self.org_country_code)), + alt_label=as_lang_string(self.course_block.org), + legal_name=as_lang_string(self.course_block.org), + location=[Location(address=[Address(country_code=self.org_country_code)])], ) awarded_by = AwardedBy( - awarding_body=awarding_body, + awarding_body=[awarding_body], awarding_date=to_iso_format(self.certificate.created_date), ) specified_by = SpecifiedBy( @@ -213,16 +213,17 @@ def build(self) -> dict: specified_by=specified_by, ) credential_subject = CredentialSubject( - given_name={"en": given_name}, - family_name={"en": family_name}, - full_name={"en": self.full_name}, - has_claim=has_claim, + given_name=as_lang_string(given_name), + family_name=as_lang_string(family_name), + full_name=as_lang_string(self.full_name), + has_claim=[has_claim], ) display_parameter = DisplayParameter( primary_language=self.primary_language, - title={"en": self.course_block.display_name}, + language=[self.language], + title=as_lang_string(self.course_block.display_name), ) - delivery_details = DeliveryDetails(delivery_address=self.user.email) + delivery_details = DeliveryDetails(delivery_address=[self.user.email]) return { "credential": { diff --git a/platform_plugin_elm_credentials/api/serializers.py b/platform_plugin_elm_credentials/api/serializers.py index b3424a6..2957c7c 100644 --- a/platform_plugin_elm_credentials/api/serializers.py +++ b/platform_plugin_elm_credentials/api/serializers.py @@ -5,7 +5,12 @@ from pydantic import BaseModel, ConfigDict, Field, field_serializer -from platform_plugin_elm_credentials.api.utils import get_current_datetime, to_camel, to_iso_format +from platform_plugin_elm_credentials.api.utils import ( + as_lang_string, + get_current_datetime, + to_camel, + to_iso_format, +) class QueryParamsModel(BaseModel): @@ -105,7 +110,7 @@ class Location(ConfigModel): id: str = "urn:epass:location:1" type: str = "Location" - address: Address = Field(validation_alias="address") + address: List[Address] = Field(validation_alias="address") class Mode(ConfigModel): @@ -124,7 +129,9 @@ class Mode(ConfigModel): }, validation_alias="in_scheme", ) - pref_label: dict = Field(default={"en": "Online"}, validation_alias="pref_label") + pref_label: dict = Field( + default_factory=lambda: as_lang_string("Online"), validation_alias="pref_label" + ) class Language(LanguageBase): @@ -146,7 +153,7 @@ class AwardingBody(ConfigModel): type: str = "Organisation" alt_label: dict = Field(validation_alias="alt_label") legal_name: dict = Field(validation_alias="legal_name") - location: Location = Field(validation_alias="location") + location: List[Location] = Field(validation_alias="location") class IdVerification(ConfigModel): @@ -166,7 +173,7 @@ class IdVerification(ConfigModel): validation_alias="in_scheme", ) pref_label: dict = Field( - default={"en": "Unsupervised with ID verification"}, + default_factory=lambda: as_lang_string("Unsupervised with ID verification"), validation_alias="pref_label", ) @@ -206,7 +213,7 @@ class AwardedBy(ConfigModel): id: str = "urn:epass:awardingProcess:1" type: str = "AwardingProcess" - awarding_body: AwardingBody = Field(validation_alias="awarding_body") + awarding_body: List[AwardingBody] = Field(validation_alias="awarding_body") awarding_date: str = Field(validation_alias="awarding_date") @@ -258,6 +265,7 @@ class DisplayParameter(ConfigModel): id: str = "urn:epass:displayParameter:1" type: str = "DisplayParameter" primary_language: PrimaryLanguage = Field(validation_alias="primary_language") + language: List[Language] = Field(validation_alias="language") title: dict = Field(validation_alias="title") @@ -273,7 +281,7 @@ class CredentialSubject(ConfigModel): given_name: dict = Field(validation_alias="given_name") family_name: dict = Field(validation_alias="family_name") full_name: dict = Field(validation_alias="full_name") - has_claim: HasClaim = Field(validation_alias="has_claim") + has_claim: List[HasClaim] = Field(validation_alias="has_claim") class Issuer(ConfigModel): @@ -300,7 +308,7 @@ class DeliveryDetails(ConfigModel): This property is used in the ELMCredentialModel model. """ - delivery_address: str = Field(validation_alias="delivery_address") + delivery_address: List[str] = Field(validation_alias="delivery_address") class ELMBody(ConfigModel): @@ -311,23 +319,24 @@ class ELMBody(ConfigModel): id: str = Field(default=f"urn:credential:{uuid4()}") type: List[str] = ["VerifiableCredential", "EuropeanDigitalCredential"] - context: List[str] = Field( + context: Optional[List[str]] = Field( serialization_alias="@context", - default=[ - "https://www.w3.org/2018/credentials/v1", - "https://data.europa.eu/snb/model/context/edc-ap", - ], + default=None, + ) + credential_schema: List[dict] = Field( + default_factory=lambda: [ + { + "id": "http://data.europa.eu/snb/model/ap/edc-generic-no-cv", + "type": "ShaclValidator2017", + } + ] ) - credential_schema: dict = { - "id": "http://data.europa.eu/snb/model/ap/edc-generic-no-cv", - "type": "ShaclValidator2017", - } valid_until: Optional[str] = Field(validation_alias="valid_until") expiration_date: Optional[str] = Field(validation_alias="expiration_date") valid_from: str = Field(default_factory=get_current_datetime) issuance_date: str = Field(default_factory=get_current_datetime) issued: str = Field(default_factory=get_current_datetime) - issuer: Issuer = Field(validation_alias="issuer") + issuer: Optional[Issuer] = Field(default=None, validation_alias="issuer") credential_subject: CredentialSubject = Field(validation_alias="credential_subject") display_parameter: DisplayParameter = Field(validation_alias="display_parameter") diff --git a/platform_plugin_elm_credentials/api/utils.py b/platform_plugin_elm_credentials/api/utils.py index eda5537..7021264 100644 --- a/platform_plugin_elm_credentials/api/utils.py +++ b/platform_plugin_elm_credentials/api/utils.py @@ -44,6 +44,22 @@ def to_camel(string: str) -> str: return words[0] + "".join(word.capitalize() for word in words[1:]) +def as_lang_string(value: str, lang: str = "en") -> dict: + """ + Wrap a value in ELMv3 multilingual langString format. + + Example: as_lang_string("Online") -> {"en": ["Online"]} + + Args: + value (str): The value to wrap. + lang (str): The language code. Defaults to "en". + + Returns: + dict: The value in langString format. + """ + return {lang: [value]} + + def get_fullname(name: str) -> Tuple[str, str]: """ Returns the first and last name from a full name. diff --git a/platform_plugin_elm_credentials/tests/test_views.py b/platform_plugin_elm_credentials/tests/test_views.py index 0c78a32..ecade2a 100644 --- a/platform_plugin_elm_credentials/tests/test_views.py +++ b/platform_plugin_elm_credentials/tests/test_views.py @@ -97,19 +97,19 @@ def test_get_elm_credentials_with_course_settings( self.assertNotIn("Content-Disposition", response.headers) self.assertEqual( response_data["deliveryDetails"]["deliveryAddress"], - self.credential_user.email, + [self.credential_user.email], ) - self.assertEqual(credential["issuer"]["legalName"]["en"], self.org) - self.assertEqual(credential["credentialSubject"]["givenName"]["en"], "John") - self.assertEqual(credential["credentialSubject"]["familyName"]["en"], "Doe") + self.assertEqual(credential["issuer"]["legalName"]["en"], [self.org]) + self.assertEqual(credential["credentialSubject"]["givenName"]["en"], ["John"]) + self.assertEqual(credential["credentialSubject"]["familyName"]["en"], ["Doe"]) self.assertEqual( credential["credentialSubject"]["fullName"]["en"], - "John Doe", + ["John Doe"], ) self.assertEqual( - credential["credentialSubject"]["hasClaim"]["awardedBy"]["awardingBody"][ - "location" - ]["address"]["countryCode"]["id"].split("/")[-1], + credential["credentialSubject"]["hasClaim"][0]["provenBy"]["awardedBy"][ + "awardingBody" + ][0]["location"][0]["address"][0]["countryCode"]["id"].split("/")[-1], self.other_course_settings["ELM_CREDENTIALS_DEFAULTS"]["org_country_code"], ) self.assertEqual( @@ -148,19 +148,19 @@ def test_get_elm_credentials_with_django_settings( self.assertNotIn("Content-Disposition", response.headers) self.assertEqual( response_data["deliveryDetails"]["deliveryAddress"], - self.credential_user.email, + [self.credential_user.email], ) - self.assertEqual(credential["issuer"]["legalName"]["en"], self.org) - self.assertEqual(credential["credentialSubject"]["givenName"]["en"], "John") - self.assertEqual(credential["credentialSubject"]["familyName"]["en"], "Doe") + self.assertEqual(credential["issuer"]["legalName"]["en"], [self.org]) + self.assertEqual(credential["credentialSubject"]["givenName"]["en"], ["John"]) + self.assertEqual(credential["credentialSubject"]["familyName"]["en"], ["Doe"]) self.assertEqual( credential["credentialSubject"]["fullName"]["en"], - "John Doe", + ["John Doe"], ) self.assertEqual( - credential["credentialSubject"]["hasClaim"]["awardedBy"]["awardingBody"][ - "location" - ]["address"]["countryCode"]["id"].split("/")[-1], + credential["credentialSubject"]["hasClaim"][0]["provenBy"]["awardedBy"][ + "awardingBody" + ][0]["location"][0]["address"][0]["countryCode"]["id"].split("/")[-1], settings.ELM_CREDENTIALS_DEFAULTS.get("org_country_code"), ) self.assertEqual( @@ -200,19 +200,19 @@ def test_get_elm_credentials_with_default_settings( self.assertNotIn("Content-Disposition", response.headers) self.assertEqual( response_data["deliveryDetails"]["deliveryAddress"], - self.credential_user.email, + [self.credential_user.email], ) - self.assertEqual(credential["issuer"]["legalName"]["en"], self.org) - self.assertEqual(credential["credentialSubject"]["givenName"]["en"], "John") - self.assertEqual(credential["credentialSubject"]["familyName"]["en"], "Doe") + self.assertEqual(credential["issuer"]["legalName"]["en"], [self.org]) + self.assertEqual(credential["credentialSubject"]["givenName"]["en"], ["John"]) + self.assertEqual(credential["credentialSubject"]["familyName"]["en"], ["Doe"]) self.assertEqual( credential["credentialSubject"]["fullName"]["en"], - "John Doe", + ["John Doe"], ) self.assertEqual( - credential["credentialSubject"]["hasClaim"]["awardedBy"]["awardingBody"][ - "location" - ]["address"]["countryCode"]["id"].split("/")[-1], + credential["credentialSubject"]["hasClaim"][0]["provenBy"]["awardedBy"][ + "awardingBody" + ][0]["location"][0]["address"][0]["countryCode"]["id"].split("/")[-1], "ESP", ) self.assertEqual(