Skip to content

Commit

Permalink
feat: [ACI-854] merge from aci.main & refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrii committed Apr 10, 2024
2 parents 79ec550 + 4e98f53 commit 4191a35
Show file tree
Hide file tree
Showing 26 changed files with 612 additions and 306 deletions.
11 changes: 9 additions & 2 deletions credentials/apps/badges/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _

from .admin_forms import BadgePenaltyForm, BadgeRequirementForm, CredlyOrganizationAdminForm, DataRuleForm, PenaltyDataRuleForm
from .admin_forms import (
BadgePenaltyForm,
BadgeRequirementForm,
CredlyOrganizationAdminForm,
DataRuleForm,
PenaltyDataRuleForm,
)

from .models import (
BadgePenalty,
Expand Down Expand Up @@ -37,7 +43,7 @@ class BadgePenaltyInline(admin.TabularInline):
extra = 0
form = BadgePenaltyForm


class FulfillmentInline(admin.TabularInline):
model = Fulfillment
extra = 0
Expand Down Expand Up @@ -168,6 +174,7 @@ class BadgePenaltyAdmin(admin.ModelAdmin):
"""
Badge requirement penalty setup admin.
"""

inlines = [
DataRulePenaltyInline,
]
Expand Down
31 changes: 21 additions & 10 deletions credentials/apps/badges/admin_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,29 @@ def _ensure_organization_exists(self, api_client):
raise forms.ValidationError(message=str(err))



class BadgePenaltyForm(forms.ModelForm):
class Meta:
model = BadgePenalty
fields = "__all__"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and hasattr(self.instance, 'template') and self.instance.template.is_active:
for field_name in self.fields:
if field_name in ("template", "requirements", "description"):
self.fields[field_name].disabled = True

if self.instance and hasattr(self.instance, 'template'):
self.fields['requirements'].queryset = BadgeRequirement.objects.filter(template=self.instance.template) # what to do on add if template is not yet set?
if self.instance.template.is_active:
for field_name in self.fields:
if field_name in ("template", "requirements", "description"):
self.fields[field_name].disabled = True

def clean(self):
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 PenaltyDataRuleForm(forms.ModelForm):
Expand All @@ -81,11 +92,11 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and hasattr(self.instance, 'penalty') and self.instance.penalty.template.is_active:
if self.instance and hasattr(self.instance, "penalty") and self.instance.penalty.template.is_active:
for field_name in self.fields:
if field_name in ("data_path", "operator", "value"):
self.fields[field_name].disabled = True


class BadgeRequirementForm(forms.ModelForm):
class Meta:
Expand All @@ -94,9 +105,9 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and hasattr(self.instance, 'template') and self.instance.template.is_active:
if self.instance and hasattr(self.instance, "template") and self.instance.template.is_active:
for field_name in self.fields:
if field_name in ("template", "event_type", "description"):
if field_name in ("template", "event_type", "description", "group"):
self.fields[field_name].disabled = True


Expand All @@ -107,7 +118,7 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and hasattr(self.instance, 'requirement') and self.instance.requirement.template.is_active:
if self.instance and hasattr(self.instance, "requirement") and self.instance.requirement.template.is_active:
for field_name in self.fields:
if field_name in ("data_path", "operator", "value"):
self.fields[field_name].disabled = True
18 changes: 6 additions & 12 deletions credentials/apps/badges/credly/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ def __init__(self, organization_id, api_key=None):
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}/"
)
self.base_api_url = urljoin(get_credly_api_base_url(settings), f"organizations/{self.organization_id}/")

def _get_organization(self, organization_id):
"""
Expand Down Expand Up @@ -93,9 +91,7 @@ def perform_request(self, method, url_suffix, data=None):
"""
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(), data=data
)
response = requests.request(method.upper(), url, headers=self._get_headers(), json=data)
self._raise_for_error(response)
return response.json()

Expand All @@ -112,9 +108,7 @@ def _raise_for_error(self, response):
try:
response.raise_for_status()
except HTTPError:
logger.error(
f"Error while processing Credly API request: {response.status_code} - {response.text}"
)
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):
Expand Down Expand Up @@ -167,14 +161,14 @@ def issue_badge(self, issue_badge_data):
"""
return self.perform_request("post", "badges/", asdict(issue_badge_data))

def revoke_badge(self, badge_id):
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/")
return self.perform_request("put", f"badges/{badge_id}/revoke/", data=data)

def sync_organization_badge_templates(self, site_id):
"""
Expand Down Expand Up @@ -209,4 +203,4 @@ def sync_organization_badge_templates(self, site_id):
},
)

return len(raw_badge_templates)
return len(raw_badge_templates)
11 changes: 5 additions & 6 deletions credentials/apps/badges/credly/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
class BadgesError(Exception):
"""
Generic Badges functionality error.
"""
"""
Specific for Credly exceptions.
"""

pass
from credentials.apps.badges.exceptions import BadgesError


class CredlyError(BadgesError):
"""
Badges error that is specific to the Credly backend.
Credly backend generic error.
"""

pass
Expand Down
4 changes: 1 addition & 3 deletions credentials/apps/badges/credly/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def post(self, request):
"""
credly_api_client = CredlyAPIClient(request.data.get("organization_id"))

event_info_response = credly_api_client.fetch_event_information(
request.data.get("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":
Expand Down
27 changes: 27 additions & 0 deletions credentials/apps/badges/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Badges exceptions.
"""


class BadgesError(Exception):
"""
Badges generic exception.
"""

pass


class BadgesProcessingError(BadgesError):
"""
Exception raised for errors that occur during badge processing.
"""

pass


class StopEventProcessing(BadgesProcessingError):
"""
Exception raised to stop processing an event due to a specific condition.
"""

pass
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ class Command(BaseCommand):

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."
)
parser.add_argument("--organization_id", type=str, help="UUID of the organization.")

def handle(self, *args, **options):
"""
Expand All @@ -33,16 +31,12 @@ def handle(self, *args, **options):
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}"
)
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}"
)
logger.info(f"Syncing badge templates for the single organization: {organization_id}")
else:
organizations_to_sync = CredlyOrganization.get_all_organization_ids()
logger.info(
Expand All @@ -55,4 +49,4 @@ def handle(self, *args, **options):

logger.info(f"Organization {organization_id}: got {processed_items} badge templates.")

logger.info("...completed!")
logger.info("...completed!")
19 changes: 19 additions & 0 deletions credentials/apps/badges/migrations/0010_auto_20240409_1326.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.20 on 2024-04-09 13:26

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('badges', '0009_auto_20240408_1316'),
]

operations = [
migrations.AddField(
model_name='badgepenalty',
name='event_type',
field=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')], default='org.openedx.learning.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),
preserve_default=False,
),
]
19 changes: 19 additions & 0 deletions credentials/apps/badges/migrations/0011_auto_20240410_1522.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.20 on 2024-04-10 15:22

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('badges', '0010_auto_20240409_1326'),
]

operations = [
migrations.AlterField(
model_name='penaltydatarule',
name='penalty',
field=models.ForeignKey(help_text='Parent penalty for this data rule.', on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='badges.badgepenalty'),
),
]
Loading

0 comments on commit 4191a35

Please sign in to comment.