diff --git a/credentials/apps/badges/admin.py b/credentials/apps/badges/admin.py
index 11ef64c39..fee35b514 100644
--- a/credentials/apps/badges/admin.py
+++ b/credentials/apps/badges/admin.py
@@ -50,7 +50,7 @@ class BadgeRequirementInline(admin.TabularInline):
"group",
)
readonly_fields = ("rules",)
- ordering = ("group",)
+ ordering = ("group",)
form = BadgeRequirementForm
formset = BadgeRequirementFormSet
@@ -58,15 +58,19 @@ def rules(self, obj):
"""
Display all data rules for the requirement.
"""
- return format_html(
- "
",
- mark_safe(
- "".join(
- f"{rule.data_path} {rule.OPERATORS[rule.operator]} {rule.value}"
- for rule in obj.rules.all()
- )
- ),
- ) if obj.rules.exists() else _("No rules specified.")
+ return (
+ format_html(
+ "",
+ mark_safe(
+ "".join(
+ f"{rule.data_path} {rule.OPERATORS[rule.operator]} {rule.value}"
+ for rule in obj.rules.all()
+ )
+ ),
+ )
+ if obj.rules.exists()
+ else _("No rules specified.")
+ )
class BadgePenaltyInline(admin.TabularInline):
@@ -94,20 +98,24 @@ def formfield_for_manytomany(self, db_field, request, **kwargs):
if template_id:
kwargs["queryset"] = BadgeRequirement.objects.filter(template_id=template_id)
return super().formfield_for_manytomany(db_field, request, **kwargs)
-
+
def rules(self, obj):
"""
Display all data rules for the penalty.
"""
- return format_html(
- "",
- mark_safe(
- "".join(
- f"{rule.data_path} {rule.OPERATORS[rule.operator]} {rule.value}"
- for rule in obj.rules.all()
- )
- ),
- ) if obj.rules.exists() else _("No rules specified.")
+ return (
+ format_html(
+ "",
+ mark_safe(
+ "".join(
+ f"{rule.data_path} {rule.OPERATORS[rule.operator]} {rule.value}"
+ for rule in obj.rules.all()
+ )
+ ),
+ )
+ if obj.rules.exists()
+ else _("No rules specified.")
+ )
class FulfillmentInline(admin.TabularInline):
@@ -168,7 +176,7 @@ def sync_organization_badge_templates(self, request, queryset):
)
messages.success(request, _("Badge templates were successfully updated."))
-
+
@admin.display(description=_("API key"))
def api_key_hidden(self, obj):
"""
@@ -176,15 +184,15 @@ def api_key_hidden(self, obj):
"""
return _("Pre-configured from the environment.") if obj.is_preconfigured else obj.api_key
-
+
def get_fields(self, request, obj=None):
fields = super().get_fields(request, obj)
-
+
if not (obj and obj.is_preconfigured):
fields = [field for field in fields if field != "api_key_hidden"]
fields.append("api_key")
return fields
-
+
def get_readonly_fields(self, request, obj=None):
readonly_fields = super().get_readonly_fields(request, obj)
diff --git a/credentials/apps/badges/admin_forms.py b/credentials/apps/badges/admin_forms.py
index 64439d1c9..a27d1b891 100644
--- a/credentials/apps/badges/admin_forms.py
+++ b/credentials/apps/badges/admin_forms.py
@@ -46,7 +46,7 @@ def clean(self):
if str(uuid) in CredlyOrganization.get_preconfigured_organizations().keys():
if api_key:
raise forms.ValidationError(_("You can't provide an API key for a configured organization."))
-
+
api_key = settings.BADGES_CONFIG["credly"]["ORGANIZATIONS"][str(uuid)]
credly_api_client = CredlyAPIClient(uuid, api_key)
@@ -80,6 +80,7 @@ class BadgePenaltyForm(forms.ModelForm):
"""
Form for BadgePenalty model.
"""
+
class Meta:
model = BadgePenalty
fields = "__all__"
@@ -138,23 +139,28 @@ def clean(self):
if data_path_type == bool and cleaned_data.get("value") not in AbstractDataRule.BOOL_VALUES:
raise forms.ValidationError(_("Value must be a boolean."))
-
+
return cleaned_data
class DataRuleFormSet(ParentMixin, forms.BaseInlineFormSet): ...
+
+
class DataRuleForm(DataRuleExtensionsMixin, forms.ModelForm):
"""
Form for DataRule model.
"""
+
class Meta:
model = DataRule
fields = "__all__"
-
+
data_path = forms.ChoiceField()
class BadgeRequirementFormSet(ParentMixin, forms.BaseInlineFormSet): ...
+
+
class BadgeRequirementForm(forms.ModelForm):
class Meta:
model = BadgeRequirement
@@ -166,13 +172,13 @@ def __init__(self, *args, parent_instance=None, **kwargs):
self.template = parent_instance
super().__init__(*args, **kwargs)
- self.fields["group"].choices = Choices(
- *[(chr(i), chr(i)) for i in range(65, 91)]
- )
+ self.fields["group"].choices = Choices(*[(chr(i), chr(i)) for i in range(65, 91)])
self.fields["group"].initial = chr(65 + self.template.requirements.count())
class PenaltyDataRuleFormSet(ParentMixin, forms.BaseInlineFormSet): ...
+
+
class PenaltyDataRuleForm(DataRuleExtensionsMixin, forms.ModelForm):
"""
Form for PenaltyDataRule model.
diff --git a/credentials/apps/badges/models.py b/credentials/apps/badges/models.py
index 9cbb048b1..0f8b21aea 100644
--- a/credentials/apps/badges/models.py
+++ b/credentials/apps/badges/models.py
@@ -35,7 +35,9 @@ class CredlyOrganization(TimeStampedModel):
"""
uuid = models.UUIDField(unique=True, help_text=_("Put your Credly Organization ID here."))
- api_key = models.CharField(max_length=255, help_text=_("Credly API shared secret for Credly Organization."), blank=True)
+ api_key = models.CharField(
+ max_length=255, help_text=_("Credly API shared secret for Credly Organization."), blank=True
+ )
name = models.CharField(
max_length=255,
null=True,
@@ -52,14 +54,14 @@ def get_all_organization_ids(cls):
Get all organization IDs.
"""
return list(cls.objects.values_list("uuid", flat=True))
-
+
@classmethod
def get_preconfigured_organizations(cls):
"""
Get preconfigured organizations.
"""
return settings.BADGES_CONFIG["credly"].get("ORGANIZATIONS", {})
-
+
@property
def is_preconfigured(self):
"""
@@ -99,7 +101,7 @@ def save(self, *args, **kwargs):
if not self.origin:
self.origin = self.ORIGIN
self.save(*args, **kwargs)
-
+
@property
def groups(self):
return self.requirements.values_list("group", flat=True).distinct()
@@ -327,12 +329,12 @@ def apply(self, data: dict) -> bool:
"""
comparison_func = getattr(operator, self.operator, None)
-
+
if comparison_func:
data_value = str(keypath(data, self.data_path))
return comparison_func(data_value, self._value_to_bool())
return False
-
+
def _value_to_bool(self):
"""
Converts the value to a boolean or returns the original value if it is not a boolean string.
@@ -410,7 +412,7 @@ def reset_requirements(self, username: str):
"""
Resets all related requirements for the user.
"""
-
+
for requirement in self.requirements.all():
requirement.reset(username)
@@ -482,7 +484,7 @@ def ratio(self) -> float:
if not self.groups:
return 0.00
-
+
true_values = len(list(filter(lambda x: x, self.groups.values())))
return round(true_values / len(self.groups.keys()), 2)
@@ -506,7 +508,7 @@ def progress(self):
Notify about the progress.
"""
notify_progress_complete(self, self.username, self.template.id)
-
+
def regress(self):
"""
Notify about the regression.
@@ -530,7 +532,7 @@ class Fulfillment(models.Model):
null=True,
related_name="fulfillments",
)
- group = models.CharField( max_length=255, null=True, blank=True, help_text=_("Group ID for the requirement."))
+ group = models.CharField(max_length=255, null=True, blank=True, help_text=_("Group ID for the requirement."))
class CredlyBadge(UserCredential):
@@ -604,5 +606,5 @@ def propagated(self):
"""
Checks if this user credential already has issued (external) Credly badge.
"""
-
+
return self.external_uuid and (self.state in self.ISSUING_STATES)
diff --git a/credentials/apps/badges/signals/handlers.py b/credentials/apps/badges/signals/handlers.py
index c080542bd..27e1ab5fc 100644
--- a/credentials/apps/badges/signals/handlers.py
+++ b/credentials/apps/badges/signals/handlers.py
@@ -45,6 +45,7 @@ def handle_badging_event(sender, signal, **kwargs):
process_event(signal, **kwargs)
+
@receiver(BADGE_REQUIREMENT_FULFILLED)
def handle_requirement_fulfilled(sender, username, **kwargs): # pylint: disable=unused-argument
"""
@@ -52,6 +53,7 @@ def handle_requirement_fulfilled(sender, username, **kwargs): # pylint: disable
"""
BadgeProgress.for_user(username=username, template_id=sender.template.id).progress()
+
@receiver(BADGE_REQUIREMENT_REGRESSED)
def handle_requirement_regressed(sender, username, **kwargs): # pylint: disable=unused-argument
"""
diff --git a/credentials/apps/badges/tests/test_admin_forms.py b/credentials/apps/badges/tests/test_admin_forms.py
index 85bb707a9..db5152331 100644
--- a/credentials/apps/badges/tests/test_admin_forms.py
+++ b/credentials/apps/badges/tests/test_admin_forms.py
@@ -46,13 +46,16 @@ def test_clean_requirements_same_template(self):
self.requirement3,
],
}
- self.assertEqual(form.clean(), {
- "template": self.badge_template2,
- "requirements": [
- self.requirement2,
- self.requirement3,
- ],
- })
+ self.assertEqual(
+ form.clean(),
+ {
+ "template": self.badge_template2,
+ "requirements": [
+ self.requirement2,
+ self.requirement3,
+ ],
+ },
+ )
def test_clean_requirements_different_template(self):
form = BadgePenaltyForm()
@@ -67,4 +70,4 @@ def test_clean_requirements_different_template(self):
with self.assertRaises(forms.ValidationError) as cm:
form.clean()
- self.assertEqual(str(cm.exception), "['All requirements must belong to the same template.']")
\ No newline at end of file
+ self.assertEqual(str(cm.exception), "['All requirements must belong to the same template.']")
diff --git a/credentials/apps/badges/tests/test_models.py b/credentials/apps/badges/tests/test_models.py
index 05ad6c370..3be5212c5 100644
--- a/credentials/apps/badges/tests/test_models.py
+++ b/credentials/apps/badges/tests/test_models.py
@@ -217,19 +217,19 @@ def setUp(self):
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
- group="A"
+ group="A",
)
self.requirement2 = BadgeRequirement.objects.create(
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
- group="B"
+ group="B",
)
self.requirement3 = BadgeRequirement.objects.create(
template=self.badge_template,
event_type="org.openedx.learning.ccx.course.passing.status.updated.v1",
description="Test description",
- group="C"
+ group="C",
)
def test_user_progress_success(self):
@@ -297,13 +297,13 @@ def setUp(self):
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
- group="A"
+ group="A",
)
self.requirement2 = BadgeRequirement.objects.create(
template=self.badge_template,
event_type="org.openedx.learning.course.passing.status.updated.v1",
description="Test description",
- group="B"
+ group="B",
)
self.group_requirement1 = BadgeRequirement.objects.create(
diff --git a/credentials/apps/badges/tests/test_services.py b/credentials/apps/badges/tests/test_services.py
index 4fc981d08..ae5e9dfbc 100644
--- a/credentials/apps/badges/tests/test_services.py
+++ b/credentials/apps/badges/tests/test_services.py
@@ -46,7 +46,7 @@ def setUp(self):
self.badge_template = BadgeTemplate.objects.create(
uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site, is_active=True
)
-
+
self.CCX_COURSE_PASSING_EVENT = "org.openedx.learning.ccx.course.passing.status.updated.v1"
def test_discovery_eventtype_related_requirements(self):
@@ -83,7 +83,7 @@ def setUp(self):
self.badge_template = BadgeTemplate.objects.create(
uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site, is_active=True
)
-
+
self.CCX_COURSE_PASSING_EVENT = "org.openedx.learning.ccx.course.passing.status.updated.v1"
def test_discovery_eventtype_related_penalties(self):
@@ -138,7 +138,12 @@ def setUp(self):
)
self.site = Site.objects.create(domain="test_domain", name="test_name")
self.badge_template = CredlyBadgeTemplate.objects.create(
- uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site, is_active=True, organization=self.organization
+ uuid=uuid.uuid4(),
+ name="test_template",
+ state="draft",
+ site=self.site,
+ is_active=True,
+ organization=self.organization,
)
self.CCX_COURSE_PASSING_EVENT = "org.openedx.learning.ccx.course.passing.status.updated.v1"
@@ -224,9 +229,7 @@ def test_process_penalties_one_datarule_fail(self):
self.assertEqual(Fulfillment.objects.filter(progress=progress, requirement=requirement1).count(), 1)
self.assertEqual(Fulfillment.objects.filter(progress=progress, requirement=requirement1).count(), 1)
- BadgePenalty.objects.create(
- template=self.badge_template, event_type=COURSE_PASSING_EVENT
- ).requirements.set(
+ BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT).requirements.set(
(requirement1, requirement2),
)
PenaltyDataRule.objects.create(
@@ -379,7 +382,7 @@ def setUp(self):
organization=self.organization,
is_active=True,
)
-
+
self.CCX_COURSE_PASSING_EVENT = "org.openedx.learning.ccx.course.passing.status.updated.v1"
self.user = identify_user(event_type=COURSE_PASSING_EVENT, event_payload=COURSE_PASSING_DATA)
@@ -530,7 +533,7 @@ def test_course_a_or_b_and_c_completion(self):
operator="eq",
value="A",
)
-
+
process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA)
self.assertEqual(Fulfillment.objects.filter(requirement=requirement_a).count(), 0)
self.assertEqual(Fulfillment.objects.filter(requirement=requirement_b).count(), 0)
@@ -549,7 +552,7 @@ def test_course_a_or_b_and_c_completion(self):
self.assertEqual(Fulfillment.objects.filter(requirement=requirement_a).count(), 0)
self.assertEqual(Fulfillment.objects.filter(requirement=requirement_b).count(), 1)
self.assertEqual(Fulfillment.objects.filter(requirement=requirement_c).count(), 1)
-
+
self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed)
def test_course_a_or_b_and_c_or_d_completion(self):
@@ -627,4 +630,4 @@ def tearDown(self):
class TestIdentifyUser(TestCase):
def test_identify_user(self):
username = identify_user(event_type=COURSE_PASSING_EVENT, event_payload=COURSE_PASSING_DATA)
- self.assertEqual(username, "test_username")
\ No newline at end of file
+ self.assertEqual(username, "test_username")
diff --git a/credentials/apps/badges/tests/test_signals.py b/credentials/apps/badges/tests/test_signals.py
index 3bc040089..8971563f7 100644
--- a/credentials/apps/badges/tests/test_signals.py
+++ b/credentials/apps/badges/tests/test_signals.py
@@ -33,9 +33,7 @@ def test_progression_signal_emission_and_receiver_execution(self):
# UserCredential object
user_credential = CredlyBadge.objects.filter(
username="test_user",
- credential_content_type=ContentType.objects.get_for_model(
- self.badge_template
- ),
+ credential_content_type=ContentType.objects.get_for_model(self.badge_template),
credential_id=self.badge_template.id,
)
@@ -44,7 +42,7 @@ def test_progression_signal_emission_and_receiver_execution(self):
# Check if user credential status is 'awarded'
self.assertTrue(user_credential[0].status == "awarded")
-
+
def test_regression_signal_emission_and_receiver_execution(self):
# Emit the signal
with mock.patch("credentials.apps.badges.issuers.notify_badge_revoked"):
@@ -58,9 +56,7 @@ def test_regression_signal_emission_and_receiver_execution(self):
# UserCredential object
user_credential = CredlyBadge.objects.filter(
username="test_user",
- credential_content_type=ContentType.objects.get_for_model(
- self.badge_template
- ),
+ credential_content_type=ContentType.objects.get_for_model(self.badge_template),
credential_id=self.badge_template.id,
)
diff --git a/credentials/apps/badges/tests/test_utils.py b/credentials/apps/badges/tests/test_utils.py
index b6e352e2d..f793b2373 100644
--- a/credentials/apps/badges/tests/test_utils.py
+++ b/credentials/apps/badges/tests/test_utils.py
@@ -21,6 +21,7 @@
COURSE_PASSING_EVENT = "org.openedx.learning.course.passing.status.updated.v1"
+
class TestKeypath(unittest.TestCase):
def test_keypath_exists(self):
payload = {
@@ -106,9 +107,7 @@ def test_extract_payload(self):
user_data = UserData(
id=1, is_active=True, pii=UserPersonalData(username="user1", email="user1@example.com ", name="John Doe")
)
- course_passing_status = CoursePassingStatusData(
- is_passing=True, course=self.course_data, user=user_data
- )
+ course_passing_status = CoursePassingStatusData(is_passing=True, course=self.course_data, user=user_data)
public_signal_kwargs = {"course_passing_status": course_passing_status}
result = extract_payload(public_signal_kwargs)
self.assertIsNotNone(result)
@@ -175,6 +174,7 @@ def test_get_event_type_keypaths(self):
for ignored_keypath in settings.BADGES_CONFIG.get("rules", {}).get("ignored_keypaths", []):
self.assertNotIn(ignored_keypath, result)
+
class TestGetCredlyBaseUrl(unittest.TestCase):
def test_get_credly_base_url_sandbox(self):
settings.BADGES_CONFIG["credly"] = {
@@ -202,7 +202,7 @@ def test_get_credly_api_base_url_sandbox(self):
"CREDLY_SANDBOX_API_BASE_URL": "https://sandbox.api.credly.com",
"USE_SANDBOX": True,
}
-
+
result = get_credly_api_base_url(settings)
self.assertEqual(result, "https://sandbox.api.credly.com")
@@ -221,7 +221,7 @@ def test_get_event_type_attr_type_by_keypath(self):
keypath = "course.course_key"
result = get_event_type_attr_type_by_keypath(COURSE_PASSING_EVENT, keypath)
self.assertEqual(result, CourseKey)
-
+
def test_get_event_type_attr_type_by_keypath_bool(self):
keypath = "is_passing"
result = get_event_type_attr_type_by_keypath(COURSE_PASSING_EVENT, keypath)
@@ -230,4 +230,4 @@ def test_get_event_type_attr_type_by_keypath_bool(self):
def test_get_event_type_attr_type_by_keypath_not_found(self):
keypath = "course.id"
result = get_event_type_attr_type_by_keypath(COURSE_PASSING_EVENT, keypath)
- self.assertIsNone(result)
\ No newline at end of file
+ self.assertIsNone(result)
diff --git a/credentials/apps/badges/utils.py b/credentials/apps/badges/utils.py
index d327fe579..fc1ad8f6d 100644
--- a/credentials/apps/badges/utils.py
+++ b/credentials/apps/badges/utils.py
@@ -151,10 +151,7 @@ def get_data_keypaths(data):
keypaths = []
for field in attr.fields(data):
if attr.has(field.type):
- keypaths += [
- f"{field.name}.{keypath}"
- for keypath in get_data_keypaths(field.type)
- ]
+ keypaths += [f"{field.name}.{keypath}" for keypath in get_data_keypaths(field.type)]
else:
keypaths.append(field.name)
return keypaths
@@ -165,8 +162,7 @@ def get_data_keypaths(data):
keypaths += [
f"{field.name}.{keypath}"
for keypath in get_data_keypaths(field.type)
- if f"{field.name}.{keypath}"
- not in settings.BADGES_CONFIG.get("rules", {}).get("ignored_keypaths", [])
+ if f"{field.name}.{keypath}" not in settings.BADGES_CONFIG.get("rules", {}).get("ignored_keypaths", [])
]
else:
keypaths.append(field.name)
@@ -201,5 +197,5 @@ def get_attr_type_by_keypath(data_attrs, keypath):
elif attr.has(attr_.type):
return get_attr_type_by_keypath(attr.fields(attr_.type), ".".join(keypath_parts[1:]))
return None
-
+
return get_attr_type_by_keypath(data_attrs, keypath)
diff --git a/credentials/settings/base.py b/credentials/settings/base.py
index adbc99f52..1d29522d3 100644
--- a/credentials/settings/base.py
+++ b/credentials/settings/base.py
@@ -653,7 +653,6 @@
"user.pii.username",
"user.pii.email",
"user.pii.name",
-
"course.display_name",
"course.start",
"course.end",