Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions platform_plugin_elm_credentials/api/credential_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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(
Expand All @@ -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": {
Expand Down
45 changes: 27 additions & 18 deletions platform_plugin_elm_credentials/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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",
)

Expand Down Expand Up @@ -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")


Expand Down Expand Up @@ -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")


Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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")

Expand Down
16 changes: 16 additions & 0 deletions platform_plugin_elm_credentials/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
48 changes: 24 additions & 24 deletions platform_plugin_elm_credentials/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down