forked from openedx/credentials
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Program Configuration Creation via API (#5)
- Loading branch information
Muhammad Haseeb
authored
Dec 3, 2020
1 parent
3121dc5
commit 376699b
Showing
23 changed files
with
611 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
credentials/apps/core/migrations/0021_siteconfiguration_edx_org_short_name.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.11.29 on 2020-11-30 07:23 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('core', '0020_siteconfiguration_edly_client_branding_and_django_settings'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='siteconfiguration', | ||
name='edx_org_short_name', | ||
field=models.CharField(help_text='Unique, short string identifier for organization, same as LMS. Please do not use spaces or special characters. Only allowed special characters are period (.), hyphen (-) and underscore (_).', max_length=255, null=True, unique=True, verbose_name='Organization short name'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
26 changes: 26 additions & 0 deletions
26
credentials/apps/edx_credentials_extensions/edly_credentials_app/api/fields.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import base64 | ||
|
||
from django.core.files.base import ContentFile | ||
from rest_framework import serializers | ||
|
||
|
||
class Base64ImageField(serializers.ImageField): | ||
""" | ||
Custom serializer field to ingest base64 encoded image. | ||
""" | ||
|
||
def to_internal_value(self, data): | ||
""" | ||
Save base 64 encoded images. | ||
SOURCE: http://matthewdaly.co.uk/blog/2015/07/04/handling-images-as-base64-strings-with-django-rest-framework/ | ||
""" | ||
if not data: | ||
return None | ||
|
||
if isinstance(data, str) and data.startswith('data:image'): | ||
file_format, image_string = data.split(';base64,') # format ~= data:image/X;base64,/xxxyyyzzz/ | ||
file_extension = file_format.split('/')[-1] | ||
data = ContentFile(base64.b64decode(image_string), name='tmp.' + file_extension) | ||
|
||
return super(Base64ImageField, self).to_internal_value(data) |
25 changes: 25 additions & 0 deletions
25
credentials/apps/edx_credentials_extensions/edly_credentials_app/api/permissions.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
""" | ||
Edly's Custom permissions classes for use with DRF. | ||
""" | ||
import logging | ||
|
||
from rest_framework import permissions | ||
|
||
from credentials.apps.edx_credentials_extensions.edly_credentials_app.utils import user_has_edx_organization_access | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class CanAccessCurrentSite(permissions.BasePermission): | ||
""" | ||
Permission to check if the current site is allowed for the user. | ||
""" | ||
|
||
def has_permission(self, request, view): | ||
""" | ||
Checks for user's permission for current site. | ||
""" | ||
if user_has_edx_organization_access(request): | ||
return True | ||
|
||
return False |
63 changes: 63 additions & 0 deletions
63
credentials/apps/edx_credentials_extensions/edly_credentials_app/api/serializers.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
""" | ||
Serializers for edly_api. | ||
""" | ||
from django.contrib.sites.models import Site | ||
|
||
from rest_framework import serializers | ||
|
||
from credentials.apps.credentials.models import ProgramCertificate, Signatory | ||
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.fields import Base64ImageField | ||
|
||
|
||
class SignatorySerializer(serializers.ModelSerializer): | ||
""" | ||
Serializer for Signatory. | ||
""" | ||
image = Base64ImageField(max_length=None, use_url=True) | ||
|
||
class Meta: | ||
model = Signatory | ||
fields = ['id', 'name', 'title', 'image'] | ||
|
||
|
||
class ProgramCertificateConfigurationSerializer(serializers.ModelSerializer): | ||
""" | ||
Serializer for Program Certificate Configuration. | ||
""" | ||
site = serializers.SlugRelatedField(slug_field='domain', queryset=Site.objects.all()) | ||
signatories = SignatorySerializer(many=True) | ||
|
||
class Meta: | ||
model = ProgramCertificate | ||
fields = [ | ||
'site', 'is_active', 'signatories', 'program_uuid', 'use_org_name', | ||
'include_hours_of_effort', 'language', | ||
] | ||
|
||
def create(self, validated_data): | ||
signatories = [] | ||
for signatory in validated_data.pop('signatories', []): | ||
signatories.append(Signatory.objects.create(**signatory)) | ||
|
||
program_certificate_configuration = ProgramCertificate(**validated_data) | ||
program_certificate_configuration.save() | ||
program_certificate_configuration.signatories.set(signatories) | ||
return program_certificate_configuration | ||
|
||
def update(self, instance, validated_data): | ||
signatory_ids = self.context.get('signatory_ids', []) | ||
|
||
signatories = [] | ||
for signatory_index, signatory in enumerate(validated_data.pop('signatories', [])): | ||
signatory_obj, _ = Signatory.objects.update_or_create( | ||
pk=signatory_ids[signatory_index], | ||
defaults=signatory | ||
) | ||
signatories.append(signatory_obj) | ||
|
||
for attr, value in validated_data.items(): | ||
setattr(instance, attr, value) | ||
|
||
instance.save() | ||
instance.signatories.set(signatories) | ||
return instance |
Empty file.
85 changes: 85 additions & 0 deletions
85
...ntials/apps/edx_credentials_extensions/edly_credentials_app/api/tests/test_serializers.py
Large diffs are not rendered by default.
Oops, something went wrong.
5 changes: 5 additions & 0 deletions
5
credentials/apps/edx_credentials_extensions/edly_credentials_app/api/urls.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.conf.urls import include, url | ||
|
||
urlpatterns = [ | ||
url(r'^v1/', include('credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.urls')), | ||
] |
Empty file.
9 changes: 9 additions & 0 deletions
9
credentials/apps/edx_credentials_extensions/edly_credentials_app/api/v1/urls.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from rest_framework import routers | ||
|
||
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.views.program_certificate_configuration import ProgramCertificateConfigurationViewSet | ||
|
||
|
||
router = routers.SimpleRouter() | ||
router.register(r'program-certificate-configuration', ProgramCertificateConfigurationViewSet, base_name='program-certificate-configuration') | ||
|
||
urlpatterns = router.urls |
Empty file.
46 changes: 46 additions & 0 deletions
46
...entials_extensions/edly_credentials_app/api/v1/views/program_certificate_configuration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
""" | ||
Views for Edly Program Certificate Configuration API. | ||
""" | ||
from rest_framework import status, viewsets | ||
from rest_framework.permissions import IsAuthenticated | ||
from rest_framework.response import Response | ||
|
||
from credentials.apps.credentials.models import ProgramCertificate | ||
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.permissions import CanAccessCurrentSite | ||
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.serializers import ( | ||
ProgramCertificateConfigurationSerializer | ||
) | ||
|
||
|
||
class ProgramCertificateConfigurationViewSet(viewsets.ModelViewSet): | ||
""" | ||
ViewSet for Program Certificate Configuration. | ||
""" | ||
lookup_field = 'program_uuid' | ||
lookup_value_regex = '[0-9a-f-]+' | ||
permission_classes = (IsAuthenticated, CanAccessCurrentSite) | ||
serializer_class = ProgramCertificateConfigurationSerializer | ||
queryset = ProgramCertificate.objects.all() | ||
|
||
def update(self, request, *args, **kwargs): | ||
""" | ||
Update existing Program Certificate Configuration. | ||
""" | ||
data = request.data.copy() | ||
|
||
signatory_ids = [] | ||
for signatory in data.get('signatories', []): | ||
signatory_ids.append(signatory.get('id')) | ||
|
||
context = { | ||
'request': request, | ||
'signatory_ids': signatory_ids | ||
} | ||
|
||
instance = ProgramCertificate.objects.get(program_uuid=kwargs.get('program_uuid')) | ||
|
||
serializer = ProgramCertificateConfigurationSerializer(instance, data=data, context=context) | ||
serializer.is_valid(raise_exception=True) | ||
self.perform_update(serializer) | ||
headers = self.get_success_headers(serializer.data) | ||
return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) |
Empty file.
101 changes: 101 additions & 0 deletions
101
...als/apps/edx_credentials_extensions/edly_credentials_app/api/v1/views/tests/test_views.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
""" | ||
Tests for edly_api Views. | ||
""" | ||
import json | ||
|
||
from django.urls import reverse | ||
|
||
from rest_framework.test import APITestCase | ||
|
||
from credentials.apps.catalog.tests.factories import ProgramFactory | ||
from credentials.apps.core.tests.factories import UserFactory | ||
from credentials.apps.core.tests.mixins import SiteMixin | ||
from credentials.apps.credentials.tests.factories import ProgramCertificateFactory | ||
|
||
JSON_CONTENT_TYPE = 'application/json' | ||
|
||
|
||
def get_test_image_as_base_64_encoded_string(): | ||
base64_header = 'data:image/png;base64,' | ||
base64_data = ( | ||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY' | ||
'42YAAAAASUVORK5CYII=' | ||
) | ||
base64_full = base64_header + base64_data | ||
return base64_full | ||
|
||
|
||
class ProgramCertificateConfigurationTests(SiteMixin, APITestCase): | ||
""" | ||
Test for Program Certificate Configuration Viewset. | ||
""" | ||
|
||
def setUp(self): | ||
super(ProgramCertificateConfigurationTests, self).setUp() | ||
self.list_url = reverse('edly_api:program-certificate-configuration-list') | ||
|
||
self.user = UserFactory(is_staff=True, is_superuser=True) | ||
|
||
program = ProgramFactory(site=self.site) | ||
self.test_data = { | ||
'site': self.site.domain, | ||
'is_active': True, | ||
'signatories': [ | ||
{ | ||
'name': 'Jhon Doe', | ||
'title': 'Test Certificate', | ||
'image': get_test_image_as_base_64_encoded_string() | ||
} | ||
], | ||
'program_uuid': str(program.uuid), | ||
'use_org_name': True, | ||
'include_hours_of_effort': True, | ||
'language': 'en' | ||
} | ||
|
||
def test_create_program_certificate_configuration(self): | ||
""" | ||
Verify program certificate configuration creation. | ||
""" | ||
response = self.client.post(self.list_url, data=json.dumps(self.test_data), content_type=JSON_CONTENT_TYPE) | ||
assert response.status_code == 401 | ||
|
||
self.client.login(username=self.user.username, password='password') | ||
response = self.client.post(self.list_url, data=json.dumps(self.test_data), content_type=JSON_CONTENT_TYPE) | ||
assert response.status_code == 201 | ||
|
||
def test_update_program_certificate_configuration(self): | ||
""" | ||
Verify program certificate configuration update. | ||
""" | ||
program_certificate_configuration = ProgramCertificateFactory() | ||
|
||
self.detail_url = reverse('edly_api:program-certificate-configuration-detail', | ||
kwargs={'program_uuid': str(program_certificate_configuration.program_uuid)}) | ||
test_data = self.test_data | ||
test_data['signatories'] = [] | ||
test_data['is_active'] = False | ||
|
||
response = self.client.patch(self.detail_url, data=json.dumps(test_data), content_type=JSON_CONTENT_TYPE) | ||
assert response.status_code == 401 | ||
|
||
self.client.login(username=self.user.username, password='password') | ||
response = self.client.patch(self.detail_url, data=json.dumps(test_data), content_type=JSON_CONTENT_TYPE) | ||
assert response.status_code == 200 | ||
|
||
def test_list_program_certificate_configuration(self): | ||
""" | ||
Verify list program certificate configuration. | ||
""" | ||
program_certificate_configuration = ProgramCertificateFactory() | ||
|
||
response = self.client.get(self.list_url) | ||
assert response.status_code == 401 | ||
|
||
self.client.login(username=self.user.username, password='password') | ||
response = self.client.get(self.list_url) | ||
assert response.status_code == 200 | ||
|
||
actual_data = response.json() | ||
actual_data = actual_data.get('results')[0] | ||
assert str(program_certificate_configuration.program_uuid) == actual_data.get('program_uuid') |
Oops, something went wrong.