Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [ACI-331, ACI-357, ACI-363] badge templates creation #58

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,38 @@

from credentials.apps.badges.toggles import is_badges_enabled

from .models import CredlyOrganization
from .models import CredlyOrganization, BadgeTemplate
from .utils import sync_badge_templates_for_organization
from .forms import CredlyOrganizationAdminForm


class CredlyOrganizationAdmin(admin.ModelAdmin):
"""
Credly organization admin setup.
"""
form = CredlyOrganizationAdminForm
list_display = ("name", "uuid", "api_key",)
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.
"""
for organization in queryset:
sync_badge_templates_for_organization(organization.uuid)


class BadgeTemplateAdmin(admin.ModelAdmin):
"""
Badge template admin setup.
"""
list_display = ("name", "uuid", "organization", "state",)
list_filter = ("state", "organization",)
search_fields = ("name", "uuid",)


# register admin configurations with respect to the feature flag
if is_badges_enabled():
admin.site.register(CredlyOrganization, CredlyOrganizationAdmin)
admin.site.register(BadgeTemplate, BadgeTemplateAdmin)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from credentials.apps.badges.apps import BadgesAppConfig
from credentials.apps.badges.toggles import is_badges_enabled
from credentials.apps.badges.toggles import is_badges_enabled, check_badges_enabled
from credentials.apps.plugins.constants import PROJECT_TYPE, PluginSettings, PluginURLs, SettingsType


Expand Down Expand Up @@ -35,10 +35,9 @@ class CredlyBadgesConfig(BadgesAppConfig):
}
} if is_badges_enabled() else {} # TODO: improve this

@ check_badges_enabled
def ready(self):
"""
Activate installed badges plugins if they are enabled.

Performs initial registrations for checks, signals, etc.
"""
super().ready()
32 changes: 32 additions & 0 deletions credentials/apps/badges/distribution/credly/credly_badges/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Django forms for the credly badges
"""

from django import forms
from django.utils.translation import gettext_lazy as _

from .models import CredlyOrganization
from .rest_api import CredlyAPIClient
from .exceptions import CredlyAPIError

class CredlyOrganizationAdminForm(forms.ModelForm):
class Meta:
model = CredlyOrganization
fields = "__all__"

def clean(self):
"""
Validate that organization is existing on Credly services.
"""
cleaned_data = super().clean()

uuid = cleaned_data.get("uuid")
api_key = cleaned_data.get("api_key")

try:
credly_api_client = CredlyAPIClient(uuid, api_key)
credly_api_client.fetch_organization()
except CredlyAPIError:
raise forms.ValidationError(_('Invalid organization ID or API key. Organization not found on Credly services.'))

return cleaned_data
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
from django.core.management.base import BaseCommand
from credly_badges.utils import sync_badge_templates_for_organization
from credly_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('--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:
./manage.py sync_organization_badge_templates
./manage.py sync_organization_badge_templates --organization_id c117c179-81b1-4f7e-a3a1-e6ae30568c13
"""
organization_id = options.get('organization_id')

if organization_id:
logger.info(f'Syncing badge templates for single organization: {organization_id}')
sync_badge_templates_for_organization(organization_id)
else:
all_organization_ids = CredlyOrganization.get_all_organization_ids()
logger.info(f'Organization id was not provided. Syncing badge templates for all organizations: {all_organization_ids}')
for organization_id in all_organization_ids:
sync_badge_templates_for_organization(organization_id)

logger.info('Done.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 4.2.7 on 2024-01-26 11:47

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


class Migration(migrations.Migration):

dependencies = [
('credly_badges', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='credlyorganization',
name='api_key',
field=models.CharField(help_text='Credly API shared secret for organization.', max_length=255),
),
migrations.AlterField(
model_name='credlyorganization',
name='name',
field=models.CharField(help_text='Organization display name.', max_length=255),
),
migrations.AlterField(
model_name='credlyorganization',
name='uuid',
field=models.UUIDField(help_text='Unique organization ID.', unique=True),
),
migrations.CreateModel(
name='BadgeTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(help_text='Unique badge template ID.', unique=True)),
('name', models.CharField(help_text='Badge template name.', max_length=255)),
('state', models.CharField(choices=[('active', 'Active'), ('archived', 'Archived'), ('draft', 'Draft'), ('inactive', 'Inactive')], default='inactive', help_text='State of the badge template.', max_length=255)),
('organization', models.ForeignKey(help_text='Organization of the badge template.', on_delete=django.db.models.deletion.CASCADE, to='credly_badges.credlyorganization')),
],
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,42 @@ class CredlyOrganization(TimeStampedModel):
uuid = models.UUIDField(unique=True, help_text=_('Unique organization ID.'))
name = models.CharField(max_length=255, help_text=_('Organization display name.'))
api_key = models.CharField(max_length=255, help_text=_('Credly API shared secret for organization.'))

def __str__(self):
return self.name

@classmethod
def get_all_organization_ids(cls):
"""
Get all organization IDs.
"""
return cls.objects.values_list('uuid', flat=True)


class BadgeTemplate(models.Model):
"""
Badge template model.
"""
STATE_CHOICES = (
('active', _('Active')),
('archived', _('Archived')),
('draft', _('Draft')),
('inactive', _('Inactive')),
)

uuid = models.UUIDField(unique=True, help_text=_('Unique badge template ID.'))
name = models.CharField(max_length=255, help_text=_('Badge template name.'))
organization = models.ForeignKey(
CredlyOrganization,
on_delete=models.CASCADE,
help_text=_('Organization of the badge template.')
)
state = models.CharField(
max_length=255,
choices=STATE_CHOICES,
default='inactive',
help_text=_('State of the badge template.')
)

def __str__(self):
return self.name
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import requests
from attrs import asdict
from django.conf import settings
from requests.packages.urllib3.exceptions import HTTPError

from requests.exceptions import HTTPError
from .exceptions import CredlyAPIError


Expand All @@ -21,6 +20,8 @@ class CredlyAPIClient:
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.

TODO: improve client to return data in a more usable format
"""

def __init__(self, organization_id, api_key):
Expand Down
29 changes: 29 additions & 0 deletions credentials/apps/badges/distribution/credly/credly_badges/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.shortcuts import get_object_or_404

from .rest_api import CredlyAPIClient
from .models import CredlyOrganization, BadgeTemplate


def sync_badge_templates_for_organization(organization_id):
"""
Sync badge templates for a specific organization and create records in the database.

Args:
organization_id (str): UUID of the organization.

Raises:
Http404: If organization is not found.
"""
organization = get_object_or_404(CredlyOrganization, uuid=organization_id)

credly_api_client = CredlyAPIClient(organization_id, organization.api_key)
badge_templates_data = credly_api_client.fetch_badge_templates()

for badge_template_data in badge_templates_data.get('data', []):
BadgeTemplate.objects.update_or_create(
uuid=badge_template_data.get('id'),
defaults={
'name': badge_template_data.get('name'),
'organization': organization,
}
)
Loading