diff --git a/credentials/apps/badges/admin_forms.py b/credentials/apps/badges/admin_forms.py index c92e4f50a2..3da9cb962e 100644 --- a/credentials/apps/badges/admin_forms.py +++ b/credentials/apps/badges/admin_forms.py @@ -2,6 +2,7 @@ Badges admin forms. """ +from attrs import fields from django import forms from django.utils.translation import gettext_lazy as _ from model_utils import Choices @@ -9,7 +10,7 @@ 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_keypaths +from credentials.apps.badges.utils import get_event_type_keypaths, get_event_type_data class CredlyOrganizationAdminForm(forms.ModelForm): @@ -84,29 +85,7 @@ 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. - """ +class ParentMixin: def get_form_kwargs(self, index): """ Pass parent instance to the form. @@ -117,15 +96,10 @@ def get_form_kwargs(self, index): return kwargs -class DataRuleForm(DataRuleBoolValidationMixin, forms.ModelForm): +class DataRuleExtensionsMixin: """ - Form for DataRule model. + Mixin for DataRule form to extend logic. """ - class Meta: - model = DataRule - fields = "__all__" - - data_path = forms.ChoiceField() def __init__(self, *args, parent_instance=None, **kwargs): """ @@ -138,14 +112,35 @@ def __init__(self, *args, parent_instance=None, **kwargs): 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. + """ -class BadgeRequirementFormSet(forms.BaseInlineFormSet): - def get_form_kwargs(self, index): - kwargs = super().get_form_kwargs(index) - kwargs["parent_instance"] = self.instance - return kwargs + cleaned_data = super().clean() + + data = get_event_type_data(event_type=self.parent_instance.event_type) + data_path_type = [attr.type for attr in fields(data) if attr.name == cleaned_data.get("data_path")][0] + + if data_path_type == bool and cleaned_data.get("value") not in AbstractDataRule.BOOL_VALUES: + raise forms.ValidationError(_("Value must be a boolean.")) + + return cleaned_data +class DataRuleFormSet(ParentMixin, forms.BaseInlineFormSet): ... +class DataRuleForm(DataRuleExtensionsMixin, forms.ModelForm): + """ + Form for DataRule model. + """ + class Meta: + model = DataRule + fields = "__all__" + + data_path = forms.ChoiceField() + + +class BadgeRequirementFormSet(ParentMixin, forms.BaseInlineFormSet): ... class BadgeRequirementForm(forms.ModelForm): class Meta: model = BadgeRequirement @@ -163,38 +158,14 @@ def __init__(self, *args, parent_instance=None, **kwargs): self.fields["group"].initial = chr(65 + self.template.requirements.count()) -class PenaltyDataRuleFormSet(forms.BaseInlineFormSet): - """ - Formset for PenaltyDataRule model. - """ - 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 PenaltyDataRuleForm(DataRuleBoolValidationMixin, forms.ModelForm): +class PenaltyDataRuleFormSet(ParentMixin, forms.BaseInlineFormSet): ... +class PenaltyDataRuleForm(DataRuleExtensionsMixin, forms.ModelForm): """ Form for PenaltyDataRule model. """ + data_path = forms.ChoiceField() + class Meta: model = PenaltyDataRule fields = "__all__" - - data_path = forms.ChoiceField() - - 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)) diff --git a/credentials/apps/badges/utils.py b/credentials/apps/badges/utils.py index fb331982ec..5f2bd82b99 100644 --- a/credentials/apps/badges/utils.py +++ b/credentials/apps/badges/utils.py @@ -115,6 +115,21 @@ def extract_payload(public_signal_kwargs: dict) -> attr.s: return value +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. @@ -126,8 +141,7 @@ def get_event_type_keypaths(event_type: str) -> list: list: A list of all possible keypaths for the given event type. """ - signal = OpenEdxPublicSignal.get_signal_by_type(event_type) - data = extract_payload(signal.init_data) + data = get_event_type_data(event_type) def get_data_keypaths(data): """