From b868feaed17f4f80f1e093072dbba9998393c380 Mon Sep 17 00:00:00 2001 From: Kyrylo Kholodenko Date: Mon, 22 Jul 2024 17:44:31 +0300 Subject: [PATCH 1/3] feat: add badge issuance with Credly backend (#2489) * feat: add badges feature * chore: update dummy translations * refactor: squash migrations + text cleanup + resolve conflict * docs: documentation update --------- Co-authored-by: Andrii Co-authored-by: Andrii Hantkovskyi Co-authored-by: KyryloKireiev Co-authored-by: wowkalucky Co-authored-by: GlugovGrGlib --- .annotation_safe_list.yml | 16 + credentials/apps/badges/__init__.py | 0 credentials/apps/badges/admin.py | 551 +++++++++++++ credentials/apps/badges/admin_forms.py | 194 +++++ credentials/apps/badges/api.py | 0 credentials/apps/badges/apps.py | 30 + credentials/apps/badges/checks.py | 41 + credentials/apps/badges/credly/__init__.py | 0 credentials/apps/badges/credly/api_client.py | 184 +++++ credentials/apps/badges/credly/data.py | 25 + credentials/apps/badges/credly/exceptions.py | 17 + credentials/apps/badges/credly/utils.py | 47 ++ credentials/apps/badges/credly/webhooks.py | 127 +++ credentials/apps/badges/exceptions.py | 15 + credentials/apps/badges/issuers.py | 198 +++++ .../apps/badges/management/__init__.py | 0 .../badges/management/commands/__init__.py | 0 .../sync_organization_badge_templates.py | 56 ++ .../apps/badges/migrations/0001_initial.py | 371 +++++++++ .../apps/badges/migrations/__init__.py | 0 credentials/apps/badges/models.py | 677 ++++++++++++++++ .../apps/badges/processing/__init__.py | 0 credentials/apps/badges/processing/generic.py | 73 ++ .../apps/badges/processing/progression.py | 50 ++ .../apps/badges/processing/regression.py | 37 + credentials/apps/badges/signals/__init__.py | 1 + credentials/apps/badges/signals/handlers.py | 88 ++ credentials/apps/badges/signals/signals.py | 93 +++ credentials/apps/badges/tests/__init__.py | 0 .../apps/badges/tests/test_admin_forms.py | 247 ++++++ .../apps/badges/tests/test_api_client.py | 128 +++ credentials/apps/badges/tests/test_issuers.py | 172 ++++ .../badges/tests/test_management_commands.py | 28 + credentials/apps/badges/tests/test_models.py | 736 +++++++++++++++++ .../apps/badges/tests/test_services.py | 758 ++++++++++++++++++ credentials/apps/badges/tests/test_signals.py | 67 ++ credentials/apps/badges/tests/test_utils.py | 242 ++++++ .../apps/badges/tests/test_webhooks.py | 122 +++ credentials/apps/badges/toggles.py | 37 + credentials/apps/badges/urls.py | 12 + credentials/apps/badges/utils.py | 203 +++++ ..._usercredential_credential_content_type.py | 24 + credentials/apps/credentials/models.py | 2 +- .../apps/credentials/tests/test_api.py | 12 +- .../conf/locale/eo/LC_MESSAGES/django.mo | Bin 30501 -> 39916 bytes .../conf/locale/eo/LC_MESSAGES/django.po | 247 ++++++ .../conf/locale/rtl/LC_MESSAGES/django.mo | Bin 20559 -> 26856 bytes .../conf/locale/rtl/LC_MESSAGES/django.po | 202 +++++ credentials/settings/base.py | 52 ++ credentials/settings/production.py | 5 +- credentials/settings/test.py | 3 + credentials/tests/test_utils.py | 159 ++++ credentials/urls.py | 6 + .../badges-admin-credly-templates-list.png | Bin 0 -> 68481 bytes .../badges-admin-credly-templates-sync.png | Bin 0 -> 43279 bytes .../images/badges/badges-admin-data-rules.png | Bin 0 -> 31560 bytes .../badges/badges-admin-penalty-rules.png | Bin 0 -> 35606 bytes .../badges/badges-admin-progress-records.png | Bin 0 -> 55733 bytes .../badges/badges-admin-requirement-rules.png | Bin 0 -> 30089 bytes .../badges/badges-admin-rules-group.png | Bin 0 -> 33578 bytes .../badges/badges-admin-template-details.png | Bin 0 -> 125853 bytes .../badges-admin-template-requirements.png | Bin 0 -> 58748 bytes docs/_static/images/badges/badges-admin.png | Bin 0 -> 30533 bytes docs/badges/configuration.rst | 195 +++++ docs/badges/examples.rst | 257 ++++++ docs/badges/index.rst | 37 + docs/badges/processing.rst | 102 +++ docs/badges/quickstart.rst | 153 ++++ docs/badges/settings.rst | 179 +++++ docs/index.rst | 2 +- requirements/all.txt | 173 ++-- requirements/base.in | 6 +- requirements/base.txt | 80 +- requirements/common_constraints.txt | 14 +- requirements/dev.txt | 143 ++-- requirements/docs.txt | 12 +- requirements/pip.txt | 4 +- requirements/pip_tools.txt | 4 +- requirements/production.txt | 96 ++- requirements/test.txt | 114 ++- requirements/translations.txt | 6 +- 81 files changed, 7678 insertions(+), 254 deletions(-) create mode 100644 credentials/apps/badges/__init__.py create mode 100644 credentials/apps/badges/admin.py create mode 100644 credentials/apps/badges/admin_forms.py create mode 100644 credentials/apps/badges/api.py create mode 100644 credentials/apps/badges/apps.py create mode 100644 credentials/apps/badges/checks.py create mode 100644 credentials/apps/badges/credly/__init__.py create mode 100644 credentials/apps/badges/credly/api_client.py create mode 100644 credentials/apps/badges/credly/data.py create mode 100644 credentials/apps/badges/credly/exceptions.py create mode 100644 credentials/apps/badges/credly/utils.py create mode 100644 credentials/apps/badges/credly/webhooks.py create mode 100644 credentials/apps/badges/exceptions.py create mode 100644 credentials/apps/badges/issuers.py create mode 100644 credentials/apps/badges/management/__init__.py create mode 100644 credentials/apps/badges/management/commands/__init__.py create mode 100644 credentials/apps/badges/management/commands/sync_organization_badge_templates.py create mode 100644 credentials/apps/badges/migrations/0001_initial.py create mode 100644 credentials/apps/badges/migrations/__init__.py create mode 100644 credentials/apps/badges/models.py create mode 100644 credentials/apps/badges/processing/__init__.py create mode 100644 credentials/apps/badges/processing/generic.py create mode 100644 credentials/apps/badges/processing/progression.py create mode 100644 credentials/apps/badges/processing/regression.py create mode 100644 credentials/apps/badges/signals/__init__.py create mode 100644 credentials/apps/badges/signals/handlers.py create mode 100644 credentials/apps/badges/signals/signals.py create mode 100644 credentials/apps/badges/tests/__init__.py create mode 100644 credentials/apps/badges/tests/test_admin_forms.py create mode 100644 credentials/apps/badges/tests/test_api_client.py create mode 100644 credentials/apps/badges/tests/test_issuers.py create mode 100644 credentials/apps/badges/tests/test_management_commands.py create mode 100644 credentials/apps/badges/tests/test_models.py create mode 100644 credentials/apps/badges/tests/test_services.py create mode 100644 credentials/apps/badges/tests/test_signals.py create mode 100644 credentials/apps/badges/tests/test_utils.py create mode 100644 credentials/apps/badges/tests/test_webhooks.py create mode 100644 credentials/apps/badges/toggles.py create mode 100644 credentials/apps/badges/urls.py create mode 100644 credentials/apps/badges/utils.py create mode 100644 credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py create mode 100644 credentials/tests/test_utils.py create mode 100644 docs/_static/images/badges/badges-admin-credly-templates-list.png create mode 100644 docs/_static/images/badges/badges-admin-credly-templates-sync.png create mode 100644 docs/_static/images/badges/badges-admin-data-rules.png create mode 100644 docs/_static/images/badges/badges-admin-penalty-rules.png create mode 100644 docs/_static/images/badges/badges-admin-progress-records.png create mode 100644 docs/_static/images/badges/badges-admin-requirement-rules.png create mode 100644 docs/_static/images/badges/badges-admin-rules-group.png create mode 100644 docs/_static/images/badges/badges-admin-template-details.png create mode 100644 docs/_static/images/badges/badges-admin-template-requirements.png create mode 100644 docs/_static/images/badges/badges-admin.png create mode 100644 docs/badges/configuration.rst create mode 100644 docs/badges/examples.rst create mode 100644 docs/badges/index.rst create mode 100644 docs/badges/processing.rst create mode 100644 docs/badges/quickstart.rst create mode 100644 docs/badges/settings.rst diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml index 5abfd0ef6..c8f4520f9 100644 --- a/.annotation_safe_list.yml +++ b/.annotation_safe_list.yml @@ -13,6 +13,22 @@ auth.Group: ".. no_pii:": "This model has no PII" auth.Permission: ".. no_pii:": "This model has no PII" +badges.BadgePenalty: + ".. no_pii:": "This model has no PII" +badges.BadgeProgress: + ".. pii": "Username" + ".. pii_types": other + ".. pii_retirement": retained +badges.BadgeRequirement: + ".. no_pii:": "This model has no PII" +badges.CredlyOrganization: + ".. no_pii:": "This model has no PII" +badges.DataRule: + ".. no_pii:": "This model has no PII" +badges.Fulfillment: + ".. no_pii:": "This model has no PII" +badges.PenaltyDataRule: + ".. no_pii:": "This model has no PII" credentials.HistoricalProgramCompletionEmailConfiguration: ".. no_pii:": "This model has no PII" contenttypes.ContentType: diff --git a/credentials/apps/badges/__init__.py b/credentials/apps/badges/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/admin.py b/credentials/apps/badges/admin.py new file mode 100644 index 000000000..bdf43756b --- /dev/null +++ b/credentials/apps/badges/admin.py @@ -0,0 +1,551 @@ +""" +Admin section configuration. +""" + +from django.contrib import admin, messages +from django.contrib.sites.shortcuts import get_current_site +from django.core.management import call_command +from django.http import HttpResponseRedirect +from django.urls import reverse +from django.utils.html import format_html +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + +from credentials.apps.badges.admin_forms import ( + BadgePenaltyForm, + BadgeRequirementForm, + BadgeRequirementFormSet, + CredlyOrganizationAdminForm, + DataRuleForm, + DataRuleFormSet, + PenaltyDataRuleForm, + PenaltyDataRuleFormSet, +) +from credentials.apps.badges.models import ( + BadgePenalty, + BadgeProgress, + BadgeRequirement, + CredlyBadge, + CredlyBadgeTemplate, + CredlyOrganization, + DataRule, + Fulfillment, + PenaltyDataRule, +) +from credentials.apps.badges.toggles import is_badges_enabled + + +class BadgeRequirementInline(admin.TabularInline): + """ + Badge template requirement inline setup. + """ + + model = BadgeRequirement + show_change_link = True + extra = 0 + fields = ( + "event_type", + "rules", + "description", + "blend", + ) + readonly_fields = ("rules",) + ordering = ("blend",) + form = BadgeRequirementForm + formset = BadgeRequirementFormSet + + 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.") + ) + + +class BadgePenaltyInline(admin.TabularInline): + """ + Badge template penalty inline setup. + """ + + model = BadgePenalty + show_change_link = True + extra = 0 + fields = ( + "event_type", + "rules", + "requirements", + ) + readonly_fields = ("rules",) + form = BadgePenaltyForm + + def formfield_for_manytomany(self, db_field, request, **kwargs): + """ + Filter requirements by parent badge template. + """ + if db_field.name == "requirements": + template_id = request.resolver_match.kwargs.get("object_id") + 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.") + ) + + +class FulfillmentInline(admin.TabularInline): + """ + Badge template fulfillment inline setup. + """ + + model = Fulfillment + extra = 0 + readonly_fields = [ + "requirement", + ] + + +class DataRuleInline(admin.TabularInline): + """ + Data rule inline setup. + """ + + model = DataRule + extra = 0 + form = DataRuleForm + formset = DataRuleFormSet + + +class CredlyOrganizationAdmin(admin.ModelAdmin): + """ + Credly organization admin setup. + """ + + form = CredlyOrganizationAdminForm + list_display = ( + "name", + "uuid", + "api_key_hidden", + ) + fields = [ + "name", + "uuid", + "api_key_hidden", + ] + readonly_fields = [ + "name", + ] + actions = ("sync_organization_badge_templates",) + + @admin.action(description="Sync organization badge templates") + def sync_organization_badge_templates(self, request, queryset): + """ + Sync badge templates for selected organizations. + """ + site = get_current_site(request) + for organization in queryset: + call_command( + "sync_organization_badge_templates", + organization_id=organization.uuid, + site_id=site.id, + ) + + messages.success(request, _("Badge templates were successfully updated.")) + + @admin.display(description=_("API key")) + def api_key_hidden(self, obj): + """ + Hide API key and display text. + """ + + 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 = list(super().get_readonly_fields(request, obj)) + + if not obj: + return readonly_fields + + if obj.is_preconfigured: + readonly_fields.append("api_key_hidden") + return readonly_fields + + +class CredlyBadgeTemplateAdmin(admin.ModelAdmin): + """ + Badge template admin setup. + """ + + exclude = [ + "icon", + ] + list_display = ( + "organization", + "state", + "name", + "uuid", + "is_active", + "image", + ) + list_filter = ( + "organization", + "is_active", + "state", + ) + search_fields = ( + "name", + "uuid", + ) + readonly_fields = [ + "organization", + "origin", + "state", + "dashboard_link", + "image", + ] + fieldsets = ( + ( + "Generic", + { + "fields": ( + "site", + "is_active", + ), + "description": _( + """ + WARNING: avoid configuration updates on activated badges. + Active badge templates are continuously processed and learners may already have progress on them. + Any changes in badge template requirements (including data rules) will affect learners' experience! + """ + ), + }, + ), + ( + "Badge template", + { + "fields": ( + "uuid", + "name", + "description", + "image", + "origin", + ) + }, + ), + ( + "Credly", + { + "fields": ( + "organization", + "state", + "dashboard_link", + ), + }, + ), + ) + inlines = [ + BadgeRequirementInline, + BadgePenaltyInline, + ] + + def has_add_permission(self, request): + return False + + def dashboard_link(self, obj): + url = obj.management_url + return format_html("{url}", url=url) + + def delete_model(self, request, obj): + """ + Prevent deletion of active badge templates. + """ + if obj.is_active: + messages.set_level(request, messages.ERROR) + messages.error(request, _("Active badge template cannot be deleted.")) + return + super().delete_model(request, obj) + + def delete_queryset(self, request, queryset): + """ + Prevent deletion of active badge templates. + """ + if queryset.filter(is_active=True).exists(): + messages.set_level(request, messages.ERROR) + messages.error(request, _("Active badge templates cannot be deleted.")) + return + super().delete_queryset(request, queryset) + + def image(self, obj): + """ + Badge template preview image. + """ + if obj.icon: + return format_html('', obj.icon) + return None + + image.short_description = _("icon") + + def save_model(self, request, obj, form, change): + pass + + def save_formset(self, request, form, formset, change): + """ + Check if template is active and has requirements. + """ + formset.save() + + if form.instance.is_active and not form.instance.requirements.exists(): + messages.set_level(request, messages.ERROR) + messages.error(request, _("Active badge template must have at least one requirement.")) + return HttpResponseRedirect(request.path) + return form.instance.save() + + +class DataRulePenaltyInline(admin.TabularInline): + model = PenaltyDataRule + extra = 0 + form = PenaltyDataRuleForm + formset = PenaltyDataRuleFormSet + + +class BadgeRequirementAdmin(admin.ModelAdmin): + """ + Badge template requirement admin setup. + """ + + inlines = [ + DataRuleInline, + ] + + list_display = [ + "id", + "__str__", + "event_type", + "template_link", + ] + list_display_links = ( + "id", + "__str__", + ) + list_filter = [ + "template", + "event_type", + ] + readonly_fields = [ + "template", + "event_type", + "template_link", + "blend", + ] + + fields = [ + "template_link", + "event_type", + "description", + "blend", + ] + + def has_add_permission(self, request): + return False + + def template_link(self, instance): + """ + Interactive link to parent (badge template). + """ + url = reverse("admin:badges_credlybadgetemplate_change", args=[instance.template.pk]) + return format_html('{}', url, instance.template) + + template_link.short_description = _("badge template") + + def response_change(self, request, obj): + if "_save" in request.POST: + return HttpResponseRedirect(reverse("admin:badges_credlybadgetemplate_change", args=[obj.template.pk])) + return super().response_change(request, obj) + + +class BadgePenaltyAdmin(admin.ModelAdmin): + """ + Badge requirement penalty setup admin. + """ + + inlines = [ + DataRulePenaltyInline, + ] + + list_display_links = ( + "id", + "template", + ) + list_display = [ + "id", + "__str__", + "event_type", + "template_link", + ] + list_display_links = ( + "id", + "__str__", + ) + list_filter = [ + "template", + "requirements", + ] + fields = [ + "template_link", + "event_type", + "requirements", + ] + readonly_fields = [ + "template_link", + "event_type", + "requirements", + ] + form = BadgePenaltyForm + + def has_add_permission(self, request): + return False + + def template_link(self, instance): + """ + Interactive link to parent (badge template). + """ + url = reverse("admin:badges_credlybadgetemplate_change", args=[instance.template.pk]) + return format_html('{}', url, instance.template) + + template_link.short_description = _("badge template") + + def formfield_for_manytomany(self, db_field, request, **kwargs): + if db_field.name == "requirements": + object_id = request.resolver_match.kwargs.get("object_id") + template_id = self.get_object(request, object_id).template_id + if template_id: + kwargs["queryset"] = BadgeRequirement.objects.filter(template_id=template_id) + return super().formfield_for_manytomany(db_field, request, **kwargs) + + def response_change(self, request, obj): + if "_save" in request.POST: + return HttpResponseRedirect(reverse("admin:badges_credlybadgetemplate_change", args=[obj.template.pk])) + return super().response_change(request, obj) + + +class BadgeProgressAdmin(admin.ModelAdmin): + """ + Badge template progress admin setup. + """ + + inlines = [ + FulfillmentInline, + ] + list_display = [ + "id", + "username", + "template", + "complete", + ] + list_display_links = ( + "id", + "username", + "template", + ) + readonly_fields = ( + "username", + "template", + "complete", + "ratio", + ) + + @admin.display(boolean=True) + def complete(self, obj): + """ + Identifies if all requirements are already fulfilled. + + NOTE: (performance) dynamic evaluation. + """ + return obj.completed + + def ratio(self, obj): + """ + Displays progress value. + """ + return obj.ratio + + def has_add_permission(self, request): + return False + + +class CredlyBadgeAdmin(admin.ModelAdmin): + """ + Credly badge admin setup. + """ + + list_display = ( + "uuid", + "username", + "credential", + "status", + "state", + "external_uuid", + ) + list_filter = ( + "status", + "state", + ) + search_fields = ( + "username", + "external_uuid", + ) + readonly_fields = ( + "credential_id", + "credential_content_type", + "username", + "download_url", + "state", + "uuid", + "external_uuid", + ) + + def has_add_permission(self, request): + return False + + +# register admin configurations with respect to the feature flag +if is_badges_enabled(): + admin.site.register(CredlyOrganization, CredlyOrganizationAdmin) + admin.site.register(CredlyBadgeTemplate, CredlyBadgeTemplateAdmin) + admin.site.register(CredlyBadge, CredlyBadgeAdmin) + admin.site.register(BadgeRequirement, BadgeRequirementAdmin) + admin.site.register(BadgePenalty, BadgePenaltyAdmin) + admin.site.register(BadgeProgress, BadgeProgressAdmin) diff --git a/credentials/apps/badges/admin_forms.py b/credentials/apps/badges/admin_forms.py new file mode 100644 index 000000000..89b70be1d --- /dev/null +++ b/credentials/apps/badges/admin_forms.py @@ -0,0 +1,194 @@ +""" +Badges admin forms. +""" + +from django import forms +from django.conf import settings +from django.utils.translation import gettext_lazy as _ +from model_utils import Choices + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.exceptions import CredlyAPIError +from credentials.apps.badges.models import ( + AbstractDataRule, + BadgePenalty, + BadgeRequirement, + CredlyOrganization, + DataRule, + PenaltyDataRule, +) +from credentials.apps.badges.utils import get_event_type_attr_type_by_keypath, get_event_type_keypaths + + +class CredlyOrganizationAdminForm(forms.ModelForm): + """ + Additional actions for Credly Organization items. + """ + + api_data = {} + + class Meta: + model = CredlyOrganization + fields = "__all__" + + def clean(self): + """ + Perform Credly API check for given organization ID. + + - Credly Organization exists; + - fetch additional data for such organization; + """ + cleaned_data = super().clean() + + uuid = cleaned_data.get("uuid") + api_key = cleaned_data.get("api_key") + + 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) + self.ensure_organization_exists(credly_api_client) + + return cleaned_data + + def save(self, commit=True): + """ + Auto-fill addition properties. + """ + instance = super().save(commit=False) + instance.name = self.api_data.get("name") + instance.save() + + return instance + + def ensure_organization_exists(self, api_client): + """ + Try to fetch organization data by the configured Credly Organization ID. + """ + try: + response_json = api_client.fetch_organization() + if org_data := response_json.get("data"): + self.api_data = org_data + except CredlyAPIError as err: + raise forms.ValidationError(message=str(err)) + + +class BadgePenaltyForm(forms.ModelForm): + """ + Form for BadgePenalty model. + """ + + class Meta: + model = BadgePenalty + fields = "__all__" + + def clean(self): + """ + Ensure that all penalties belong to the same template. + """ + cleaned_data = super().clean() + requirements = cleaned_data.get("requirements") + + if requirements and not all( + requirement.template.id == cleaned_data.get("template").id for requirement in requirements + ): + raise forms.ValidationError(_("All requirements must belong to the same template.")) + return cleaned_data + + +class ParentMixin: + def get_form_kwargs(self, index): + """ + Pass parent instance to the form. + """ + + kwargs = super().get_form_kwargs(index) + kwargs["parent_instance"] = self.instance + return kwargs + + +class DataRuleExtensionsMixin: + """ + Mixin for DataRule form to extend logic. + """ + + def __init__(self, *args, parent_instance=None, **kwargs): + """ + Load data paths based on the parent instance event type. + """ + self.parent_instance = parent_instance + super().__init__(*args, **kwargs) + + if self.parent_instance: + event_type = self.parent_instance.event_type + self.fields["data_path"].choices = Choices(*get_event_type_keypaths(event_type=event_type)) + + def clean(self): + """ + Validate boolean fields. + """ + + cleaned_data = super().clean() + + data_path_type = get_event_type_attr_type_by_keypath( + self.parent_instance.event_type, cleaned_data.get("data_path") + ) + + 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): + pass + + +class DataRuleForm(DataRuleExtensionsMixin, forms.ModelForm): + """ + Form for DataRule model. + """ + + class Meta: + model = DataRule + fields = "__all__" + + data_path = forms.ChoiceField() + + +class BadgeRequirementFormSet(ParentMixin, forms.BaseInlineFormSet): + pass + + +class BadgeRequirementForm(forms.ModelForm): + class Meta: + model = BadgeRequirement + fields = "__all__" + + blend = forms.ChoiceField() + + def __init__(self, *args, parent_instance=None, **kwargs): + self.template = parent_instance + super().__init__(*args, **kwargs) + + self.fields["blend"].choices = Choices(*[(chr(i), chr(i)) for i in range(65, 91)]) + self.fields["blend"].initial = chr(65 + self.template.requirements.count()) + + +class PenaltyDataRuleFormSet(ParentMixin, forms.BaseInlineFormSet): + pass + + +class PenaltyDataRuleForm(DataRuleExtensionsMixin, forms.ModelForm): + """ + Form for PenaltyDataRule model. + """ + + data_path = forms.ChoiceField() + + class Meta: + model = PenaltyDataRule + fields = "__all__" diff --git a/credentials/apps/badges/api.py b/credentials/apps/badges/api.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/apps.py b/credentials/apps/badges/apps.py new file mode 100644 index 000000000..783fd7d1a --- /dev/null +++ b/credentials/apps/badges/apps.py @@ -0,0 +1,30 @@ +from django.apps import AppConfig + +from credentials.apps.badges.toggles import check_badges_enabled + + +class BadgesConfig(AppConfig): + """ + Core badges application configuration. + """ + + name = "credentials.apps.badges" + verbose_name = "Badges" + + @check_badges_enabled + def ready(self): + """ + Performs initial registrations for checks, signals, etc. + """ + + from credentials.apps.badges import signals # pylint: disable=unused-import,import-outside-toplevel + from credentials.apps.badges.checks import ( # pylint: disable=unused-import,import-outside-toplevel + badges_checks, + ) + from credentials.apps.badges.signals.handlers import ( # pylint: disable=import-outside-toplevel + listen_to_badging_events, + ) + + listen_to_badging_events() + + super().ready() diff --git a/credentials/apps/badges/checks.py b/credentials/apps/badges/checks.py new file mode 100644 index 000000000..1cc7bdfc2 --- /dev/null +++ b/credentials/apps/badges/checks.py @@ -0,0 +1,41 @@ +""" +Badges app self-checks. +""" + +from django.core.checks import Error, Tags, register + +from .utils import credly_check, get_badging_event_types + + +@register(Tags.compatibility) +def badges_checks(*args, **kwargs): + """ + Checks the consistency of the badges configurations. + + Raises compatibility Errors upon: + - BADGES_CONFIG['events'] is empty + - Credly settings are not properly configured + + Returns: + List of any Errors. + """ + errors = [] + + if not get_badging_event_types(): + errors.append( + Error( + "BADGES_CONFIG['events'] must include at least one event.", + hint="Add at least one event to BADGES_CONFIG['events'] setting.", + id="badges.E001", + ) + ) + if not credly_check(): + errors.append( + Error( + "Credly settings are not properly configured.", + hint="Make sure all required settings are present in BADGES_CONFIG['credly'].", + id="badges.E002", + ) + ) + + return errors diff --git a/credentials/apps/badges/credly/__init__.py b/credentials/apps/badges/credly/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/credly/api_client.py b/credentials/apps/badges/credly/api_client.py new file mode 100644 index 000000000..793f4c85e --- /dev/null +++ b/credentials/apps/badges/credly/api_client.py @@ -0,0 +1,184 @@ +import base64 +import logging +from functools import lru_cache +from urllib.parse import urljoin + +import requests +from attrs import asdict +from django.conf import settings +from django.contrib.sites.models import Site +from requests.exceptions import HTTPError + +from credentials.apps.badges.credly.exceptions import CredlyAPIError, CredlyError +from credentials.apps.badges.credly.utils import get_credly_api_base_url +from credentials.apps.badges.models import CredlyBadgeTemplate, CredlyOrganization + + +logger = logging.getLogger(__name__) + + +class CredlyAPIClient: + """ + A client for interacting with the Credly API. + + This class provides methods for performing various operations on the Credly API, + such as fetching organization details, fetching badge templates, issuing badges, + and revoking badges. + """ + + def __init__(self, organization_id, api_key=None): + """ + Initializes a CredlyRestAPI object. + + Args: + organization_id (str, uuid): ID of the organization. + api_key (str): optional ID of the organization. + """ + if api_key is None: + self.organization = self._get_organization(organization_id) + api_key = self.organization.api_key + + self.api_key = api_key + self.organization_id = organization_id + + self.base_api_url = urljoin(get_credly_api_base_url(settings), f"organizations/{self.organization_id}/") + + def _get_organization(self, organization_id): + """ + Check if Credly Organization with provided ID exists. + """ + try: + organization = CredlyOrganization.objects.get(uuid=organization_id) + return organization + except CredlyOrganization.DoesNotExist: + raise CredlyError(f"CredlyOrganization with the uuid {organization_id} does not exist!") + + def perform_request(self, method, url_suffix, data=None): + """ + Perform an HTTP request to the specified URL suffix. + + Args: + method (str): HTTP method to use for the request. + url_suffix (str): URL suffix to append to the base Credly API URL. + data (dict, optional): Data to send with the request. + + Returns: + dict: JSON response from the API. + + Raises: + requests.HTTPError: If the API returns an error response. + """ + url = urljoin(self.base_api_url, url_suffix) + logger.debug(f"Credly API: {method.upper()} {url}") + response = requests.request(method.upper(), url, headers=self._get_headers(), json=data, timeout=10) + self._raise_for_error(response) + return response.json() + + def _raise_for_error(self, response): + """ + Raises a CredlyAPIError if the response status code indicates an error. + + Args: + response (requests.Response): Response object from the Credly API request. + + Raises: + CredlyAPIError: If the response status code indicates an error. + """ + try: + response.raise_for_status() + except HTTPError: + logger.error(f"Error while processing Credly API request: {response.status_code} - {response.text}") + raise CredlyAPIError(f"Credly API:{response.text}({response.status_code})") + + def _get_headers(self): + """ + Returns the headers for making API requests to Credly. + """ + return { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": f"Basic {self._build_authorization_token()}", + } + + @lru_cache + def _build_authorization_token(self): + """ + Build the authorization token for the Credly API. + + Returns: + str: Authorization token. + """ + return base64.b64encode(self.api_key.encode("ascii")).decode("ascii") + + def fetch_organization(self): + """ + Fetches Credly Organization data. + """ + return self.perform_request("get", "") + + def fetch_badge_templates(self): + """ + Fetches the badge templates from the Credly API. + """ + return self.perform_request("get", f"badge_templates/?filter=state::{CredlyBadgeTemplate.STATES.active}") + + def fetch_event_information(self, event_id): + """ + Fetches the event information from the Credly API. + + Args: + event_id (str): ID of the event. + """ + return self.perform_request("get", f"events/{event_id}/") + + def issue_badge(self, issue_badge_data): + """ + Issues a badge using the Credly REST API. + + Args: + issue_badge_data (IssueBadgeData): Data required to issue the badge. + """ + return self.perform_request("post", "badges/", asdict(issue_badge_data)) + + def revoke_badge(self, badge_id, data): + """ + Revoke a badge with the given badge ID. + + Args: + badge_id (str): ID of the badge to revoke. + """ + return self.perform_request("put", f"badges/{badge_id}/revoke/", data=data) + + def sync_organization_badge_templates(self, site_id): + """ + Pull active badge templates for a given Credly Organization. + + Args: + site_id (int): ID of the site. + + Returns: + int | None: processed items. + """ + try: + site = Site.objects.get(id=site_id) + except Site.DoesNotExist: + logger.error(f"Site with the id {site_id} does not exist!") + raise + + badge_templates_data = self.fetch_badge_templates() + raw_badge_templates = badge_templates_data.get("data", []) + + for raw_badge_template in raw_badge_templates: + CredlyBadgeTemplate.objects.update_or_create( + uuid=raw_badge_template.get("id"), + organization=self.organization, + defaults={ + "site": site, + "name": raw_badge_template.get("name"), + "state": raw_badge_template.get("state"), + "description": raw_badge_template.get("description"), + "icon": raw_badge_template.get("image_url"), + }, + ) + + return len(raw_badge_templates) diff --git a/credentials/apps/badges/credly/data.py b/credentials/apps/badges/credly/data.py new file mode 100644 index 000000000..80d6ec3c6 --- /dev/null +++ b/credentials/apps/badges/credly/data.py @@ -0,0 +1,25 @@ +from datetime import datetime + +import attr + + +@attr.s(auto_attribs=True, frozen=True) +class CredlyBadgeData: + """ + Represents the data required to issue a badge. + + Attributes: + recipient_email (str): Email address of the badge recipient. + issued_to_first_name (str): First name of the badge recipient. + issued_to_last_name (str): Last name of the badge recipient. + badge_template_id (str): ID of the badge template. + issued_at (datetime): Timestamp when the badge was issued. + + Reference: https://credly.com/docs/issued_badges + """ + + recipient_email: str + issued_to_first_name: str + issued_to_last_name: str + badge_template_id: str + issued_at: datetime diff --git a/credentials/apps/badges/credly/exceptions.py b/credentials/apps/badges/credly/exceptions.py new file mode 100644 index 000000000..7fbb2a437 --- /dev/null +++ b/credentials/apps/badges/credly/exceptions.py @@ -0,0 +1,17 @@ +""" +Specific for Credly exceptions. +""" + +from credentials.apps.badges.exceptions import BadgesError + + +class CredlyError(BadgesError): + """ + Credly backend generic error. + """ + + +class CredlyAPIError(CredlyError): + """ + Credly API errors. + """ diff --git a/credentials/apps/badges/credly/utils.py b/credentials/apps/badges/credly/utils.py new file mode 100644 index 000000000..618136f41 --- /dev/null +++ b/credentials/apps/badges/credly/utils.py @@ -0,0 +1,47 @@ +""" +Credly specific utilities. +""" + + +def get_credly_api_base_url(settings): + """ + Determines the base URL for the Credly API based on application settings. + + Parameters: + - settings: A configuration object containing the application's settings, + including those specific to Credly API integration. + + Returns: + - str: The base URL for the Credly API. This will be the URL for the sandbox + environment if `USE_SANDBOX` is set to a truthy value in the configuration; + otherwise, it will be the production environment's URL. + """ + + credly_config = settings.BADGES_CONFIG["credly"] + + if credly_config.get("USE_SANDBOX"): + return credly_config["CREDLY_SANDBOX_API_BASE_URL"] + + return credly_config["CREDLY_API_BASE_URL"] + + +def get_credly_base_url(settings): + """ + Determines the base URL for the Credly service based on application settings. + + Parameters: + - settings: A configuration object containing the application's settings. + + Returns: + - str: The base URL for the Credly service (web site). + This will be the URL for the sandbox environment if `USE_SANDBOX` is + set to a truthy value in the configuration; + otherwise, it will be the production environment's URL. + """ + + credly_config = settings.BADGES_CONFIG["credly"] + + if credly_config.get("USE_SANDBOX"): + return credly_config["CREDLY_SANDBOX_BASE_URL"] + + return credly_config["CREDLY_BASE_URL"] diff --git a/credentials/apps/badges/credly/webhooks.py b/credentials/apps/badges/credly/webhooks.py new file mode 100644 index 000000000..b103ffe68 --- /dev/null +++ b/credentials/apps/badges/credly/webhooks.py @@ -0,0 +1,127 @@ +import logging + +from django.contrib.sites.shortcuts import get_current_site +from django.shortcuts import get_object_or_404 +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from ..models import CredlyBadgeTemplate, CredlyOrganization +from .api_client import CredlyAPIClient + + +logger = logging.getLogger(__name__) + + +class CredlyWebhook(APIView): + """ + Public API (webhook endpoint) to handle incoming Credly updates. + + Usage: + POST /credly-badges/api/webhook/ + """ + + authentication_classes = [] + permission_classes = [] + + def post(self, request): + """ + Handle incoming update events from the Credly service. + + https://sandbox.credly.com/docs/webhooks#requirements + + Handled events: + - badge_template.created + - badge_template.changed + - badge_template.deleted + + - tries to recognize Credly Organization context; + - validates event type and its payload; + - performs corresponding item (badge template) updates; + + Returned statuses: + - 204 + - 404 + """ + credly_api_client = CredlyAPIClient(request.data.get("organization_id")) + + event_info_response = credly_api_client.fetch_event_information(request.data.get("id")) + event_type = request.data.get("event_type") + + if event_type == "badge_template.created": + self.handle_badge_template_created_event(request, event_info_response) + elif event_type == "badge_template.changed": + self.handle_badge_template_changed_event(request, event_info_response) + elif event_type == "badge_template.deleted": + self.handle_badge_template_deleted_event(request, event_info_response) + else: + logger.error(f"Unknown event type: {event_type}") + + return Response(status=status.HTTP_204_NO_CONTENT) + + @staticmethod + def _get_badge_template_from_data(data): + badge_template = data.get("data", {}).get("badge_template", {}) + return badge_template + + @staticmethod + def handle_badge_template_created_event(request, data): + """ + Create a new badge template. + """ + + badge_template = CredlyWebhook._get_badge_template_from_data(data) + owner = badge_template.get("owner", {}) + + organization = get_object_or_404(CredlyOrganization, uuid=owner.get("id")) + + CredlyBadgeTemplate.objects.update_or_create( + uuid=badge_template.get("id"), + organization=organization, + defaults={ + "site": get_current_site(request), + "name": badge_template.get("name"), + "state": badge_template.get("state"), + "description": badge_template.get("description"), + "icon": badge_template.get("image_url"), + }, + ) + + @staticmethod + def handle_badge_template_changed_event(request, data): + """ + Change the badge template. + """ + + badge_template = CredlyWebhook._get_badge_template_from_data(data) + owner = badge_template.get("owner", {}) + + organization = get_object_or_404(CredlyOrganization, uuid=owner.get("id")) + + CredlyBadgeTemplate.objects.update_or_create( + uuid=badge_template.get("id"), + organization=organization, + defaults={ + "site": get_current_site(request), + "name": badge_template.get("name"), + "state": badge_template.get("state"), + "description": badge_template.get("description"), + "icon": badge_template.get("image_url"), + }, + ) + + if badge_template.get("state") != CredlyBadgeTemplate.STATES.active: + CredlyBadgeTemplate.objects.filter( + uuid=badge_template.get("id"), + organization=organization, + ).update(is_active=False) + + @staticmethod + def handle_badge_template_deleted_event(request, data): + """ + Deletes the badge template by provided uuid. + """ + CredlyBadgeTemplate.objects.filter( + uuid=CredlyWebhook._get_badge_template_from_data(data).get("id"), + site=get_current_site(request), + ).delete() diff --git a/credentials/apps/badges/exceptions.py b/credentials/apps/badges/exceptions.py new file mode 100644 index 000000000..e2719006f --- /dev/null +++ b/credentials/apps/badges/exceptions.py @@ -0,0 +1,15 @@ +""" +Badges exceptions. +""" + + +class BadgesError(Exception): + """ + Badges generic exception. + """ + + +class BadgesProcessingError(BadgesError): + """ + Exception raised for errors that occur during badge processing. + """ diff --git a/credentials/apps/badges/issuers.py b/credentials/apps/badges/issuers.py new file mode 100644 index 000000000..faaa63331 --- /dev/null +++ b/credentials/apps/badges/issuers.py @@ -0,0 +1,198 @@ +""" +This module provides classes for issuing badge credentials to users. +""" + +from django.contrib.contenttypes.models import ContentType +from django.db import transaction +from django.utils.translation import gettext as _ + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.data import CredlyBadgeData +from credentials.apps.badges.credly.exceptions import CredlyAPIError +from credentials.apps.badges.models import BadgeTemplate, CredlyBadge, CredlyBadgeTemplate, UserCredential +from credentials.apps.badges.signals.signals import notify_badge_awarded, notify_badge_revoked +from credentials.apps.core.api import get_user_by_username +from credentials.apps.credentials.constants import UserCredentialStatus +from credentials.apps.credentials.issuers import AbstractCredentialIssuer + + +class BadgeTemplateIssuer(AbstractCredentialIssuer): + """ + Issues BadgeTemplate credentials to users. + """ + + issued_credential_type = BadgeTemplate + issued_user_credential_type = UserCredential + + def get_credential(self, credential_id): + """ + Get credential by id. + """ + + return self.issued_credential_type.objects.get(id=credential_id) + + @transaction.atomic + def issue_credential( + self, + credential, + username, + status=UserCredentialStatus.AWARDED, + attributes=None, + date_override=None, + request=None, + lms_user_id=None, + ): + """ + Issue a credential to the user. + + This action is idempotent. If the user has already earned the credential, a new one WILL NOT be issued. The + existing credential WILL be modified. + + Arguments: + credential (AbstractCredential): Type of credential to issue. + username (str): username of user for which credential required + status (str): status of credential + attributes (List[dict]): optional list of attributes that should be associated with the issued credential. + request (HttpRequest): request object to build program record absolute uris + + Returns: + UserCredential + """ + + user_credential, __ = self.issued_user_credential_type.objects.update_or_create( + username=username, + credential_content_type=ContentType.objects.get_for_model(credential), + credential_id=credential.id, + defaults={ + "status": status, + }, + ) + + self.set_credential_attributes(user_credential, attributes) + self.set_credential_date_override(user_credential, date_override) + + return user_credential + + def award(self, *, username, credential_id): + """ + Awards a badge. + + Creates user credential record for the given badge template, for a given user. + Notifies about the awarded badge (public signal). + + Returns: UserCredential + """ + + credential = self.get_credential(credential_id) + user_credential = self.issue_credential(credential, username) + + notify_badge_awarded(user_credential) + return user_credential + + def revoke(self, credential_id, username): + """ + Revokes a badge. + + Changes user credential status to REVOKED, for a given user. + Notifies about the revoked badge (public signal). + + Returns: UserCredential + """ + + credential = self.get_credential(credential_id) + user_credential = self.issue_credential(credential, username, status=UserCredentialStatus.REVOKED) + + notify_badge_revoked(user_credential) + return user_credential + + +class CredlyBadgeTemplateIssuer(BadgeTemplateIssuer): + """ + Issues CredlyBadgeTemplate credentials to users. + """ + + issued_credential_type = CredlyBadgeTemplate + issued_user_credential_type = CredlyBadge + + def issue_credly_badge(self, *, user_credential): + """ + Requests Credly service for external badge issuing based on internal user credential (CredlyBadge). + """ + + user = get_user_by_username(user_credential.username) + badge_template = user_credential.credential + + credly_badge_data = CredlyBadgeData( + recipient_email=user.email, + issued_to_first_name=(user.first_name or user.username), + issued_to_last_name=(user.last_name or user.username), + badge_template_id=str(badge_template.uuid), + issued_at=badge_template.created.strftime("%Y-%m-%d %H:%M:%S %z"), + ) + + try: + credly_api = CredlyAPIClient(badge_template.organization.uuid) + response = credly_api.issue_badge(credly_badge_data) + except CredlyAPIError: + user_credential.state = "error" + user_credential.save() + raise + + user_credential.external_uuid = response.get("data").get("id") + user_credential.state = response.get("data").get("state") + user_credential.save() + + def revoke_credly_badge(self, credential_id, user_credential): + """ + Requests Credly service for external badge revoking based on internal user credential (CredlyBadge). + """ + + credential = self.get_credential(credential_id) + credly_api = CredlyAPIClient(credential.organization.uuid) + revoke_data = { + "reason": _("Open edX internal user credential was revoked"), + } + try: + response = credly_api.revoke_badge(user_credential.external_uuid, revoke_data) + except CredlyAPIError: + user_credential.state = "error" + user_credential.save() + raise + + user_credential.state = response.get("data").get("state") + user_credential.save() + + def award(self, *, username, credential_id): + """ + Awards a Credly badge. + + - Creates user credential record for the given badge template, for a given user; + - Notifies about the awarded badge (public signal); + - Issues external Credly badge (Credly API); + + Returns: (CredlyBadge) user credential + """ + + credly_badge = super().award(username=username, credential_id=credential_id) + + # do not issue new badges if the badge was issued already + if not credly_badge.propagated: + self.issue_credly_badge(user_credential=credly_badge) + + return credly_badge + + def revoke(self, credential_id, username): + """ + Revokes a Credly badge. + + - Changes user credential status to REVOKED, for a given user; + - Notifies about the revoked badge (public signal); + - Revokes external Credly badge (Credly API); + + Returns: (CredlyBadge) user credential + """ + + user_credential = super().revoke(credential_id, username) + if user_credential.propagated: + self.revoke_credly_badge(credential_id, user_credential) + return user_credential diff --git a/credentials/apps/badges/management/__init__.py b/credentials/apps/badges/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/management/commands/__init__.py b/credentials/apps/badges/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/management/commands/sync_organization_badge_templates.py b/credentials/apps/badges/management/commands/sync_organization_badge_templates.py new file mode 100644 index 000000000..b55e968ab --- /dev/null +++ b/credentials/apps/badges/management/commands/sync_organization_badge_templates.py @@ -0,0 +1,56 @@ +import logging + +from django.core.management.base import BaseCommand + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.models import CredlyOrganization + + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Sync badge templates for a specific organization or all organizations" + + def add_arguments(self, parser): + parser.add_argument("--site_id", type=int, help="Site ID.") + parser.add_argument("--organization_id", type=str, help="UUID of the organization.") + + def handle(self, *args, **options): + """ + Sync badge templates for a specific organization or all organizations. + + Usage: + site_id=1 + org_id=c117c179-81b1-4f7e-a3a1-e6ae30568c13 + + ./manage.py sync_organization_badge_templates --site_id $site_id + ./manage.py sync_organization_badge_templates --site_id $site_id --organization_id $org_id + """ + DEFAULT_SITE_ID = 1 + organizations_to_sync = [] + + site_id = options.get("site_id") + organization_id = options.get("organization_id") + + if site_id is None: + logger.warning(f"Side ID wasn't provided: assuming site_id = {DEFAULT_SITE_ID}") + site_id = DEFAULT_SITE_ID + + if organization_id: + organizations_to_sync.append(organization_id) + logger.info(f"Syncing badge templates for the single organization: {organization_id}") + else: + organizations_to_sync = CredlyOrganization.get_all_organization_ids() + logger.info( + "Organization ID wasn't provided: syncing badge templates for all organizations - " + f"{organizations_to_sync}", + ) + + for organization_id in organizations_to_sync: + credly_api = CredlyAPIClient(organization_id) + processed_items = credly_api.sync_organization_badge_templates(site_id) + + logger.info(f"Organization {organization_id}: got {processed_items} badge templates.") + + logger.info("...completed!") diff --git a/credentials/apps/badges/migrations/0001_initial.py b/credentials/apps/badges/migrations/0001_initial.py new file mode 100644 index 000000000..8fd88cb41 --- /dev/null +++ b/credentials/apps/badges/migrations/0001_initial.py @@ -0,0 +1,371 @@ +# Generated by Django 4.2.13 on 2024-06-11 17:18 + +from django.db import migrations, models +import django.db.models.deletion +import django_extensions.db.fields +import model_utils.fields +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("credentials", "0029_alter_usercredential_credential_content_type"), + ("sites", "0002_alter_domain_unique"), + ] + + operations = [ + migrations.CreateModel( + name="BadgeProgress", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("username", models.CharField(max_length=255)), + ], + options={ + "verbose_name_plural": "badge progress records", + }, + ), + migrations.CreateModel( + name="BadgeRequirement", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "event_type", + models.CharField( + choices=[ + ( + "org.openedx.learning.course.passing.status.updated.v1", + "org.openedx.learning.course.passing.status.updated.v1", + ), + ( + "org.openedx.learning.ccx.course.passing.status.updated.v1", + "org.openedx.learning.ccx.course.passing.status.updated.v1", + ), + ], + help_text='Public signal type. Available events are configured in "BADGES_CONFIG" setting. The crucial aspect for event to carry UserData in its payload.', + max_length=255, + ), + ), + ("description", models.TextField(blank=True, help_text="Provide more details if needed.", null=True)), + ( + "blend", + models.CharField( + blank=True, + help_text="Optional. Group requirements together using the same Group ID for interchangeable (OR processing logic).", + max_length=255, + null=True, + verbose_name="group", + ), + ), + ], + ), + migrations.CreateModel( + name="BadgeTemplate", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "created", + django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name="created"), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name="modified"), + ), + ("is_active", models.BooleanField(default=False)), + ("uuid", models.UUIDField(default=uuid.uuid4, help_text="Unique badge template ID.", unique=True)), + ("name", models.CharField(help_text="Badge template name.", max_length=255)), + ("description", models.TextField(blank=True, help_text="Badge template description.", null=True)), + ("icon", models.ImageField(blank=True, null=True, upload_to="badge_templates/icons")), + ("origin", models.CharField(blank=True, help_text="Badge template type.", max_length=128, null=True)), + ( + "state", + model_utils.fields.StatusField( + choices=[("draft", "draft"), ("active", "active"), ("archived", "archived")], + default="draft", + help_text="Credly badge template state (auto-managed).", + max_length=100, + no_check_for_status=True, + ), + ), + ("site", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="sites.site")), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="CredlyBadge", + fields=[ + ( + "usercredential_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="credentials.usercredential", + ), + ), + ( + "state", + model_utils.fields.StatusField( + choices=[ + ("created", "created"), + ("no_response", "no_response"), + ("error", "error"), + ("pending", "pending"), + ("accepted", "accepted"), + ("rejected", "rejected"), + ("revoked", "revoked"), + ("expired", "expired"), + ], + default="created", + help_text="Credly badge issuing state", + max_length=100, + no_check_for_status=True, + ), + ), + ( + "external_uuid", + models.UUIDField(blank=True, help_text="Credly service badge identifier", null=True, unique=True), + ), + ], + options={ + "get_latest_by": "modified", + "abstract": False, + }, + bases=("credentials.usercredential",), + ), + migrations.CreateModel( + name="CredlyOrganization", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "created", + django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name="created"), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name="modified"), + ), + ("uuid", models.UUIDField(help_text="Put your Credly Organization ID here.", unique=True)), + ( + "api_key", + models.CharField( + blank=True, help_text="Credly API shared secret for Credly Organization.", max_length=255 + ), + ), + ( + "name", + models.CharField( + blank=True, help_text="Verbose name for Credly Organization.", max_length=255, null=True + ), + ), + ], + options={ + "get_latest_by": "modified", + "abstract": False, + }, + ), + migrations.CreateModel( + name="Fulfillment", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "blend", + models.CharField( + blank=True, + help_text="Group ID for the requirement.", + max_length=255, + null=True, + verbose_name="group", + ), + ), + ("progress", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="badges.badgeprogress")), + ( + "requirement", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="fulfillments", + to="badges.badgerequirement", + ), + ), + ], + ), + migrations.AddField( + model_name="badgerequirement", + name="template", + field=models.ForeignKey( + help_text="Badge template this requirement serves for.", + on_delete=django.db.models.deletion.CASCADE, + related_name="requirements", + to="badges.badgetemplate", + ), + ), + migrations.AddField( + model_name="badgeprogress", + name="template", + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="badges.badgetemplate" + ), + ), + migrations.CreateModel( + name="BadgePenalty", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "event_type", + models.CharField( + choices=[ + ( + "org.openedx.learning.course.passing.status.updated.v1", + "org.openedx.learning.course.passing.status.updated.v1", + ), + ( + "org.openedx.learning.ccx.course.passing.status.updated.v1", + "org.openedx.learning.ccx.course.passing.status.updated.v1", + ), + ], + help_text='Public signal type. Use namespaced types, e.g: "org.openedx.learning.student.registration.completed.v1"', + max_length=255, + ), + ), + ( + "requirements", + models.ManyToManyField( + help_text="Badge requirements for which this penalty is defined.", to="badges.badgerequirement" + ), + ), + ( + "template", + models.ForeignKey( + help_text="Badge template this penalty serves for.", + on_delete=django.db.models.deletion.CASCADE, + to="badges.badgetemplate", + ), + ), + ], + options={ + "verbose_name_plural": "Badge penalties", + }, + ), + migrations.CreateModel( + name="PenaltyDataRule", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "data_path", + models.CharField( + help_text='Public signal\'s data payload nested property path, e.g: "user.pii.username".', + max_length=255, + verbose_name="key path", + ), + ), + ( + "operator", + models.CharField( + choices=[("eq", "="), ("ne", "!=")], + default="eq", + help_text="Expected value comparison operator. https://docs.python.org/3/library/operator.html", + max_length=32, + ), + ), + ( + "value", + models.CharField( + help_text='Expected value for the nested property, e.g: "cucumber1997".', + max_length=255, + verbose_name="expected value", + ), + ), + ( + "penalty", + models.ForeignKey( + help_text="Parent penalty for this data rule.", + on_delete=django.db.models.deletion.CASCADE, + related_name="rules", + to="badges.badgepenalty", + ), + ), + ], + options={ + "unique_together": {("penalty", "data_path", "operator", "value")}, + }, + ), + migrations.CreateModel( + name="DataRule", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "data_path", + models.CharField( + help_text='Public signal\'s data payload nested property path, e.g: "user.pii.username".', + max_length=255, + verbose_name="key path", + ), + ), + ( + "operator", + models.CharField( + choices=[("eq", "="), ("ne", "!=")], + default="eq", + help_text="Expected value comparison operator. https://docs.python.org/3/library/operator.html", + max_length=32, + ), + ), + ( + "value", + models.CharField( + help_text='Expected value for the nested property, e.g: "cucumber1997".', + max_length=255, + verbose_name="expected value", + ), + ), + ( + "requirement", + models.ForeignKey( + help_text="Parent requirement for this data rule.", + on_delete=django.db.models.deletion.CASCADE, + related_name="rules", + to="badges.badgerequirement", + ), + ), + ], + options={ + "unique_together": {("requirement", "data_path", "operator", "value")}, + }, + ), + migrations.CreateModel( + name="CredlyBadgeTemplate", + fields=[ + ( + "badgetemplate_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="badges.badgetemplate", + ), + ), + ( + "organization", + models.ForeignKey( + help_text="Credly Organization - template owner.", + on_delete=django.db.models.deletion.CASCADE, + to="badges.credlyorganization", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("badges.badgetemplate",), + ), + ] diff --git a/credentials/apps/badges/migrations/__init__.py b/credentials/apps/badges/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/models.py b/credentials/apps/badges/models.py new file mode 100644 index 000000000..84ad3a3e9 --- /dev/null +++ b/credentials/apps/badges/models.py @@ -0,0 +1,677 @@ +""" +Badges DB models. +""" + +import logging +import operator +import uuid +from urllib.parse import urljoin + +from django.conf import settings +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django_extensions.db.models import TimeStampedModel +from model_utils import Choices +from model_utils.fields import StatusField +from openedx_events.learning.data import BadgeData, BadgeTemplateData, UserData, UserPersonalData + +from credentials.apps.badges.credly.utils import get_credly_base_url +from credentials.apps.badges.signals.signals import ( + notify_progress_complete, + notify_progress_incomplete, + notify_requirement_fulfilled, + notify_requirement_regressed, +) +from credentials.apps.badges.utils import keypath +from credentials.apps.core.api import get_user_by_username +from credentials.apps.credentials.models import AbstractCredential, UserCredential + + +logger = logging.getLogger(__name__) + + +class CredlyOrganization(TimeStampedModel): + """ + Credly Organization configuration. + """ + + 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, + ) + name = models.CharField( + max_length=255, + null=True, + blank=True, + help_text=_("Verbose name for Credly Organization."), + ) + + def __str__(self): + return f"{self.name or self.uuid}" + + @classmethod + 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): + """ + Checks if the organization is preconfigured. + """ + + return str(self.uuid) in CredlyOrganization.get_preconfigured_organizations().keys() + + +class BadgeTemplate(AbstractCredential): + """ + Describes badge template credential type. + + NOTE: currently hidden in the admin as a base class (see more details on the CredlyBadgeTemplate). + """ + + ORIGIN = "openedx" + + STATES = Choices("draft", "active", "archived") + + uuid = models.UUIDField(unique=True, default=uuid.uuid4, help_text=_("Unique badge template ID.")) + name = models.CharField(max_length=255, help_text=_("Badge template name.")) + description = models.TextField(null=True, blank=True, help_text=_("Badge template description.")) + icon = models.ImageField(upload_to="badge_templates/icons", null=True, blank=True) + origin = models.CharField(max_length=128, null=True, blank=True, help_text=_("Badge template type.")) + state = StatusField( + choices_name="STATES", + help_text=_("Credly badge template state (auto-managed)."), + ) + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + super().save() + # auto-evaluate type: + if not self.origin: + self.origin = self.ORIGIN + self.save(*args, **kwargs) + + @property + def groups(self): + """ + Returns unique groups for the badge template. + """ + + return self.requirements.values_list("blend", flat=True).distinct() + + @classmethod + def by_uuid(cls, template_uuid): + """ + Returns badge template by UUID. + """ + + return cls.objects.filter(uuid=template_uuid, origin=cls.ORIGIN).first() + + def user_progress(self, username: str) -> float: + """ + Determines a completion progress for user. + """ + progress = BadgeProgress.for_user(username=username, template_id=self.id) + return progress.ratio + + def is_completed(self, username: str) -> bool: + """ + Checks if user has completed this badge template. + """ + return self.user_progress(username) == 1.00 + + +class CredlyBadgeTemplate(BadgeTemplate): + """ + Credly badge template credential. + + Credly badge templates should not be created manually, instead they are pulled from the Credly Organization (API). + Before being processed badge template must be activated. + Before activation badge template must be configured (requirements and optional penalties). + """ + + ORIGIN = "credly" + + organization = models.ForeignKey( + CredlyOrganization, + on_delete=models.CASCADE, + help_text=_("Credly Organization - template owner."), + ) + + @property + def management_url(self): + """ + Build external Credly dashboard URL. + """ + credly_host_base_url = get_credly_base_url(settings) + return urljoin( + credly_host_base_url, f"mgmt/organizations/{self.organization.uuid}/badges/templates/{self.uuid}/details" + ) + + +class BadgeRequirement(models.Model): + """ + Describes what must happen for badge template to progress. + + - what unique event is expected to happen; + - what exact conditions the expected event must carry in its payload; + + NOTE: all attached to a badge template requirements must be fulfilled by default; + to achieve "OR" processing logic for 2 attached requirements just group them (put identical group ID). + """ + + EVENT_TYPES = Choices(*settings.BADGES_CONFIG["events"]) + + template = models.ForeignKey( + BadgeTemplate, + on_delete=models.CASCADE, + related_name="requirements", + help_text=_("Badge template this requirement serves for."), + ) + event_type = models.CharField( + max_length=255, + choices=EVENT_TYPES, + help_text=_( + 'Public signal type. Available events are configured in "BADGES_CONFIG" setting. ' + "The crucial aspect for event to carry UserData in its payload." + ), + ) + description = models.TextField(null=True, blank=True, help_text=_("Provide more details if needed.")) + blend = models.CharField( + max_length=255, + null=True, + blank=True, + help_text=_( + "Optional. Group requirements together using the same Group ID for interchangeable (OR processing logic)." + ), + verbose_name=_("group"), + ) + + def __str__(self): + return f"BadgeRequirement:{self.id}:{self.template.uuid}" + + def fulfill(self, username: str): + """ + Marks itself as "done" for the user. + + Side effects: + - notifies about a progression if any; + + Returns: (bool) if progression happened + """ + template_id = self.template.id + progress = BadgeProgress.for_user(username=username, template_id=template_id) + fulfillment, created = Fulfillment.objects.get_or_create(progress=progress, requirement=self, blend=self.blend) + + if created: + notify_requirement_fulfilled( + sender=self, + username=username, + badge_template_id=template_id, + fulfillment_id=fulfillment.id, + ) + return created + + def reset(self, username: str): + """ + Marks itself as "undone" for the user. + + - removes user progress for the requirement if any; + - notifies about the regression if any; + + Returns: (bool) if any progress existed. + """ + template_id = self.template.id + fulfillment = Fulfillment.objects.filter( + requirement=self, + progress__username=username, + ).first() + deleted = self._delete_fulfillment_if_exists(fulfillment) + if deleted: + notify_requirement_regressed( + sender=self, + username=username, + badge_template_id=template_id, + ) + return bool(deleted) + + def is_fulfilled(self, username: str) -> bool: + """ + Checks if the requirement is fulfilled for the user. + """ + + return self.fulfillments.filter(progress__username=username, progress__template=self.template).exists() + + def _delete_fulfillment_if_exists(self, fulfillment): + """ + Deletes the fulfillment if it exists. + """ + + if not fulfillment: + return False + + fulfillment.delete() + return True + + @classmethod + def is_group_fulfilled(cls, *, group: str, template: BadgeTemplate, username: str) -> bool: + """ + Checks if the group is fulfilled. + """ + + progress = BadgeProgress.for_user(username=username, template_id=template.id) + requirements = cls.objects.filter(template=template, blend=group) + fulfilled_requirements = requirements.filter(fulfillments__progress=progress).count() + + return fulfilled_requirements > 0 + + def apply_rules(self, data: dict) -> bool: + """ + Evaluates payload rules. + """ + + return all(rule.apply(data) for rule in self.rules.all()) if self.rules.exists() else False + + @property + def is_active(self): + """ + Checks if the requirement is active. + """ + + return self.template.is_active + + +class AbstractDataRule(models.Model): + """ + Abstract DataRule configuration model. + + .. no_req_or_pen: This model has no requirement or penalty. + """ + + OPERATORS = Choices( + ("eq", "="), + ("ne", "!="), + ) + + TRUE_VALUES = ["True", "true", "Yes", "yes", "+"] + FALSE_VALUES = ["False", "false", "No", "no", "-"] + BOOL_VALUES = TRUE_VALUES + FALSE_VALUES + + data_path = models.CharField( + max_length=255, + help_text=_('Public signal\'s data payload nested property path, e.g: "user.pii.username".'), + verbose_name=_("key path"), + ) + operator = models.CharField( + max_length=32, + choices=OPERATORS, + default=OPERATORS.eq, + help_text=_("Expected value comparison operator. https://docs.python.org/3/library/operator.html"), + ) + value = models.CharField( + max_length=255, + help_text=_('Expected value for the nested property, e.g: "cucumber1997".'), + verbose_name=_("expected value"), + ) + + class Meta: + abstract = True + + def apply(self, data: dict) -> bool: + """ + Evaluates itself on the input data (event payload). + + This method retrieves a value specified by a data path within a given dictionary, + converts that value to a string, and then applies a comparison operation against + a predefined value. The comparison operation is determined by the `self.operator` + attribute, which should match the name of an operator function in the `operator` + module. + + Parameters: + - data (dict): A dictionary containing data against which the comparison operation + will be applied. The specific value to be compared is determined by + the `self.data_path` attribute, which specifies the path to the value + within the dictionary. + + Returns: + - bool: True if the rule "worked". + + Example: + Assuming `self.operator` is set to "eq", `self.data_path` is set to "user.age", + and `self.value` is "30", then calling `apply({"user": {"age": 30}})` will return True + because the age matches the specified value. + """ + + 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. + """ + + if self.value in self.TRUE_VALUES: + return "True" + if self.value in self.FALSE_VALUES: + return "False" + return self.value + + +class DataRule(AbstractDataRule): + """ + Specifies expected data attribute value for event payload. + NOTE: all data rules for a single requirement follow "AND" processing logic. + """ + + requirement = models.ForeignKey( + BadgeRequirement, + on_delete=models.CASCADE, + help_text=_("Parent requirement for this data rule."), + related_name="rules", + ) + + class Meta: + unique_together = ("requirement", "data_path", "operator", "value") + + def __str__(self): + return f"{self.requirement.template.uuid}:{self.data_path}:{self.operator}:{self.value}" + + @property + def is_active(self): + """ + Checks if the rule is active. + """ + + return self.requirement.template.is_active + + +class BadgePenalty(models.Model): + """ + Describes badge regression rules for particular BadgeRequirement. + """ + + EVENT_TYPES = Choices(*settings.BADGES_CONFIG["events"]) + + template = models.ForeignKey( + BadgeTemplate, + on_delete=models.CASCADE, + help_text=_("Badge template this penalty serves for."), + ) + event_type = models.CharField( + max_length=255, + choices=EVENT_TYPES, + help_text=_( + 'Public signal type. Use namespaced types, e.g: "org.openedx.learning.student.registration.completed.v1"' + ), + ) + requirements = models.ManyToManyField( + BadgeRequirement, + help_text=_("Badge requirements for which this penalty is defined."), + ) + + class Meta: + verbose_name_plural = _("Badge penalties") + + def __str__(self): + return f"BadgePenalty:{self.id}:{self.template.uuid}" + + def apply_rules(self, data: dict) -> bool: + """ + Evaluates payload rules. + """ + + return all(rule.apply(data) for rule in self.rules.all()) if self.rules.exists() else False + + def reset_requirements(self, username: str): + """ + Resets all related requirements for the user. + """ + + for requirement in self.requirements.all(): + requirement.reset(username) + + @property + def is_active(self): + """ + Checks if the penalty is active. + """ + + return self.template.is_active + + +class PenaltyDataRule(AbstractDataRule): + """ + Specifies expected data attribute value for penalty rule. + NOTE: all data rules for a single penalty follow "AND" processing logic. + """ + + penalty = models.ForeignKey( + BadgePenalty, + on_delete=models.CASCADE, + help_text=_("Parent penalty for this data rule."), + related_name="rules", + ) + + class Meta: + unique_together = ("penalty", "data_path", "operator", "value") + + def __str__(self): + return f"{self.penalty.template.uuid}:{self.data_path}:{self.operator}:{self.value}" + + @property + def is_active(self): + """ + Checks if the rule is active. + """ + + return self.penalty.template.is_active + + +class BadgeProgress(models.Model): + """ + Tracks a single badge template progress for user. + + - allows multiple requirements status tracking; + - user-centric; + """ + + username = models.CharField(max_length=255) + template = models.ForeignKey( + BadgeTemplate, + models.SET_NULL, + blank=True, + null=True, + ) + + class Meta: + verbose_name_plural = _("badge progress records") + + def __str__(self): + return f"BadgeProgress:{self.username}" + + @classmethod + def for_user(cls, *, username, template_id): + """ + Service shortcut. + """ + + progress, __ = cls.objects.get_or_create(username=username, template_id=template_id) + return progress + + @property + def ratio(self) -> float: + """ + Calculates badge template progress ratio. + """ + + if not self.groups: + return 0.00 + + true_values_count = self._get_groups_true_values_count() + return round(true_values_count / len(self.groups.keys()), 2) + + @property + def groups(self): + """ + Returns gorups and their statuses (fulfilled or not). + """ + + return { + group: BadgeRequirement.is_group_fulfilled(group=group, template=self.template, username=self.username) + for group in self.template.groups + } + + @property + def completed(self): + """ + Checks if the badge template is completed. + """ + + return self.ratio == 1.00 + + def progress(self): + """ + Notify about the progress. + """ + + notify_progress_complete(self, self.username, self.template.id) + + def regress(self): + """ + Notify about the regression. + """ + + notify_progress_incomplete(self, self.username, self.template.id) + + def reset(self): + """ + Resets the progress. + """ + + Fulfillment.objects.filter(progress=self).delete() + + def _get_groups_true_values_count(self): + """ + Returns the count of groups with fulfilled requirements. + """ + + result = 0 + for fulfilled in self.groups.values(): + if fulfilled: + result += 1 + return result + + +class Fulfillment(models.Model): + """ + Tracks completed badge template requirement for user. + """ + + progress = models.ForeignKey(BadgeProgress, on_delete=models.CASCADE) + requirement = models.ForeignKey( + BadgeRequirement, + models.SET_NULL, + blank=True, + null=True, + related_name="fulfillments", + ) + blend = models.CharField( + max_length=255, + null=True, + blank=True, + help_text=_("Group ID for the requirement."), + verbose_name=_("group"), + ) + + +class CredlyBadge(UserCredential): + """ + Earned Credly badge (Badge template credential) for user. + + - tracks distributed (external Credly service) state for Credly badge. + """ + + STATES = Choices( + "created", + "no_response", + "error", + "pending", + "accepted", + "rejected", + "revoked", + "expired", + ) + ISSUING_STATES = { + STATES.pending, + STATES.accepted, + STATES.rejected, + } + + state = StatusField( + choices_name="STATES", + help_text=_("Credly badge issuing state"), + default=STATES.created, + ) + + external_uuid = models.UUIDField( + blank=True, + null=True, + unique=True, + help_text=_("Credly service badge identifier"), + ) + + def as_badge_data(self) -> BadgeData: + """ + Represents itself as a BadgeData instance. + """ + + user = get_user_by_username(self.username) + badge_template = self.credential + + badge_data = BadgeData( + uuid=str(self.uuid), + user=UserData( + pii=UserPersonalData( + username=self.username, + email=user.email, + name=user.get_full_name(), + ), + id=user.lms_user_id, + is_active=user.is_active, + ), + template=BadgeTemplateData( + uuid=str(badge_template.uuid), + origin=badge_template.origin, + name=badge_template.name, + description=badge_template.description, + image_url=str(badge_template.icon), + ), + ) + + return badge_data + + @property + 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/processing/__init__.py b/credentials/apps/badges/processing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/processing/generic.py b/credentials/apps/badges/processing/generic.py new file mode 100644 index 000000000..105831d42 --- /dev/null +++ b/credentials/apps/badges/processing/generic.py @@ -0,0 +1,73 @@ +""" +Main processing logic. +""" + +import logging + +from credentials.apps.badges.exceptions import BadgesProcessingError +from credentials.apps.badges.processing.progression import process_requirements +from credentials.apps.badges.processing.regression import process_penalties +from credentials.apps.badges.utils import extract_payload, get_user_data +from credentials.apps.core.api import get_or_create_user_from_event_data + + +logger = logging.getLogger(__name__) + + +def process_event(sender, **kwargs): + """ + Badge templates configuration interpreter. + + Responsibilities: + - identifies a target User based on event's payload ("whose action"); + - runs badges progressive pipeline (requirements processing); + - runs badges regressive pipeline (penalties processing); + """ + + event_type = sender.event_type + + try: + # user identification + username = identify_user(event_type=event_type, event_payload=extract_payload(kwargs)) + + # requirements processing + process_requirements(event_type, username, extract_payload(kwargs)) + + # penalties processing + process_penalties(event_type, username, extract_payload(kwargs)) + + except BadgesProcessingError as error: + logger.error(f"Badges processing error: {error}") + return None + return None + + +def identify_user(*, event_type, event_payload): + """ + Identifies event user based on provided keyword arguments and returns the username. + + This function extracts user data from the given event's keyword arguments, attempts to identify existing user + or creates a new user based on this data, and then returns the username. + + Args: + event_type (str): The type of the event. + event_payload (dict): The payload of the event. + + Returns: + str: The username of the identified (and created if needed) user. + + Raises: + BadgesProcessingError: if user data was not found. + """ + + user_data = get_user_data(event_payload) + + if not user_data: + message = ( + f"User data cannot be found (got: {user_data}): {event_payload}. " + f"Does event {event_type} include user data at all?" + ) + raise BadgesProcessingError(message) + + user, __ = get_or_create_user_from_event_data(user_data) + return user.username diff --git a/credentials/apps/badges/processing/progression.py b/credentials/apps/badges/processing/progression.py new file mode 100644 index 000000000..a967018fb --- /dev/null +++ b/credentials/apps/badges/processing/progression.py @@ -0,0 +1,50 @@ +""" +Badge progression processing. +""" + +import logging +from typing import List + +from attrs import asdict + +from credentials.apps.badges.models import BadgeRequirement + + +logger = logging.getLogger(__name__) + + +def discover_requirements(event_type: str) -> List[BadgeRequirement]: + """ + Picks all relevant requirements based on the event type. + """ + + return BadgeRequirement.objects.filter(event_type=event_type, template__is_active=True) + + +def process_requirements(event_type, username, payload): + """ + Finds all relevant requirements, tests them one by one, marks as completed if needed. + """ + + requirements = discover_requirements(event_type=event_type) + completed_templates = set() + + logger.debug("BADGES: found %s requirements to process.", len(requirements)) + + for requirement in requirements: + + # remember: the badge template is already "done" + if requirement.template.is_completed(username): + completed_templates.add(requirement.template_id) + + # drop early: if the badge template is already "done" + if requirement.template_id in completed_templates: + continue + + # drop early: if the requirement is already "done" + if requirement.is_fulfilled(username): + continue + + # process: payload rules + if requirement.apply_rules(asdict(payload)): + requirement.fulfill(username) diff --git a/credentials/apps/badges/processing/regression.py b/credentials/apps/badges/processing/regression.py new file mode 100644 index 000000000..e88e51842 --- /dev/null +++ b/credentials/apps/badges/processing/regression.py @@ -0,0 +1,37 @@ +""" +Badge regression processing. +""" + +import logging +from typing import List + +from attrs import asdict + +from credentials.apps.badges.models import BadgePenalty + + +logger = logging.getLogger(__name__) + + +def discover_penalties(event_type: str) -> List[BadgePenalty]: + """ + Picks all relevant penalties based on the event type. + """ + + return BadgePenalty.objects.filter(event_type=event_type, template__is_active=True) + + +def process_penalties(event_type, username, payload): + """ + Finds all relevant penalties, tests them one by one, marks related requirement as not completed if needed. + """ + + penalties = discover_penalties(event_type=event_type) + + logger.debug("BADGES: found %s penalties to process.", len(penalties)) + + for penalty in penalties: + + # process: payload rules + if penalty.apply_rules(asdict(payload)): + penalty.reset_requirements(username) diff --git a/credentials/apps/badges/signals/__init__.py b/credentials/apps/badges/signals/__init__.py new file mode 100644 index 000000000..4c11d0992 --- /dev/null +++ b/credentials/apps/badges/signals/__init__.py @@ -0,0 +1 @@ +from .signals import * diff --git a/credentials/apps/badges/signals/handlers.py b/credentials/apps/badges/signals/handlers.py new file mode 100644 index 000000000..2044e0849 --- /dev/null +++ b/credentials/apps/badges/signals/handlers.py @@ -0,0 +1,88 @@ +""" +These signal handlers are auto-subscribed to all expected badging signals (event types). + +See: +""" + +import logging + +from django.dispatch import receiver +from openedx_events.tooling import OpenEdxPublicSignal, load_all_signals + +from credentials.apps.badges.issuers import CredlyBadgeTemplateIssuer +from credentials.apps.badges.models import BadgeProgress +from credentials.apps.badges.processing.generic import process_event +from credentials.apps.badges.signals import ( + BADGE_PROGRESS_COMPLETE, + BADGE_PROGRESS_INCOMPLETE, + BADGE_REQUIREMENT_FULFILLED, + BADGE_REQUIREMENT_REGRESSED, +) +from credentials.apps.badges.utils import get_badging_event_types + + +logger = logging.getLogger(__name__) + + +def listen_to_badging_events(): + """ + Subscribes the main processing handler to badging events subset. + """ + + load_all_signals() + + for event_type in get_badging_event_types(): + signal = OpenEdxPublicSignal.get_signal_by_type(event_type) + signal.connect(handle_badging_event, dispatch_uid=event_type) + + +def handle_badging_event(sender, signal, **kwargs): # pylint: disable=unused-argument + """ + Generic handler for incoming from the Event bus public signals. + """ + + logger.debug(f"BADGES: incoming signal - {signal}") + + process_event(signal, **kwargs) + + +@receiver(BADGE_REQUIREMENT_FULFILLED) +def handle_requirement_fulfilled(sender, username, **kwargs): + """ + On user's Badge progression (completion). + """ + BadgeProgress.for_user(username=username, template_id=sender.template.id).progress() + + +@receiver(BADGE_REQUIREMENT_REGRESSED) +def handle_requirement_regressed(sender, username, **kwargs): + """ + On user's Badge regression (incompletion). + """ + BadgeProgress.for_user(username=username, template_id=sender.template.id).regress() + + +@receiver(BADGE_PROGRESS_COMPLETE) +def handle_badge_completion(sender, username, badge_template_id, **kwargs): # pylint: disable=unused-argument + """ + Fires once ALL requirements for a badge template were marked as "done". + + - username + - badge template ID + """ + + logger.debug("BADGES: progress is complete for %s on the %s", username, badge_template_id) + + CredlyBadgeTemplateIssuer().award(username=username, credential_id=badge_template_id) + + +@receiver(BADGE_PROGRESS_INCOMPLETE) +def handle_badge_regression(sender, username, badge_template_id, **kwargs): # pylint: disable=unused-argument + """ + On user's Badge regression (incompletion). + + - username + - badge template ID + """ + + CredlyBadgeTemplateIssuer().revoke(badge_template_id, username) diff --git a/credentials/apps/badges/signals/signals.py b/credentials/apps/badges/signals/signals.py new file mode 100644 index 000000000..db224ff54 --- /dev/null +++ b/credentials/apps/badges/signals/signals.py @@ -0,0 +1,93 @@ +""" +Badges internal signals. +""" + +import logging + +from django.dispatch import Signal +from openedx_events.learning.signals import BADGE_AWARDED, BADGE_REVOKED + + +logger = logging.getLogger(__name__) + + +# a single requirements for a badge template was finished +BADGE_REQUIREMENT_FULFILLED = Signal() + +# a single penalty worked on a badge template +BADGE_REQUIREMENT_REGRESSED = Signal() + +# all badge template requirements are finished +BADGE_PROGRESS_COMPLETE = Signal() + +# badge template penalty reset some of fulfilled requirements, so badge template became incomplete +BADGE_PROGRESS_INCOMPLETE = Signal() + + +def notify_requirement_fulfilled(*, sender, username, badge_template_id, **kwargs): + """ + Notifies about user's partial progression on the badge template. + """ + + BADGE_REQUIREMENT_FULFILLED.send( + sender=sender, + username=username, + badge_template_id=badge_template_id, + ) + + +def notify_requirement_regressed(*, sender, username, badge_template_id): + """ + Notifies about user's regression on the badge template. + """ + + BADGE_REQUIREMENT_REGRESSED.send( + sender=sender, + username=username, + badge_template_id=badge_template_id, + ) + + +def notify_progress_complete(sender, username, badge_template_id): + """ + Notifies about user's completion on the badge template. + """ + + BADGE_PROGRESS_COMPLETE.send( + sender=sender, + username=username, + badge_template_id=badge_template_id, + ) + + +def notify_progress_incomplete(sender, username, badge_template_id): + """ + Notifies about user's regression on the badge template. + """ + BADGE_PROGRESS_INCOMPLETE.send( + sender=sender, + username=username, + badge_template_id=badge_template_id, + ) + + +def notify_badge_awarded(user_credential): + """ + Emits a public signal about the badge template completion for user. + + - username + - badge template ID + """ + + BADGE_AWARDED.send_event(badge=user_credential.as_badge_data()) + + +def notify_badge_revoked(user_credential): + """ + Emit public event about badge template regression. + + - username + - badge template ID + """ + + BADGE_REVOKED.send_event(badge=user_credential.as_badge_data()) diff --git a/credentials/apps/badges/tests/__init__.py b/credentials/apps/badges/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/credentials/apps/badges/tests/test_admin_forms.py b/credentials/apps/badges/tests/test_admin_forms.py new file mode 100644 index 000000000..6a5914e93 --- /dev/null +++ b/credentials/apps/badges/tests/test_admin_forms.py @@ -0,0 +1,247 @@ +import uuid +from unittest.mock import MagicMock, patch + +from django import forms +from django.contrib.sites.models import Site +from django.test import TestCase, override_settings + +from credentials.apps.badges.admin_forms import ( + BadgePenaltyForm, + CredlyOrganizationAdminForm, + DataRuleExtensionsMixin, + ParentMixin, +) +from credentials.apps.badges.credly.exceptions import CredlyAPIError +from credentials.apps.badges.models import BadgeRequirement, BadgeTemplate + + +COURSE_PASSING_EVENT = "org.openedx.learning.course.passing.status.updated.v1" + + +class BadgePenaltyFormTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template1 = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.badge_template2 = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.requirement1 = BadgeRequirement.objects.create( + template=self.badge_template1, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 1", + ) + self.requirement2 = BadgeRequirement.objects.create( + template=self.badge_template2, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 2", + ) + self.requirement3 = BadgeRequirement.objects.create( + template=self.badge_template2, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 3", + ) + + def test_clean_requirements_same_template(self): + form = BadgePenaltyForm() + form.cleaned_data = { + "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() + form.cleaned_data = { + "template": self.badge_template1, + "requirements": [ + self.requirement2, + self.requirement1, + ], + } + + with self.assertRaises(forms.ValidationError) as cm: + form.clean() + + self.assertEqual(str(cm.exception), "['All requirements must belong to the same template.']") + + @override_settings(BADGES_CONFIG={"credly": {"ORGANIZATIONS": {}}}) + def test_clean(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "test_uuid", + "api_key": "test_api_key", + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {} + + with patch("credentials.apps.badges.admin_forms.CredlyAPIClient") as mock_client: + mock_client.return_value = MagicMock() + + form.clean() + + mock_get_orgs.assert_called_once() + mock_client.assert_called_once_with("test_uuid", "test_api_key") + + @override_settings(BADGES_CONFIG={"credly": {"ORGANIZATIONS": {"test_uuid": "test_api_key"}}}) + def test_clean_with_configured_organization(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "test_uuid", + "api_key": None, + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {"test_uuid": "test_org"} + + with patch("credentials.apps.badges.admin_forms.CredlyAPIClient") as mock_client: + mock_client.return_value = MagicMock() + + form.clean() + + mock_get_orgs.assert_called_once() + mock_client.assert_called_once_with("test_uuid", "test_api_key") + + def test_clean_with_invalid_organization(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "invalid_uuid", + "api_key": "test_api_key", + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {"test_uuid": "test_org"} + + with self.assertRaises(forms.ValidationError) as cm: + form.clean() + + self.assertIn("You specified an invalid authorization token.", str(cm.exception)) + + def test_clean_cannot_provide_api_key_for_configured_organization(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "test_uuid", + "api_key": "test_api_key", + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {"test_uuid": "test_org"} + + with self.assertRaises(forms.ValidationError) as cm: + form.clean() + + self.assertEqual( + str(cm.exception), + '["You can\'t provide an API key for a configured organization."]', + ) + + def test_ensure_organization_exists(self): + form = CredlyOrganizationAdminForm() + api_client = MagicMock() + api_client.fetch_organization.return_value = {"data": {"org_id": "test_org_id"}} + + form.ensure_organization_exists(api_client) + + api_client.fetch_organization.assert_called_once() + self.assertEqual(form.api_data, {"org_id": "test_org_id"}) + + def test_ensure_organization_exists_with_error(self): + form = CredlyOrganizationAdminForm() + api_client = MagicMock() + api_client.fetch_organization.side_effect = CredlyAPIError("API Error") + + with self.assertRaises(forms.ValidationError) as cm: + form.ensure_organization_exists(api_client) + + api_client.fetch_organization.assert_called_once() + self.assertEqual(str(cm.exception), "['API Error']") + + +class TestParentMixin(ParentMixin): + pass + + +class ParentMixinTestCase(TestCase): + def setUp(self): + self.instance = MagicMock() + self.instance.some_attribute = "some_value" + + self.mixin = TestParentMixin() + self.mixin.instance = self.instance + + def test_get_form_kwargs_passes_parent_instance(self): + with patch.object( + TestParentMixin, + "get_form_kwargs", + return_value={"parent_instance": self.instance}, + ) as super_method: + result = self.mixin.get_form_kwargs(0) + + super_method.assert_called_once_with(0) + + self.assertIn("parent_instance", result) + self.assertEqual(result["parent_instance"], self.instance) + + +class TestForm(DataRuleExtensionsMixin, forms.Form): + data_path = forms.ChoiceField(choices=[]) + value = forms.CharField() + + +class DataRuleExtensionsMixinTestCase(TestCase): + def setUp(self): + self.parent_instance = MagicMock() + self.parent_instance.event_type = COURSE_PASSING_EVENT + + def test_init_sets_choices_based_on_event_type(self): + form = TestForm(parent_instance=self.parent_instance) + self.assertEqual( + form.fields["data_path"].choices, + [("is_passing", "is_passing"), ("course.course_key", "course.course_key")], + ) + + def test_clean_with_valid_boolean_value(self): + form = TestForm( + data={"data_path": "is_passing", "value": "True"}, + parent_instance=self.parent_instance, + ) + form.is_valid() + self.assertRaises(KeyError, lambda: form.errors["__all__"]) + + def test_clean_with_invalid_boolean_value(self): + form = TestForm( + data={"data_path": "is_passing", "value": "invalid"}, + parent_instance=self.parent_instance, + ) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors["__all__"], ["Value must be a boolean."]) + + def test_clean_with_non_boolean_data_path(self): + form = TestForm( + data={"data_path": "course.course_key", "value": "some_value"}, + parent_instance=self.parent_instance, + ) + form.is_valid() + self.assertRaises(KeyError, lambda: form.errors["__all__"]) diff --git a/credentials/apps/badges/tests/test_api_client.py b/credentials/apps/badges/tests/test_api_client.py new file mode 100644 index 000000000..72b544b32 --- /dev/null +++ b/credentials/apps/badges/tests/test_api_client.py @@ -0,0 +1,128 @@ +from unittest import mock + +from attrs import asdict +from django.test import TestCase +from faker import Faker +from openedx_events.learning.data import BadgeData, BadgeTemplateData, UserData, UserPersonalData + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.exceptions import CredlyError +from credentials.apps.badges.models import BadgeTemplate, CredlyOrganization + + +class CredlyApiClientTestCase(TestCase): + def setUp(self): + fake = Faker() + self.api_client = CredlyAPIClient("test_organization_id", "test_api_key") + self.badge_data = BadgeData( + uuid=fake.uuid4(), + user=UserData( + id=1, + is_active=True, + pii=UserPersonalData(username="test_user", email="test_email@mail.com", name="Test User"), + ), + template=BadgeTemplateData( + uuid=fake.uuid4(), + name="Test Badge", + origin="Credly", + description="Test Badge Description", + image_url="https://test.com/image.png", + ), + ) + self.organization = CredlyOrganization.objects.create( + uuid=fake.uuid4(), api_key="test-api-key", name="test_organization" + ) + + def test_get_organization_nonexistent(self): + with mock.patch("credentials.apps.badges.credly.api_client.CredlyOrganization.objects.get") as mock_get: + mock_get.side_effect = CredlyOrganization.DoesNotExist + with self.assertRaises(CredlyError) as cm: + CredlyAPIClient("nonexistent_organization_id") + self.assertEqual( + str(cm.exception), + "CredlyOrganization with the uuid nonexistent_organization_id does not exist!", + ) + + def test_perform_request(self): + with mock.patch("credentials.apps.badges.credly.api_client.requests.request") as mock_request: + mock_response = mock.Mock() + mock_response.json.return_value = {"key": "value"} + mock_request.return_value = mock_response + result = self.api_client.perform_request("GET", "/api/endpoint") + mock_request.assert_called_once_with( + "GET", + "https://sandbox-api.credly.com/api/endpoint", + headers={ + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": "Basic dGVzdF9hcGlfa2V5", + }, + json=None, + timeout=10, + ) + self.assertEqual(result, {"key": "value"}) + + def test_fetch_organization(self): + with mock.patch.object(CredlyAPIClient, "perform_request") as mock_perform_request: + mock_perform_request.return_value = {"organization": "data"} + result = self.api_client.fetch_organization() + mock_perform_request.assert_called_once_with("get", "") + self.assertEqual(result, {"organization": "data"}) + + def test_fetch_badge_templates(self): + with mock.patch.object(CredlyAPIClient, "perform_request") as mock_perform_request: + mock_perform_request.return_value = {"badge_templates": ["template1", "template2"]} + result = self.api_client.fetch_badge_templates() + mock_perform_request.assert_called_once_with("get", "badge_templates/?filter=state::active") + self.assertEqual(result, {"badge_templates": ["template1", "template2"]}) + + def test_fetch_event_information(self): + event_id = "event123" + with mock.patch.object(CredlyAPIClient, "perform_request") as mock_perform_request: + mock_perform_request.return_value = {"event": "data"} + result = self.api_client.fetch_event_information(event_id) + mock_perform_request.assert_called_once_with("get", f"events/{event_id}/") + self.assertEqual(result, {"event": "data"}) + + def test_issue_badge(self): + issue_badge_data = self.badge_data + with mock.patch.object(CredlyAPIClient, "perform_request") as mock_perform_request: + mock_perform_request.return_value = {"badge": "issued"} + result = self.api_client.issue_badge(issue_badge_data) + mock_perform_request.assert_called_once_with("post", "badges/", asdict(issue_badge_data)) + self.assertEqual(result, {"badge": "issued"}) + + def test_revoke_badge(self): + badge_id = "badge123" + data = {"data": "value"} + with mock.patch.object(CredlyAPIClient, "perform_request") as mock_perform_request: + mock_perform_request.return_value = {"badge": "revoked"} + result = self.api_client.revoke_badge(badge_id, data) + mock_perform_request.assert_called_once_with("put", f"badges/{badge_id}/revoke/", data=data) + self.assertEqual(result, {"badge": "revoked"}) + + def test_sync_organization_badge_templates(self): + with mock.patch.object(CredlyAPIClient, "fetch_badge_templates") as mock_fetch_badge_templates: + mock_fetch_badge_templates.return_value = { + "data": [ + { + "id": Faker().uuid4(), + "name": "Badge Template 1", + "state": "active", + "description": "Badge Template 1 Description", + }, + { + "id": Faker().uuid4(), + "name": "Badge Template 2", + "state": "active", + "description": "Badge Template 2 Description", + }, + ] + } + api_client = CredlyAPIClient(self.organization.uuid) + result = api_client.sync_organization_badge_templates(1) + mock_fetch_badge_templates.assert_called_once() + self.assertEqual(result, 2) + badge_templates = BadgeTemplate.objects.all() + self.assertEqual(badge_templates.count(), 2) + self.assertEqual(badge_templates[0].name, "Badge Template 1") diff --git a/credentials/apps/badges/tests/test_issuers.py b/credentials/apps/badges/tests/test_issuers.py new file mode 100644 index 000000000..787f2ba27 --- /dev/null +++ b/credentials/apps/badges/tests/test_issuers.py @@ -0,0 +1,172 @@ +from unittest import mock +from unittest.mock import patch + +import faker +from django.contrib.auth import get_user_model +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.exceptions import CredlyAPIError +from credentials.apps.badges.issuers import CredlyBadgeTemplateIssuer +from credentials.apps.badges.models import CredlyBadge, CredlyBadgeTemplate, CredlyOrganization +from credentials.apps.credentials.constants import UserCredentialStatus + + +User = get_user_model() + + +class CredlyBadgeTemplateIssuerTestCase(TestCase): + issued_credential_type = CredlyBadgeTemplate + issued_user_credential_type = CredlyBadge + issuer = CredlyBadgeTemplateIssuer + + def setUp(self): + # Create a test badge template + self.fake = faker.Faker() + credly_organization = CredlyOrganization.objects.create( + uuid=self.fake.uuid4(), api_key=self.fake.uuid4(), name=self.fake.word() + ) + self.badge_template = self.issued_credential_type.objects.create( + origin=self.issued_credential_type.ORIGIN, + site_id=1, + uuid=self.fake.uuid4(), + name=self.fake.word(), + state="active", + organization=credly_organization, + ) + User.objects.create_user(username="test_user", email="test_email@fff.com", password="test_password") + + def _perform_request(self, method, endpoint, data=None): # pylint: disable=unused-argument + fake = faker.Faker() + return {"data": {"id": fake.uuid4(), "state": "issued"}} + + def test_create_user_credential_with_status_awared(self): + # Call create_user_credential with valid arguments + with mock.patch("credentials.apps.badges.issuers.notify_badge_awarded") as mock_notify_badge_awarded: + + with mock.patch.object(self.issuer, "issue_credly_badge") as mock_issue_credly_badge: + self.issuer().award(credential_id=self.badge_template.id, username="test_user") + + mock_notify_badge_awarded.assert_called_once() + mock_issue_credly_badge.assert_called_once() + + # Check if user credential is created + self.assertTrue( + self.issued_user_credential_type.objects.filter( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + ).exists() + ) + + def test_create_user_credential_with_status_revoked(self): + # Call create_user_credential with valid arguments + self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.pending, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + with mock.patch("credentials.apps.badges.issuers.notify_badge_revoked") as mock_notify_badge_revoked: + with mock.patch.object(self.issuer, "revoke_credly_badge") as mock_revoke_credly_badge: + self.issuer().revoke(self.badge_template.id, "test_user") + + mock_revoke_credly_badge.assert_called_once() + mock_notify_badge_revoked.assert_called_once() + + # Check if user credential is created + self.assertTrue( + self.issued_user_credential_type.objects.filter( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + status=UserCredentialStatus.REVOKED, + ).exists() + ) + + @patch.object(CredlyAPIClient, "perform_request", _perform_request) + def test_issue_credly_badge(self): + # Create a test user credential + user_credential = self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.pending, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + # Call the issue_credly_badge method + self.issuer().issue_credly_badge(user_credential=user_credential) + + # Check if the user credential is updated with the external UUID and state + self.assertIsNotNone(user_credential.external_uuid) + self.assertEqual(user_credential.state, "issued") + + # Check if the user credential is saved + user_credential.refresh_from_db() + self.assertIsNotNone(user_credential.external_uuid) + self.assertEqual(user_credential.state, "issued") + + def test_issue_credly_badge_with_error(self): + # Create a test user credential + user_credential = self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.pending, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + # Mock the CredlyAPIClient and its issue_badge method to raise CredlyAPIError + with mock.patch("credentials.apps.badges.credly.api_client.CredlyAPIClient") as mock_credly_api_client: + mock_issue_badge = mock_credly_api_client.return_value.issue_badge + mock_issue_badge.side_effect = CredlyAPIError + + # Call the issue_credly_badge method and expect CredlyAPIError to be raised + with self.assertRaises(CredlyAPIError): + self.issuer().issue_credly_badge(user_credential=user_credential) + + # Check if the user credential state is updated to "error" + user_credential.refresh_from_db() + self.assertEqual(user_credential.state, "error") + + @patch.object(CredlyAPIClient, "revoke_badge") + def test_revoke_credly_badge_success(self, mock_revoke_badge): + user_credential = self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.accepted, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + mock_revoke_badge.return_value = {"data": {"state": "revoked"}} + + self.issuer().revoke_credly_badge(self.badge_template.id, user_credential) + + user_credential.refresh_from_db() + self.assertEqual(user_credential.state, "revoked") + + @patch.object(CredlyAPIClient, "revoke_badge", side_effect=CredlyAPIError("Revocation failed")) + def test_revoke_credly_badge_failure(self, mock_revoke_badge): # pylint: disable=unused-argument + user_credential = self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.accepted, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + with self.assertRaises(CredlyAPIError): + self.issuer().revoke_credly_badge(self.badge_template.id, user_credential) + + user_credential.refresh_from_db() + self.assertEqual(user_credential.state, "error") diff --git a/credentials/apps/badges/tests/test_management_commands.py b/credentials/apps/badges/tests/test_management_commands.py new file mode 100644 index 000000000..2209bdbd3 --- /dev/null +++ b/credentials/apps/badges/tests/test_management_commands.py @@ -0,0 +1,28 @@ +from unittest import mock + +import faker +from django.core.management import call_command +from django.test import TestCase + +from credentials.apps.badges.models import CredlyOrganization + + +class TestSyncOrganizationBadgeTemplatesCommand(TestCase): + def setUp(self): + self.faker = faker.Faker() + self.credly_organization = CredlyOrganization.objects.create( + uuid=self.faker.uuid4(), api_key=self.faker.uuid4(), name=self.faker.word() + ) + CredlyOrganization.objects.bulk_create([CredlyOrganization(uuid=self.faker.uuid4()) for _ in range(5)]) + + @mock.patch("credentials.apps.badges.management.commands.sync_organization_badge_templates.CredlyAPIClient") + def test_handle_no_arguments(self, mock_credly_api_client): + call_command("sync_organization_badge_templates") + self.assertEqual(mock_credly_api_client.call_count, 6) + self.assertEqual(mock_credly_api_client.return_value.sync_organization_badge_templates.call_count, 6) + + @mock.patch("credentials.apps.badges.management.commands.sync_organization_badge_templates.CredlyAPIClient") + def test_handle_with_organization_id(self, mock_credly_api_client): + call_command("sync_organization_badge_templates", "--organization_id", self.credly_organization.uuid) + mock_credly_api_client.assert_called_once_with(self.credly_organization.uuid) + mock_credly_api_client.return_value.sync_organization_badge_templates.assert_called_once_with(1) diff --git a/credentials/apps/badges/tests/test_models.py b/credentials/apps/badges/tests/test_models.py new file mode 100644 index 000000000..cd123b921 --- /dev/null +++ b/credentials/apps/badges/tests/test_models.py @@ -0,0 +1,736 @@ +import uuid +from unittest.mock import patch + +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.contrib.sites.models import Site +from django.test import TestCase +from faker import Faker +from openedx_events.learning.data import BadgeData, BadgeTemplateData, UserData, UserPersonalData + +from credentials.apps.badges.models import ( + BadgePenalty, + BadgeProgress, + BadgeRequirement, + BadgeTemplate, + CredlyBadge, + CredlyBadgeTemplate, + CredlyOrganization, + DataRule, + Fulfillment, + PenaltyDataRule, +) +from credentials.apps.core.models import User + + +class DataRulesTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + self.requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + + def test_multiple_data_rules_for_requirement(self): + data_rule1 = DataRule.objects.create( + requirement=self.requirement, + data_path="course_passing_status.user.pii.username", + operator="eq", + value="cucumber1997", + ) + data_rule2 = DataRule.objects.create( + requirement=self.requirement, + data_path="course_passing_status.user.pii.email", + operator="eq", + value="test@example.com", + ) + + data_rules = DataRule.objects.filter(requirement=self.requirement) + + self.assertEqual(data_rules.count(), 2) + self.assertIn(data_rule1, data_rules) + self.assertIn(data_rule2, data_rules) + + +class RequirementApplyRulesCheckTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template1 = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template1", state="draft", site=self.site + ) + self.badge_template2 = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template2", state="draft", site=self.site + ) + self.badge_requirement = BadgeRequirement.objects.create( + template=self.badge_template1, + event_type="org.openedx.learning.course.passing.status.updated.v1", + ) + self.data_rule1 = DataRule.objects.create( + requirement=self.badge_requirement, + data_path="course_passing_status.user.pii.username", + operator="eq", + value="test-username", + ) + self.data_rule2 = DataRule.objects.create( + requirement=self.badge_requirement, + data_path="course_passing_status.user.pii.email", + operator="eq", + value="test@example.com", + ) + self.data_rule = DataRule.objects.create + + def test_apply_rules_check_success(self): + data = {"course_passing_status": {"user": {"pii": {"username": "test-username", "email": "test@example.com"}}}} + self.assertTrue(self.badge_requirement.apply_rules(data)) + + def test_apply_rules_check_failed(self): + data = {"course_passing_status": {"user": {"pii": {"username": "test-username2", "email": "test@example.com"}}}} + self.assertFalse(self.badge_requirement.apply_rules(data)) + + +class BadgeRequirementTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.credlybadge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + + self.requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + self.requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + self.requirement3 = BadgeRequirement.objects.create( + template=self.credlybadge_template, + event_type="org.openedx.learning.ccx.course.passing.status.updated.v1", + description="Test description", + ) + self.requirement4 = BadgeRequirement.objects.create( + template=self.credlybadge_template, + event_type="org.openedx.learning.ccx.course.passing.status.updated.v1", + description="Test description", + ) + + self.requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + + def test_multiple_requirements_for_badgetemplate(self): + requirements = BadgeRequirement.objects.filter(template=self.badge_template) + + self.assertEqual(requirements.count(), 3) + self.assertIn(self.requirement1, requirements) + self.assertIn(self.requirement2, requirements) + + def test_multiple_requirements_for_credlybadgetemplate(self): + requirements = BadgeRequirement.objects.filter(template=self.credlybadge_template) + + self.assertEqual(requirements.count(), 2) + self.assertIn(self.requirement3, requirements) + self.assertIn(self.requirement4, requirements) + + def test_fulfill(self): + username = "test_user" + template_id = self.badge_template.id + progress = BadgeProgress.objects.create(username=username, template=self.badge_template) + with patch("credentials.apps.badges.models.notify_requirement_fulfilled") as mock_notify: + created = self.requirement.fulfill(username) + fulfillment = Fulfillment.objects.get( + progress=progress, + requirement=self.requirement, + blend=self.requirement.blend, + ) + + self.assertTrue(created) + self.assertTrue(mock_notify.called) + mock_notify.assert_called_with( + sender=self.requirement, + username=username, + badge_template_id=template_id, + fulfillment_id=fulfillment.id, + ) + + def test_is_active(self): + self.requirement.template.is_active = True + self.assertTrue(self.requirement.is_active) + + self.requirement.template.is_active = False + self.assertFalse(self.requirement.is_active) + + +class RequirementFulfillmentCheckTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template1 = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template1", state="draft", site=self.site + ) + self.badge_template2 = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template2", state="draft", site=self.site + ) + self.badge_progress = BadgeProgress.objects.create(template=self.badge_template1, username="test1") + self.badge_requirement = BadgeRequirement.objects.create( + template=self.badge_template1, + event_type="org.openedx.learning.course.passing.status.updated.v1", + ) + self.fulfillment = Fulfillment.objects.create(progress=self.badge_progress, requirement=self.badge_requirement) + + def test_fulfillment_check_success(self): + is_fulfilled = self.badge_requirement.is_fulfilled("test1") + self.assertTrue(is_fulfilled) + + def test_fulfillment_check_wrong_username(self): + is_fulfilled = self.badge_requirement.is_fulfilled("asd") + self.assertFalse(is_fulfilled) + + +class BadgeRequirementGroupTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.badge_requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + blend="group1", + ) + self.badge_requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.ccx.course.passing.status.updated.v1", + blend="group1", + ) + self.badge_requirement3 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + ) + + def test_requirement_group(self): + groups = self.badge_template.requirements.filter(blend="group1") + self.assertEqual(groups.count(), 2) + self.assertIsNone(self.badge_requirement3.blend) + + +class BadgeTemplateUserProgressTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.credlybadge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + self.requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="A", + ) + self.requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="B", + ) + self.requirement3 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.ccx.course.passing.status.updated.v1", + description="Test description", + blend="C", + ) + + def test_user_progress_success(self): + Fulfillment.objects.create( + progress=BadgeProgress.objects.create(username="test_user", template=self.badge_template), + requirement=self.requirement1, + ) + self.assertEqual(self.badge_template.user_progress("test_user"), 0.33) + + def test_user_progress_no_fulfillments(self): + Fulfillment.objects.filter(progress__template=self.badge_template).delete() + self.assertEqual(self.badge_template.user_progress("test_user"), 0.0) + + def test_user_progress_no_requirements(self): + BadgeRequirement.objects.filter(template=self.badge_template).delete() + self.assertEqual(self.badge_template.user_progress("test_user"), 0.0) + + +class BadgeTemplateUserCompletionTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.credlybadge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + self.requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + + def test_is_completed_success(self): + Fulfillment.objects.create( + progress=BadgeProgress.objects.create(username="test_user", template=self.badge_template), + requirement=self.requirement1, + ) + self.assertTrue(self.badge_template.is_completed("test_user")) + + def test_is_completed_failure(self): + self.assertFalse(self.badge_template.is_completed("test_usfer")) + + def test_is_completed_no_requirements(self): + BadgeRequirement.objects.filter(template=self.badge_template).delete() + self.assertEqual(self.badge_template.is_completed("test_user"), 0.0) + + +class BadgeTemplateRatioTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.credlybadge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + self.requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="A", + ) + self.requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="B", + ) + + self.group_requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="test-group1", + ) + self.group_requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="test-group1", + ) + + self.group_requirement3 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="test-group2", + ) + self.group_requirement4 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + blend="test-group2", + ) + self.progress = BadgeProgress.objects.create(username="test_user", template=self.badge_template) + + def test_ratio_no_fulfillments(self): + self.assertEqual(self.progress.ratio, 0.00) + + def test_ratio_success(self): + Fulfillment.objects.create( + progress=self.progress, + requirement=self.requirement1, + ) + self.assertEqual(self.progress.ratio, 0.25) + + Fulfillment.objects.create( + progress=self.progress, + requirement=self.requirement2, + ) + self.assertEqual(self.progress.ratio, 0.50) + + Fulfillment.objects.create( + progress=self.progress, + requirement=self.group_requirement1, + ) + self.assertEqual(self.progress.ratio, 0.75) + + Fulfillment.objects.create( + progress=self.progress, + requirement=self.group_requirement3, + ) + self.assertEqual(self.progress.ratio, 1.00) + + def test_ratio_no_requirements(self): + BadgeRequirement.objects.filter(template=self.badge_template).delete() + self.assertEqual(self.progress.ratio, 0.00) + + +class CredlyBadgeAsBadgeDataTestCase(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username="test_user", + email="test@example.com", + full_name="Test User", + lms_user_id=1, + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.credential = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), + origin="test_origin", + name="test_template", + description="test_description", + icon="test_icon", + site=self.site, + ) + self.badge = CredlyBadge.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.credential), + credential_id=self.credential.id, + state=CredlyBadge.STATES.pending, + uuid=uuid.uuid4(), + ) + + def test_as_badge_data(self): + expected_badge_data = BadgeData( + uuid=str(self.badge.uuid), + user=UserData( + pii=UserPersonalData( + username=self.user.username, + email=self.user.email, + name=self.user.get_full_name(), + ), + id=self.user.lms_user_id, + is_active=self.user.is_active, + ), + template=BadgeTemplateData( + uuid=str(self.credential.uuid), + origin=self.credential.origin, + name=self.credential.name, + description=self.credential.description, + image_url=str(self.credential.icon), + ), + ) + actual_badge_data = self.badge.as_badge_data() + self.assertEqual(actual_badge_data, expected_badge_data) + + +class BadgePenaltyTestCase(TestCase): + def setUp(self): + self.fake = Faker() + self.badge_template = BadgeTemplate.objects.create( + uuid=self.fake.uuid4(), + name="test_template", + state="draft", + site=Site.objects.create(domain="test_domain", name="test_name"), + is_active=True, + ) + self.badge_requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + self.badge_penalty = BadgePenalty.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.student.registration.completed.v1", + ) + self.badge_penalty.requirements.add(self.badge_requirement) + + def test_apply_rules_with_empty_rules(self): + data = {"key": "value"} + self.assertFalse(self.badge_penalty.apply_rules(data)) + + def test_apply_rules_with_non_empty_rules(self): + data = {"key": "value"} + self.badge_penalty.rules.create(data_path="key", operator="eq", value="value") + self.assertTrue(self.badge_penalty.apply_rules(data)) + + def test_reset_requirements(self): + username = "test-username" + with patch("credentials.apps.badges.models.BadgeRequirement.reset") as mock_reset: + self.badge_penalty.reset_requirements(username) + mock_reset.assert_called_once_with(username) + + def test_is_active(self): + self.assertTrue(self.badge_penalty.is_active) + + +class IsGroupFulfilledTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.badge_requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + blend="group1", + ) + self.badge_requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.ccx.course.passing.status.updated.v1", + blend="group1", + ) + self.badge_requirement3 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + ) + self.username = "test_user" + + def test_is_group_fulfilled_with_fulfilled_requirements(self): + progress = BadgeProgress.objects.create(username=self.username, template=self.badge_template) + Fulfillment.objects.create(progress=progress, requirement=self.badge_requirement1) + + is_fulfilled = BadgeRequirement.is_group_fulfilled( + group="group1", template=self.badge_template, username=self.username + ) + + self.assertTrue(is_fulfilled) + + def test_is_group_fulfilled_with_unfulfilled_requirements(self): + is_fulfilled = BadgeRequirement.is_group_fulfilled( + group="group1", template=self.badge_template, username=self.username + ) + + self.assertFalse(is_fulfilled) + + def test_is_group_fulfilled_with_invalid_group(self): + is_fulfilled = BadgeRequirement.is_group_fulfilled( + group="invalid_group", template=self.badge_template, username=self.username + ) + + self.assertFalse(is_fulfilled) + + +class CredlyOrganizationTestCase(TestCase): + def setUp(self): + self.fake = Faker() + self.uuid = self.fake.uuid4() + self.organization = CredlyOrganization.objects.create( + uuid=self.uuid, api_key="test-api-key", name="test_organization" + ) + + def test_str_representation(self): + self.assertEqual(str(self.organization), "test_organization") + + def test_get_all_organization_ids(self): + organization_ids = [str(uuid) for uuid in CredlyOrganization.get_all_organization_ids()] + self.assertEqual(organization_ids, [self.uuid]) + + def test_get_preconfigured_organizations(self): + preconfigured_organizations = CredlyOrganization.get_preconfigured_organizations() + self.assertEqual( + preconfigured_organizations, + settings.BADGES_CONFIG["credly"].get("ORGANIZATIONS", {}), + ) + + def test_is_preconfigured(self): + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_preconfigured: + mock_get_preconfigured.return_value = {str(self.uuid): "Test Organization"} + self.assertTrue(self.organization.is_preconfigured) + mock_get_preconfigured.assert_called_once() + + +class BadgeProgressTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.badge_progress = BadgeProgress.objects.create(template=self.badge_template, username="test_user") + + def test_reset_progress(self): + Fulfillment.objects.create(progress=self.badge_progress) + self.assertEqual(Fulfillment.objects.filter(progress=self.badge_progress).count(), 1) + + self.badge_progress.reset() + + self.assertEqual(Fulfillment.objects.filter(progress=self.badge_progress).count(), 0) + + +class BadgePenaltyIsActiveTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + self.requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + self.penalty = BadgePenalty.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + ) + + def test_is_active(self): + self.penalty.template.is_active = True + self.assertTrue(self.penalty.is_active) + + def test_is_not_active(self): + self.penalty.template.is_active = False + self.assertFalse(self.penalty.is_active) + + +class DataRuleIsActiveTestCase(TestCase): + def setUp(self): + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + self.rule = DataRule.objects.create( + requirement=self.requirement, + data_path="course_passing_status.user.pii.username", + operator="eq", + value="cucumber1997", + ) + + def test_is_active(self): + self.rule.requirement.template.is_active = True + self.assertTrue(self.rule.is_active) + + self.rule.requirement.template.is_active = False + self.assertFalse(self.rule.is_active) + + +class BadgeTemplateTestCase(TestCase): + def test_by_uuid(self): + template_uuid = uuid.uuid4() + site = Site.objects.create(domain="test_domain", name="test_name") + badge_template = BadgeTemplate.objects.create(uuid=template_uuid, origin=BadgeTemplate.ORIGIN, site=site) + + retrieved_template = BadgeTemplate.by_uuid(template_uuid) + + self.assertEqual(retrieved_template, badge_template) + + +class CredlyBadgeTemplateTestCase(TestCase): + def setUp(self): + uuid4 = uuid.uuid4() + self.organization = CredlyOrganization.objects.create( + uuid=uuid4, api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid4, + name="test_template", + state="draft", + site=self.site, + ) + + def test_management_url(self): + credly_host_base_url = "https://example.com/" + with patch("credentials.apps.badges.models.get_credly_base_url") as mock_get_credly_base_url: + mock_get_credly_base_url.return_value = credly_host_base_url + expected_url = ( + f"{credly_host_base_url}mgmt/organizations/" + f"{self.organization.uuid}/badges/templates/{self.badge_template.uuid}/details" + ) + self.assertEqual(self.badge_template.management_url, expected_url) + mock_get_credly_base_url.assert_called_with(settings) + + +class PenaltyDataruleIsActiveTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = BadgeTemplate.objects.create( + uuid=uuid.uuid4(), name="test_template", state="draft", site=self.site + ) + self.credlybadge_template = CredlyBadgeTemplate.objects.create( + organization=self.organization, + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + ) + + self.requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + description="Test description", + ) + self.penalty = BadgePenalty.objects.create( + template=self.badge_template, + event_type="org.openedx.learning.course.passing.status.updated.v1", + ) + self.rule = PenaltyDataRule.objects.create( + penalty=self.penalty, + data_path="course_passing_status.user.pii.username", + operator="eq", + value="cucumber1997", + ) + + def test_is_active(self): + self.requirement.template.is_active = True + self.assertTrue(self.rule.is_active) + + self.requirement.template.is_active = False + self.assertFalse(self.rule.is_active) diff --git a/credentials/apps/badges/tests/test_services.py b/credentials/apps/badges/tests/test_services.py new file mode 100644 index 000000000..87dc685e2 --- /dev/null +++ b/credentials/apps/badges/tests/test_services.py @@ -0,0 +1,758 @@ +import uuid +from unittest.mock import MagicMock, patch + +from django.contrib.auth import get_user_model +from django.contrib.sites.models import Site +from django.test import TestCase +from opaque_keys.edx.keys import CourseKey +from openedx_events.learning.data import CourseData, CoursePassingStatusData, UserData, UserPersonalData + +from credentials.apps.badges.exceptions import BadgesProcessingError +from credentials.apps.badges.models import ( + BadgePenalty, + BadgeProgress, + BadgeRequirement, + BadgeTemplate, + CredlyBadgeTemplate, + CredlyOrganization, + DataRule, + Fulfillment, + PenaltyDataRule, +) +from credentials.apps.badges.processing.generic import identify_user, process_event +from credentials.apps.badges.processing.progression import discover_requirements, process_requirements +from credentials.apps.badges.processing.regression import discover_penalties, process_penalties +from credentials.apps.badges.signals import BADGE_PROGRESS_COMPLETE +from credentials.apps.badges.signals.handlers import handle_badge_completion + + +COURSE_PASSING_EVENT = "org.openedx.learning.course.passing.status.updated.v1" +COURSE_PASSING_DATA = CoursePassingStatusData( + is_passing=True, + course=CourseData(course_key=CourseKey.from_string("course-v1:edX+DemoX.1+2014"), display_name="A"), + user=UserData( + id=1, + is_active=True, + pii=UserPersonalData(username="test_username", email="test_email", name="John Doe"), + ), +) +User = get_user_model() + + +class BadgeRequirementDiscoveryTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + 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): + BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + ) + BadgeRequirement.objects.create( + template=self.badge_template, + event_type=self.CCX_COURSE_PASSING_EVENT, + description="Test ccx course passing award description", + ) + BadgeRequirement.objects.create( + template=self.badge_template, + event_type=self.CCX_COURSE_PASSING_EVENT, + description="Test ccx course passing revoke description", + ) + course_passing_requirements = discover_requirements(event_type=COURSE_PASSING_EVENT) + ccx_course_passing_requirements = discover_requirements(event_type=self.CCX_COURSE_PASSING_EVENT) + self.assertEqual(course_passing_requirements.count(), 1) + self.assertEqual(ccx_course_passing_requirements.count(), 2) + self.assertEqual(course_passing_requirements[0].description, "Test course passing award description") + self.assertEqual(ccx_course_passing_requirements[0].description, "Test ccx course passing award description") + self.assertEqual(ccx_course_passing_requirements[1].description, "Test ccx course passing revoke description") + + +class BadgePenaltyDiscoveryTestCase(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + 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): + penalty1 = BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT) + penalty1.requirements.add( + BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + ) + ) + penalty2 = BadgePenalty.objects.create(template=self.badge_template, event_type=self.CCX_COURSE_PASSING_EVENT) + penalty2.requirements.add( + BadgeRequirement.objects.create( + template=self.badge_template, + event_type=self.CCX_COURSE_PASSING_EVENT, + description="Test ccx course passing award description", + ) + ) + penalty3 = BadgePenalty.objects.create(template=self.badge_template, event_type=self.CCX_COURSE_PASSING_EVENT) + penalty3.requirements.add( + BadgeRequirement.objects.create( + template=self.badge_template, + event_type=self.CCX_COURSE_PASSING_EVENT, + description="Test ccx course passing revoke description", + ) + ) + course_passing_penalties = discover_penalties(event_type=COURSE_PASSING_EVENT) + ccx_course_passing_penalties = discover_penalties(event_type=self.CCX_COURSE_PASSING_EVENT) + self.assertEqual(course_passing_penalties.count(), 1) + self.assertEqual(ccx_course_passing_penalties.count(), 2) + self.assertEqual( + course_passing_penalties[0].requirements.first().description, "Test course passing award description" + ) + self.assertEqual( + ccx_course_passing_penalties[0].requirements.first().description, + "Test ccx course passing award description", + ) + self.assertEqual( + ccx_course_passing_penalties[1].requirements.first().description, + "Test ccx course passing revoke description", + ) + + +class TestProcessPenalties(TestCase): + def setUp(self): + self.user = User.objects.create_user( + username="test_username", email="test@example.com", full_name="Test User", lms_user_id=1 + ) + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + 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, + ) + + self.CCX_COURSE_PASSING_EVENT = "org.openedx.learning.ccx.course.passing.status.updated.v1" + + def test_process_penalties_all_datarules_success(self): + requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 1", + ) + requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 2", + ) + DataRule.objects.create( + requirement=requirement1, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement2, + data_path="course.display_name", + operator="ne", + value="B", + ) + + progress = BadgeProgress.objects.create(username="test_username", template=self.badge_template) + Fulfillment.objects.create(progress=progress, requirement=requirement1) + Fulfillment.objects.create(progress=progress, requirement=requirement2) + + self.assertEqual(BadgeProgress.objects.filter(username="test_username").count(), 1) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 2) + self.assertEqual(Fulfillment.objects.filter(progress=progress, requirement=requirement1).count(), 1) + self.assertEqual(Fulfillment.objects.filter(progress=progress, requirement=requirement1).count(), 1) + + bp = BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT) + bp.requirements.set( + (requirement1, requirement2), + ) + PenaltyDataRule.objects.create( + penalty=bp, + data_path="course.display_name", + operator="ne", + value="test_username1", + ) + self.badge_template.is_active = True + self.badge_template.save() + process_penalties(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 0) + + def test_process_penalties_one_datarule_fail(self): + requirement1 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 1", + ) + requirement2 = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description 2", + ) + DataRule.objects.create( + requirement=requirement1, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement2, + data_path="course.display_name", + operator="eq", + value="B", + ) + + progress = BadgeProgress.objects.create(username="test_username") + Fulfillment.objects.create(progress=progress, requirement=requirement1) + Fulfillment.objects.create(progress=progress, requirement=requirement2) + + self.assertEqual(BadgeProgress.objects.filter(username="test_username").count(), 1) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 2) + 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( + (requirement1, requirement2), + ) + PenaltyDataRule.objects.create( + penalty=BadgePenalty.objects.first(), + data_path="course.display_name", + operator="eq", + value="A", + ) + PenaltyDataRule.objects.create( + penalty=BadgePenalty.objects.first(), + data_path="course.display_name", + operator="ne", + value="A", + ) + process_penalties(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 2) + + def test_process_single_requirement_penalty(self): + requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + ) + DataRule.objects.create( + requirement=requirement, + data_path="course.display_name", + operator="ne", + value="B", + ) + progress = BadgeProgress.objects.create(username="test_username", template=self.badge_template) + Fulfillment.objects.create(progress=progress, requirement=requirement) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 1) + + penalty = BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT) + penalty.requirements.add(requirement) + PenaltyDataRule.objects.create( + penalty=penalty, + data_path="course.display_name", + operator="eq", + value="A", + ) + process_penalties(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 0) + + def test_process_one_of_grouped_requirements_penalty(self): + requirement_a = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + blend="a_or_b", + ) + requirement_b = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + blend="a_or_b", + ) + DataRule.objects.create( + requirement=requirement_a, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement_b, + data_path="course.display_name", + operator="eq", + value="A", + ) + progress = BadgeProgress.objects.create(username="test_username", template=self.badge_template) + Fulfillment.objects.create(progress=progress, requirement=requirement_a) + Fulfillment.objects.create(progress=progress, requirement=requirement_b) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 2) + + penalty = BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT) + penalty.requirements.add(requirement_b) + PenaltyDataRule.objects.create( + penalty=penalty, + data_path="course.display_name", + operator="ne", + value="B", + ) + process_penalties(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 1) + + def test_process_mixed_penalty(self): + requirement_a = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + blend="a_or_b", + ) + requirement_b = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + blend="a_or_b", + ) + requirement_c = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="Test course passing award description", + ) + DataRule.objects.create( + requirement=requirement_a, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement_b, + data_path="course.display_name", + operator="eq", + value="B", + ) + DataRule.objects.create( + requirement=requirement_c, + data_path="course.display_name", + operator="ne", + value="C", + ) + progress = BadgeProgress.objects.create(username="test_username", template=self.badge_template) + Fulfillment.objects.create(progress=progress, requirement=requirement_a) + Fulfillment.objects.create(progress=progress, requirement=requirement_c) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 2) + + penalty = BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT) + penalty.requirements.add(requirement_a, requirement_c) + PenaltyDataRule.objects.create( + penalty=penalty, + data_path="course.display_name", + operator="ne", + value="B", + ) + process_penalties(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(progress=progress).count(), 0) + + +class TestProcessRequirements(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test-api-key", name="test_organization" + ) + 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, + 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) + + # disconnect BADGE_PROGRESS_COMPLETE signal + BADGE_PROGRESS_COMPLETE.disconnect(handle_badge_completion) + + def tearDown(self): + BADGE_PROGRESS_COMPLETE.connect(handle_badge_completion) + + # test cases + # A course completion - course A w/o a group; + # A or B course completion - courses A, B have the same group value; + # A or B or C course completion - courses A, B, C have the same group value; + # A or - courses A is the only course in the group; + # (A or B) and C - A, B have the same group value; course C w/o a group; + # (A or B) and (C or D) - courses A, B have the same group value; courses C, D have the same group value; + + def test_course_a_completion(self): + """ + Test course A completion. + + A course completion - course A w/o a group. + """ + + requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A course passing award description", + ) + DataRule.objects.create( + requirement=requirement, + data_path="course.display_name", + operator="eq", + value="A", + ) + process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement).count(), 1) + self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + def test_course_a_or_b_completion(self): + """ + Test course A or B completion. + + A or B course completion - courses A, B have the same group value. + """ + + requirement_a = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B course passing award description", + blend="a_or_b", + ) + requirement_b = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B course passing award description", + blend="a_or_b", + ) + DataRule.objects.create( + requirement=requirement_a, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement_b, + data_path="course.display_name", + operator="eq", + value="B", + ) + process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_a).count(), 1) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_b).count(), 0) + self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + def test_course_a_or_b_or_c_completion(self): + """ + Test course A or B or C completion. + + A or B or C course completion - courses A, B, C have the same group value. + """ + + requirement_a = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B or C course passing award description", + blend="a_or_b_or_c", + ) + requirement_b = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B or C course passing award description", + blend="a_or_b_or_c", + ) + requirement_c = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B or C course passing award description", + blend="a_or_b_or_c", + ) + DataRule.objects.create( + requirement=requirement_a, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement_b, + data_path="course.display_name", + operator="eq", + value="B", + ) + DataRule.objects.create( + requirement=requirement_c, + data_path="course.display_name", + operator="eq", + value="C", + ) + process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_a).count(), 1) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_b).count(), 0) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_c).count(), 0) + self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + def test_course_a_or_completion(self): + """ + Test course A or completion. + + A or - courses A is the only course in the group. + """ + + requirement = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or course passing award description", + blend="a_or", + ) + DataRule.objects.create( + requirement=requirement, + data_path="course.display_name", + operator="eq", + value="A", + ) + process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement).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_completion(self): + """ + Test course A or B and C completion. + + (A or B) and C - A, B have the same group value; course C w/o a group. + """ + + requirement_a = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B course passing award description", + blend="a_or_b", + ) + requirement_b = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B course passing award description", + blend="a_or_b", + ) + requirement_c = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="C course passing award description", + ) + DataRule.objects.create( + requirement=requirement_a, + data_path="course.display_name", + operator="ne", + value="A", + ) + DataRule.objects.create( + requirement=requirement_c, + data_path="course.display_name", + 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) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_c).count(), 1) + self.assertFalse(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + DataRule.objects.create( + requirement=requirement_b, + data_path="course.display_name", + 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(), 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): + """ + Test course A or B and C or D completion. + + (A or B) and (C or D) - courses A, B have the same group value; courses C, D have the same group value. + """ + + requirement_a = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B course passing award description", + blend="a_or_b", + ) + requirement_b = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="A or B course passing award description", + blend="a_or_b", + ) + requirement_c = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="C or D course passing award description", + blend="c_or_d", + ) + requirement_d = BadgeRequirement.objects.create( + template=self.badge_template, + event_type=COURSE_PASSING_EVENT, + description="C or D course passing award description", + blend="c_or_d", + ) + DataRule.objects.create( + requirement=requirement_a, + data_path="course.display_name", + operator="eq", + value="A", + ) + DataRule.objects.create( + requirement=requirement_b, + data_path="course.display_name", + operator="eq", + value="B", + ) + DataRule.objects.create( + requirement=requirement_d, + data_path="course.display_name", + operator="eq", + value="D", + ) + + self.assertFalse(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_a).count(), 1) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_b).count(), 0) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_c).count(), 0) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_d).count(), 0) + self.assertFalse(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + DataRule.objects.create( + requirement=requirement_c, + data_path="course.display_name", + operator="eq", + value="A", + ) + process_requirements(COURSE_PASSING_EVENT, "test_username", COURSE_PASSING_DATA) + + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_a).count(), 1) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_b).count(), 0) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_c).count(), 1) + self.assertEqual(Fulfillment.objects.filter(requirement=requirement_d).count(), 0) + self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + +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") + + def test_identify_user_not_found(self): + event_type = "unknown_event_type" + event_payload = None + + with self.assertRaises(BadgesProcessingError) as cm: + identify_user(event_type="unknown_event_type", event_payload=event_payload) + + self.assertEqual( + str(cm.exception), + f"User data cannot be found (got: None): {event_payload}. " + f"Does event {event_type} include user data at all?", + ) + + +def mock_progress_regress(*args, **kwargs): + return None + + +class TestProcessEvent(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test_api_key", name="test_organization" + ) + 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, + organization=self.organization, + is_active=True, + ) + DataRule.objects.create( + requirement=BadgeRequirement.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT), + data_path="is_passing", + operator="eq", + value="True", + ) + PenaltyDataRule.objects.create( + penalty=BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT), + data_path="is_passing", + operator="eq", + value="False", + ) + self.sender = MagicMock() + self.sender.event_type = COURSE_PASSING_EVENT + + @patch.object(BadgeProgress, "progress", mock_progress_regress) + def test_process_event_passing(self): + event_payload = COURSE_PASSING_DATA + process_event(sender=self.sender, kwargs=event_payload) + self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + def test_process_event_not_passing(self): + event_payload = CoursePassingStatusData( + is_passing=False, + course=CourseData(course_key=CourseKey.from_string("course-v1:edX+DemoX.1+2014"), display_name="A"), + user=UserData( + id=1, + is_active=True, + pii=UserPersonalData(username="test_username", email="test_email", name="John Doe"), + ), + ) + process_event(sender=self.sender, kwargs=event_payload) + self.assertFalse(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + @patch.object(BadgeProgress, "regress", mock_progress_regress) + def test_process_event_not_found(self): + sender = MagicMock() + sender.event_type = "unknown_event_type" + event_payload = None + + with patch("credentials.apps.badges.processing.generic.logger.error") as mock_event_not_found: + process_event(sender=sender, kwargs=event_payload) + mock_event_not_found.assert_called_once() + + def test_process_event_no_user_data(self): + event_payload = CoursePassingStatusData( + is_passing=True, + course=CourseData(course_key=CourseKey.from_string("course-v1:edX+DemoX.1+2014"), display_name="A"), + user=None, + ) + + with patch("credentials.apps.badges.processing.generic.logger.error") as mock_no_user_data: + process_event(sender=self.sender, kwargs=event_payload) + mock_no_user_data.assert_called_once() diff --git a/credentials/apps/badges/tests/test_signals.py b/credentials/apps/badges/tests/test_signals.py new file mode 100644 index 000000000..0a79663b4 --- /dev/null +++ b/credentials/apps/badges/tests/test_signals.py @@ -0,0 +1,67 @@ +from unittest import mock + +import faker +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from credentials.apps.badges.issuers import CredlyBadgeTemplateIssuer +from credentials.apps.badges.models import CredlyBadge, CredlyBadgeTemplate, CredlyOrganization +from credentials.apps.badges.signals.signals import BADGE_PROGRESS_COMPLETE, BADGE_PROGRESS_INCOMPLETE + + +class BadgeSignalReceiverTestCase(TestCase): + def setUp(self): + # Create a test badge template + fake = faker.Faker() + credly_organization = CredlyOrganization.objects.create( + uuid=fake.uuid4(), api_key=fake.uuid4(), name=fake.word() + ) + self.badge_template = CredlyBadgeTemplate.objects.create( + name="test", site_id=1, organization=credly_organization + ) + + def test_progression_signal_emission_and_receiver_execution(self): + # Emit the signal + with mock.patch("credentials.apps.badges.issuers.notify_badge_awarded"): + with mock.patch.object(CredlyBadgeTemplateIssuer, "issue_credly_badge"): + BADGE_PROGRESS_COMPLETE.send( + sender=self, + username="test_user", + badge_template_id=self.badge_template.id, + ) + + # UserCredential object + user_credential = CredlyBadge.objects.filter( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + ) + + # Check if user credential is created + self.assertTrue(user_credential.exists()) + + # Check if user credential status is 'awarded' + self.assertEqual(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"): + with mock.patch.object(CredlyBadgeTemplateIssuer, "revoke_credly_badge"): + BADGE_PROGRESS_INCOMPLETE.send( + sender=self, + username="test_user", + badge_template_id=self.badge_template.id, + ) + + # UserCredential object + user_credential = CredlyBadge.objects.filter( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + ) + + # Check if user credential is created + self.assertTrue(user_credential.exists()) + + # Check if user credential status is 'revoked' + self.assertEqual(user_credential[0].status, "revoked") diff --git a/credentials/apps/badges/tests/test_utils.py b/credentials/apps/badges/tests/test_utils.py new file mode 100644 index 000000000..2d79f873c --- /dev/null +++ b/credentials/apps/badges/tests/test_utils.py @@ -0,0 +1,242 @@ +import unittest +from datetime import datetime +from unittest.mock import patch + +from attr import asdict +from django.conf import settings +from opaque_keys.edx.keys import CourseKey +from openedx_events.learning.data import CourseData, CoursePassingStatusData, UserData, UserPersonalData + +from credentials.apps.badges.checks import badges_checks +from credentials.apps.badges.credly.utils import get_credly_api_base_url, get_credly_base_url +from credentials.apps.badges.utils import ( + credly_check, + extract_payload, + get_event_type_attr_type_by_keypath, + get_event_type_keypaths, + get_user_data, + keypath, +) + + +COURSE_PASSING_EVENT = "org.openedx.learning.course.passing.status.updated.v1" + + +class TestKeypath(unittest.TestCase): + def test_keypath_exists(self): + payload = { + "course": { + "key": "105-3332", + } + } + result = keypath(payload, "course.key") + self.assertEqual(result, "105-3332") + + def test_keypath_not_exists(self): + payload = { + "course": { + "id": "105-3332", + } + } + result = keypath(payload, "course.key") + self.assertIsNone(result) + + def test_keypath_deep(self): + payload = {"course": {"data": {"identification": {"id": 25}}}} + result = keypath(payload, "course.data.identification.id") + self.assertEqual(result, 25) + + def test_keypath_invalid_path(self): + payload = { + "course": { + "key": "105-3332", + } + } + result = keypath(payload, "course.id") + self.assertIsNone(result) + + +class TestGetUserData(unittest.TestCase): + def setUp(self): + self.course_data_1 = CourseData( + course_key="CS101", + display_name="Introduction to Computer Science", + start=datetime(2024, 4, 1), + end=datetime(2024, 6, 1), + ) + self.user_data_1 = UserData( + id=1, is_active=True, pii=UserPersonalData(username="user1", email="user1@example.com", name="John Doe") + ) + + self.course_data_2 = CourseData( + course_key="PHY101", + display_name="Introduction to Physics", + start=datetime(2024, 4, 15), + end=datetime(2024, 7, 15), + ) + self.user_data_2 = UserData( + id=2, is_active=False, pii=UserPersonalData(username="user2", email="user2@example.com", name="Jane Doe") + ) + + self.passing_status_1 = { + "course_passing_status": CoursePassingStatusData( + is_passing=True, course=self.course_data_1, user=self.user_data_1 + ) + } + + def test_get_user_data_from_course_enrollment(self): + result_1 = get_user_data(extract_payload(self.passing_status_1)) + self.assertIsNotNone(result_1) + self.assertEqual(result_1.id, 1) + self.assertTrue(result_1.is_active) + self.assertEqual(result_1.pii.username, "user1") + self.assertEqual(result_1.pii.email, "user1@example.com") + self.assertEqual(result_1.pii.name, "John Doe") + + +class TestExtractPayload(unittest.TestCase): + def setUp(self): + self.course_data = CourseData( + course_key="105-3332", + display_name="Introduction to Computer Science", + start=datetime(2024, 4, 1), + end=datetime(2024, 6, 1), + ) + + 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) + public_signal_kwargs = {"course_passing_status": course_passing_status} + result = extract_payload(public_signal_kwargs) + self.assertIsNotNone(result) + self.assertEqual(asdict(result), asdict(course_passing_status)) + + def test_extract_payload_empty_payload(self): + public_signal_kwargs = {"public_signal_kwargs": {}} + result = extract_payload(**public_signal_kwargs) + self.assertIsNone(result) + + +class TestBadgesChecks(unittest.TestCase): + @patch("credentials.apps.badges.checks.get_badging_event_types") + def test_badges_checks_empty_events(self, mock_get_badging_event_types): + mock_get_badging_event_types.return_value = [] + errors = badges_checks() + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].msg, "BADGES_CONFIG['events'] must include at least one event.") + self.assertEqual(errors[0].hint, "Add at least one event to BADGES_CONFIG['events'] setting.") + self.assertEqual(errors[0].id, "badges.E001") + + @patch("credentials.apps.badges.checks.get_badging_event_types") + def test_badges_checks_non_empty_events(self, mock_get_badging_event_types): + mock_get_badging_event_types.return_value = ["event1", "event2"] + errors = badges_checks() + self.assertEqual(len(errors), 0) + + @patch("credentials.apps.badges.checks.credly_check") + def test_badges_checks_credly_not_configured(self, mock_credly_check): + mock_credly_check.return_value = False + errors = badges_checks() + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].msg, "Credly settings are not properly configured.") + self.assertEqual(errors[0].hint, "Make sure all required settings are present in BADGES_CONFIG['credly'].") + self.assertEqual(errors[0].id, "badges.E002") + + +class TestCredlyCheck(unittest.TestCase): + def test_credly_configured(self): + settings.BADGES_CONFIG = { + "credly": { + "CREDLY_BASE_URL": "https://www.credly.com", + "CREDLY_API_BASE_URL": "https://api.credly.com", + "CREDLY_SANDBOX_BASE_URL": "https://sandbox.credly.com", + "CREDLY_SANDBOX_API_BASE_URL": "https://sandbox.api.credly.com", + "USE_SANDBOX": True, + } + } + result = credly_check() + self.assertTrue(result) + + def test_credly_not_configured(self): + settings.BADGES_CONFIG = {} + result = credly_check() + self.assertFalse(result) + + def test_credly_missing_keys(self): + settings.BADGES_CONFIG = { + "credly": { + "CREDLY_BASE_URL": "https://www.credly.com", + "CREDLY_API_BASE_URL": "https://api.credly.com", + "USE_SANDBOX": True, + } + } + result = credly_check() + self.assertFalse(result) + + +class TestGetEventTypeKeypaths(unittest.TestCase): + def test_get_event_type_keypaths(self): + result = get_event_type_keypaths(COURSE_PASSING_EVENT) + + 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"] = { + "CREDLY_BASE_URL": "https://www.credly.com", + "CREDLY_SANDBOX_BASE_URL": "https://sandbox.credly.com", + "USE_SANDBOX": True, + } + result = get_credly_base_url(settings) + self.assertEqual(result, "https://sandbox.credly.com") + + def test_get_credly_base_url_production(self): + settings.BADGES_CONFIG["credly"] = { + "CREDLY_BASE_URL": "https://www.credly.com", + "CREDLY_SANDBOX_BASE_URL": "https://sandbox.credly.com", + "USE_SANDBOX": False, + } + result = get_credly_base_url(settings) + self.assertEqual(result, "https://www.credly.com") + + +class TestGetCredlyApiBaseUrl(unittest.TestCase): + def test_get_credly_api_base_url_sandbox(self): + settings.BADGES_CONFIG["credly"] = { + "CREDLY_API_BASE_URL": "https://api.credly.com", + "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") + + def test_get_credly_api_base_url_production(self): + settings.BADGES_CONFIG["credly"] = { + "CREDLY_API_BASE_URL": "https://api.credly.com", + "CREDLY_SANDBOX_API_BASE_URL": "https://sandbox.api.credly.com", + "USE_SANDBOX": False, + } + result = get_credly_api_base_url(settings) + self.assertEqual(result, "https://api.credly.com") + + +class TestGetEventTypeAttrTypeByKeypath(unittest.TestCase): + def test_get_event_type_attr_type_by_keypath(self): + key_path = "course.course_key" + result = get_event_type_attr_type_by_keypath(COURSE_PASSING_EVENT, key_path) + self.assertEqual(result, CourseKey) + + def test_get_event_type_attr_type_by_keypath_bool(self): + key_path = "is_passing" + result = get_event_type_attr_type_by_keypath(COURSE_PASSING_EVENT, key_path) + self.assertEqual(result, bool) + + def test_get_event_type_attr_type_by_keypath_not_found(self): + key_path = "course.id" + result = get_event_type_attr_type_by_keypath(COURSE_PASSING_EVENT, key_path) + self.assertIsNone(result) diff --git a/credentials/apps/badges/tests/test_webhooks.py b/credentials/apps/badges/tests/test_webhooks.py new file mode 100644 index 000000000..d9365fcfd --- /dev/null +++ b/credentials/apps/badges/tests/test_webhooks.py @@ -0,0 +1,122 @@ +from unittest.mock import MagicMock, patch + +from django.test import TestCase +from django.test.client import RequestFactory +from faker import Faker + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.webhooks import CredlyWebhook +from credentials.apps.badges.models import CredlyBadgeTemplate, CredlyOrganization + + +def get_organization(self, organization_id): # pylint: disable=unused-argument + organization = MagicMock(spec=CredlyOrganization) + organization.uuid = organization_id + organization.api_key = "test_api_key" + return organization + + +def perform_request(self, method, endpoint, data=None): # pylint: disable=unused-argument + return {"key": "value"} + + +class CredlyWebhookTestCase(TestCase): + def setUp(self): + self.rf = RequestFactory() + self.fake = Faker() + self.organization = CredlyOrganization.objects.create(uuid=self.fake.uuid4(), api_key="test_api_key") + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_created_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.CredlyWebhook.handle_badge_template_created_event" + ) as mock_handle: + req = self.rf.post( + "/credly/webhook/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.organization.uuid, + "event_type": "badge_template.created", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + res = CredlyWebhook.as_view()(req) + self.assertEqual(res.status_code, 204) + mock_handle.assert_called_once() + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_changed_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.CredlyWebhook.handle_badge_template_changed_event" + ) as mock_handle: + req = self.rf.post( + "/credly/webhook/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.organization.uuid, + "event_type": "badge_template.changed", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + res = CredlyWebhook.as_view()(req) + self.assertEqual(res.status_code, 204) + mock_handle.assert_called_once() + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_deleted_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.CredlyWebhook.handle_badge_template_deleted_event" + ) as mock_handle: + req = self.rf.post( + "/credly/webhook/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.fake.uuid4(), + "event_type": "badge_template.deleted", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + res = CredlyWebhook.as_view()(req) + self.assertEqual(res.status_code, 204) + mock_handle.assert_called_once() + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_nonexistent_event(self): + with patch("credentials.apps.badges.credly.webhooks.logger.error") as mock_handle: + req = self.rf.post( + "/credly/webhookd/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.fake.uuid4(), + "event_type": "unknown_event", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + CredlyWebhook.as_view()(req) + mock_handle.assert_called_once() + + def test_handle_badge_template_deleted_event(self): + request_data = { + "organization_id": "test_organization_id", + "id": "test_event_id", + "event_type": "badge_template.deleted", + "data": { + "badge_template": { + "id": self.fake.uuid4(), + "owner": {"id": self.fake.uuid4()}, + "name": "Test Template", + "state": "active", + "description": "Test Description", + "image_url": "http://example.com/image.png", + } + }, + } + request = self.rf.post("/credly/webhook/", data=request_data) + + CredlyWebhook.handle_badge_template_deleted_event(request, request_data) + + self.assertEqual(CredlyBadgeTemplate.objects.count(), 0) diff --git a/credentials/apps/badges/toggles.py b/credentials/apps/badges/toggles.py new file mode 100644 index 000000000..b82d510d5 --- /dev/null +++ b/credentials/apps/badges/toggles.py @@ -0,0 +1,37 @@ +""" +Badges app toggles. +""" + +from edx_toggles.toggles import SettingToggle + + +# .. toggle_name: BADGES_ENABLED +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Determines if the Credentials IDA uses badges functionality. +# .. toggle_life_expectancy: permanent +# .. toggle_permanent_justification: Badges are optional for usage. +# .. toggle_creation_date: 2024-01-12 +# .. toggle_use_cases: open_edx +ENABLE_BADGES = SettingToggle("BADGES_ENABLED", default=False, module_name=__name__) + + +def is_badges_enabled(): + """ + Check main feature flag. + """ + + return ENABLE_BADGES.is_enabled() + + +def check_badges_enabled(func): + """ + Decorator for checking the applicability of a badges app. + """ + + def wrapper(*args, **kwargs): + if is_badges_enabled(): + return func(*args, **kwargs) + return None + + return wrapper diff --git a/credentials/apps/badges/urls.py b/credentials/apps/badges/urls.py new file mode 100644 index 000000000..c2d906812 --- /dev/null +++ b/credentials/apps/badges/urls.py @@ -0,0 +1,12 @@ +""" +URLs for badges. +""" + +from django.urls import path + +from .credly.webhooks import CredlyWebhook + + +urlpatterns = [ + path("credly/webhook/", CredlyWebhook.as_view(), name="credly-webhook"), +] diff --git a/credentials/apps/badges/utils.py b/credentials/apps/badges/utils.py new file mode 100644 index 000000000..719ae66ea --- /dev/null +++ b/credentials/apps/badges/utils.py @@ -0,0 +1,203 @@ +import inspect +from typing import Union + +import attr +from attrs import asdict +from django.conf import settings +from openedx_events.learning.data import UserData +from openedx_events.tooling import OpenEdxPublicSignal + + +def get_badging_event_types(): + """ + Figures out which events are available for badges. + """ + return settings.BADGES_CONFIG.get("events", []) + + +def credly_check(): + """ + Checks if Credly is configured. + """ + + credly_settings = settings.BADGES_CONFIG.get("credly", None) + if credly_settings is None: + return False + keys = ( + "CREDLY_BASE_URL", + "CREDLY_API_BASE_URL", + "CREDLY_SANDBOX_BASE_URL", + "CREDLY_SANDBOX_API_BASE_URL", + "USE_SANDBOX", + ) + return all(key in credly_settings.keys() for key in keys) + + +def keypath(payload, keys_path): + """ + Retrieve the value from a nested dictionary using a dot-separated key path. + + Traverses a nested dictionary `payload` to find the value specified by the dot-separated + key path `keys_path`. Each key in `keys_path` represents a level in the nested dictionary. + + Parameters: + - payload (dict): The nested dictionary to search. + - keys_path (str): The dot-separated path of keys to traverse in the dictionary. + + Returns: + - The value found at the specified key path in the dictionary, or None if any key in the path + does not exist or the traversal leads to a non-dictionary object before reaching the final key. + + Example: + >>> payload = {'a': {'b': {'c': 1}}} + >>> keypath(payload, 'a.b.c') + 1 + >>> keypath(payload, 'a.b.d') + None + """ + + keys = keys_path.split(".") + current = payload + + def traverse(current, keys): + """ + Recursive function to traverse the dictionary. + """ + + if not keys: + return current + key = keys[0] + if attr.has(current): + current = asdict(current) + if isinstance(current, dict) and key in current: + return traverse(current[key], keys[1:]) + else: + return None + + return traverse(current, keys) + + +def get_user_data(data: attr.s) -> UserData: + """ + Extracts UserData object from any dataclass that contains UserData as a field. + + Parameters: + - data: Input dict that contains attr class, which has UserData somewhere deep. + + Returns: + UserData: UserData object contained within the input dataclass. + """ + + if isinstance(data, UserData): + return data + + for _, attr_value in inspect.getmembers(data): + if isinstance(attr_value, UserData): + return attr_value + elif attr.has(attr_value): + user_data = get_user_data(attr_value) + if user_data: + return user_data + return None + + +def extract_payload(public_signal_kwargs: dict) -> Union[None, attr.s]: + """ + Extracts the event payload from the event data. + + Parameters: + - public_signal_kwargs: The event data. + + Returns: + attr.s: The extracted event payload. + """ + for value in public_signal_kwargs.values(): + if attr.has(value): + return value + return None + + +def get_event_type_data(event_type: str) -> attr.s: + """ + Extracts the dataclass for a given event type. + + Parameters: + - event_type: The event type to extract dataclass for. + + Returns: + attr.s: The dataclass for the given event type. + """ + + signal = OpenEdxPublicSignal.get_signal_by_type(event_type) + return extract_payload(signal.init_data) + + +def get_event_type_keypaths(event_type: str) -> list: + """ + Extracts all possible keypaths for a given event type. + + Parameters: + - event_type: The event type to extract keypaths for. + + Returns: + list: A list of all possible keypaths for the given event type. + """ + + data = get_event_type_data(event_type) + + def get_data_keypaths(data): + """ + Extracts all possible keypaths for a given dataclass. + """ + + 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)] + else: + keypaths.append(field.name) + return keypaths + + 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) + if f"{field.name}.{keypath}" not in settings.BADGES_CONFIG.get("rules", {}).get("ignored_keypaths", []) + ] + else: + keypaths.append(field.name) + return keypaths + + +def get_event_type_attr_type_by_keypath(event_type: str, key_path: str): + """ + Extracts the attribute type for a given keypath in the event type. + + Parameters: + - event_type: The event type to extract dataclass for. + - key_path: The keypath to extract attribute type for. + + Returns: + type: The attribute type for the given keypath in the event data. + """ + + data = get_event_type_data(event_type) + data_attrs = attr.fields(data) + + def get_attr_type_by_keypath(data_attrs, key_path): + """ + Extracts the attribute type for a given keypath in the dataclass. + """ + + keypath_parts = key_path.split(".") + for attr_ in data_attrs: + if attr_.name == keypath_parts[0]: + if len(keypath_parts) == 1: + return attr_.type + 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, key_path) diff --git a/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py b/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py new file mode 100644 index 000000000..5430aa8e5 --- /dev/null +++ b/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.13 on 2024-06-11 17:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("credentials", "0029_signatory_not_mandatory"), + ] + + operations = [ + migrations.AlterField( + model_name="usercredential", + name="credential_content_type", + field=models.ForeignKey( + limit_choices_to={"model__in": ("coursecertificate", "programcertificate", "credlybadgetemplate")}, + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ] diff --git a/credentials/apps/credentials/models.py b/credentials/apps/credentials/models.py index 27901e59e..368d43284 100644 --- a/credentials/apps/credentials/models.py +++ b/credentials/apps/credentials/models.py @@ -168,7 +168,7 @@ class UserCredential(TimeStampedModel): credential_content_type = models.ForeignKey( ContentType, - limit_choices_to={"model__in": ("coursecertificate", "programcertificate")}, + limit_choices_to={"model__in": ("coursecertificate", "programcertificate", "credlybadgetemplate")}, on_delete=models.CASCADE, ) credential_id = models.PositiveIntegerField() diff --git a/credentials/apps/credentials/tests/test_api.py b/credentials/apps/credentials/tests/test_api.py index f49e6aa7f..18dcc6e73 100644 --- a/credentials/apps/credentials/tests/test_api.py +++ b/credentials/apps/credentials/tests/test_api.py @@ -464,8 +464,8 @@ def test_award_course_credential(self): assert credential.username == self.user.username assert credential.credential_id == course_cert_config.id assert credential.status == "awarded" - # 12 is the content type for "Course Certificate" - assert credential.credential_content_type_id == 12 + # 22 is the content type for "Course Certificate" + assert credential.credential_content_type_id == 22 def test_revoke_course_credential(self): """ @@ -488,8 +488,8 @@ def test_revoke_course_credential(self): assert credential.username == self.user.username assert credential.credential_id == course_cert_config.id assert credential.status == "revoked" - # 12 is the content type for "Course Certificate" - assert credential.credential_content_type_id == 12 + # 22 is the content type for "Course Certificate" + assert credential.credential_content_type_id == 22 def test_update_existing_cert(self): """ @@ -555,8 +555,8 @@ def test_award_course_cert_no_course_certificate(self): assert credential.username == self.user.username assert credential.credential_id == course_cert_config.id assert credential.status == "awarded" - # 12 is the content type for "Course Certificate" - assert credential.credential_content_type_id == 12 + # 22 is the content type for "Course Certificate" + assert credential.credential_content_type_id == 22 def test_award_course_cert_no_course_certificate_exception_occurs(self): """ diff --git a/credentials/conf/locale/eo/LC_MESSAGES/django.mo b/credentials/conf/locale/eo/LC_MESSAGES/django.mo index ee2f42d9c97f1b1ccd3d135625ebb55bbdaabea0..66074e39aa072cfb5f645c60c94be5db85f3ea08 100644 GIT binary patch delta 9500 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 diff --git a/credentials/conf/locale/eo/LC_MESSAGES/django.po b/credentials/conf/locale/eo/LC_MESSAGES/django.po index f7bf930bb..190bb2654 100644 --- a/credentials/conf/locale/eo/LC_MESSAGES/django.po +++ b/credentials/conf/locale/eo/LC_MESSAGES/django.po @@ -17,6 +17,253 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: apps/badges/admin.py +msgid "No rules specified." +msgstr "Nö rülés spéçïfïéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#" + +#: apps/badges/admin.py +msgid "Badge templates were successfully updated." +msgstr "" +"Bädgé témplätés wéré süççéssfüllý üpdätéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя #" + +#: apps/badges/admin.py +msgid "API key" +msgstr "ÀPÌ kéý Ⱡ'σяєм ιρѕυм #" + +#: apps/badges/admin.py +msgid "Pre-configured from the environment." +msgstr "" +"Pré-çönfïgüréd fröm thé énvïrönmént. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυ#" + +#: apps/badges/admin.py +msgid "" +"\n" +" WARNING: avoid configuration updates on activated badges.\n" +" Active badge templates are continuously processed and learners may already have progress on them.\n" +" Any changes in badge template requirements (including data rules) will affect learners' experience!\n" +" " +msgstr "" +"\n" +" WÀRNÌNG: ävöïd çönfïgürätïön üpdätés ön äçtïvätéd ßädgés.\n" +" Àçtïvé ßädgé témplätés äré çöntïnüöüslý pröçésséd änd léärnérs mäý älréädý hävé prögréss ön thém.\n" +" Àný çhängés ïn ßädgé témpläté réqüïréménts (ïnçlüdïng dätä rülés) wïll äfféçt léärnérs' éxpérïénçé!\n" +" Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υ#" + +#: apps/badges/admin.py +msgid "Active badge template cannot be deleted." +msgstr "" +"Àçtïvé ßädgé témpläté çännöt ßé délétéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя#" + +#: apps/badges/admin.py +msgid "Active badge templates cannot be deleted." +msgstr "" +"Àçtïvé ßädgé témplätés çännöt ßé délétéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя #" + +#: apps/badges/admin.py +msgid "icon" +msgstr "ïçön Ⱡ'σяєм ι#" + +#: apps/badges/admin.py +msgid "Active badge template must have at least one requirement." +msgstr "" +"Àçtïvé ßädgé témpläté müst hävé ät léäst öné réqüïrémént. Ⱡ'σяєм ιρѕυм ∂σłσя" +" ѕιт αмєт, ¢σηѕє¢тєтυя α#" + +#: apps/badges/admin.py +msgid "badge template" +msgstr "ßädgé témpläté Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" + +#: apps/badges/admin_forms.py +msgid "You can't provide an API key for a configured organization." +msgstr "" +"Ýöü çän't prövïdé än ÀPÌ kéý för ä çönfïgüréd örgänïzätïön. Ⱡ'σяєм ιρѕυм " +"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" + +#: apps/badges/admin_forms.py +msgid "All requirements must belong to the same template." +msgstr "" +"Àll réqüïréménts müst ßélöng tö thé sämé témpläté. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт " +"αмєт, ¢σηѕє¢тєтυя α#" + +#: apps/badges/admin_forms.py +msgid "Value must be a boolean." +msgstr "Välüé müst ßé ä ßööléän. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#" + +#: apps/badges/issuers.py +msgid "Open edX internal user credential was revoked" +msgstr "" +"Öpén édX ïntérnäl üsér çrédéntïäl wäs révökéd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя #" + +#: apps/badges/models.py +msgid "Put your Credly Organization ID here." +msgstr "" +"Püt ýöür Çrédlý Örgänïzätïön ÌD héré. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυ#" + +#: apps/badges/models.py +msgid "Credly API shared secret for Credly Organization." +msgstr "" +"Çrédlý ÀPÌ shäréd séçrét för Çrédlý Örgänïzätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт " +"αмєт, ¢σηѕє¢тєтυя α#" + +#: apps/badges/models.py +msgid "Verbose name for Credly Organization." +msgstr "" +"Vérßösé nämé för Çrédlý Örgänïzätïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυ#" + +#: apps/badges/models.py +msgid "Unique badge template ID." +msgstr "Ûnïqüé ßädgé témpläté ÌD. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#" + +#: apps/badges/models.py +msgid "Badge template name." +msgstr "Bädgé témpläté nämé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #" + +#: apps/badges/models.py +msgid "Badge template description." +msgstr "Bädgé témpläté désçrïptïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє#" + +#: apps/badges/models.py +msgid "Badge template type." +msgstr "Bädgé témpläté týpé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #" + +#: apps/badges/models.py +msgid "Credly badge template state (auto-managed)." +msgstr "" +"Çrédlý ßädgé témpläté stäté (äütö-mänägéd). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя #" + +#: apps/badges/models.py +msgid "Credly Organization - template owner." +msgstr "" +"Çrédlý Örgänïzätïön - témpläté öwnér. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυ#" + +#: apps/badges/models.py +msgid "Badge template this requirement serves for." +msgstr "" +"Bädgé témpläté thïs réqüïrémént sérvés för. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя #" + +#: apps/badges/models.py +msgid "" +"Public signal type. Available events are configured in \"BADGES_CONFIG\" " +"setting. The crucial aspect for event to carry UserData in its payload." +msgstr "" +"Püßlïç sïgnäl týpé. Àväïläßlé événts äré çönfïgüréd ïn \"BÀDGÉS_ÇÖNFÌG\" " +"séttïng. Thé çrüçïäl äspéçt för évént tö çärrý ÛsérDätä ïn ïts päýlöäd. " +"Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ " +"тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм," +" qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ " +"¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє " +"¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт " +"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт#" + +#: apps/badges/models.py +msgid "Provide more details if needed." +msgstr "Prövïdé möré détäïls ïf néédéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#" + +#: apps/badges/models.py +msgid "" +"Optional. Group requirements together using the same Group ID for " +"interchangeable (OR processing logic)." +msgstr "" +"Öptïönäl. Gröüp réqüïréménts tögéthér üsïng thé sämé Gröüp ÌD för " +"ïntérçhängéäßlé (ÖR pröçéssïng lögïç). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#" + +#: apps/badges/models.py +msgid "group" +msgstr "gröüp Ⱡ'σяєм ιρѕ#" + +#: apps/badges/models.py +msgid "" +"Public signal's data payload nested property path, e.g: " +"\"user.pii.username\"." +msgstr "" +"Püßlïç sïgnäl's dätä päýlöäd néstéd pröpértý päth, é.g: " +"\"üsér.pïï.üsérnämé\". Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυ#" + +#: apps/badges/models.py +msgid "key path" +msgstr "kéý päth Ⱡ'σяєм ιρѕυм ∂#" + +#: apps/badges/models.py +msgid "" +"Expected value comparison operator. " +"https://docs.python.org/3/library/operator.html" +msgstr "" +"Éxpéçtéd välüé çömpärïsön öpérätör. " +"https://döçs.pýthön.örg/3/lïßrärý/öpérätör.html Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт," +" ¢σηѕє¢т#" + +#: apps/badges/models.py +msgid "Expected value for the nested property, e.g: \"cucumber1997\"." +msgstr "" +"Éxpéçtéd välüé för thé néstéd pröpértý, é.g: \"çüçümßér1997\". Ⱡ'σяєм ιρѕυм " +"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" + +#: apps/badges/models.py +msgid "expected value" +msgstr "éxpéçtéd välüé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" + +#: apps/badges/models.py +msgid "Parent requirement for this data rule." +msgstr "" +"Pärént réqüïrémént för thïs dätä rülé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя#" + +#: apps/badges/models.py +msgid "Badge template this penalty serves for." +msgstr "" +"Bädgé témpläté thïs pénältý sérvés för. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " +"¢σηѕє¢тєтυя#" + +#: apps/badges/models.py +msgid "" +"Public signal type. Use namespaced types, e.g: " +"\"org.openedx.learning.student.registration.completed.v1\"" +msgstr "" +"Püßlïç sïgnäl týpé. Ûsé näméspäçéd týpés, é.g: " +"\"örg.öpénédx.léärnïng.stüdént.régïsträtïön.çömplétéd.v1\" Ⱡ'σяєм ιρѕυм " +"∂σłσя ѕιт αм#" + +#: apps/badges/models.py +msgid "Badge requirements for which this penalty is defined." +msgstr "" +"Bädgé réqüïréménts för whïçh thïs pénältý ïs défïnéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт" +" αмєт, ¢σηѕє¢тєтυя α#" + +#: apps/badges/models.py +msgid "Badge penalties" +msgstr "Bädgé pénältïés Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#" + +#: apps/badges/models.py +msgid "Parent penalty for this data rule." +msgstr "" +"Pärént pénältý för thïs dätä rülé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєт#" + +#: apps/badges/models.py +msgid "badge progress records" +msgstr "ßädgé prögréss réçörds Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢#" + +#: apps/badges/models.py +msgid "Group ID for the requirement." +msgstr "Gröüp ÌD för thé réqüïrémént. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#" + +#: apps/badges/models.py +msgid "Credly badge issuing state" +msgstr "Çrédlý ßädgé ïssüïng stäté Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#" + +#: apps/badges/models.py +msgid "Credly service badge identifier" +msgstr "Çrédlý sérvïçé ßädgé ïdéntïfïér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#" + #: apps/core/admin.py msgid "Activate selected entries" msgstr "Àçtïväté séléçtéd éntrïés Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#" diff --git a/credentials/conf/locale/rtl/LC_MESSAGES/django.mo b/credentials/conf/locale/rtl/LC_MESSAGES/django.mo index a7b91c13bd989fd75e08c5a4283333d7bed9d298..cca532ada8da2f2e674bcf41b750b7063439edaa 100644 GIT binary patch delta 9753 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 Sy6wU{`te9ZiW_X$5dA-sNxSO+ diff --git a/credentials/conf/locale/rtl/LC_MESSAGES/django.po b/credentials/conf/locale/rtl/LC_MESSAGES/django.po index af28e4f9f..469f79ecd 100644 --- a/credentials/conf/locale/rtl/LC_MESSAGES/django.po +++ b/credentials/conf/locale/rtl/LC_MESSAGES/django.po @@ -17,6 +17,208 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: apps/badges/admin.py +msgid "No rules specified." +msgstr "Nø ɹnlǝs sdǝɔᴉɟᴉǝd." + +#: apps/badges/admin.py +msgid "Badge templates were successfully updated." +msgstr "Ƀɐdƃǝ ʇǝɯdlɐʇǝs ʍǝɹǝ snɔɔǝssɟnllʎ nddɐʇǝd." + +#: apps/badges/admin.py +msgid "API key" +msgstr "ȺⱣƗ ʞǝʎ" + +#: apps/badges/admin.py +msgid "Pre-configured from the environment." +msgstr "Ᵽɹǝ-ɔønɟᴉƃnɹǝd ɟɹøɯ ʇɥǝ ǝnʌᴉɹønɯǝnʇ." + +#: apps/badges/admin.py +msgid "" +"\n" +" WARNING: avoid configuration updates on activated badges.\n" +" Active badge templates are continuously processed and learners may already have progress on them.\n" +" Any changes in badge template requirements (including data rules) will affect learners' experience!\n" +" " +msgstr "" +"\n" +" WȺɌNƗNǤ: ɐʌøᴉd ɔønɟᴉƃnɹɐʇᴉøn nddɐʇǝs øn ɐɔʇᴉʌɐʇǝd bɐdƃǝs.\n" +" Ⱥɔʇᴉʌǝ bɐdƃǝ ʇǝɯdlɐʇǝs ɐɹǝ ɔønʇᴉnnønslʎ dɹøɔǝssǝd ɐnd lǝɐɹnǝɹs ɯɐʎ ɐlɹǝɐdʎ ɥɐʌǝ dɹøƃɹǝss øn ʇɥǝɯ.\n" +" Ⱥnʎ ɔɥɐnƃǝs ᴉn bɐdƃǝ ʇǝɯdlɐʇǝ ɹǝbnᴉɹǝɯǝnʇs (ᴉnɔlndᴉnƃ dɐʇɐ ɹnlǝs) ʍᴉll ɐɟɟǝɔʇ lǝɐɹnǝɹs' ǝxdǝɹᴉǝnɔǝ!\n" +" " + +#: apps/badges/admin.py +msgid "Active badge template cannot be deleted." +msgstr "Ⱥɔʇᴉʌǝ bɐdƃǝ ʇǝɯdlɐʇǝ ɔɐnnøʇ bǝ dǝlǝʇǝd." + +#: apps/badges/admin.py +msgid "Active badge templates cannot be deleted." +msgstr "Ⱥɔʇᴉʌǝ bɐdƃǝ ʇǝɯdlɐʇǝs ɔɐnnøʇ bǝ dǝlǝʇǝd." + +#: apps/badges/admin.py +msgid "icon" +msgstr "ᴉɔøn" + +#: apps/badges/admin.py +msgid "Active badge template must have at least one requirement." +msgstr "Ⱥɔʇᴉʌǝ bɐdƃǝ ʇǝɯdlɐʇǝ ɯnsʇ ɥɐʌǝ ɐʇ lǝɐsʇ ønǝ ɹǝbnᴉɹǝɯǝnʇ." + +#: apps/badges/admin.py +msgid "badge template" +msgstr "bɐdƃǝ ʇǝɯdlɐʇǝ" + +#: apps/badges/admin_forms.py +msgid "You can't provide an API key for a configured organization." +msgstr "Ɏøn ɔɐn'ʇ dɹøʌᴉdǝ ɐn ȺⱣƗ ʞǝʎ ɟøɹ ɐ ɔønɟᴉƃnɹǝd øɹƃɐnᴉzɐʇᴉøn." + +#: apps/badges/admin_forms.py +msgid "All requirements must belong to the same template." +msgstr "Ⱥll ɹǝbnᴉɹǝɯǝnʇs ɯnsʇ bǝlønƃ ʇø ʇɥǝ sɐɯǝ ʇǝɯdlɐʇǝ." + +#: apps/badges/admin_forms.py +msgid "Value must be a boolean." +msgstr "Vɐlnǝ ɯnsʇ bǝ ɐ bøølǝɐn." + +#: apps/badges/issuers.py +msgid "Open edX internal user credential was revoked" +msgstr "Ødǝn ǝdX ᴉnʇǝɹnɐl nsǝɹ ɔɹǝdǝnʇᴉɐl ʍɐs ɹǝʌøʞǝd" + +#: apps/badges/models.py +msgid "Put your Credly Organization ID here." +msgstr "Ᵽnʇ ʎønɹ Ȼɹǝdlʎ Øɹƃɐnᴉzɐʇᴉøn ƗĐ ɥǝɹǝ." + +#: apps/badges/models.py +msgid "Credly API shared secret for Credly Organization." +msgstr "Ȼɹǝdlʎ ȺⱣƗ sɥɐɹǝd sǝɔɹǝʇ ɟøɹ Ȼɹǝdlʎ Øɹƃɐnᴉzɐʇᴉøn." + +#: apps/badges/models.py +msgid "Verbose name for Credly Organization." +msgstr "Vǝɹbøsǝ nɐɯǝ ɟøɹ Ȼɹǝdlʎ Øɹƃɐnᴉzɐʇᴉøn." + +#: apps/badges/models.py +msgid "Unique badge template ID." +msgstr "Ʉnᴉbnǝ bɐdƃǝ ʇǝɯdlɐʇǝ ƗĐ." + +#: apps/badges/models.py +msgid "Badge template name." +msgstr "Ƀɐdƃǝ ʇǝɯdlɐʇǝ nɐɯǝ." + +#: apps/badges/models.py +msgid "Badge template description." +msgstr "Ƀɐdƃǝ ʇǝɯdlɐʇǝ dǝsɔɹᴉdʇᴉøn." + +#: apps/badges/models.py +msgid "Badge template type." +msgstr "Ƀɐdƃǝ ʇǝɯdlɐʇǝ ʇʎdǝ." + +#: apps/badges/models.py +msgid "Credly badge template state (auto-managed)." +msgstr "Ȼɹǝdlʎ bɐdƃǝ ʇǝɯdlɐʇǝ sʇɐʇǝ (ɐnʇø-ɯɐnɐƃǝd)." + +#: apps/badges/models.py +msgid "Credly Organization - template owner." +msgstr "Ȼɹǝdlʎ Øɹƃɐnᴉzɐʇᴉøn - ʇǝɯdlɐʇǝ øʍnǝɹ." + +#: apps/badges/models.py +msgid "Badge template this requirement serves for." +msgstr "Ƀɐdƃǝ ʇǝɯdlɐʇǝ ʇɥᴉs ɹǝbnᴉɹǝɯǝnʇ sǝɹʌǝs ɟøɹ." + +#: apps/badges/models.py +msgid "" +"Public signal type. Available events are configured in \"BADGES_CONFIG\" " +"setting. The crucial aspect for event to carry UserData in its payload." +msgstr "" +"Ᵽnblᴉɔ sᴉƃnɐl ʇʎdǝ. Ⱥʌɐᴉlɐblǝ ǝʌǝnʇs ɐɹǝ ɔønɟᴉƃnɹǝd ᴉn \"ɃȺĐǤɆS_ȻØNFƗǤ\" " +"sǝʇʇᴉnƃ. Ŧɥǝ ɔɹnɔᴉɐl ɐsdǝɔʇ ɟøɹ ǝʌǝnʇ ʇø ɔɐɹɹʎ ɄsǝɹĐɐʇɐ ᴉn ᴉʇs dɐʎløɐd." + +#: apps/badges/models.py +msgid "Provide more details if needed." +msgstr "Ᵽɹøʌᴉdǝ ɯøɹǝ dǝʇɐᴉls ᴉɟ nǝǝdǝd." + +#: apps/badges/models.py +msgid "" +"Optional. Group requirements together using the same Group ID for " +"interchangeable (OR processing logic)." +msgstr "" +"Ødʇᴉønɐl. Ǥɹønd ɹǝbnᴉɹǝɯǝnʇs ʇøƃǝʇɥǝɹ nsᴉnƃ ʇɥǝ sɐɯǝ Ǥɹønd ƗĐ ɟøɹ " +"ᴉnʇǝɹɔɥɐnƃǝɐblǝ (ØɌ dɹøɔǝssᴉnƃ løƃᴉɔ)." + +#: apps/badges/models.py +msgid "group" +msgstr "ƃɹønd" + +#: apps/badges/models.py +msgid "" +"Public signal's data payload nested property path, e.g: " +"\"user.pii.username\"." +msgstr "" +"Ᵽnblᴉɔ sᴉƃnɐl's dɐʇɐ dɐʎløɐd nǝsʇǝd dɹødǝɹʇʎ dɐʇɥ, ǝ.ƃ: " +"\"nsǝɹ.dᴉᴉ.nsǝɹnɐɯǝ\"." + +#: apps/badges/models.py +msgid "key path" +msgstr "ʞǝʎ dɐʇɥ" + +#: apps/badges/models.py +msgid "" +"Expected value comparison operator. " +"https://docs.python.org/3/library/operator.html" +msgstr "" +"Ɇxdǝɔʇǝd ʌɐlnǝ ɔøɯdɐɹᴉsøn ødǝɹɐʇøɹ. " +"ɥʇʇds://døɔs.dʎʇɥøn.øɹƃ/3/lᴉbɹɐɹʎ/ødǝɹɐʇøɹ.ɥʇɯl" + +#: apps/badges/models.py +msgid "Expected value for the nested property, e.g: \"cucumber1997\"." +msgstr "Ɇxdǝɔʇǝd ʌɐlnǝ ɟøɹ ʇɥǝ nǝsʇǝd dɹødǝɹʇʎ, ǝ.ƃ: \"ɔnɔnɯbǝɹ1997\"." + +#: apps/badges/models.py +msgid "expected value" +msgstr "ǝxdǝɔʇǝd ʌɐlnǝ" + +#: apps/badges/models.py +msgid "Parent requirement for this data rule." +msgstr "Ᵽɐɹǝnʇ ɹǝbnᴉɹǝɯǝnʇ ɟøɹ ʇɥᴉs dɐʇɐ ɹnlǝ." + +#: apps/badges/models.py +msgid "Badge template this penalty serves for." +msgstr "Ƀɐdƃǝ ʇǝɯdlɐʇǝ ʇɥᴉs dǝnɐlʇʎ sǝɹʌǝs ɟøɹ." + +#: apps/badges/models.py +msgid "" +"Public signal type. Use namespaced types, e.g: " +"\"org.openedx.learning.student.registration.completed.v1\"" +msgstr "" +"Ᵽnblᴉɔ sᴉƃnɐl ʇʎdǝ. Ʉsǝ nɐɯǝsdɐɔǝd ʇʎdǝs, ǝ.ƃ: " +"\"øɹƃ.ødǝnǝdx.lǝɐɹnᴉnƃ.sʇndǝnʇ.ɹǝƃᴉsʇɹɐʇᴉøn.ɔøɯdlǝʇǝd.ʌ1\"" + +#: apps/badges/models.py +msgid "Badge requirements for which this penalty is defined." +msgstr "Ƀɐdƃǝ ɹǝbnᴉɹǝɯǝnʇs ɟøɹ ʍɥᴉɔɥ ʇɥᴉs dǝnɐlʇʎ ᴉs dǝɟᴉnǝd." + +#: apps/badges/models.py +msgid "Badge penalties" +msgstr "Ƀɐdƃǝ dǝnɐlʇᴉǝs" + +#: apps/badges/models.py +msgid "Parent penalty for this data rule." +msgstr "Ᵽɐɹǝnʇ dǝnɐlʇʎ ɟøɹ ʇɥᴉs dɐʇɐ ɹnlǝ." + +#: apps/badges/models.py +msgid "badge progress records" +msgstr "bɐdƃǝ dɹøƃɹǝss ɹǝɔøɹds" + +#: apps/badges/models.py +msgid "Group ID for the requirement." +msgstr "Ǥɹønd ƗĐ ɟøɹ ʇɥǝ ɹǝbnᴉɹǝɯǝnʇ." + +#: apps/badges/models.py +msgid "Credly badge issuing state" +msgstr "Ȼɹǝdlʎ bɐdƃǝ ᴉssnᴉnƃ sʇɐʇǝ" + +#: apps/badges/models.py +msgid "Credly service badge identifier" +msgstr "Ȼɹǝdlʎ sǝɹʌᴉɔǝ bɐdƃǝ ᴉdǝnʇᴉɟᴉǝɹ" + #: apps/core/admin.py msgid "Activate selected entries" msgstr "Ⱥɔʇᴉʌɐʇǝ sǝlǝɔʇǝd ǝnʇɹᴉǝs" diff --git a/credentials/settings/base.py b/credentials/settings/base.py index e0c253931..25f70cc81 100644 --- a/credentials/settings/base.py +++ b/credentials/settings/base.py @@ -76,6 +76,7 @@ "credentials.apps.records", "credentials.apps.plugins", "credentials.apps.verifiable_credentials", + "credentials.apps.badges", ] INSTALLED_APPS += THIRD_PARTY_APPS @@ -545,6 +546,37 @@ # disable indexing on history_date SIMPLE_HISTORY_DATE_INDEX = False +# Badges settings +BADGES_ENABLED = False +# .. setting_name: BADGES_CONFIG +# .. setting_description: Dictionary with badges settings including enabled badge events, processors, collectors, etc. +BADGES_CONFIG = { + # # list of the events that should be available in rules configuration interface: + "events": [ + "org.openedx.learning.course.passing.status.updated.v1", + "org.openedx.learning.ccx.course.passing.status.updated.v1", + ], + "credly": { + "CREDLY_BASE_URL": "https://credly.com/", + "CREDLY_API_BASE_URL": "https://api.credly.com/v1/", + "CREDLY_SANDBOX_BASE_URL": "https://sandbox.credly.com/", + "CREDLY_SANDBOX_API_BASE_URL": "https://sandbox-api.credly.com/v1/", + "USE_SANDBOX": False, + }, + "rules": { + "ignored_keypaths": [ + "user.id", + "user.is_active", + "user.pii.username", + "user.pii.email", + "user.pii.name", + "course.display_name", + "course.start", + "course.end", + ], + }, +} + # Event Bus Settings EVENT_BUS_PRODUCER = "edx_event_bus_redis.create_producer" EVENT_BUS_CONSUMER = "edx_event_bus_redis.RedisEventConsumer" @@ -554,6 +586,26 @@ # .. setting_default: all events disabled # .. setting_description: Dictionary of event_types mapped to dictionaries of topic to topic-related configuration. EVENT_BUS_PRODUCER_CONFIG = { + # .. setting_name: EVENT_BUS_PRODUCER_CONFIG['org.openedx.learning.badge.awarded.v1'] + # ['learning-badges-lifecycle']['enabled'] + # .. toggle_implementation: SettingToggle + # .. toggle_default: True + # .. toggle_description: Enables sending org.openedx.learning.badge.awarded.v1 events over the event bus. + # .. toggle_warning: The default may be changed in a later release. + # .. toggle_use_cases: opt_in + "org.openedx.learning.badge.awarded.v1": { + "learning-badges-lifecycle": {"event_key_field": "badge.uuid", "enabled": BADGES_ENABLED}, + }, + # .. setting_name: EVENT_BUS_PRODUCER_CONFIG['org.openedx.learning.badge.revoked.v1'] + # ['learning-badges-lifecycle']['enabled'] + # .. toggle_implementation: SettingToggle + # .. toggle_default: True + # .. toggle_description: Enables sending org.openedx.learning.badge.revoked.v1 events over the event bus. + # .. toggle_warning: The default may be changed in a later release. + # .. toggle_use_cases: opt_in + "org.openedx.learning.badge.revoked.v1": { + "learning-badges-lifecycle": {"event_key_field": "badge.uuid", "enabled": BADGES_ENABLED}, + }, # .. setting_name: EVENT_BUS_PRODUCER_CONFIG['org.openedx.learning.program.certificate.awarded.v1'] # ['learning-program-certificate-lifecycle']['enabled'] # .. toggle_implementation: DjangoSetting diff --git a/credentials/settings/production.py b/credentials/settings/production.py index 9d9853098..74c644a56 100644 --- a/credentials/settings/production.py +++ b/credentials/settings/production.py @@ -29,6 +29,9 @@ AWS_SES_REGION_NAME = environ.get("AWS_SES_REGION_NAME", "us-east-1") AWS_SES_REGION_ENDPOINT = environ.get("AWS_SES_REGION_ENDPOINT", "email.us-east-1.amazonaws.com") +# Inject plugin settings before the configuration file overrides (so it is possible to manage those settings via environment). +add_plugins(__name__, PROJECT_TYPE, SettingsType.PRODUCTION) + CONFIG_FILE = get_env_setting("CREDENTIALS_CFG") with open(CONFIG_FILE, encoding="utf-8") as f: config_from_yaml = yaml.safe_load(f) @@ -60,5 +63,3 @@ for override, value in DB_OVERRIDES.items(): DATABASES["default"][override] = value - -add_plugins(__name__, PROJECT_TYPE, SettingsType.PRODUCTION) diff --git a/credentials/settings/test.py b/credentials/settings/test.py index 730a5290c..3efd26246 100644 --- a/credentials/settings/test.py +++ b/credentials/settings/test.py @@ -83,3 +83,6 @@ } LEARNER_RECORD_MFE_RECORDS_PAGE_URL = "http://learner-record-mfe" +add_plugins(__name__, PROJECT_TYPE, SettingsType.TEST) + +BADGES_CONFIG["credly"]["USE_SANDBOX"] = True diff --git a/credentials/tests/test_utils.py b/credentials/tests/test_utils.py new file mode 100644 index 000000000..7d803cd7e --- /dev/null +++ b/credentials/tests/test_utils.py @@ -0,0 +1,159 @@ +import platform +import sys +import unittest +from logging.handlers import SysLogHandler +from os import path +from unittest.mock import patch + +from django.core.exceptions import ImproperlyConfigured + +from credentials.settings.utils import get_env_setting, get_logger_config, str2bool + + +class TestGetEnvSetting(unittest.TestCase): + @patch.dict("os.environ", {"TEST_SETTING": "test_value"}) + def test_get_env_setting_existing_setting(self): + result = get_env_setting("TEST_SETTING") + self.assertEqual(result, "test_value") + + @patch.dict("os.environ", {}) + def test_get_env_setting_missing_setting(self): + with self.assertRaises(ImproperlyConfigured): + get_env_setting("MISSING_SETTING") + + +class TestGetLoggerConfig(unittest.TestCase): + def test_default_config(self): + config = get_logger_config() + self.assertEqual(config["version"], 1) + self.assertFalse(config["disable_existing_loggers"]) + self.assertIn("standard", config["formatters"]) + self.assertIn("syslog_format", config["formatters"]) + self.assertIn("raw", config["formatters"]) + self.assertIn("console", config["handlers"]) + self.assertIn("django", config["loggers"]) + self.assertIn("requests", config["loggers"]) + self.assertIn("factory", config["loggers"]) + self.assertIn("django.request", config["loggers"]) + self.assertIn("", config["loggers"]) + + def test_dev_env_true(self): + config = get_logger_config(dev_env=True) + self.assertIn("local", config["handlers"]) + self.assertEqual(config["handlers"]["local"]["class"], "logging.handlers.RotatingFileHandler") + self.assertEqual(config["handlers"]["local"]["level"], "INFO") + self.assertEqual(config["handlers"]["local"]["formatter"], "standard") + self.assertEqual(config["handlers"]["local"]["filename"], path.join("/var/tmp", "edx.log")) + self.assertEqual(config["handlers"]["local"]["maxBytes"], 1024 * 1024 * 2) + self.assertEqual(config["handlers"]["local"]["backupCount"], 5) + + def test_dev_env_false(self): + config = get_logger_config(dev_env=False) + self.assertIn("local", config["handlers"]) + self.assertEqual(config["handlers"]["local"]["level"], "INFO") + self.assertEqual(config["handlers"]["local"]["class"], "logging.handlers.SysLogHandler") + self.assertEqual( + config["handlers"]["local"]["address"], "/var/run/syslog" if sys.platform == "darwin" else "/dev/log" + ) + self.assertEqual(config["handlers"]["local"]["formatter"], "syslog_format") + self.assertEqual(config["handlers"]["local"]["facility"], SysLogHandler.LOG_LOCAL0) + + def test_debug_true(self): + config = get_logger_config(debug=True) + self.assertEqual(config["handlers"]["console"]["level"], "DEBUG") + + def test_debug_false(self): + config = get_logger_config(debug=False) + self.assertEqual(config["handlers"]["console"]["level"], "INFO") + + def test_local_loglevel_invalid(self): + config = get_logger_config(local_loglevel="INVALID") + self.assertEqual(config["handlers"]["local"]["level"], "INFO") + + def test_local_loglevel_info(self): + config = get_logger_config(local_loglevel="INFO") + self.assertEqual(config["handlers"]["local"]["level"], "INFO") + + def test_local_loglevel_debug(self): + config = get_logger_config(local_loglevel="DEBUG") + self.assertEqual(config["handlers"]["local"]["level"], "DEBUG") + + def test_local_loglevel_warning(self): + config = get_logger_config(local_loglevel="WARNING") + self.assertEqual(config["handlers"]["local"]["level"], "WARNING") + + def test_local_loglevel_error(self): + config = get_logger_config(local_loglevel="ERROR") + self.assertEqual(config["handlers"]["local"]["level"], "ERROR") + + def test_local_loglevel_critical(self): + config = get_logger_config(local_loglevel="CRITICAL") + self.assertEqual(config["handlers"]["local"]["level"], "CRITICAL") + + def test_hostname(self): + config = get_logger_config() + hostname = platform.node().split(".")[0] + self.assertIn(hostname, config["formatters"]["syslog_format"]["format"]) + + def test_service_variant(self): + config = get_logger_config() + self.assertIn("service_variant=credentials", config["formatters"]["syslog_format"]["format"]) + + def test_logging_env(self): + config = get_logger_config() + self.assertIn("env:no_env", config["formatters"]["syslog_format"]["format"]) + + def test_edx_filename(self): + config = get_logger_config(dev_env=True) + self.assertIn("/var/tmp/edx.log", config["handlers"]["local"]["filename"]) + + def test_edx_filename_not_used(self): + config = get_logger_config(dev_env=False) + self.assertNotIn("filename", config["handlers"]["local"]) + + def test_invalid_platform(self): + original_platform = sys.platform + sys.platform = "invalid" + config = get_logger_config(dev_env=False) + self.assertEqual(config["handlers"]["local"]["address"], "/dev/log") + sys.platform = original_platform + + +class TestStr2Bool(unittest.TestCase): + def test_str2bool_true(self): + result = str2bool("true") + self.assertTrue(result) + + def test_str2bool_false(self): + result = str2bool("false") + self.assertFalse(result) + + def test_str2bool_yes(self): + result = str2bool("yes") + self.assertTrue(result) + + def test_str2bool_no(self): + result = str2bool("no") + self.assertFalse(result) + + def test_str2bool_t(self): + result = str2bool("t") + self.assertTrue(result) + + def test_str2bool_f(self): + result = str2bool("f") + self.assertFalse(result) + + def test_str2bool_1(self): + result = str2bool("1") + self.assertTrue(result) + + def test_str2bool_0(self): + result = str2bool("0") + self.assertFalse(result) + + def test_str2bool_case_insensitive(self): + result = str2bool("TrUe") + self.assertTrue(result) + result = str2bool("FaLsE") + self.assertFalse(result) diff --git a/credentials/urls.py b/credentials/urls.py index c68e740b9..2deef43f6 100644 --- a/credentials/urls.py +++ b/credentials/urls.py @@ -27,6 +27,7 @@ from edx_django_utils.plugins import get_plugin_url_patterns from rest_framework import permissions +from credentials.apps.badges.toggles import is_badges_enabled from credentials.apps.core import views as core_views from credentials.apps.plugins.constants import PROJECT_TYPE from credentials.apps.records.views import ProgramListingView @@ -78,6 +79,11 @@ ), ] +if is_badges_enabled(): + urlpatterns += [ + re_path(r"^badges/", include(("credentials.apps.badges.urls", "badges"), namespace="badges")), + ] + # edx-drf-extensions csrf app urlpatterns += [ path("", include("csrf.urls")), diff --git a/docs/_static/images/badges/badges-admin-credly-templates-list.png b/docs/_static/images/badges/badges-admin-credly-templates-list.png new file mode 100644 index 0000000000000000000000000000000000000000..2cfbe04f17e69cd64459605d28caa6b40696591e GIT binary patch literal 68481 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
    l_t2vWPZwR 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 literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-credly-templates-sync.png b/docs/_static/images/badges/badges-admin-credly-templates-sync.png new file mode 100644 index 0000000000000000000000000000000000000000..a7d2eea747cb0cbc6e65446d26817adb81864715 GIT binary patch literal 43279 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 G@BaZRjUitE literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-data-rules.png b/docs/_static/images/badges/badges-admin-data-rules.png new file mode 100644 index 0000000000000000000000000000000000000000..68f9570da86ac2962b57654349cf106a5c4030f5 GIT binary patch literal 31560 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 literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-progress-records.png b/docs/_static/images/badges/badges-admin-progress-records.png new file mode 100644 index 0000000000000000000000000000000000000000..31754994fcf29762d0c3b011ee4d278a3cee0895 GIT binary patch literal 55733 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 literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-requirement-rules.png b/docs/_static/images/badges/badges-admin-requirement-rules.png new file mode 100644 index 0000000000000000000000000000000000000000..41ee1f7cf92f19e413cad5c4f48bfc42ce341177 GIT binary patch literal 30089 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 literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-rules-group.png b/docs/_static/images/badges/badges-admin-rules-group.png new file mode 100644 index 0000000000000000000000000000000000000000..a6061824c8196d17fed26092243e3d4eb143e4cd GIT binary patch literal 33578 zcmZsj1yoes_xA-+iHAl|x>Lj;q`Nx=Bvra4h8~a->28K@qXmJni<`m$Y6!^Y(?|uf#z5BpVl$&E2 zsQ>&GjXC4~Kfj~M+?+^9{aEbYJ&}8|5~3QeDBF-np5*OQfzt7_SkIr#y#M~hXvE`X zlRu?9Hl7&p^_e45MVy=u4V^|rhM6WaU>+lKFlD1@^5MwKo%duevhWKkx8(B+4#l^wI4{6S4QDQw?=NM7ZtdW%}v-iTW-xRtZlKt&BG(-`+CP z4i&MlPje)C_qoUaGs!&#^hqu$55+fosKHamBB9402>-c1UKbAvE%Ux&?D_2jGiK3$ zz4UW=A^JP%2{sQ(ZuZnsVpDDCkdXENX%g*cktmMn(fhY8T2Xi({In%6h{&*W5`&P% zJxPCe+A8UPTZD_Mu-ElSa`Udo4orhHe4oU>L0&8v7fm_4emnj^@?Ucv-84ruitnyD zYUtLcAHQQ&ICs#pr18vfhy9OnN@zsTB(2s}iEn$(8uooJ%4I#c3(+d@pEX_s_j8bv z7QH=EAcCs?%r{H<8?q7|Q&BRBycj(B;ImJ6?V&!#Sr+}^5m+f{R2v?9u|h89Mp z?I%^>$%p@^mq6RO4w?zL@oWM&w~C8W`2}#~UY<;kf)an~CkZS+yChANI(E5C}0$r#JdSxU%2rH!9@ z-2uhFt#*}r#e3<06r`p!2+%_MVnGTxkT`RDN3%@^gi6FN{?Xe3PCGY!ZR^0Q6 z#uv$8e(Fy8<+f$$=$?k^>Y(3{3xpP#54)nmo0ag1_yWfr%^B4>VM4Msaf1Q~5G0J!b^JBN^fZJyc6BIZk z7?!VfoWzcQ{Z*3ge9}c|vulv#SpMk0J`wx)q7%*Wjn^Am*SOOaVvLwJ)U_lp&!0Ou zEY<#5=<3t7S2yie`rMXogY|PdvWRa(>;MeC`KB#J$Agd3JgjQu3`S+;ZTZ{y}3& znHTquzT%Q9M98t3D1J)yWr@emk{xp5DBf@wn`#!$KGfiM;lBCT$@+z1(%frgb0#wW z6RfPs_hQM&E+EUcVSaA*%Jd>mxoOR`53-9hV{F2+bCIOEgO#4obzA^P6c^7uR&Jh| zOcT<0`?i6w_FXITJwpFt_7OIM)4?E->xGWTl#ng#Z33t+lzgfA>?*M1uTEVGI$?_4 zyn*C3=XX|T9f)q+k0*x(cC)VpVNmwK=ZJ^wQ*%_=2_0Tr24_xUuh`OQ0KX-Id>)4+ zzRF~r22oV+Na)9fIO~UIQN1Ab|9fH0P{d>IB8mt{6zi8grucvg z&}^`#*R0FyIhWUK^$45mw5?~M{@5T-fdvl|gLK7Y;v(&3ut>z0z(ed5IN6STi_1k;I~z1a(U+Hi1t_MS4W&Q;xl}s zoQEFo%Sx5XB6W-+&e>}#*^;;^X9w8Fva56%r}|O*yS*MrfnhGk+?17jgT`FV;ki(d zv$|QI#qxAB6tXp60bX*Uh?&>thZ=>oW(dvAc^E2lFBFU;K8S^FQ{T)>W*Q$09hb#j z=`!Ime{N%I_cl5@k7wF0S>O(Z?b`+XcHMX^lR=G7kqz zV9kcDwCnF-kT-K{Q@w3LAjR$a-4bQ+vLAO*xCdO1ldsiG>A>*SZp4K4yv?hemaS1Q znk-}Q=yn%o1J1tsVY zlOrupj$E+vF$x-J1t7|l$SR4CY;{93oSbt8)_ zD=xDmVv0SXB&0p;k&Z^l9gRyl%A!g=3ZZoMp8dJOluZ1pkCu;=_DwPy1leqjz~EA< zUdNFXIouxpktM$hZ6@B!_=>HVuwfE~J;iu*wX_HKsE``rW7O49sNFc?U~GUWOsX9A z>X(7Ho2xgtNQBN!YumeT;tT3)w4zx>lAVSjo3pYv!d%l(!biG_kCyYfEq(6RbB1!U zBdfUSnu7Bq3&gqV)Ch4XOMytH1qDqXS3YtNw;9K9?xEi~)G}p!Z&}(9X5s;Id6Y!R z#^k;j`o)4U>?w(7qJUAjIz_V3!t5JelB}i@pBEFmh>z+lwRPs9k(58rWl+jCb=Nq$?%wto|wk&FS6G^CMKJ*t90?N;+v- zjIXPqkDW)lXNqL*8qOqRi^p#kM)2nAZDWLig-oF7A1jtDdmFr4&MRi@k4yxGB?WUZ ziP&F4&sK(Zeo>We;lP~|OS1Qz&9mBFDEvI;PumdH=lfoY1QccYQr=5a1cF1f{I7rk ze?ar1xf$6=h^gjC$tsAvib8H$&7~ixJbruY4I&Q+3yR7dd+R)A7!zVJWixtMudT13 zrOlQ@x#*Zvj3C&9G#4M4+5P<|EsU)_NRMgXNH!=Px9*g_Nr)J$4Y%=Um&A#Rbxa22 z_gY<>r!HH)R2RF2*DPwHTjUBgU1+)Q4pw0diZUvj)4B)2Q40Y~Ym~K)mUB&@0^WA8X|3Z?n6skTMZ(JJrvh%FkHQ;4AWdM9%)P6`63gb%&6qZ z_H3$Em_bZk`p`f02N(vULRO=(ze>_;{ji!k z%?MH~FC^MrBJDl2=U_@x%4}zDdG{ihLBjFdd*w3Jw)ahcjNv zS3%Ul;cu*CK9oWviv^Ca5^R-qb6#} zydDp{&mKhj7nktz(z{}6bz}NkSN`Rdzcwy2Ph6(QG(5(~DEO18k|Ebst|d|Y6h6L~ zMEd+=^^}mAnGt^XnbFYFu%Sn!vJ=xdYeI+aOLU?MwfVB-b$y#&If4S=8u<9&XL27` zyabW!7ZSSH)NfA9Cf(6>m=C^(eVG(XqH1``DE5sa>m`9$)@CF&4G=Ndg_fevsKJlk zjIap*a)y25HOKd%J!D!=7cA@Vs}cSyyVHB^OHvpCXSMcfDKSIF2Agsi=~`8I(u=+m zmom^p4-^SiO0v@OSTZ|&5`srMav@gA1+Uz5hqpO}3NqHIroZqmDMNGA<@?QGD= zQc0IW&&~F3x#PLjbGry~Q!HXcAHrBQSFsICTk zMY|bNEIbxWAf8Mo9DK9UnX{p7+T3$(Mhk}W9^3krbI&ay?2r8g@@SUVh+d@ivLm5u z??#%NQVjVgYOLTxZ{M!vyIWc$iHM?Mj!=3=i2D)qUoIp%Uuv23&fdmgxc0QD zzDQ{DYCbFSYDv?{%;`+?s{W0^SY-qKx;fKN>37~+qIH6qkLCxPf3kJ3+2Ws9`APTV z(&A*cS75WulS!AR$8+Ao3c|S`?Vl7<;Wi?Z1R*&K_1vV%3x33}$6^G(%os`XysKy- zv0zk4M{phmqTLwvD_TcH(sesT(i&)$PWf?LbI9ATzy6-g7^5k;Z_ScD^h+L6A#4qV z;uOWvy<}1zjP!(G1k!!Af#hw>cz{6nXEev<_uA=HOdioLJT%j~O?aLgMLAf2F?IQV zh7e0yzOs3Jo;}!^7Fhumw2=7dPfK6wx<4nmzgfsA_hGt|mY?l3B#m(&`OVEBr{K5k zFc&a+#88j(&o&QE$gg_Q6%QC8?k8PFeI3OgYj9z=fjc!NDya6jZF>XW&{temg&xey zC|B(m;ayi&yz_npkNB_H4kH`4ceHi&&O6q1-~s+VvqDEMV%|MHL{zEzNm?l+=yA>^ z-W%_Mu;h{gAol62)&2}2or<8@$VoF+XHBC#3cK*!Kde^8w^2+)@01)-yiM`tM89{$ zr9G2`P1RbQpL2%@szutYEc@MVr%=`Y;%dK6FWM#dX7Jm%omB+c6kGbGTa`^pSz zBoxgitm7`IVWG?aCE9U{K(bmH!O?VMu)$S~YAdZ8$zOQevZeza)>O6sC6XU+vO8jn z>AN^38t(VXp^1Y7Pm7Z0vWX*GDEj7enVqKa)Gd*h_BGF#VetGdcc0EL*J77icj;R9P2g_ zlv93xb$MpHIm%Mn%9Bvf>$qAUPQedtkWb%sKiWiCT3X84y??5EZ)TBP07ha}o0;0b zm?r4Kt;@oHV{l-6@Cn{b*sflg@i^{En5S&PTdtp)!fQ=ul3z{-D&9GXJE}w0)~2OC zA_qrl=@$i`(+wmL(#uHO9@%*+C)QqTX?_zx=f{$N5eQdokS~&8gN|Cm`u{_M>Ujq$l zqEo9>$I&SY-(7+?*&z{J+?p^s&+oExnG;0R*cyLP82b~ub4SMY~E3YCm7_s z9>?1;Mw|-VNiz13CVhhc;W(J0fYab<6cXMVwa4eXB(ZFT^tUh5AJg&^ZBE+%mp}lw z?&4A1yql2@G=u$lYO}9gc4k2zIVXwxzP*lGq^?5r{V&k6Lq#V#W0V8mSyTxPIyyFH zG4zY!Bj>b4aFS^Pao?qo)I~YQZ3Ax1xEuq36d?=KcN3EpMpeILx$m3_)#nGXq`e#d zbax?!M`forp^DP4!WRMiN}sJ<=cCkU?ubH0l!)RIlMqqr_oWEoosQ6_OHyH^W0T|0 zVcUzfaIcGwUC%qRwnzxL{ouPQQ{)J7z9;mwq;58A3Y5*h>GpMHwrFLxy-!tFcdMwl zc!b+>h^15?HXISK+=^4WV1fIRjOLyXw@gMu%SGQpSB;({sIm-%wr>x%hCEP|C(+0_ z-SxiSI)-~+GnP<#Y**}T7MD3gC(Bx{&b_W5km&usI_(sO!giZy!k)bR*>tf33yFno zSN6^dU+v+xAj5d(Klwexc8Fn8c4jN7j^tN<+Kt@US(43Jw>8Kd zv99%kZ41)*WHrT(Z*KNk!*cXac`J@%?{nC<*A<;}-q*;T+yn!MTJ%C)@X;8gG^LlL zr4fdxotXuG*)EPQsOVuWt}m&b?lCOBK1jc=@1*qjg@j)t(^EJtqG20(DPl3DW&>%q zReju!Gq$i2{)_FZxmXr;(vtw(t&+O5I=a!!V@8F<-?q(XrYBQo=^Ne5wx@pXi1)DJrlatD46iqrTghF2`^+m3 z!^bqz?I!eHj;;oGV7oiA;JtQ|ozU0)pIqkM@~=I-+wi4by=A;ZR(eZn#&vc|VLSV7 z94(g)*DhB_6NWBzvktwqUYf&?*aF2fg#4}EB|O)^vbu=mvK;ub*dI?S3J% zU4I$~hALTkSWa8yLsCoAH=;e2_7oJSuQu}2&zHCzR~76g2E2}6dqWiZbc1&vJZ4_) zOIaJ;XvGztEvZs!B{e3MjWYk#&5-bnR%P~6E4EOX(*DLsz6UXhzLP3l3}`}sLf^jq zvCsfZe(IuKsFQlmxs66Q|0z^-7*|9r&_M|U(Q;Lz z`yt@*>|mz&TnCxm_VK)j-L-h+`%J$=XoS$YxeM}W;$nb+&iFe0>X6C%;ONIAs?*vy zM4a1EFc)W^yv?IM6U4d_4>Q)SoU4$0F6WZO9!l ztI01A)8B0s!ayT68R0TO?QrO2Uo!w7y*>GUANP$e=crB7(YP(Jk|<5QA7lIQu`oB@ zq4!x;=yj<>kvO>ia9AFB@sP~=_le-qsJhU!QQe%gmGy2o|IQztPN-q|((a7y_O>ss zUaNZ0AArl6TcKSYm3cSBDDib;L0itp%4Q9(_DObs_vFi-d!EBMK4|-mQh^Vnwnp+J zV#-TATh99IF5m9~p0IjN3{1^EukUWt)fQoIj_P!qUd*1jmt}o;MQbg2Q@u4pui~o*nknlnG~nls!}IV`J%> z9MrU^boAA<%Wl&N)C+m;Mx{StF_rft@QIq}gW$m*RM)4S07ygAV;wH3o8MFtMmq7h zADW-6Bp5=C!g=#l8-~LA2E4BhRE(os!hQ+tchM>FkkiP%AKZet%+NY9@yh|4l{CtH+N_}_xTaQgZd7tv0seuJ zz61F_7Lxqyb}g5TD`&vcw*AN}PRp->R3%;{N)hvRiC}7B(d%^rYY+v|p0`rFPm;Xx zJehM@Fq(Eb%PX8Pmds?9%^2k3*~SIs#-A+&Qq2O5SwrezTvcw(*tICm7_e{?OJGxA1W+O&f$bP9a;v_hx){01$M?~%s`6rR=`m8_s8U7ZAo4z`jh@l9%GcneD zs(0l{w=qi`Fqb#`jCNvpM+UmjI5gDelA|*V$)NKr-Q5#MsTQNpp7v(*h;!HGXo8aEsJG&721y@h+))vKT%}?ZaZj--BSS!g^X~kd_c$(3(x`WdAU<*DXe>_dAHk%Afx7^T50zMqx84Z| zS1Khh!q{^}Xs|3TNo}!-l!|hWIs3#|o_#6YHG_cS(>M2iVsV_3DN6EgH>{-z8On28 zvIj?UBGq`RH=^@G)^l^LEYOV<6L>%=v~+R4Dp>XVk6bR~iYE#v$Pqn@kM>;nq^Maf!Of!nRI=V&MKtQ!!V^<CXm9f!5h65qsSlD5>9$u__}K;C70`F; z)3HN?h6@s-ZLQVvh0O)gc@Ib%hGZszAZpuAjO#R`GT0qm7R$#1!BlcU6ir;G`L(z$ ztb9jfGQo2~u`th&)j`bpnUb1JabARZj@*YIa_7%3IH`E(=kVlm21k{0ojzhr{1FKO zWlbeh(WlWWE#^5{OXv;8!-qH4I%2S8|D;YK=$2uCUPy1%4-t$ zbd&FO7wry_YS~uAeqcfHiw9y4k`I1(2AJ%pQ+8y;9zLjHjG6pGlxO0^d157e5# zZF0gsZRhY>fY>3C6P(_PGcoAT@pV;Q<#Y~|FZy}c$T_489s9TpPH|`(_Bq%5EWEcj zkSFkyUetBjSm<+$3=bD{tbs4MD&_W5G4_~AnCXB?m?t5> z0o=`}CTzvYOken=5H;X<;~~@RayheUMmsRS?)kU^s)OGluHP&ygVZyYmBcH9ENj9;3r= zvKdJcWfP6Il(g}jfpfCbug;RbOjP95%mh^Q?p8Fvip!d(y~hvT1R2#=lmw47S|Tg= zEr08TJhLk5Y6~JniW5eM(6A{tYUhmw;txTq9Orzb-4}{TPp{J->H2gsjG7-ZaTj})MzatFWoX%Hfa zsL+Pgt%u`}Yqs{pwOhtI|3{Ya0ct~p&#id30#>7?)mhxDYqx~-&CLC3i@E0h)^!~pR`QSM4O4EFbU<@gLhvLcjE%&MKS;{kd!dKOQPi% z-=lsnvql7|RvZtdrGf&cxS#z?Xl`@)5FnRtmQlmK&CX-#e5|bB?vZ`unkDWK=&?nc-L7CG}FX`7TpL+K}JcFF;pXK z#ZX&Q`2Ay0_x}(K6=qbf293)(7x;===&QdSRE@BJXYKko5Gyz7T8aOlH#^YxX-#D^U{v#8Jgr53iOV7rOZ>9Nt}<>?xmjG*~eZj|iL zR0zs%Q`3%oA-YA?Q^Zpke|rXn&%w6oPV+Dq{9~`#B$smpv11g}4tyCNUk7p1@dvb^A9x#4NkW^M+`9xMy=tadg3*&mMawlTC zX{-h+aBI0Y!H0KdNv8WF?ht?=jGq!o#uMvjHB?k=+4Og^(0pM^mL0M-~l$AZ>1LCKFKzx>v*2(mZB$?V46% znUW4u4eXnMDse~%11a6R^u5kj!Y*Q{+I9h6pkQlxnHuC+>&&GYyCA+cosTxL|pwuI1csMdiCrw=)2X{u%N=F z=4Y;=W&;0JX93_6%@T7gIQ94S<(jZ<&6uX+fWSeh-71j6gAmgu;}Wa=kS)_+iBaS4 z28{DJuK=(%8%ZNR3m9wjAui>%lF-?*!j$f;fx7w<)Ma=68-!i6GV!U`2@KxAHLl%C z@##BC7=!Tjr6ZHU^wp$MM1wf4;HXXGUPsu)igphTF0%QzFP2`JLDTF_ly-7FV+R9` zzw2C!n{))Y85H1E)8Gq4UE0`97U5qtY3MX0>E*pU-!vyhW-~genTJaNp`ULxNYQ zOIMSD3JWd;CqaZv=Y0f`9pMyPnucJ|3E&p>RRcmZEg^Mke9i2T*5>zLu1&oiASuYb z>jqmgp)E^yuGHSbcV%GD9N0no$*g7Xk>g3|<#(H=4ourtn%;(8vwr71{m6e;-di&L zicaN)j~F??ROf&>wo7;K>U^Tb{gEopzztMbzTB=JQL>$}sRMB|pJq`lBsh{qr;@Fx zM~hbjt_=Oq`lSI#$#%V}Z$`j{f4kfd&cTFiBUE-$7COs;0W1&swHz(_h7XmB2jbY4 zH68DV$7SIgxUD?i#RG8Ti7mip9C_wlvtg%;ArRVooV3rPegnn^0gyHjcD`8*rlS9x z>V0*#bG}t}l0E5gwIWlbTx6L2yRrnRLnsa1cW_>*mt~fjrsf~u+j?dfk|oGJWBr=r zi$a>flpp|@=0%$UF6sF$Y?ciPxbTSyQ55ao3ar|<{gm0^}W!It%>*{?PCz46e^j2wK^by5!01gJVQAen~q32!z zyzF}PQCJ$=Pa~j1C&<6ZS7uqeUuu$V*1b90g*&}aq1y>QRvGQQp(|d;Rp|{paL*BJ zDxq0#q2oy-q+5o}liam*?|Ga058|Va(V}WOdruLiH| zHJ|VpQxIE{8ec!nPbP~Az$WnJ%gD(%_kO3HZ|&z~YEYvpU=f26D!utZB`g`B2V^l} zL(;*)s)nt*QZ2d5yB~^6YUOk_QA5L%ywuO{`S zw_z_uGveWnTS(67$-!vQeu+1K3_h(ft-Q&i$mJg*?>0`;KRK@erH;vOxJP4A*tHq{MgK%z3FkN z|G8c&^>Y`x^<20_lx`q+II^BvTCe{PHqY#DA>^U@Ziakp*MXIW^kB>m4+VO zwnME+3>xZ6RO?kX6|!rBU0SQZh5SA5^mN%PhbIh`T{P;;KjB@#T z&`iA+VVc^a|K?hO9 z1TSNW1TzXSBn|!|e=}$o^CBA^^N5#T(yZ%g(@MOqr!GMVQ2>7Q_FIWBv{+7;PN}G! z`Yv;05zTCNiOqC0;;5ff&Ev*2@SEsU8~^;oG9V`q#t{8jfXEb-EGbCm>klKS(w?4PR>CMA@>5ATicy=WEI_+bc0u1_Dvv!7PEoqx_}Rokf4G zx*uPZo6bDgDHt$ zjV9V0cWQBa;ZSubLRN)KYpsQ^o0EdDv?yy}H=-bKL$iR~IW<6YnUdx;0u~y|>;47V zy>eYwx;kj0gF9BO{&G<_{pBRCSgxI?jN5zih3ueD!ez$M4t8JhFp;OFUolx|r0HPl z&B;Li{3mJKI-eB5vhlgoWvd@(^SvP=+g1W?%~Dcs6gJ;(MYA`_hBXF}Mh0FSun|sM zXq>(#TbPiiyC9`VAS@%2dIw}>TatM%m{UCgfC);mt_*U~j?)UyC1>H4k|W@crDrtv zYEJd8VUpI9MReiEsh5JtmrsGJ$*R|M&_HCnccrLOk#&MPW0V9=3b`T!@9u{j!|Yav z^SD?;77KAajab87>ICap3sUhbC56l-3yiQ&zKaWc0o~p)|)b~?V0*Uva<@yU z((@TC7T9E#RchfJ-BcD%V$v_$%H*k63Em$LS7^|8$g!0yj~jT{q`LsD{s4)m&QJrd zvC`tu{u(5;JMuEJB-21LO~#Ypks`M0Fcfn!<{OU$``57Ws5};9odKG6EqPoyOR4>( zPHWOo8&%?08oa4t zAwrBa6<;$)iCRDvw_L9e?DP@7HG0n4E(I|%dx+7erG^8xw&RUwJE195CnLhm$Wl>y zjRwd#+3Gkc=Xb?q?vWkvZr{A(0h(Ekas2GKBu+Oq+EI*)=&F;pFPQ(#k8jvS<3Qgq20IQ1e#FMC}o z3G<;oIguMRlOcQsuV`29yc9vYp=n4zjMk}#Pr1l4|D#*uDQ{_!vQl1K#Apr+Jpo;t z&cd}_fe>PYSi;7| zV7BupM|RSJAu|SES5n3CShP5c=PVAQN+rbE8s5IyiG`u$9vCdLWSnvIT-{z6>;#xZ zdl~51arhjP_+Qig$rK9*V+jI~z1?uLbZ}!IomA)_oPy0raHYCuthALpvxN~%t0o}pb zI>f0(9OH9vHF^|TrT8`{m-NV2dZ>kEq#EmuJ9NZetF;F$?St3Q^I58YPsLMjGxG{uOXG>cVcpD8Pu1D?4ergPSY;P0o12 zx!`2dj-dYaTcJ2RsArM#qJN&_=>XRJk(+%RM!GPTz1UeYAyc(?v*6R5j{YOEbkEkK zjq!Hm!e>PF6ca^=QzzZ_TGKOhnS1r~{+|QnTqR6rcBN2z7LF|wN1jC4Bm!cIzr{zv zk{R^)MD3g8|H%B+&{M5Ge&1EAl{^?ji+$af=$Q2qUHs0Jtf-;;Bs-tJ*M71l$zP3@ zPSM-)b&LE*smOQcK%aKsN zVFUM{k1+mCU^JfjB#Rwcul`E11?Y|~pfxB3{;Bk-aNP1VA|DZ$GeQrbrn8?>?&~Ey z-(O>UZeuFQ_uZhbWAZg2@a`eX3U?LayXRCVh|iF|5O{G5Fg5q5rf#NE--)+46PW!; zzUdDoZ8E4)1C*9;xRuZSM^I6}hYbKM%W@9xxbu(}G)pV18$Hh@{1{07-xkTd6r=qd zgi;~J8d{#sik~|cMX8s6aB-0V;8tpHwCS!RM(^*K+UgB@Y;VjkT?t|{Hhs~Ff>R^k z4At4e37mo$?@Rzkqvs54**GU_0k)=9SkRMA-R{uIX#?es_X@mFH=Ga*h)me`Cy=9n)@vbN$Fh+4 zpf>KW2l&jKCE`(pw+er}f@i2r9w*L*046g5Sq{x_n;2#jF%u`cMKsmvWc=o9S{Gd- zL!-u{y0KFbu6wZLigU@=mDH#=6@X9+mL1N=8el$BR@S&5O!B(2d#2jS#FnLtHc7|P z*McI>?dKpHkOSBd4#JNy4YkZix+4Byos~yyISDbVJpzcT464mEnvj z*TJcifGntr?_p1ZVveL{+*_GEz*LWaezCnn<1=*SddvD(v|(ot)VkeF+54!(%q$OsaF4sGExCDaoBBoffY982h)*mMG#ofX23f)F@?UIo-0LZt{`tz89*Ho(R_8jr8y?Pu#GO08cD73?GUKT z*Lz90>fIZJkK zcH0F*`R2+wKvcIAE_`)rdQe%FgC?c<_HC1<8dkDJV{@}tJ;IDfOoM#rXp2XvHk zW!^}ulN-6-a_xJOGyJb|8HwCB$s=FEuQtwL?tOqD3Jd_HFc>iHmzJ5CbK@<3RsZ%o zF5WS&Boj0&t1UDbOMh-*Xo*~aStkm5I72@gNDN+$=2XJL6HeINHVKL}8?bYTVlt1N z0bR)G9iV}&dw$88M*MBq~7hJb3CQzvINM_wUvu-VBO36w!W zKsZ>-QS_=OJJwzAQtCqaLVQM+_=ESp{$FC?>C#L0ol2?0n3)>opYQf(H4S)IPT<}x zB+SMs20mdu%(>&)0_>9Tg3$ed*lp>_g!2nrx1&-=<{0xV_xtH8QlRGj9yZ%wi7yqm zNwS?JQ%q&)2g{?cmMxfn82z>FkUN-<6~OZBQ*5DyGnU0AvxWI-o4w}#ueiED)Nd9QUwVx-U0+@B zv$)Ge2<(5o*o*Mq#u2{!baD%VuK%>+@@QsyR?1!&j~GP=uAYFe&epCj&eqa*Z6*O8 z=jkuXT=3QJhDO?ku-E>%xCZRX#U~2r*NbG^J)DuMhVQljD{I(scYi09w?N|B z=y5;)?)Fv`vOCIliZ+~PBX#gBBX*j@=Azf|YO!w1HPxYq#dd$WdwLtvsj%(&<-v80 zQ(D*fckL0Z=vtrvcd-L35e;Tg;I6Pol&%>Sn+ndd7V!`{Oa2l2A+Lb`An7;h0PPoc z0Yp|-6ST5FeZ81IW;8COUbb9c2)o?WQ*J=&L^jsfR5wTq>Bd9@cs0q)-F~fh#@11q z(k-V2>Ji!x z(T~1)0zub|Db|ZOia@UEEZmLoFtqzMOscEEc`4U3S&e;2TadxlvtxQ1vZ#RWlst;NQhbP#PwO=WtblLW-N+Uw^X2OrDt@pmZ#fCn z>C=6j=WO{;d=>b4$784QK*?K}-+YkzWK>;wMbExrKKon!VzAeXXDQ)Ug9>M|SQyp1 zrxl#(L=X0g4Us)1(9I$P6BxklnwBD+w1rQ9-`|zFQ80sn($o%gtMq`WY5!aGu-sGZ zi3o9=)AZ|0vl~GnA5un1V6UxXNK-N4bZWqBm0sa;nMv6G2Ihn51IVd(FC2_Vzoha{ zJ(dz5%b4qO9}=6ft6%sc)v4thoJzr)U3n0%YrB^{N+1uofxzy%IW`7>po|-=CQ5f) zS}q{Ce$lU;HgXf7>~*ul{1Q+3&G><84W$df``#d3$Ao!p9fD&=6KA%djkHaYre$9x3e_#$}nL*UsFFOI+;5f>l~@UW-1F<~K#K}Ii?2yLFqbW`xy zrmp4E_EpFi4x8{Pb7no)1u5MY3ZAyCXN8bv+dKW`WU%!_1}{2>wIYKTHe3VQsbBYm z0%fRKp#Hex&BJFH)5-HL;)%>(F|E(@_11FoqSpmR?CkouCJu36SthnkN1FMCba!d9c(!e95lX-!`vu=#>bwmK72P+Koc!WYB^NXT<9fbtn> z`!jp+QW#I;{gN6zC}?Y38k0@udeX^QV>eui!NFdGZh(ERXVNE<@?2yz z2Rw%iGibCI2-|#Hs^WcvcKivJI~Rxb>kblZ{#9nnwqVQ)#(;C)Sd(-(0!v1 zlmG>oV~u4G{VrF==>oJ>>l6ySxulZu*;O5+R;N_S#{qw=dB=eI?p}5)YZ-W}tfkon zbw#CDRdfDmTz7!Y++p)=z7*@@{T#0z3oKdWhD6c z3kT%f*TZgQUVl;WjfN2M#Up0wPTXR()-)Yu*HWXWV`b=QBtWatz_QI#H>xIomY~h- zE43EbkAWCz$j>=2SZh%mNE1vED|{GHLHF4v4cJmZ1&KwL^Gljh~}U#`X9e_9nKQEnA~+;ApiN5&w5dUh zcmL#^GV&r;h28LBWPX<3jm|#5Yv&VHT&>D$vfe#P$&-?Oe%S_m<_3{WGBED|0h3s? zcsBk{WOO*REx=m(fK#mrD9L}ZB!wK{t-|YDh0G7NfBO6!^;4(Ji)Btfw)lU2y>(cW z-}67LxG0N)z%Gg)0!z2Fq;z*nh;)N2B`GK%3xd*(l!SD5NH+q~0!m1SQUU_Mdv}cw z@9*>IA6%FBp8K3RbLPy%6r{W|@ZT9JgJ;b+v|X^Bu?B zC18~yi-tr1z<@#~Q6)UR@%?2IzrF^XknNuL{WR3a)Eye3ZzY?~wqM-;kqzZdmF+9G zE=XE%gME=z3xqPKJ91Bujih2>BFqUCoNjXpiYysBx^RuTe6I?O2=_xohl^ix7<3{@ zx)NWr8*K0sX3M5vi#-f?Dp*S#t*)(-!SGFVl+c$}e@&D{FFr9orKii+sGbF*pfbzp zcak}=-5jUt;XJ4v5n@9Y`iDyld5`*Q-!zd>v>?4o>X=l!7#_}Zq2xiDB?%sFMh2-|>#_ZT z(skF)xmhmaWO_^~#nhic4bn6UuFCfb@7!6HXX&MXA=exL#N*)>%rBy0moMWGFHq7o z2bx*p z^iEN60~-=l?+$&8!>5#dBBif}&ptsoX}KaOK1=nKC$8JmOsV4aeBLsaM8xv93W6q1 zFB&f39w!(=;1o$!vRtPS1NC}s`GqoRDQG=RI;E57#B8KNa#5^IGTYH1GzTQk+fP7V zCMo$tA(!POK-6k;+)v<3ef4a9f8*naFJ_HPl9Z*MjPVibOo{0MH#4?wP%4O4YtOXc z_^exAn)9o13rdnZSP{!uftBVO-g%gD+2|Jv3pt*$_`Ay%P~Ug{%Td53DUbVoV)&Rj z2R^BK+*`KWcc@922;;I2?`^=G9#Aa7FK`YqWocLoEiY(FE7xS)b>r33@esjHZ9Wql zl5XhkrclMc3hDk5HSY<8fIXyC`!m8P)vBrz>GRc6%7809Kd^8nsFq~i)HSbu;-Xc| z&byri8|%o4>~+8QnEd)VSK;JQc}n_fWRlF1?8#_nt~fUHj2mFnkGNPDmg(%_ zCs)hgQhgIqdk9@+p;aBcS@M8#!%8lU1c&in}7An}y4m5}0%~ zBI!pW(C$`6XUl2-g`-<>{SkO(4^LzqvXLcwR^Ur1l{sI3kAZr zMeWa9??lCDXU}J0ayaLOH%cUVxWf?M9(-h6q_Q%Lssk(0p*flSE0q~SD~aKEQ>pNk zm+HJ*y7nZ>3agFNPxx+rypC`K+(km_jH_!3JktvUmTZbFnF#`g8ig>LRxL_QzL*}i zmCGq1fsnDddk!*r9i85*1#yuti& z7!nEfPkHU`?`zGehFhd)6Y`UxOC}mZb48r;i#wnHepfY^ajb_XN<-ZT#wsd18Tlzw z1wO*qp}p5V7?GX!Gsxn#V?JMbN=6!WnSOtadtacw>z0v(GS~(rAb6W+sIy^Mzwk_##X+K|X z*SpFtZ%Z!%-o_&_dUKUIA3vJ6^6OnM5>xnY=u6&2PpTkUB6^r4*a zP_c@5aA+=!HojDDF~aM^!Rzo(d%SK!^r9jz0g>y^M=`@FSw@56l|j#Jl*3apcO)8l zl3u)+B&H)!cX~)qAiY)z`XYu8V#u9mR1uzaPG&Sw>R=GRlk*U9laJLj{f6g=gSwL> zaEmNHahRD@jrXTf7S^>SO1_RLDrJkBmpAC6K4;M1px|<~iIifZO78S7?%l1`cnDS? z&}eqcuW4bEO&;sc-7w0srlYzyK)p+bHDuQWT9AYMRTWXi9CgnHK+dE^P>W8Y7F4IPpxW= zcjq@qaK=u4U5WlWhr>{HxaFH}+K7G#0>T%XLqA=eYE0#^+x=BQW%`}el0glXZX0F! ztGk7rUFDxwQToc9L@MOS?B#Bd3(y?!xZ0Rh_LnX=^Z?vWit8V7 z@{*ZUM!a!6At3_?vzc`Sxb$=TO-dN4-)-ra5sY6o^|zNogYf~rTqykp`7<_<@CqYWZKF&z{+b^ZjZgD9Vy11(sjSSpYb(9 zEk@yf#NyB%z4OzW(=N;2kyDeC3GY#(dqBO>H#I?I3FLi$2}MWgm7}niC0K{MEK>>d zA3rsi2p%zYa3$@$^IGW-`TWHCDLZhyf%>LsWkz<5eqHYdEF?>ZZgXNL)yi9oTlv*# z1F|beuQzXl-PCMtHtS>r&AK`sF*$hGWjRO5@Z&Dx{8`|!1=z57;2eyx-UX&qYO;Rnr=m)NGJ6K#u=Y2kHyoL3bP zDpdZX+seCseo3;j@u#V6YBG485MPdUp6`mus|?~?OAp>I?kvZM;-#QrD;_G{s=JO&_}_g6!g2lBqw>f!ks0VR>AT^JBr zMXK;Lx6^fGI70abh?60;amC&0R`aGNEI5C2|uTP={c&B-kLV zTRN+H-=piX0qG%ECI%wiZ@(z!qJ!ZA7_~PFa}ssj?`Ek&{{lM@N&-RVW?Z^yG++Y_ z$R)S1aBJ4YT7YDeN39{G<2DoO7j)`~0vMNgvkaXqYNJe%U{_*&=ombvs-ZC=!02!~ z7X{-%2POepvjqSGqFm)y@KKLAsgZf7yg?^NPTc}FxtWl5;%k4qrX~lv##rV=SEozR zewfjzdoPY%FBNk`T;;Z10Db(kznc*#PzMEZyN$1K0zLb3%RKU0b1qY(m(O_8w;t@m zAe$gyyo~$_MYIHfgs7tai4%>yNKNy&L_%fV042q+HtLF<{g=1K|7NEdVgZ@QB-5)w zH@5`;Y!MII8`?w|&l{SxuQ|j%BmA5FdIDs>f;!@(Q6rJ#VkGswJeJQ0{63ftB)@vr zr&=BgxO0Z?U^4z91&@vZAROkKc?*@d3k+%!F8%rtLeD3Oydof<@uOR4)3T8FJg5U6 zFbK9t6dyY6aU1Iz59Qg7+*BIXmz-0fq31v!4`~2IQ2VrB7^REipYejzewgN6PLTel zRJ!r*+})4YO2FzIX2_tOGv!ba3s;y?Um(u?E*lTpb82A0A7IJDyQ*LmMAs%)u+alb%Ba!YR_h{^-2yhs$6lbFUe;QEy#n+fB2n^mCm8m_vAU zI&!bSqa|Mkv>#Z%Y*b0JzMc3La{EGrVqE?mXt?{AYG-+h)OQ^4|%x$GQt%eB}S?g>oB!e;he3 z-p>K@I@YDO<>%z%!}+^GAdJ0H#IVsuAOfOcHidbP-~U;}PhdUcK?tkVCxmftMF_0o z`tWb@_Yuwoe=MuH%z)HSNRz_j`>OZnZ2Kf;aI*Gc?igK{$}m^ zNWQY|ue@?3>&05ds(7-84h03J2q>tda#ebCqXM`CN@p z^R60C;)Dk@+g0AYY8<^Ukm`&SL=}S9l=)*n4|k?NRXghQ1K2*KS$q(>R`$oX8m7?x zIH05cWjZXB-Z54DEDoVPg*cG0@JzV`jSI=6bNu0W+3;cOQ3sB70|ot%1i{AKHSOua z39fvqQFYmL0-CSAZ|1$TkKgxIKApCzsTRV`Y!hn0g0|5pdZ1>DcgdG=Lvpj5=$efA z^Jywl^Qk@bebft}8x-UeMw&17rpw;t6nJ~rCvcb6=O(L#0CX`;N$sct`QjU&dvX^DtVj1d@6Ud<5>K%R#XUxe4B(tB z%aFPoy-5Cq>Pl&~s^_6Q{C7RU2R|RlLqfs9TrJkIdj6PT8lUe~B7;yMnrI#`btleD zjSxeEI}!y$hV*$qSGpJJrvCmStAkzMnK`+!I3n?O z!!Q53n5qh{vmvde8SS4?_$p~O#r8+|@vi%RzWd_f2~!IJR#~!e?WmRr4r+}q_%4GJ z&3NE!z-`G#Ah+lTyIJ^`MSt0EAY+aN_0N!-{=J+B3W-k`E{hFK6g@KR%^%({2D0^N z+l6N#2dIz9p~1{TWaCL1-znJE{>5|YYT#ZAV1=H=NupWi2TEo(oSNXQm*9^3HFe~x z3`xL@-jW#}p&_QyDW&e7S20kemRGqujD_9>ubUzXDv}h2>r1=D|EKU|1sIFr1Z(&{ zN-ZxF@IL#Gx>EtlV4#>BA!vyLgRe0mi8*op7cnGKp`YKLQ;Et@fJGAT+4MY^`O786X=l5RG26MyQ3CF)%yAO8=B-sNwU z%8q-NDrf-;P$!@?p|ACi*G>N6GBAZQ-{Ib@-2RZg6 zHqdVcJY$6x)h`I+DMy&mlOF_Mjt9+#JFzyJ(RCs~OLAppB+YGdi)flDkhZ)K0lh?7 z*7pGFKbkr|{LrAyf{gpGuPKTI^nyNEMWy$AW5K&T^P3!DX^t-?LV!M$0)F{9-E>Ga=qd3_V;)$P4BHENE^?V!no4W3x()Pv=436)-sbll3A z85z`XDB&;y`c+>~eQS(*R73mPP-DYTk+5ZPyaNqIMg( z4n|f7u|%=Y_~E`HRH7{Fdcbea{l|VnG2C~THv#Mpwn?kZEBEb%#;wwy+Obu!RM&@WJ7Yh*ee!2 zBjotlvOvR>CplF-c83Mv1X(R{X02YUF>WM`JsSQ%&q1CT#ATUc3zHukyd0Uu- z=CRI#SL1o_82aC6hc)0(mb3!U+cRmjqm@Z59A=MzdN*``covj^WK4GYW};99Chz)A zQEnaop9`n|G0co8!|e7iTgZ05T1?9P?hCA6XMKPRO}rrNehieXW;d-~TdJmKHb$uSmt7q7+mUJdE5 zbMI2hpP?Q-D(uAn^fwHGT$dnGU|r~9EkURW$Vxw2J32aw0<5i^!hO?Q?PZY&v{6GT z(;=kpJ;KjKA$5@}n_@|}2BiXD;Se%AHd+kON68t`Q2h?DVY~S=chOvlC%D&?M#pVk z!yC_B8Am-JWsDQ>ccKJH`*YDL9&LZpAVAS-IgP3ly?f6C&MNYtIq4ogj^K5ZzitGf z3_vmLI%5)gA98{;D{QqgfSdk6-LGUEMTeB(HOc?K?!)o#HK);q(mz;Z=Cpye?L{xf z-zX_2`LlTH$Vf$yK%Nse>QOw!V}Arxm$S6-V=YwTn@vtgiq~I%!Uou0f82a!D~ydk zpIU_sB6So7fC_ZS=6GiRh-upUf8aJ{k-?1|sc3JsRdfVJmnB_~l^)EmmVY~G4r`;b z_Tcg8ql>~R`xSyht`HK-bG+3OU3_UoQ50|s9)WMy3(7eJs7Z7OG~F>#sL;8sQCH~2^DJ>QoF(lw%=%(sICo= zUH`_zOi2V^A@{)r=B0Wf73OuE9Q{{5}xDX|t(iJf|!FV`C6a{b8j_f~oe<|D(nW6cnY{$9g4u@5=I}-^`^3J`JM+ z+vY?sx(Y#{SICR;vChEnDMrxWZ5BmL>{rj|X z;Ub`YQ0f~{oR@s*_g!4`?yjz_c_i2#x1K}r90kmo1+z%^FNjsVDga~EO0zYIx6Nkl zyzbVK0EX81L5Fr>2}K{3?ac>i9~%Kt<-Usf^S;r4F+{St=C4;SBPQ5fnp+W8rE8Zx zoP+)qeUg3Thk&q(+x)~TwH-d^zyHTi%mi(6)D8rXxCAGUx7hGgo?H1}xS@aHLtOjD zpEJ9s2_cQOcFTHA*F%l-!a9o-)<2)~xs4^yPU*_wU?lmnJ^o&C#>Adaea(4qK=hp7 z91HH{nBV7im@6d;;dDr=pKz|MzxU`uDxrkBc@KaJ zHv*M~Q%+|q0-#BDU@KR6?yjKVhAPB_l~#XLODOfeEbgIE1Wxc{leu!gK6d15g#Y@U z1CSae%;lW~bTAAEW#dm=e199Q1+aW|0jH*coNFm-=u%#M-*+{_a-^WsDPaUc7TrD- z5n~)u0nUw5{6|cbP_h9iSgmA600J#@U@{T@FZ|gQA`l*JkVqUiIo^GMv>&hVZ=j`o z4NJgu5Mr#OB_SebZ(Cld{^3S8(t{9g1*x`yy=VcJ*&>l*rUW*3iILBBVULv-lBRRV z_sgxv`bS1bZ`3}RM4mT+s0g2fsI~I4edMWT7~r{7Ncb3dP4Ug_A^}8%b*Z*R7m{Ym z#`pJt+GK?|_P67GL&&0CUkNM|0@_HKr|G+uL4c!F#*CrCeW2m!`0GgGD@*;MBlC&) zTF8{?lM>%8efb|8=KW6Tk|A&;3@WnHA1Lx_vq`Q%DmxRZluj@L0@VITk#O6@t_}IW zATjl8!^Ki32j-0}knIEps6j@CKD-A7xdq!GmV?1i$G*h#So#GYTbTq5U+HhHMxz&+rDZZit!feC532kYh=O@4@@k=W5 z7xYN`&_`l`uMhqZ0-B! zk$d})k?{ZpJYhhe0)Kq7Z;*@NkHo}8h#?=);a92V(rxbM2574Ej*QbBatY0eqFY`B z!6=%HB`H&T*-$V4B!8^_oG82Nf`Jg17=*5mvmxqeb@d99(XXC?_RPYV5*cm0cQCMk zq>4UR8{<(84MzAXgHLy8E&?K#@+q%SEy}io4@n4;8gd;>fa0~N#X8i3ZU|AzRa)!t z=9sfn%W$Kp_Je^V1_stA(GS|?+okkS?U8ic#ju^K$akttA%Ip_S4X*Y%c6(>$&v#0DMla=g~wd|iHV6| z;;DM?kXKc|XuIGWkKNkQp;dDg93<*M@NzpFxx(6Qq7WIlX;Ux)JY(s4>Zx zjvs+w6nB9?**U@*U~GfX_xUR_C4y+OMX)%Bc*!!Y--^B>-^V#-A=Yg;rL>DM^CsJW z?r`BZH=3?sP|cvd>?@@o6u%a>(RByxd_#o#$|#jYbOV!2m|B-v-k$|Z6ZHW} zsOw280(e2d8#ge#oax#)%E#~L)mjm@=?FHd846-0QU2zh5Z)tPY%)END2n^pj=s&Y zv9a$R92|1j1j6AMAFy9(E2J@vX`J*t=!5;HD&-;8-76CHRq`Md3zR!BiX|i@8Vl!% z7zFBYcj!JwDQQ(*Wcl`{b$SQjg8==iPy>WALfS$t5ee{E-c85cCEt&ao==CN7M)n*&2*G(9-Sywm_OWb@cRA0`Z|}bs_Ujb4J|W;<=$lX*GLXGYI8RC ze!jSBG8nKSLM<#xs+F&l_Y7ClZkm~O+3hH2ViBjV-8(2m8(6(j3})+TF(Q6xSy?`4 z`{oMqpGm3t2GY_sd^!xb;pJ0WudnHQN0!N|&k8YPy_sZMUsF5M<-+P+BE3#nQ1N6~ z5n)Nrp3zY~#Lla7n%8mU)Ykr(w4nc3kd|JhChNzVx8bI%?qaA`=2$h(MPPeqgTA*6 zSugY1LTd_^|48w6jIh{Ke>HDTgMy=nHD#78wb#ia7IpVK+uR*D%ky=T?n>Dcv95p6 ziMW}?c+=)YwLb35&LdNAW=+6;r*!73^N+Uma$Tx<;pkm{R*B2bYvdtD##FXT-n?mu zux7F#Dz5{P7`)|SUI}6gcvC#7>tOrF=1!o{bcR@PCcF8x=0x_CPC~s|soqH8le9fA zsn8yt1Xo$OXWh%?DUEn)>MC1Y()-jqqxKO@g8kbw`WYUzb0ptfZx%$^9l5ndPs4YL zW`;^+!l!1A2eTacZ}Dz>9^{5fMaRXb5P5y`-#mDE@}T6ASU_uAd(Dnrp~HjHou8f~ zG=ih_w|GBeRQ4V_ESQ7>NqYV7JHd$?H5UewpTG)_iz@C5- z_l6Mp&MH5~D;8ASbu$p;KZSbg!XG_5MRdYGhG)+rw-Bhnr!*_$LyM7>}Qw{&h{v~1mL3BWi>4%bB1r~_vdJX7stEw;;P~40=$QEmE-S3?+~38 zGfmaW?Jo-uHhRxes2M*J-QYKX;7+lZa>|s5K}eWwiZizO z$gejDsf8{J+`e@sw9|`tY*3BYZaVi3>j-uvAza#i^Nr^cBe3S*UT^MvDRJVBN}yFz zirWaaUt?WH}v&L&;oi;ad>#E*ddqt)A5XaU)#EJWK zCiAJ{X?83$H@$r^As)CjYsQT0@papSutTH0gK#Zn zZ9`9w5y!bPMw3r5vsJCVx;vWIZ7rOKjfc6^{Hz9NEM?E6Akw3n=Ic>XG+r!m#KUtT z{EP3w9Mf?ALPQSZ0&fVL(!e}GUAM_b){mnb22(%NM8QOT^ZWBx05`zHSHoNAl7j5@ z?eqGSe4P-=Jti*E!h6SnJ)3j?m^GeCKYpM7Wcv8gZ2XkM{$f3!+;!%?%GRbZwF#rQ zXL8&duPc_Qda8~kYx^L(y3VZlVKsKaa#jM5H`?r`wUe%<*_0;q^tii(2iLkrj`k6+ zDL=chd3k)Q8?n(B-3$Bl`T#@wc-Ll`)N8<$48cvtr&{s{~_<_NS-*wcxX? zp5t!fe4!$4xUhr&b1(0oOEEplf}vh&_TQ!|w=^j}l*ptBxE_~RAO!aJnY8q`cPQF4 zF1-qAkPpxwpU7uCZXv+fFS(IBJ9GA>B3Dj`=QXWeohS?Q1gq`XF}neTYs`Aj$7}8E zW9*j#(Pq1$r{A)2?B0;ZcNQTevIOA4oH8$C-cJui%_=0!k7^L6Ph~H3{J0`n+x=Q2 z{;~8J!==JT$MO=G1_W|{Wmv<;qBZ-X;6lt8VXffZ)AS~mcY6~BT?W864{K@cEeO&C zTI{I72{BtWGa5SDm_gKDG^gJsEfevU!c`oHE2^{G(kCe}6oWh*UVT>1x1OTh*j2d-DtwUBNKKnpJ_rh+md#5u z97{F+&Z?E%z)R|WTKzo=7g7w8aLbqI!o%4T)MdOhR5tnk=o-neGp?UKY><@E6cf72lf1aeW4VBwoCAABqi1=Cn*T8mR;np*<{%WY@@Ti3_ zk2?BVT+sky<)(v}G!w5O^8@?O^_m(lXI%C-adaoDa-RhDOc(Y&&fTcXa~oSI9u)kv zwckP@%Vq03mXY?O4&;w>sd!ZK%zM2Kc)KqkDsQ@YKts&t{$usR7Kb&*Td+Sq`Ex8>aLw@AoxQ)6FE7Hs@Qt;~64jon0n?lJ4qm zi1DcdTO{$B0=Zj;$rG`Oy_v0!Qpd%{nIuYAtCBAWHm&YV!7B@hE_2+c+ZB5=#!dm< z-0shA{VDUmti_gmBf+yf+@L1E!2Fe zb10)aRYXuV=HaC8&MkGT{S!W%jE48c~Qk~i*smL7ElXZL=g zhKqtMT$t~+7>68}1z}s|@rOaHJ)?l=?HV@=*6JGjgCVc*J$oAuVM2)&Y$3pHP`_${ z@kj>_hi4inv9q#n-6Yo4J?D1YVm1q1@yD4Wnp$fc(t6b7+as0e;qjT_z^y^^(zwDh zh*W5Ma{Nv8SdZ%~ID}G%-Oyqn)NV*V;p4a1nG{ypS(V9-w|p6M>GnLGGkL~#7Ht(L zQnF)7r^>w>Z}$6>L{L|l7O zUXuE*8XTbiLFGV@?l?60@$B-HN$3h+;#JyKb(OlT1)WdPc?D41JbYakFT$J zBvQ4u)l`$Rm+_Hjr8MEFERVm-sY`FU&2-B!!p%MZjiQmI2JV~eD4Y}E)5uTkF6)xX zzkFU(;d(Ohm?#%xA{XMbo^hz)U%-v-YM3Zo1bs@R>(BHiE zvrFD*sk`5zHmp)KKlhF_awKqM#3_C_^c7-m|GJ%>N8QTh)Z%1w2M;Kx6Auwqv9Vb% z^%f*iNN!zTb2!IpI#BaN5tRgT^8)FZH~X9J?P>&hA5VYo=P#5-&GtO!i+O*c{(Z%} zE4p@37!Qq_lC2EE!Nv1JL-3Q)g0wUnCKt7*9fT(fi*a(h3A>k%A_EldBIFf_wRajO zTpO!qgBdXj$UGYoB8O=@%D6?Bw20D`ri!?Krm@(G_IY&lJ$AfP<-hooh-({zXLrsr zNqHz<10mqJrm<+__BBl#r~9i~Q4F;`(^mt1{#IM3;n%??BNg@VHM!wWyp==P!AbEM zMJ{JtH+!BcDnz+i-eMQ{oI}HXt@&E}S{-rW(|Xl6I)jt8_V=G5ic5uT7vHZ1bXGl> ztwAs_x6|4)6r>bemymnae%gt-wVA`DS^v3wiYwsk#4|28^wv=Q^UCpho}#69(Pd56 zsw$q`2GXX?MXz4i6VWt!v5qKcTOD5;1<9>tcELGQL^ExGcm_QKB0i()TXol4N88Bh zfLS2e#;{gIcK*VW?X$xmW3hd<}iR6L86+0Ku71rJ}nGk)}3$mMfu z>;NwFoeF#>b87xco-^Ma2GG8F)Mc4(SxXvNdP`m`bcZx`hF!+A^@|$yQ$KMllqOEr z6P!Kb{dum4nx2do?_9mg__DFYi&4kiVWRS96pjLvrtYNC2*2qwxx*LO#50ea<;nT0sXX zbF=SM^TJ#1u<0ggP8@2OKLsND7Y)EBi%UxfHo&$2z3y???V3}LX}z;)g|U|JfrD66 zj=)E|PzOh>d|^+|Z=YTe?z!*l+eqjJSR8VZk~s)Ye1fK05dya2>|HpdLSX{*oHz1zj@L9_m?f5~^TnX>Z?t)TK|5N!B9li>yuU`j?u&;R#NI$wtBblqZ zO*3C(+A(l_|RO=lSK7oLn4c7cb7 zt7#t&@`OW9&(vU|zv#?-M9Z&hmpseu>xQ4%Z z;_lK4RJ|ah6OSZ8Tj{ArYtXNAI|T#?HnGd{p^g^Yr9Lzl zlE>U=qmQ`S>+0&HxFo+OZLeX%Sbi}UtM-DJK(k`+MHHVs$%{1Puvv#z5x2=&;hI>$ zzSDR9;_gMASRRh~honfXyRDS6EL2&(DDKqx(9qmDu!g5C7=CNuv5sVFAr-Tf>|0s;a7Pjsl|S{4M4^ z8$N0r;U+B%`S6w89+A5!!VU3$z^-`(l=Ot9uD@(ROBE2BJ7Pdaw~pLPT0@XyZ>K{# z$2b%}UgVpA^I)Z+2%k5YIOpWQV-RL z7|c0l2-ST1xhHdk^a1g>p}}P-v*M5f)H5eLw&)_0!A+M{YAl1AdnLQb%z{v=et{1pw|jm%``P2uIBVm zs~@Ou!V~^%rqq~HSZPewvfyg>BPv;PO;wzy*e literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-template-details.png b/docs/_static/images/badges/badges-admin-template-details.png new file mode 100644 index 0000000000000000000000000000000000000000..ff222b48d9ec6eef092ade8df64bea4a4942f74b GIT binary patch literal 125853 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 dZT_tA0i`@`oD|5W^5dM6F^$z_G`F18( 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 literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin-template-requirements.png b/docs/_static/images/badges/badges-admin-template-requirements.png new file mode 100644 index 0000000000000000000000000000000000000000..0568ecdd2e0ec9371daa2394b1c56e53f0a20154 GIT binary patch literal 58748 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 bu`8?~*96LxM54}Hz|S*jB`KJMzTf`=>Z;BX literal 0 HcmV?d00001 diff --git a/docs/_static/images/badges/badges-admin.png b/docs/_static/images/badges/badges-admin.png new file mode 100644 index 0000000000000000000000000000000000000000..e05d8e9c5487ae93d00754041fcc1baf2b4fca22 GIT binary patch literal 30533 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/admin/badges/credly_organization to add Credly Organization. + a. Add UUID (unique identifier) for the Credly organization + b. Add the authorization token of the Credly organization. + +Please note that UUID and authorization token will be given to you during the creation of the Credly Organization on the Credly side + +Check: the system pulls the Organization's data and updates its name. + +.. _Configuration: configuration.html + + +3. Synchronize badge templates +------------------------------ + Note: For detailed information, go to the `Configuration`_ section. + +From the “Credly Organizations” list, select the Organization(s) you want to use and select ``Sync organization badge templates`` action. + +The system pulls the list of badge templates from the Credly Organization. Navigate to the “Credly badge templates” list and check newly created templates. + +.. _Configuration: configuration.html + +4. Setup badge requirements +--------------------------- + +.. note:: + + Requirements describe **what** and **how** must happen on the system to earn a badge. + +The crucial part of the badge template configuration is the requirements specification. At least one requirement must be associated with a badge template. + +Go to the first badge template details page (admin/badges/credly_badge_templates) and add requirements for it: + +1. find the “Badge Requirements” section; +2. add a new item and select an event type (what is expected to happen); + a. optionally, put a description; +3. save and navigate to the Requirement details (Change link); + a. optionally, specify data rules in the “Data Rules” section (how exactly it is expected to happen); +4. add a new item and describe the rule; +5. select a key path - specific data element; +6. select an operator - how to compare the value; +7. enter a value - expected parameter’s value. + +.. note:: + + A configuration for the badge template that must be issued on a specific course completion looks as following: + + - Requirement 1: + - event type: ``org.openedx.learning.course.passing.status.updated.v1`` + - description: ``On the Demo course completion.`` + - Data rule 1: + - key path: ``course.course_key`` + - operator: ``equals`` + - value: ``course-v1:edX+DemoX+Demo_Course`` + - Data rule 2: + - key path: ``is_passing`` + - operator: ``equals`` + - value: ``true`` + +It is possible to put more than one requirement in a badge template. + +5. Activate configured badge templates +-------------------------------------- + + To active a badge template check the ``is active`` checkbox on its edit page. + +Once badge requirements are set up, it should be “enabled” to start “working”. + +Once enabled, the badge template will be active and ready. + +.. warning:: + + Configuration updates for active badge templates are discouraged since they may cause learners’ inconsistent experience. + +6. See users Badge Progress +--------------------------- + +Current badge progress can be seen in the “Badge progress records” section in the Credentials admin panel. + +Since badge templates can have more than one requirement, there can be partially completed badges. + +7. See awarded user credentials +------------------------------- + +Already earned badges are listed in the "Credly badges" section of the admin panel. + +.. note:: + + The Credly Badge is an extended version of a user credential record. + +Once badge progress is complete (all requirements were *fulfilled*), the system: + +1. creates internal user credentials (CredlyBadge); +2. notifies about the badge awarding (public signal); +3. requests Credly service to issue the badge (API request). + +8. See issued Credly badges +--------------------------- + +Earned internal badges (user credentials) spread to the Credly service. + +On a successful Credly badge issuing, the CredlyBadge user credential is updated with its requisites: + +1. external UUID; +2. external state; + +The Credly badge is visible in the Credly service. + + +9. Badge template withdrawal +---------------------------- + +Badge template can be deactivated by putting it in the inactive state (``is active`` checkbox). + +Inactive badge templates are ignored during the processing. diff --git a/docs/badges/settings.rst b/docs/badges/settings.rst new file mode 100644 index 000000000..093dd8ad4 --- /dev/null +++ b/docs/badges/settings.rst @@ -0,0 +1,179 @@ +Settings +======== + +.. note:: + + You can find technical details on how to set up proper configurations for badges to be active in this section. + +Badges feature settings allow configuration: + +1. feature availability; +2. event bus public signals subset for badges; +3. the Credly service integration details (URLs, sandbox usage, etc.); + + +Feature switch +-------------- + +The Badges feature is under a feature switch (disabled by default). + +To enable the feature, update these settings as follows: + +.. code-block:: python + + # Platform services settings: + FEATURES["BADGES_ENABLED"] = True + + # Credentials service settings: + BADGES_ENABLED = True + + +Default settings +---------------- + +The feature has its configuration: + +.. code-block:: python + + # Credentials settings: + BADGES_CONFIG = { + # these events become available in requirements/penalties setup: + "events": [ + "org.openedx.learning.course.passing.status.updated.v1", + "org.openedx.learning.ccx.course.passing.status.updated.v1", + ], + # Credly integration: + "credly": { + "CREDLY_BASE_URL": "https://credly.com/", + "CREDLY_API_BASE_URL": "https://api.credly.com/v1/", + "CREDLY_SANDBOX_BASE_URL": "https://sandbox.credly.com/", + "CREDLY_SANDBOX_API_BASE_URL": "https://sandbox-api.credly.com/v1/", + "USE_SANDBOX": False, + }, + # requirements data rules: + "rules": { + "ignored_keypaths": [ + "user.id", + "user.is_active", + "user.pii.username", + "user.pii.email", + "user.pii.name", + ], + }, + } + +- ``events`` - explicit event bus signals list (only events with PII user data in payload are applicable). +- ``credly`` - Credly integration details. +- ``rules.ignored_keypaths`` - event payload paths to exclude from data rule options (see: Configuration_). + +Credly integration +~~~~~~~~~~~~~~~~~~ + +- USE_SANDBOX - enables Credly sandbox usage (development, testing); +- CREDLY_BASE_URL - Credly service host URL; +- CREDLY_API_BASE_URL - Credly API host URL; +- CREDLY_SANDBOX_BASE_URL - Credly sandbox host URL; +- CREDLY_SANDBOX_API_BASE_URL - Credly sandbox API host URL; + + +Event bus settings +------------------ + + ``learning-badges-lifecycle`` is the event bus topic for all Badges related events. + +The Badges feature has updated event bus producer configurations for the Platform and the Credentials services. + +Source public signals +~~~~~~~~~~~~~~~~~~~~~ + +Platform's event bus producer configuration was extended with 2 public signals: + +1. information about the fact someone’s course grade was updated (allows course completion recognition); +2. information about the fact someone’s CCX course grade was updated (allows CCX course completion recognition). + +.. code-block:: python + + # Platform services settings: + EVENT_BUS_PRODUCER_CONFIG = { + ... + + "org.openedx.learning.course.passing.status.updated.v1": { + "learning-badges-lifecycle": { + "event_key_field": "course_passing_status.course.course_key", + "enabled": _should_send_learning_badge_events, + }, + }, + "org.openedx.learning.ccx.course.passing.status.updated.v1": { + "learning-badges-lifecycle": { + "event_key_field": "course_passing_status.course.course_key", + "enabled": _should_send_learning_badge_events, + }, + }, + } + +Emitted public signals +~~~~~~~~~~~~~~~~~~~~~~ + +The Badges feature introduced 2 own event types: + +1. information about the fact someone has earned a badge; +2. information about the fact someone's badge was revoked; + +.. code-block:: python + + # Credentials service settings: + EVENT_BUS_PRODUCER_CONFIG = { + ... + + "org.openedx.learning.badge.awarded.v1": { + "learning-badges-lifecycle": {"event_key_field": "badge.uuid", "enabled": True }, + }, + "org.openedx.learning.badge.revoked.v1": { + "learning-badges-lifecycle": {"event_key_field": "badge.uuid", "enabled": True }, + }, + } + +Consuming workers +~~~~~~~~~~~~~~~~~ + +.. note:: + + Consumers implementation depends on the used event bus. + +Event bus options: + +- Redis Streams +- Kafka +- ... + +The Credentials and the Platform services **produce** (push) their public signals as messages to the stream. + +To **consume** (pull) those messages a consumer process is required. + +Redis Streams +############# + +When the Redis Streams event bus is used, the ``-learning-badges-lifecycle`` stream is used for messages transport. + +For producing and consuming a single package (broker) is used - event-bus-redis_. + +"Event Bus Redis" is implemented as a Django application and provides a Django management command for consuming messages +(see all details in the package's README). + +.. code-block:: bash + + # Credentials service consumer example: + /edx/app/credentials/credentials/manage.py consume_events -t learning-badges-lifecycle -g credentials_dev --extra={"consumer_name":"credentials_dev.consumer1"} + + # LMS service consumer example: + /edx/app/edxapp/edx-platform/manage.py lms consume_events -t learning-badges-lifecycle -g lms_dev --extra={"consumer_name":"lms_dev.consumer1"} + +.. note:: + + **Credentials event bus consumer** is crucial for the Badges feature, since it is responsible for all incoming events processing. + + **LMS event bus consumer** is only required if LMS wants to receive information about badges processing results (awarding/revocation). + + +.. _Configuration: configuration.html +.. _event-bus-redis: https://github.com/openedx/event-bus-redis \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index f1054682b..b987b1aed 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,4 +28,4 @@ This repository contains the edX Credentials Service, used as the backend to sup lms_user_id program_completion_emails decisions - + badges/index diff --git a/requirements/all.txt b/requirements/all.txt index 171aa7fba..96dbf6a3a 100644 --- a/requirements/all.txt +++ b/requirements/all.txt @@ -10,12 +10,17 @@ asgiref==3.8.1 # -r requirements/production.txt # django # django-cors-headers - # django-simple-history -astroid==3.1.0 + # django-stubs +astroid==3.2.3 # via # -r requirements/dev.txt # pylint # pylint-celery +async-timeout==4.0.3 + # via + # -r requirements/dev.txt + # -r requirements/production.txt + # redis attrs==23.2.0 # via # -r requirements/dev.txt @@ -34,30 +39,26 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # -r requirements/production.txt # django # djangorestframework -bcrypt==4.1.3 - # via - # -r requirements/dev.txt - # paramiko black==24.4.2 # via -r requirements/dev.txt bleach==6.1.0 # via # -r requirements/dev.txt # -r requirements/production.txt -boto3==1.34.99 +boto3==1.34.145 # via # -r requirements/production.txt # django-ses -botocore==1.34.99 +botocore==1.34.145 # via # -r requirements/production.txt # boto3 # s3transfer -cachetools==5.3.3 +cachetools==5.4.0 # via # -r requirements/dev.txt # tox -certifi==2024.2.2 +certifi==2024.7.4 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -111,9 +112,9 @@ coreschema==0.0.4 # -r requirements/dev.txt # -r requirements/production.txt # coreapi -coverage==7.5.1 +coverage==7.6.0 # via -r requirements/dev.txt -cryptography==42.0.7 +cryptography==42.0.8 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -139,7 +140,7 @@ distlib==0.3.8 # via # -r requirements/dev.txt # virtualenv -django==4.2.13 +django==4.2.14 # via # -c requirements/common_constraints.txt # -r requirements/dev.txt @@ -150,9 +151,13 @@ django==4.2.13 # django-debug-toolbar # django-extensions # django-filter + # django-model-utils # django-ses + # django-simple-history # django-statici18n # django-storages + # django-stubs + # django-stubs-ext # django-waffle # djangorestframework # drf-jwt @@ -165,6 +170,7 @@ django==4.2.13 # edx-django-utils # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-i18n-tools # edx-toggles # openedx-events @@ -175,7 +181,7 @@ django-appconf==1.0.6 # -r requirements/dev.txt # -r requirements/production.txt # django-statici18n -django-cors-headers==4.3.1 +django-cors-headers==4.4.0 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -185,7 +191,7 @@ django-crum==0.7.9 # -r requirements/production.txt # edx-django-utils # edx-toggles -django-debug-toolbar==4.3.0 +django-debug-toolbar==4.4.6 # via -r requirements/dev.txt django-extensions==3.2.3 # via @@ -195,6 +201,10 @@ django-filter==24.2 # via # -r requirements/dev.txt # -r requirements/production.txt +django-model-utils==4.5.1 + # via + # -r requirements/dev.txt + # -r requirements/production.txt django-ratelimit==4.1.0 # via # -r requirements/dev.txt @@ -203,9 +213,9 @@ django-rest-swagger==2.2.0 # via # -r requirements/dev.txt # -r requirements/production.txt -django-ses==4.0.0 +django-ses==4.1.0 # via -r requirements/production.txt -django-simple-history==3.5.0 +django-simple-history==3.7.0 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -217,10 +227,16 @@ django-statici18n==2.5.0 # via # -r requirements/dev.txt # -r requirements/production.txt -django-storages==1.14.3 +django-storages==1.14.4 # via # -r requirements/dev.txt # -r requirements/production.txt +django-stubs==5.0.2 + # via -r requirements/dev.txt +django-stubs-ext==5.0.2 + # via + # -r requirements/dev.txt + # django-stubs django-waffle==4.1.0 # via # -r requirements/dev.txt @@ -232,7 +248,7 @@ django-webpack-loader==3.1.0 # via # -r requirements/dev.txt # -r requirements/production.txt -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -254,7 +270,7 @@ drf-yasg==1.21.7 # via # -r requirements/dev.txt # -r requirements/production.txt -edx-ace==1.8.0 +edx-ace==1.9.1 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -262,7 +278,12 @@ edx-auth-backends==4.3.0 # via # -r requirements/dev.txt # -r requirements/production.txt -edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.13 +edx-ccx-keys==1.3.0 + # via + # -r requirements/dev.txt + # -r requirements/production.txt + # openedx-events +edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.17 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -274,12 +295,14 @@ edx-django-sites-extensions==4.2.0 # via # -r requirements/dev.txt # -r requirements/production.txt -edx-django-utils==5.13.0 +edx-django-utils==5.14.2 # via # -r requirements/dev.txt # -r requirements/production.txt + # edx-ace # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-rest-api-client # edx-toggles # openedx-events @@ -291,20 +314,25 @@ edx-event-bus-kafka==5.7.0 # via # -r requirements/dev.txt # -r requirements/production.txt +edx-event-bus-redis @ git+https://github.com/openedx/event-bus-redis.git@v0.5.0 + # via + # -r requirements/dev.txt + # -r requirements/production.txt edx-i18n-tools==1.6.0 # via # -r requirements/dev.txt # -r requirements/production.txt # edx-credentials-themes -edx-lint==5.3.6 +edx-lint==5.3.7 # via -r requirements/dev.txt -edx-opaque-keys[django]==2.9.0 +edx-opaque-keys[django]==2.10.0 # via # -r requirements/dev.txt # -r requirements/production.txt + # edx-ccx-keys # edx-drf-extensions # openedx-events -edx-rest-api-client==5.7.0 +edx-rest-api-client==5.7.1 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -313,27 +341,28 @@ edx-toggles==5.2.0 # -r requirements/dev.txt # -r requirements/production.txt # edx-event-bus-kafka -exceptiongroup==1.2.1 + # edx-event-bus-redis +exceptiongroup==1.2.2 # via # -r requirements/dev.txt # pytest factory-boy==3.3.0 # via -r requirements/dev.txt -faker==25.0.1 +faker==26.0.0 # via # -r requirements/dev.txt # factory-boy -fastavro==1.9.4 +fastavro==1.9.5 # via # -r requirements/dev.txt # -r requirements/production.txt # openedx-events -filelock==3.14.0 +filelock==3.15.4 # via # -r requirements/dev.txt # tox # virtualenv -fontawesomefree==6.5.1 +fontawesomefree==6.6.0 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -407,20 +436,23 @@ mccabe==0.7.0 # via # -r requirements/dev.txt # pylint +mypy==1.10.1 + # via -r requirements/dev.txt mypy-extensions==1.0.0 # via # -r requirements/dev.txt # black + # mypy mysqlclient==2.2.4 # via # -r requirements/dev.txt # -r requirements/production.txt -newrelic==9.9.0 +newrelic==9.12.0 # via # -r requirements/dev.txt # -r requirements/production.txt # edx-django-utils -nodeenv==1.8.0 +nodeenv==1.9.1 # via -r requirements/production.txt oauthlib==3.2.2 # via @@ -433,16 +465,17 @@ openapi-codec==1.3.2 # -r requirements/dev.txt # -r requirements/production.txt # django-rest-swagger -openedx-atlas==0.6.0 +openedx-atlas==0.6.1 # via # -r requirements/dev.txt # -r requirements/production.txt -openedx-events==9.9.2 +openedx-events==9.11.0 # via # -r requirements/dev.txt # -r requirements/production.txt # edx-event-bus-kafka -packaging==24.0 + # edx-event-bus-redis +packaging==24.1 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -466,11 +499,11 @@ pbr==6.0.0 # -r requirements/dev.txt # -r requirements/production.txt # stevedore -pillow==10.3.0 +pillow==10.4.0 # via # -r requirements/dev.txt # -r requirements/production.txt -platformdirs==4.2.1 +platformdirs==4.2.2 # via # -r requirements/dev.txt # black @@ -487,7 +520,7 @@ polib==1.2.0 # -r requirements/dev.txt # -r requirements/production.txt # edx-i18n-tools -psutil==5.9.8 +psutil==6.0.0 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -511,7 +544,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # segment-analytics-python # social-auth-core -pylint==3.1.0 +pylint==3.2.5 # via # -r requirements/dev.txt # edx-lint @@ -535,7 +568,7 @@ pymemcache==4.0.0 # via # -r requirements/dev.txt # -r requirements/production.txt -pymongo==4.4.0 +pymongo==4.8.0 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -550,15 +583,11 @@ pypng==0.20220715.0 # -r requirements/dev.txt # -r requirements/production.txt # qrcode -pyproject-api==1.6.1 +pyproject-api==1.7.1 # via # -r requirements/dev.txt # tox -pyrsistent==0.20.0 - # via - # -r requirements/dev.txt - # jsonschema -pytest==8.2.0 +pytest==8.2.2 # via # -r requirements/dev.txt # pytest-django @@ -592,8 +621,6 @@ pytz==2024.1 # -r requirements/production.txt # django-ses # drf-yasg -pywatchman==2.0.0 ; "linux" in sys_platform - # via -r requirements/dev.txt pyyaml==6.0.1 # via # -r requirements/dev.txt @@ -607,7 +634,12 @@ qrcode==7.4.2 # via # -r requirements/dev.txt # -r requirements/production.txt -requests==2.31.0 +redis==5.0.7 + # via + # -r requirements/dev.txt + # -r requirements/production.txt + # walrus +requests==2.32.3 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -625,9 +657,9 @@ requests-oauthlib==2.0.0 # -r requirements/dev.txt # -r requirements/production.txt # social-auth-core -responses==0.25.0 +responses==0.25.3 # via -r requirements/dev.txt -s3transfer==0.10.1 +s3transfer==0.10.2 # via # -r requirements/production.txt # boto3 @@ -658,6 +690,7 @@ six==1.16.0 # bleach # edx-ace # edx-auth-backends + # edx-ccx-keys # edx-django-release-util # edx-lint # python-dateutil @@ -666,7 +699,7 @@ slumber==0.7.1 # -r requirements/dev.txt # -r requirements/production.txt # edx-rest-api-client -social-auth-app-django==5.4.1 +social-auth-app-django==5.4.2 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -677,7 +710,7 @@ social-auth-core==4.5.4 # -r requirements/production.txt # edx-auth-backends # social-auth-app-django -sqlparse==0.5.0 +sqlparse==0.5.1 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -691,7 +724,7 @@ stevedore==5.2.0 # edx-ace # edx-django-utils # edx-opaque-keys -testfixtures==8.2.0 +testfixtures==8.3.0 # via -r requirements/dev.txt text-unidecode==1.3 # via @@ -702,24 +735,33 @@ tomli==2.0.1 # via # -r requirements/dev.txt # black + # django-stubs + # mypy # pylint # pyproject-api # pytest # tox -tomlkit==0.12.4 +tomlkit==0.13.0 # via # -r requirements/dev.txt # pylint -tox==4.15.0 +tox==4.16.0 # via -r requirements/dev.txt -typing-extensions==4.11.0 +types-pyyaml==6.0.12.20240311 + # via + # -r requirements/dev.txt + # django-stubs +typing-extensions==4.12.2 # via # -r requirements/dev.txt # -r requirements/production.txt # asgiref # astroid # black + # django-stubs + # django-stubs-ext # edx-opaque-keys + # mypy # pylint # qrcode uritemplate==4.1.1 @@ -728,7 +770,7 @@ uritemplate==4.1.1 # -r requirements/production.txt # coreapi # drf-yasg -urllib3==1.26.18 +urllib3==1.26.19 # via # -c requirements/constraints.txt # -r requirements/dev.txt @@ -736,24 +778,25 @@ urllib3==1.26.18 # botocore # requests # responses -virtualenv==20.26.1 +virtualenv==20.26.3 # via # -r requirements/dev.txt # tox -webencodings==0.5.1 +walrus==0.9.4 # via # -r requirements/dev.txt # -r requirements/production.txt - # bleach -websocket-client==0.59.0 + # edx-event-bus-redis +webencodings==0.5.1 # via # -r requirements/dev.txt - # docker-compose + # -r requirements/production.txt + # bleach xss-utils==0.6.0 # via # -r requirements/dev.txt # -r requirements/production.txt -zipp==3.18.1 +zipp==3.19.2 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -762,7 +805,7 @@ zope-event==5.0 # via # -r requirements/production.txt # gevent -zope-interface==6.3 +zope-interface==6.4.post2 # via # -r requirements/production.txt # gevent diff --git a/requirements/base.in b/requirements/base.in index 3340d6ba6..8f14189f8 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -16,6 +16,7 @@ django django-cors-headers django-extensions django-filter +django-model-utils django-ratelimit django-rest-swagger django-simple-history @@ -41,6 +42,7 @@ markdown mysqlclient newrelic openedx-atlas +openedx-events pillow pygments python-memcached @@ -52,6 +54,6 @@ segment-analytics-python social-auth-app-django xss-utils - # TODO Install in configuration -git+https://github.com/openedx/credentials-themes.git@0.4.13#egg=edx_credentials_themes==0.4.13 +git+https://github.com/openedx/event-bus-redis.git@v0.5.0 +git+https://github.com/openedx/credentials-themes.git@0.4.17#egg=edx_credentials_themes==0.4.17 diff --git a/requirements/base.txt b/requirements/base.txt index 8bbab7ede..18cb577a9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,7 +8,8 @@ asgiref==3.8.1 # via # django # django-cors-headers - # django-simple-history +async-timeout==4.0.3 + # via redis attrs==23.2.0 # via # edx-ace @@ -22,7 +23,7 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # djangorestframework bleach==6.1.0 # via -r requirements/base.in -certifi==2024.2.2 +certifi==2024.7.4 # via requests cffi==1.16.0 # via @@ -43,7 +44,7 @@ coreapi==2.3.3 # openapi-codec coreschema==0.0.4 # via coreapi -cryptography==42.0.7 +cryptography==42.0.8 # via # pyjwt # social-auth-core @@ -53,7 +54,7 @@ defusedxml==0.8.0rc2 # social-auth-core didkit==0.3.2 # via -r requirements/base.in -django==4.2.13 +django==4.2.14 # via # -c requirements/common_constraints.txt # -r requirements/base.in @@ -62,6 +63,8 @@ django==4.2.13 # django-crum # django-extensions # django-filter + # django-model-utils + # django-simple-history # django-statici18n # django-storages # django-waffle @@ -76,6 +79,7 @@ django==4.2.13 # edx-django-utils # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-i18n-tools # edx-toggles # openedx-events @@ -83,7 +87,7 @@ django==4.2.13 # xss-utils django-appconf==1.0.6 # via django-statici18n -django-cors-headers==4.3.1 +django-cors-headers==4.4.0 # via -r requirements/base.in django-crum==0.7.9 # via @@ -93,17 +97,19 @@ django-extensions==3.2.3 # via -r requirements/base.in django-filter==24.2 # via -r requirements/base.in +django-model-utils==4.5.1 + # via -r requirements/base.in django-ratelimit==4.1.0 # via -r requirements/base.in django-rest-swagger==2.2.0 # via -r requirements/base.in -django-simple-history==3.5.0 +django-simple-history==3.7.0 # via -r requirements/base.in django-sortedm2m==3.1.1 # via -r requirements/base.in django-statici18n==2.5.0 # via -r requirements/base.in -django-storages==1.14.3 +django-storages==1.14.4 # via -r requirements/base.in django-waffle==4.1.0 # via @@ -113,7 +119,7 @@ django-waffle==4.1.0 # edx-toggles django-webpack-loader==3.1.0 # via -r requirements/base.in -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements/base.in # django-rest-swagger @@ -126,21 +132,25 @@ drf-jwt==1.19.2 # via edx-drf-extensions drf-yasg==1.21.7 # via -r requirements/base.in -edx-ace==1.8.0 +edx-ace==1.9.1 # via -r requirements/base.in edx-auth-backends==4.3.0 # via -r requirements/base.in -edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.13 +edx-ccx-keys==1.3.0 + # via openedx-events +edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.17 # via -r requirements/base.in edx-django-release-util==1.4.0 # via -r requirements/base.in edx-django-sites-extensions==4.2.0 # via -r requirements/base.in -edx-django-utils==5.13.0 +edx-django-utils==5.14.2 # via # -r requirements/base.in + # edx-ace # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-rest-api-client # edx-toggles # openedx-events @@ -148,22 +158,26 @@ edx-drf-extensions==10.3.0 # via -r requirements/base.in edx-event-bus-kafka==5.7.0 # via -r requirements/base.in +edx-event-bus-redis @ git+https://github.com/openedx/event-bus-redis.git@v0.5.0 + # via -r requirements/base.in edx-i18n-tools==1.6.0 # via edx-credentials-themes -edx-opaque-keys[django]==2.9.0 +edx-opaque-keys[django]==2.10.0 # via # -r requirements/base.in + # edx-ccx-keys # edx-drf-extensions # openedx-events -edx-rest-api-client==5.7.0 +edx-rest-api-client==5.7.1 # via -r requirements/base.in edx-toggles==5.2.0 # via # -r requirements/base.in # edx-event-bus-kafka -fastavro==1.9.4 + # edx-event-bus-redis +fastavro==1.9.5 # via openedx-events -fontawesomefree==6.5.1 +fontawesomefree==6.6.0 # via -r requirements/base.in idna==3.7 # via requests @@ -190,7 +204,7 @@ markupsafe==2.1.5 # via jinja2 mysqlclient==2.2.4 # via -r requirements/base.in -newrelic==9.9.0 +newrelic==9.12.0 # via # -r requirements/base.in # edx-django-utils @@ -200,21 +214,24 @@ oauthlib==3.2.2 # social-auth-core openapi-codec==1.3.2 # via django-rest-swagger -openedx-atlas==0.6.0 +openedx-atlas==0.6.1 # via -r requirements/base.in -openedx-events==9.9.2 - # via edx-event-bus-kafka -packaging==24.0 +openedx-events==9.11.0 + # via + # -r requirements/base.in + # edx-event-bus-kafka + # edx-event-bus-redis +packaging==24.1 # via drf-yasg path==16.14.0 # via edx-i18n-tools pbr==6.0.0 # via stevedore -pillow==10.3.0 +pillow==10.4.0 # via -r requirements/base.in polib==1.2.0 # via edx-i18n-tools -psutil==5.9.8 +psutil==6.0.0 # via edx-django-utils pycparser==2.22 # via cffi @@ -230,7 +247,7 @@ pyjwt[crypto]==2.8.0 # social-auth-core pymemcache==4.0.0 # via -r requirements/base.in -pymongo==4.4.0 +pymongo==4.8.0 # via edx-opaque-keys pynacl==1.5.0 # via edx-django-utils @@ -258,7 +275,9 @@ pyyaml==6.0.1 # edx-i18n-tools qrcode==7.4.2 # via -r requirements/base.in -requests==2.31.0 +redis==5.0.7 + # via walrus +requests==2.32.3 # via # -r requirements/base.in # coreapi @@ -286,11 +305,12 @@ six==1.16.0 # bleach # edx-ace # edx-auth-backends + # edx-ccx-keys # edx-django-release-util # python-dateutil slumber==0.7.1 # via edx-rest-api-client -social-auth-app-django==5.4.1 +social-auth-app-django==5.4.2 # via # -r requirements/base.in # edx-auth-backends @@ -298,7 +318,7 @@ social-auth-core==4.5.4 # via # edx-auth-backends # social-auth-app-django -sqlparse==0.5.0 +sqlparse==0.5.1 # via django stevedore==5.2.0 # via @@ -308,7 +328,7 @@ stevedore==5.2.0 # edx-opaque-keys text-unidecode==1.3 # via python-slugify -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via # asgiref # edx-opaque-keys @@ -317,13 +337,15 @@ uritemplate==4.1.1 # via # coreapi # drf-yasg -urllib3==1.26.18 +urllib3==1.26.19 # via # -c requirements/constraints.txt # requests +walrus==0.9.4 + # via edx-event-bus-redis webencodings==0.5.1 # via bleach xss-utils==0.6.0 # via -r requirements/base.in -zipp==3.18.1 +zipp==3.19.2 # via importlib-metadata diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 397652e03..77753fcd2 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -1,8 +1,4 @@ -# This is a temporary solution to override the real common_constraints.txt -# In edx-lint, until the pyjwt constraint in edx-lint has been removed. -# See BOM-2721 for more details. -# Below is the copied and edited version of common_constraints - +# This is a temporary solution to override the real common_constraints.txt\n# In edx-lint, until the pyjwt constraint in edx-lint has been removed.\n# See BOM-2721 for more details.\n# Below is the copied and edited version of common_constraints\n # A central location for most common version constraints # (across edx repos) for pip-installation. # @@ -22,6 +18,7 @@ Django<5.0 # elasticsearch>=7.14.0 includes breaking changes in it which caused issues in discovery upgrade process. # elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html +# See https://github.com/openedx/edx-platform/issues/35126 for more info elasticsearch<7.14.0 # django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected @@ -34,3 +31,10 @@ elasticsearch<7.14.0 # So we need to pin it globally, for now. # Ticket for unpinning: https://github.com/openedx/edx-lint/issues/407 importlib-metadata<7 + +# Cause: https://github.com/openedx/event-tracking/pull/290 +# event-tracking 2.4.1 upgrades to pymongo 4.4.0 which is not supported on edx-platform. +# We will pin event-tracking to do not break existing installations +# This can be unpinned once https://github.com/openedx/edx-platform/issues/34586 +# has been resolved and edx-platform is running with pymongo>=4.4.0 +event-tracking<2.4.1 diff --git a/requirements/dev.txt b/requirements/dev.txt index 34fb8e292..29646a6bb 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -9,12 +9,16 @@ asgiref==3.8.1 # -r requirements/test.txt # django # django-cors-headers - # django-simple-history -astroid==3.1.0 + # django-stubs +astroid==3.2.3 # via # -r requirements/test.txt # pylint # pylint-celery +async-timeout==4.0.3 + # via + # -r requirements/test.txt + # redis attrs==23.2.0 # via # -r requirements/test.txt @@ -30,17 +34,15 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # -r requirements/test.txt # django # djangorestframework -bcrypt==4.1.3 - # via paramiko black==24.4.2 # via -r requirements/test.txt bleach==6.1.0 # via -r requirements/test.txt -cachetools==5.3.3 +cachetools==5.4.0 # via # -r requirements/test.txt # tox -certifi==2024.2.2 +certifi==2024.7.4 # via # -r requirements/test.txt # requests @@ -87,9 +89,9 @@ coreschema==0.0.4 # via # -r requirements/test.txt # coreapi -coverage==7.5.1 +coverage==7.6.0 # via -r requirements/test.txt -cryptography==42.0.7 +cryptography==42.0.8 # via # -r requirements/test.txt # pyjwt @@ -111,7 +113,7 @@ distlib==0.3.8 # via # -r requirements/test.txt # virtualenv -django==4.2.13 +django==4.2.14 # via # -c requirements/common_constraints.txt # -r requirements/test.txt @@ -121,8 +123,12 @@ django==4.2.13 # django-debug-toolbar # django-extensions # django-filter + # django-model-utils + # django-simple-history # django-statici18n # django-storages + # django-stubs + # django-stubs-ext # django-waffle # djangorestframework # drf-jwt @@ -135,6 +141,7 @@ django==4.2.13 # edx-django-utils # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-i18n-tools # edx-toggles # openedx-events @@ -144,31 +151,37 @@ django-appconf==1.0.6 # via # -r requirements/test.txt # django-statici18n -django-cors-headers==4.3.1 +django-cors-headers==4.4.0 # via -r requirements/test.txt django-crum==0.7.9 # via # -r requirements/test.txt # edx-django-utils # edx-toggles -django-debug-toolbar==4.3.0 +django-debug-toolbar==4.4.6 # via -r requirements/dev.in django-extensions==3.2.3 # via -r requirements/test.txt django-filter==24.2 # via -r requirements/test.txt +django-model-utils==4.5.1 + # via -r requirements/test.txt django-ratelimit==4.1.0 # via -r requirements/test.txt django-rest-swagger==2.2.0 # via -r requirements/test.txt -django-simple-history==3.5.0 +django-simple-history==3.7.0 # via -r requirements/test.txt django-sortedm2m==3.1.1 # via -r requirements/test.txt django-statici18n==2.5.0 # via -r requirements/test.txt -django-storages==1.14.3 +django-storages==1.14.4 # via -r requirements/test.txt +django-stubs==5.0.2 + # via -r requirements/dev.in +django-stubs-ext==5.0.2 + # via django-stubs django-waffle==4.1.0 # via # -r requirements/test.txt @@ -177,7 +190,7 @@ django-waffle==4.1.0 # edx-toggles django-webpack-loader==3.1.0 # via -r requirements/test.txt -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements/test.txt # django-rest-swagger @@ -194,21 +207,27 @@ drf-jwt==1.19.2 # edx-drf-extensions drf-yasg==1.21.7 # via -r requirements/test.txt -edx-ace==1.8.0 +edx-ace==1.9.1 # via -r requirements/test.txt edx-auth-backends==4.3.0 # via -r requirements/test.txt -edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.13 +edx-ccx-keys==1.3.0 + # via + # -r requirements/test.txt + # openedx-events +edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.17 # via -r requirements/test.txt edx-django-release-util==1.4.0 # via -r requirements/test.txt edx-django-sites-extensions==4.2.0 # via -r requirements/test.txt -edx-django-utils==5.13.0 +edx-django-utils==5.14.2 # via # -r requirements/test.txt + # edx-ace # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-rest-api-client # edx-toggles # openedx-events @@ -216,44 +235,48 @@ edx-drf-extensions==10.3.0 # via -r requirements/test.txt edx-event-bus-kafka==5.7.0 # via -r requirements/test.txt +edx-event-bus-redis @ git+https://github.com/openedx/event-bus-redis.git@v0.5.0 + # via -r requirements/test.txt edx-i18n-tools==1.6.0 # via # -r requirements/dev.in # -r requirements/test.txt # edx-credentials-themes -edx-lint==5.3.6 +edx-lint==5.3.7 # via -r requirements/test.txt -edx-opaque-keys[django]==2.9.0 +edx-opaque-keys[django]==2.10.0 # via # -r requirements/test.txt + # edx-ccx-keys # edx-drf-extensions # openedx-events -edx-rest-api-client==5.7.0 +edx-rest-api-client==5.7.1 # via -r requirements/test.txt edx-toggles==5.2.0 # via # -r requirements/test.txt # edx-event-bus-kafka -exceptiongroup==1.2.1 + # edx-event-bus-redis +exceptiongroup==1.2.2 # via # -r requirements/test.txt # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==25.0.1 +faker==26.0.0 # via # -r requirements/test.txt # factory-boy -fastavro==1.9.4 +fastavro==1.9.5 # via # -r requirements/test.txt # openedx-events -filelock==3.14.0 +filelock==3.15.4 # via # -r requirements/test.txt # tox # virtualenv -fontawesomefree==6.5.1 +fontawesomefree==6.6.0 # via -r requirements/test.txt httpretty==1.1.4 # via -r requirements/test.txt @@ -303,13 +326,16 @@ mccabe==0.7.0 # via # -r requirements/test.txt # pylint +mypy==1.10.1 + # via -r requirements/dev.in mypy-extensions==1.0.0 # via # -r requirements/test.txt # black + # mypy mysqlclient==2.2.4 # via -r requirements/test.txt -newrelic==9.9.0 +newrelic==9.12.0 # via # -r requirements/test.txt # edx-django-utils @@ -322,13 +348,14 @@ openapi-codec==1.3.2 # via # -r requirements/test.txt # django-rest-swagger -openedx-atlas==0.6.0 +openedx-atlas==0.6.1 # via -r requirements/test.txt -openedx-events==9.9.2 +openedx-events==9.11.0 # via # -r requirements/test.txt # edx-event-bus-kafka -packaging==24.0 + # edx-event-bus-redis +packaging==24.1 # via # -r requirements/test.txt # black @@ -348,9 +375,9 @@ pbr==6.0.0 # via # -r requirements/test.txt # stevedore -pillow==10.3.0 +pillow==10.4.0 # via -r requirements/test.txt -platformdirs==4.2.1 +platformdirs==4.2.2 # via # -r requirements/test.txt # black @@ -366,7 +393,7 @@ polib==1.2.0 # via # -r requirements/test.txt # edx-i18n-tools -psutil==5.9.8 +psutil==6.0.0 # via # -r requirements/test.txt # edx-django-utils @@ -385,7 +412,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # segment-analytics-python # social-auth-core -pylint==3.1.0 +pylint==3.2.5 # via # -r requirements/test.txt # edx-lint @@ -407,7 +434,7 @@ pylint-plugin-utils==0.8.2 # pylint-django pymemcache==4.0.0 # via -r requirements/test.txt -pymongo==4.4.0 +pymongo==4.8.0 # via # -r requirements/test.txt # edx-opaque-keys @@ -419,13 +446,11 @@ pypng==0.20220715.0 # via # -r requirements/test.txt # qrcode -pyproject-api==1.6.1 +pyproject-api==1.7.1 # via # -r requirements/test.txt # tox -pyrsistent==0.20.0 - # via jsonschema -pytest==8.2.0 +pytest==8.2.2 # via # -r requirements/test.txt # pytest-django @@ -451,8 +476,6 @@ pytz==2024.1 # via # -r requirements/test.txt # drf-yasg -pywatchman==2.0.0 ; "linux" in sys_platform - # via -r requirements/dev.in pyyaml==6.0.1 # via # -r requirements/test.txt @@ -463,7 +486,11 @@ pyyaml==6.0.1 # responses qrcode==7.4.2 # via -r requirements/test.txt -requests==2.31.0 +redis==5.0.7 + # via + # -r requirements/test.txt + # walrus +requests==2.32.3 # via # -r requirements/test.txt # coreapi @@ -479,7 +506,7 @@ requests-oauthlib==2.0.0 # via # -r requirements/test.txt # social-auth-core -responses==0.25.0 +responses==0.25.3 # via -r requirements/test.txt sailthru-client==2.2.3 # via @@ -502,6 +529,7 @@ six==1.16.0 # bleach # edx-ace # edx-auth-backends + # edx-ccx-keys # edx-django-release-util # edx-lint # python-dateutil @@ -509,7 +537,7 @@ slumber==0.7.1 # via # -r requirements/test.txt # edx-rest-api-client -social-auth-app-django==5.4.1 +social-auth-app-django==5.4.2 # via # -r requirements/test.txt # edx-auth-backends @@ -518,7 +546,7 @@ social-auth-core==4.5.4 # -r requirements/test.txt # edx-auth-backends # social-auth-app-django -sqlparse==0.5.0 +sqlparse==0.5.1 # via # -r requirements/test.txt # django @@ -530,7 +558,7 @@ stevedore==5.2.0 # edx-ace # edx-django-utils # edx-opaque-keys -testfixtures==8.2.0 +testfixtures==8.3.0 # via -r requirements/test.txt text-unidecode==1.3 # via @@ -540,23 +568,30 @@ tomli==2.0.1 # via # -r requirements/test.txt # black + # django-stubs + # mypy # pylint # pyproject-api # pytest # tox -tomlkit==0.12.4 +tomlkit==0.13.0 # via # -r requirements/test.txt # pylint -tox==4.15.0 +tox==4.16.0 # via -r requirements/test.txt -typing-extensions==4.11.0 +types-pyyaml==6.0.12.20240311 + # via django-stubs +typing-extensions==4.12.2 # via # -r requirements/test.txt # asgiref # astroid # black + # django-stubs + # django-stubs-ext # edx-opaque-keys + # mypy # pylint # qrcode uritemplate==4.1.1 @@ -564,25 +599,27 @@ uritemplate==4.1.1 # -r requirements/test.txt # coreapi # drf-yasg -urllib3==1.26.18 +urllib3==1.26.19 # via # -c requirements/constraints.txt # -r requirements/test.txt # requests # responses -virtualenv==20.26.1 +virtualenv==20.26.3 # via # -r requirements/test.txt # tox +walrus==0.9.4 + # via + # -r requirements/test.txt + # edx-event-bus-redis webencodings==0.5.1 # via # -r requirements/test.txt # bleach -websocket-client==0.59.0 - # via docker-compose xss-utils==0.6.0 # via -r requirements/test.txt -zipp==3.18.1 +zipp==3.19.2 # via # -r requirements/test.txt # importlib-metadata diff --git a/requirements/docs.txt b/requirements/docs.txt index c95b7868d..70ad6e3f0 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -14,7 +14,7 @@ babel==2.15.0 # sphinx beautifulsoup4==4.12.3 # via pydata-sphinx-theme -certifi==2024.2.2 +certifi==2024.7.4 # via requests charset-normalizer==3.3.2 # via requests @@ -36,7 +36,7 @@ jsx-lexer==2.0.1 # via -r requirements/docs.in markupsafe==2.1.5 # via jinja2 -packaging==24.0 +packaging==24.1 # via # pydata-sphinx-theme # sphinx @@ -50,7 +50,7 @@ pygments==2.18.0 # sphinx pytz==2024.1 # via babel -requests==2.31.0 +requests==2.32.3 # via sphinx snowballstemmer==2.2.0 # via sphinx @@ -75,11 +75,11 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via pydata-sphinx-theme -urllib3==1.26.18 +urllib3==1.26.19 # via # -c requirements/constraints.txt # requests -zipp==3.18.1 +zipp==3.19.2 # via importlib-metadata diff --git a/requirements/pip.txt b/requirements/pip.txt index e3ffcc7b6..3aba9984a 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.43.0 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==24.0 +pip==24.1.2 # via -r requirements/pip.in -setuptools==69.5.1 +setuptools==71.0.3 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index 75dfbb563..cd5c274bd 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -12,7 +12,7 @@ importlib-metadata==6.11.0 # via # -c requirements/common_constraints.txt # build -packaging==24.0 +packaging==24.1 # via build pip-tools==7.4.1 # via -r requirements/pip_tools.in @@ -26,7 +26,7 @@ tomli==2.0.1 # pip-tools wheel==0.43.0 # via pip-tools -zipp==3.18.1 +zipp==3.19.2 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/production.txt b/requirements/production.txt index 25334f6d0..44b4d78ec 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -9,7 +9,10 @@ asgiref==3.8.1 # -r requirements/base.txt # django # django-cors-headers - # django-simple-history +async-timeout==4.0.3 + # via + # -r requirements/base.txt + # redis attrs==23.2.0 # via # -r requirements/base.txt @@ -27,13 +30,13 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # djangorestframework bleach==6.1.0 # via -r requirements/base.txt -boto3==1.34.99 +boto3==1.34.145 # via django-ses -botocore==1.34.99 +botocore==1.34.145 # via # boto3 # s3transfer -certifi==2024.2.2 +certifi==2024.7.4 # via # -r requirements/base.txt # requests @@ -64,7 +67,7 @@ coreschema==0.0.4 # via # -r requirements/base.txt # coreapi -cryptography==42.0.7 +cryptography==42.0.8 # via # -r requirements/base.txt # pyjwt @@ -76,7 +79,7 @@ defusedxml==0.8.0rc2 # social-auth-core didkit==0.3.2 # via -r requirements/base.txt -django==4.2.13 +django==4.2.14 # via # -c requirements/common_constraints.txt # -r requirements/base.txt @@ -85,7 +88,9 @@ django==4.2.13 # django-crum # django-extensions # django-filter + # django-model-utils # django-ses + # django-simple-history # django-statici18n # django-storages # django-waffle @@ -100,6 +105,7 @@ django==4.2.13 # edx-django-utils # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-i18n-tools # edx-toggles # openedx-events @@ -109,7 +115,7 @@ django-appconf==1.0.6 # via # -r requirements/base.txt # django-statici18n -django-cors-headers==4.3.1 +django-cors-headers==4.4.0 # via -r requirements/base.txt django-crum==0.7.9 # via @@ -120,19 +126,21 @@ django-extensions==3.2.3 # via -r requirements/base.txt django-filter==24.2 # via -r requirements/base.txt +django-model-utils==4.5.1 + # via -r requirements/base.txt django-ratelimit==4.1.0 # via -r requirements/base.txt django-rest-swagger==2.2.0 # via -r requirements/base.txt -django-ses==4.0.0 +django-ses==4.1.0 # via -r requirements/production.in -django-simple-history==3.5.0 +django-simple-history==3.7.0 # via -r requirements/base.txt django-sortedm2m==3.1.1 # via -r requirements/base.txt django-statici18n==2.5.0 # via -r requirements/base.txt -django-storages==1.14.3 +django-storages==1.14.4 # via -r requirements/base.txt django-waffle==4.1.0 # via @@ -142,7 +150,7 @@ django-waffle==4.1.0 # edx-toggles django-webpack-loader==3.1.0 # via -r requirements/base.txt -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements/base.txt # django-rest-swagger @@ -159,21 +167,27 @@ drf-jwt==1.19.2 # edx-drf-extensions drf-yasg==1.21.7 # via -r requirements/base.txt -edx-ace==1.8.0 +edx-ace==1.9.1 # via -r requirements/base.txt edx-auth-backends==4.3.0 # via -r requirements/base.txt -edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.13 +edx-ccx-keys==1.3.0 + # via + # -r requirements/base.txt + # openedx-events +edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.17 # via -r requirements/base.txt edx-django-release-util==1.4.0 # via -r requirements/base.txt edx-django-sites-extensions==4.2.0 # via -r requirements/base.txt -edx-django-utils==5.13.0 +edx-django-utils==5.14.2 # via # -r requirements/base.txt + # edx-ace # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-rest-api-client # edx-toggles # openedx-events @@ -181,26 +195,30 @@ edx-drf-extensions==10.3.0 # via -r requirements/base.txt edx-event-bus-kafka==5.7.0 # via -r requirements/base.txt +edx-event-bus-redis @ git+https://github.com/openedx/event-bus-redis.git@v0.5.0 + # via -r requirements/base.txt edx-i18n-tools==1.6.0 # via # -r requirements/base.txt # edx-credentials-themes -edx-opaque-keys[django]==2.9.0 +edx-opaque-keys[django]==2.10.0 # via # -r requirements/base.txt + # edx-ccx-keys # edx-drf-extensions # openedx-events -edx-rest-api-client==5.7.0 +edx-rest-api-client==5.7.1 # via -r requirements/base.txt edx-toggles==5.2.0 # via # -r requirements/base.txt # edx-event-bus-kafka -fastavro==1.9.4 + # edx-event-bus-redis +fastavro==1.9.5 # via # -r requirements/base.txt # openedx-events -fontawesomefree==6.5.1 +fontawesomefree==6.6.0 # via -r requirements/base.txt gevent==24.2.1 # via -r requirements/production.in @@ -248,12 +266,12 @@ markupsafe==2.1.5 # jinja2 mysqlclient==2.2.4 # via -r requirements/base.txt -newrelic==9.9.0 +newrelic==9.12.0 # via # -r requirements/base.txt # -r requirements/production.in # edx-django-utils -nodeenv==1.8.0 +nodeenv==1.9.1 # via -r requirements/production.in oauthlib==3.2.2 # via @@ -264,13 +282,14 @@ openapi-codec==1.3.2 # via # -r requirements/base.txt # django-rest-swagger -openedx-atlas==0.6.0 +openedx-atlas==0.6.1 # via -r requirements/base.txt -openedx-events==9.9.2 +openedx-events==9.11.0 # via # -r requirements/base.txt # edx-event-bus-kafka -packaging==24.0 + # edx-event-bus-redis +packaging==24.1 # via # -r requirements/base.txt # drf-yasg @@ -283,13 +302,13 @@ pbr==6.0.0 # via # -r requirements/base.txt # stevedore -pillow==10.3.0 +pillow==10.4.0 # via -r requirements/base.txt polib==1.2.0 # via # -r requirements/base.txt # edx-i18n-tools -psutil==5.9.8 +psutil==6.0.0 # via # -r requirements/base.txt # edx-django-utils @@ -310,7 +329,7 @@ pyjwt[crypto]==2.8.0 # social-auth-core pymemcache==4.0.0 # via -r requirements/base.txt -pymongo==4.4.0 +pymongo==4.8.0 # via # -r requirements/base.txt # edx-opaque-keys @@ -353,7 +372,11 @@ pyyaml==6.0.1 # edx-i18n-tools qrcode==7.4.2 # via -r requirements/base.txt -requests==2.31.0 +redis==5.0.7 + # via + # -r requirements/base.txt + # walrus +requests==2.32.3 # via # -r requirements/base.txt # coreapi @@ -368,7 +391,7 @@ requests-oauthlib==2.0.0 # via # -r requirements/base.txt # social-auth-core -s3transfer==0.10.1 +s3transfer==0.10.2 # via boto3 sailthru-client==2.2.3 # via @@ -391,13 +414,14 @@ six==1.16.0 # bleach # edx-ace # edx-auth-backends + # edx-ccx-keys # edx-django-release-util # python-dateutil slumber==0.7.1 # via # -r requirements/base.txt # edx-rest-api-client -social-auth-app-django==5.4.1 +social-auth-app-django==5.4.2 # via # -r requirements/base.txt # edx-auth-backends @@ -406,7 +430,7 @@ social-auth-core==4.5.4 # -r requirements/base.txt # edx-auth-backends # social-auth-app-django -sqlparse==0.5.0 +sqlparse==0.5.1 # via # -r requirements/base.txt # django @@ -421,7 +445,7 @@ text-unidecode==1.3 # via # -r requirements/base.txt # python-slugify -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via # -r requirements/base.txt # asgiref @@ -432,25 +456,29 @@ uritemplate==4.1.1 # -r requirements/base.txt # coreapi # drf-yasg -urllib3==1.26.18 +urllib3==1.26.19 # via # -c requirements/constraints.txt # -r requirements/base.txt # botocore # requests +walrus==0.9.4 + # via + # -r requirements/base.txt + # edx-event-bus-redis webencodings==0.5.1 # via # -r requirements/base.txt # bleach xss-utils==0.6.0 # via -r requirements/base.txt -zipp==3.18.1 +zipp==3.19.2 # via # -r requirements/base.txt # importlib-metadata zope-event==5.0 # via gevent -zope-interface==6.3 +zope-interface==6.4.post2 # via gevent # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/test.txt b/requirements/test.txt index 735101bed..d6c8abe05 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -9,11 +9,14 @@ asgiref==3.8.1 # -r requirements/base.txt # django # django-cors-headers - # django-simple-history -astroid==3.1.0 +astroid==3.2.3 # via # pylint # pylint-celery +async-timeout==4.0.3 + # via + # -r requirements/base.txt + # redis attrs==23.2.0 # via # -r requirements/base.txt @@ -33,9 +36,9 @@ black==24.4.2 # via -r requirements/test.in bleach==6.1.0 # via -r requirements/base.txt -cachetools==5.3.3 +cachetools==5.4.0 # via tox -certifi==2024.2.2 +certifi==2024.7.4 # via # -r requirements/base.txt # requests @@ -77,9 +80,9 @@ coreschema==0.0.4 # via # -r requirements/base.txt # coreapi -coverage==7.5.1 +coverage==7.6.0 # via -r requirements/test.in -cryptography==42.0.7 +cryptography==42.0.8 # via # -r requirements/base.txt # pyjwt @@ -105,6 +108,8 @@ distlib==0.3.8 # django-crum # django-extensions # django-filter + # django-model-utils + # django-simple-history # django-statici18n # django-storages # django-waffle @@ -119,6 +124,7 @@ distlib==0.3.8 # edx-django-utils # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-i18n-tools # edx-toggles # openedx-events @@ -128,7 +134,7 @@ django-appconf==1.0.6 # via # -r requirements/base.txt # django-statici18n -django-cors-headers==4.3.1 +django-cors-headers==4.4.0 # via -r requirements/base.txt django-crum==0.7.9 # via @@ -139,17 +145,19 @@ django-extensions==3.2.3 # via -r requirements/base.txt django-filter==24.2 # via -r requirements/base.txt +django-model-utils==4.5.1 + # via -r requirements/base.txt django-ratelimit==4.1.0 # via -r requirements/base.txt django-rest-swagger==2.2.0 # via -r requirements/base.txt -django-simple-history==3.5.0 +django-simple-history==3.7.0 # via -r requirements/base.txt django-sortedm2m==3.1.1 # via -r requirements/base.txt django-statici18n==2.5.0 # via -r requirements/base.txt -django-storages==1.14.3 +django-storages==1.14.4 # via -r requirements/base.txt django-waffle==4.1.0 # via @@ -159,7 +167,7 @@ django-waffle==4.1.0 # edx-toggles django-webpack-loader==3.1.0 # via -r requirements/base.txt -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements/base.txt # django-rest-swagger @@ -176,21 +184,27 @@ drf-jwt==1.19.2 # edx-drf-extensions drf-yasg==1.21.7 # via -r requirements/base.txt -edx-ace==1.8.0 +edx-ace==1.9.1 # via -r requirements/base.txt edx-auth-backends==4.3.0 # via -r requirements/base.txt -edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.13 +edx-ccx-keys==1.3.0 + # via + # -r requirements/base.txt + # openedx-events +edx-credentials-themes @ git+https://github.com/openedx/credentials-themes.git@0.4.17 # via -r requirements/base.txt edx-django-release-util==1.4.0 # via -r requirements/base.txt edx-django-sites-extensions==4.2.0 # via -r requirements/base.txt -edx-django-utils==5.13.0 +edx-django-utils==5.14.2 # via # -r requirements/base.txt + # edx-ace # edx-drf-extensions # edx-event-bus-kafka + # edx-event-bus-redis # edx-rest-api-client # edx-toggles # openedx-events @@ -198,38 +212,42 @@ edx-drf-extensions==10.3.0 # via -r requirements/base.txt edx-event-bus-kafka==5.7.0 # via -r requirements/base.txt +edx-event-bus-redis @ git+https://github.com/openedx/event-bus-redis.git@v0.5.0 + # via -r requirements/base.txt edx-i18n-tools==1.6.0 # via # -r requirements/base.txt # edx-credentials-themes -edx-lint==5.3.6 +edx-lint==5.3.7 # via -r requirements/test.in -edx-opaque-keys[django]==2.9.0 +edx-opaque-keys[django]==2.10.0 # via # -r requirements/base.txt + # edx-ccx-keys # edx-drf-extensions # openedx-events -edx-rest-api-client==5.7.0 +edx-rest-api-client==5.7.1 # via -r requirements/base.txt edx-toggles==5.2.0 # via # -r requirements/base.txt # edx-event-bus-kafka -exceptiongroup==1.2.1 + # edx-event-bus-redis +exceptiongroup==1.2.2 # via pytest factory-boy==3.3.0 # via -r requirements/test.in -faker==25.0.1 +faker==26.0.0 # via factory-boy -fastavro==1.9.4 +fastavro==1.9.5 # via # -r requirements/base.txt # openedx-events -filelock==3.14.0 +filelock==3.15.4 # via # tox # virtualenv -fontawesomefree==6.5.1 +fontawesomefree==6.6.0 # via -r requirements/base.txt httpretty==1.1.4 # via -r requirements/test.in @@ -279,7 +297,7 @@ mypy-extensions==1.0.0 # via black mysqlclient==2.2.4 # via -r requirements/base.txt -newrelic==9.9.0 +newrelic==9.12.0 # via # -r requirements/base.txt # edx-django-utils @@ -292,13 +310,14 @@ openapi-codec==1.3.2 # via # -r requirements/base.txt # django-rest-swagger -openedx-atlas==0.6.0 +openedx-atlas==0.6.1 # via -r requirements/base.txt -openedx-events==9.9.2 +openedx-events==9.11.0 # via # -r requirements/base.txt # edx-event-bus-kafka -packaging==24.0 + # edx-event-bus-redis +packaging==24.1 # via # -r requirements/base.txt # black @@ -316,9 +335,9 @@ pbr==6.0.0 # via # -r requirements/base.txt # stevedore -pillow==10.3.0 +pillow==10.4.0 # via -r requirements/base.txt -platformdirs==4.2.1 +platformdirs==4.2.2 # via # black # pylint @@ -332,7 +351,7 @@ polib==1.2.0 # via # -r requirements/base.txt # edx-i18n-tools -psutil==5.9.8 +psutil==6.0.0 # via # -r requirements/base.txt # edx-django-utils @@ -351,7 +370,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # segment-analytics-python # social-auth-core -pylint==3.1.0 +pylint==3.2.5 # via # edx-lint # pylint-celery @@ -367,7 +386,7 @@ pylint-plugin-utils==0.8.2 # pylint-django pymemcache==4.0.0 # via -r requirements/base.txt -pymongo==4.4.0 +pymongo==4.8.0 # via # -r requirements/base.txt # edx-opaque-keys @@ -379,9 +398,9 @@ pypng==0.20220715.0 # via # -r requirements/base.txt # qrcode -pyproject-api==1.6.1 +pyproject-api==1.7.1 # via tox -pytest==8.2.0 +pytest==8.2.2 # via # -r requirements/test.in # pytest-django @@ -417,7 +436,11 @@ pyyaml==6.0.1 # responses qrcode==7.4.2 # via -r requirements/base.txt -requests==2.31.0 +redis==5.0.7 + # via + # -r requirements/base.txt + # walrus +requests==2.32.3 # via # -r requirements/base.txt # coreapi @@ -433,7 +456,7 @@ requests-oauthlib==2.0.0 # via # -r requirements/base.txt # social-auth-core -responses==0.25.0 +responses==0.25.3 # via -r requirements/test.in sailthru-client==2.2.3 # via @@ -456,6 +479,7 @@ six==1.16.0 # bleach # edx-ace # edx-auth-backends + # edx-ccx-keys # edx-django-release-util # edx-lint # python-dateutil @@ -463,7 +487,7 @@ slumber==0.7.1 # via # -r requirements/base.txt # edx-rest-api-client -social-auth-app-django==5.4.1 +social-auth-app-django==5.4.2 # via # -r requirements/base.txt # edx-auth-backends @@ -472,7 +496,7 @@ social-auth-core==4.5.4 # -r requirements/base.txt # edx-auth-backends # social-auth-app-django -sqlparse==0.5.0 +sqlparse==0.5.1 # via # -r requirements/base.txt # django @@ -483,7 +507,7 @@ stevedore==5.2.0 # edx-ace # edx-django-utils # edx-opaque-keys -testfixtures==8.2.0 +testfixtures==8.3.0 # via -r requirements/test.in text-unidecode==1.3 # via @@ -496,11 +520,11 @@ tomli==2.0.1 # pyproject-api # pytest # tox -tomlkit==0.12.4 +tomlkit==0.13.0 # via pylint -tox==4.15.0 +tox==4.16.0 # via -r requirements/test.in -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via # -r requirements/base.txt # asgiref @@ -514,21 +538,25 @@ uritemplate==4.1.1 # -r requirements/base.txt # coreapi # drf-yasg -urllib3==1.26.18 +urllib3==1.26.19 # via # -c requirements/constraints.txt # -r requirements/base.txt # requests # responses -virtualenv==20.26.1 +virtualenv==20.26.3 # via tox +walrus==0.9.4 + # via + # -r requirements/base.txt + # edx-event-bus-redis webencodings==0.5.1 # via # -r requirements/base.txt # bleach xss-utils==0.6.0 # via -r requirements/base.txt -zipp==3.18.1 +zipp==3.19.2 # via # -r requirements/base.txt # importlib-metadata diff --git a/requirements/translations.txt b/requirements/translations.txt index c9cc58eea..5209d8db7 100644 --- a/requirements/translations.txt +++ b/requirements/translations.txt @@ -10,7 +10,7 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # via # -c requirements/constraints.txt # django -django==4.2.13 +django==4.2.14 # via # -c requirements/common_constraints.txt # edx-i18n-tools @@ -26,7 +26,7 @@ polib==1.2.0 # via edx-i18n-tools pyyaml==6.0.1 # via edx-i18n-tools -sqlparse==0.5.0 +sqlparse==0.5.1 # via django -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via asgiref From 7ef72a6b8d8a1955d2907c9324c3af1f6be881a9 Mon Sep 17 00:00:00 2001 From: KyryloKireiev Date: Tue, 23 Jul 2024 17:39:19 +0300 Subject: [PATCH 2/3] feat: backport adding badge issuance --- requirements/all.txt | 32 ++++++-------------------------- requirements/base.txt | 2 +- requirements/dev.txt | 24 ++++-------------------- requirements/django.txt | 2 +- requirements/pip.txt | 2 +- requirements/production.txt | 6 +++--- requirements/test.txt | 8 ++++---- 7 files changed, 20 insertions(+), 56 deletions(-) diff --git a/requirements/all.txt b/requirements/all.txt index 96dbf6a3a..8bc091504 100644 --- a/requirements/all.txt +++ b/requirements/all.txt @@ -10,8 +10,7 @@ asgiref==3.8.1 # -r requirements/production.txt # django # django-cors-headers - # django-stubs -astroid==3.2.3 +astroid==3.2.4 # via # -r requirements/dev.txt # pylint @@ -45,11 +44,11 @@ bleach==6.1.0 # via # -r requirements/dev.txt # -r requirements/production.txt -boto3==1.34.145 +boto3==1.34.146 # via # -r requirements/production.txt # django-ses -botocore==1.34.145 +botocore==1.34.146 # via # -r requirements/production.txt # boto3 @@ -114,7 +113,7 @@ coreschema==0.0.4 # coreapi coverage==7.6.0 # via -r requirements/dev.txt -cryptography==42.0.8 +cryptography==43.0.0 # via # -r requirements/dev.txt # -r requirements/production.txt @@ -156,8 +155,6 @@ django==4.2.14 # django-simple-history # django-statici18n # django-storages - # django-stubs - # django-stubs-ext # django-waffle # djangorestframework # drf-jwt @@ -231,12 +228,6 @@ django-storages==1.14.4 # via # -r requirements/dev.txt # -r requirements/production.txt -django-stubs==5.0.2 - # via -r requirements/dev.txt -django-stubs-ext==5.0.2 - # via - # -r requirements/dev.txt - # django-stubs django-waffle==4.1.0 # via # -r requirements/dev.txt @@ -436,8 +427,6 @@ mccabe==0.7.0 # via # -r requirements/dev.txt # pylint -mypy==1.10.1 - # via -r requirements/dev.txt mypy-extensions==1.0.0 # via # -r requirements/dev.txt @@ -544,7 +533,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # segment-analytics-python # social-auth-core -pylint==3.2.5 +pylint==3.2.6 # via # -r requirements/dev.txt # edx-lint @@ -587,7 +576,7 @@ pyproject-api==1.7.1 # via # -r requirements/dev.txt # tox -pytest==8.2.2 +pytest==8.3.1 # via # -r requirements/dev.txt # pytest-django @@ -735,8 +724,6 @@ tomli==2.0.1 # via # -r requirements/dev.txt # black - # django-stubs - # mypy # pylint # pyproject-api # pytest @@ -747,10 +734,6 @@ tomlkit==0.13.0 # pylint tox==4.16.0 # via -r requirements/dev.txt -types-pyyaml==6.0.12.20240311 - # via - # -r requirements/dev.txt - # django-stubs typing-extensions==4.12.2 # via # -r requirements/dev.txt @@ -758,10 +741,7 @@ typing-extensions==4.12.2 # asgiref # astroid # black - # django-stubs - # django-stubs-ext # edx-opaque-keys - # mypy # pylint # qrcode uritemplate==4.1.1 diff --git a/requirements/base.txt b/requirements/base.txt index 18cb577a9..e387db327 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -44,7 +44,7 @@ coreapi==2.3.3 # openapi-codec coreschema==0.0.4 # via coreapi -cryptography==42.0.8 +cryptography==43.0.0 # via # pyjwt # social-auth-core diff --git a/requirements/dev.txt b/requirements/dev.txt index 29646a6bb..e6c25a26c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -9,8 +9,7 @@ asgiref==3.8.1 # -r requirements/test.txt # django # django-cors-headers - # django-stubs -astroid==3.2.3 +astroid==3.2.4 # via # -r requirements/test.txt # pylint @@ -91,7 +90,7 @@ coreschema==0.0.4 # coreapi coverage==7.6.0 # via -r requirements/test.txt -cryptography==42.0.8 +cryptography==43.0.0 # via # -r requirements/test.txt # pyjwt @@ -127,8 +126,6 @@ django==4.2.14 # django-simple-history # django-statici18n # django-storages - # django-stubs - # django-stubs-ext # django-waffle # djangorestframework # drf-jwt @@ -178,10 +175,6 @@ django-statici18n==2.5.0 # via -r requirements/test.txt django-storages==1.14.4 # via -r requirements/test.txt -django-stubs==5.0.2 - # via -r requirements/dev.in -django-stubs-ext==5.0.2 - # via django-stubs django-waffle==4.1.0 # via # -r requirements/test.txt @@ -326,8 +319,6 @@ mccabe==0.7.0 # via # -r requirements/test.txt # pylint -mypy==1.10.1 - # via -r requirements/dev.in mypy-extensions==1.0.0 # via # -r requirements/test.txt @@ -412,7 +403,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # segment-analytics-python # social-auth-core -pylint==3.2.5 +pylint==3.2.6 # via # -r requirements/test.txt # edx-lint @@ -450,7 +441,7 @@ pyproject-api==1.7.1 # via # -r requirements/test.txt # tox -pytest==8.2.2 +pytest==8.3.1 # via # -r requirements/test.txt # pytest-django @@ -568,8 +559,6 @@ tomli==2.0.1 # via # -r requirements/test.txt # black - # django-stubs - # mypy # pylint # pyproject-api # pytest @@ -580,18 +569,13 @@ tomlkit==0.13.0 # pylint tox==4.16.0 # via -r requirements/test.txt -types-pyyaml==6.0.12.20240311 - # via django-stubs typing-extensions==4.12.2 # via # -r requirements/test.txt # asgiref # astroid # black - # django-stubs - # django-stubs-ext # edx-opaque-keys - # mypy # pylint # qrcode uritemplate==4.1.1 diff --git a/requirements/django.txt b/requirements/django.txt index 092a8ec12..549c1f8bb 100644 --- a/requirements/django.txt +++ b/requirements/django.txt @@ -1 +1 @@ -django==4.2.13 +django==4.2.14 diff --git a/requirements/pip.txt b/requirements/pip.txt index 3aba9984a..ebe14bbef 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.43.0 # The following packages are considered to be unsafe in a requirements file: pip==24.1.2 # via -r requirements/pip.in -setuptools==71.0.3 +setuptools==71.1.0 # via -r requirements/pip.in diff --git a/requirements/production.txt b/requirements/production.txt index 44b4d78ec..e828d9fc7 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -30,9 +30,9 @@ backports-zoneinfo==0.2.1 ; python_version < "3.9" # djangorestframework bleach==6.1.0 # via -r requirements/base.txt -boto3==1.34.145 +boto3==1.34.146 # via django-ses -botocore==1.34.145 +botocore==1.34.146 # via # boto3 # s3transfer @@ -67,7 +67,7 @@ coreschema==0.0.4 # via # -r requirements/base.txt # coreapi -cryptography==42.0.8 +cryptography==43.0.0 # via # -r requirements/base.txt # pyjwt diff --git a/requirements/test.txt b/requirements/test.txt index d6c8abe05..d44cef2f6 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -9,7 +9,7 @@ asgiref==3.8.1 # -r requirements/base.txt # django # django-cors-headers -astroid==3.2.3 +astroid==3.2.4 # via # pylint # pylint-celery @@ -82,7 +82,7 @@ coreschema==0.0.4 # coreapi coverage==7.6.0 # via -r requirements/test.in -cryptography==42.0.8 +cryptography==43.0.0 # via # -r requirements/base.txt # pyjwt @@ -370,7 +370,7 @@ pyjwt[crypto]==2.8.0 # edx-rest-api-client # segment-analytics-python # social-auth-core -pylint==3.2.5 +pylint==3.2.6 # via # edx-lint # pylint-celery @@ -400,7 +400,7 @@ pypng==0.20220715.0 # qrcode pyproject-api==1.7.1 # via tox -pytest==8.2.2 +pytest==8.3.1 # via # -r requirements/test.in # pytest-django From fc2bb9c478f0b3eb29a36e632af4d2878e3b532f Mon Sep 17 00:00:00 2001 From: KyryloKireiev Date: Tue, 23 Jul 2024 17:55:04 +0300 Subject: [PATCH 3/3] fix: Resolve migrations wrong dependencies --- .../0029_alter_usercredential_credential_content_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py b/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py index 5430aa8e5..a60194db9 100644 --- a/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py +++ b/credentials/apps/credentials/migrations/0029_alter_usercredential_credential_content_type.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), - ("credentials", "0029_signatory_not_mandatory"), + ("credentials", "0028_alter_historicalprogramcompletionemailconfiguration_options"), ] operations = [