From 20bd0f033483da8bcb106f63ee47e08ff42520d4 Mon Sep 17 00:00:00 2001 From: Andrii Date: Fri, 17 May 2024 17:35:27 +0300 Subject: [PATCH] refactor: [ACI-978] some enhancements --- credentials/apps/badges/admin.py | 41 +++++++++++++++++++ credentials/apps/badges/admin_forms.py | 25 +++++++++-- .../0020_remove_badgepenalty_description.py | 17 ++++++++ credentials/apps/badges/models.py | 12 +++--- credentials/settings/base.py | 4 ++ 5 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 credentials/apps/badges/migrations/0020_remove_badgepenalty_description.py diff --git a/credentials/apps/badges/admin.py b/credentials/apps/badges/admin.py index 15772bf2b..4987897a4 100644 --- a/credentials/apps/badges/admin.py +++ b/credentials/apps/badges/admin.py @@ -51,6 +51,7 @@ class BadgeRequirementInline(admin.TabularInline): "group", ) readonly_fields = ("rules",) + ordering = ("group",) form = BadgeRequirementForm formset = BadgeRequirementFormSet @@ -77,6 +78,12 @@ class BadgePenaltyInline(admin.TabularInline): 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): @@ -88,6 +95,20 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): if template_id: kwargs["queryset"] = BadgeRequirement.objects.filter(template_id=template_id) return super().formfield_for_manytomany(db_field, request, **kwargs) + + def rules(self, obj): + """ + Display all data rules for the penalty. + """ + return format_html( + "", + mark_safe( + "".join( + f"
  • {rule.data_path} {rule.OPERATORS[rule.operator]} {rule.value}
  • " + for rule in obj.rules.all() + ) + ), + ) if obj.rules.exists() else _("No rules specified.") class FulfillmentInline(admin.TabularInline): @@ -309,6 +330,7 @@ class BadgeRequirementAdmin(admin.ModelAdmin): "template", "event_type", "template_link", + "group", ] fields = [ @@ -358,11 +380,30 @@ class BadgePenaltyAdmin(admin.ModelAdmin): "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") diff --git a/credentials/apps/badges/admin_forms.py b/credentials/apps/badges/admin_forms.py index 873cdb8c6..c92e4f50a 100644 --- a/credentials/apps/badges/admin_forms.py +++ b/credentials/apps/badges/admin_forms.py @@ -8,7 +8,7 @@ from credentials.apps.badges.credly.api_client import CredlyAPIClient from credentials.apps.badges.credly.exceptions import CredlyAPIError -from credentials.apps.badges.models import BadgePenalty, BadgeRequirement, CredlyOrganization, DataRule, PenaltyDataRule +from credentials.apps.badges.models import AbstractDataRule, BadgePenalty, BadgeRequirement, CredlyOrganization, DataRule, PenaltyDataRule from credentials.apps.badges.utils import get_event_type_keypaths @@ -84,6 +84,25 @@ def clean(self): return cleaned_data +class DataRuleBoolValidationMixin: + """ + Mixin for DataRule form to validate boolean fields. + """ + + def clean(self): + """ + Validate boolean fields. + """ + + cleaned_data = super().clean() + + last_key = cleaned_data.get("data_path").split(".")[-1] + if "is_" in last_key and cleaned_data.get("value") not in AbstractDataRule.BOOL_VALUES: + raise forms.ValidationError(_("Value must be a boolean.")) + + return cleaned_data + + class DataRuleFormSet(forms.BaseInlineFormSet): """ Formset for DataRule model. @@ -98,7 +117,7 @@ def get_form_kwargs(self, index): return kwargs -class DataRuleForm(forms.ModelForm): +class DataRuleForm(DataRuleBoolValidationMixin, forms.ModelForm): """ Form for DataRule model. """ @@ -158,7 +177,7 @@ def get_form_kwargs(self, index): return kwargs -class PenaltyDataRuleForm(forms.ModelForm): +class PenaltyDataRuleForm(DataRuleBoolValidationMixin, forms.ModelForm): """ Form for PenaltyDataRule model. """ diff --git a/credentials/apps/badges/migrations/0020_remove_badgepenalty_description.py b/credentials/apps/badges/migrations/0020_remove_badgepenalty_description.py new file mode 100644 index 000000000..cc65c05c3 --- /dev/null +++ b/credentials/apps/badges/migrations/0020_remove_badgepenalty_description.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.20 on 2024-05-17 14:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('badges', '0019_fulfillment_group'), + ] + + operations = [ + migrations.RemoveField( + model_name='badgepenalty', + name='description', + ), + ] diff --git a/credentials/apps/badges/models.py b/credentials/apps/badges/models.py index 1e18414bf..c4325b29f 100644 --- a/credentials/apps/badges/models.py +++ b/credentials/apps/badges/models.py @@ -267,6 +267,10 @@ class AbstractDataRule(models.Model): # ('gt', '>'), ) + 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".'), @@ -324,12 +328,9 @@ def _value_to_bool(self): Converts the value to a boolean or returns the original value if it is not a boolean string. """ - TRUE_VALUES = ["True", "true", "Yes", "yes", "+"] - FALSE_VALUES = ["False", "false", "No", "no", "-"] - - if self.value in TRUE_VALUES: + if self.value in self.TRUE_VALUES: return "True" - if self.value in FALSE_VALUES: + if self.value in self.FALSE_VALUES: return "False" return self.value @@ -381,7 +382,6 @@ class BadgePenalty(models.Model): BadgeRequirement, help_text=_("Badge requirements for which this penalty is defined."), ) - description = models.TextField(null=True, blank=True, help_text=_("Provide more details if needed.")) class Meta: verbose_name_plural = _("Badge penalties") diff --git a/credentials/settings/base.py b/credentials/settings/base.py index 40f048f40..963505997 100644 --- a/credentials/settings/base.py +++ b/credentials/settings/base.py @@ -616,6 +616,10 @@ "user.pii.username", "user.pii.email", "user.pii.name", + + "course.display_name", + "course.start", + "course.end", ], }, }