diff --git a/openedx/core/djangoapps/credentials/admin.py b/openedx/core/djangoapps/credentials/admin.py new file mode 100644 index 0000000000..114ff00bbc --- /dev/null +++ b/openedx/core/djangoapps/credentials/admin.py @@ -0,0 +1,16 @@ +""" +django admin pages for credentials support models. +""" + +from django.contrib import admin + +from config_models.admin import ConfigurationModelAdmin + +from openedx.core.djangoapps.credentials.models import CredentialsApiConfig + + +class CredentialsApiConfigAdmin(ConfigurationModelAdmin): # pylint: disable=missing-docstring + pass + + +admin.site.register(CredentialsApiConfig, CredentialsApiConfigAdmin) diff --git a/openedx/core/djangoapps/credentials/migrations/0001_initial.py b/openedx/core/djangoapps/credentials/migrations/0001_initial.py new file mode 100644 index 0000000000..d249c4f649 --- /dev/null +++ b/openedx/core/djangoapps/credentials/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='CredentialsApiConfig', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), + ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), + ('internal_service_url', models.URLField(verbose_name='Internal Service URL')), + ('public_service_url', models.URLField(verbose_name='Public Service URL')), + ('enable_learner_issuance', models.BooleanField(default=False, help_text='Enable issuance of credentials via Credential Service.', verbose_name='Enable Learner Issuance')), + ('enable_studio_authoring', models.BooleanField(default=False, help_text='Enable authoring of Credential Service credentials in Studio.', verbose_name='Enable Authoring of Credential in Studio')), + ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')), + ], + options={ + 'ordering': ('-change_date',), + 'abstract': False, + }, + ), + ] diff --git a/openedx/core/djangoapps/credentials/migrations/__init__.py b/openedx/core/djangoapps/credentials/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/credentials/models.py b/openedx/core/djangoapps/credentials/models.py index a4baa925ae..303c1338c6 100644 --- a/openedx/core/djangoapps/credentials/models.py +++ b/openedx/core/djangoapps/credentials/models.py @@ -1,3 +1,66 @@ """ Models for credentials support for the LMS and Studio. """ + +from urlparse import urljoin + +from django.utils.translation import ugettext_lazy as _ +from django.db import models + +from config_models.models import ConfigurationModel + + +class CredentialsApiConfig(ConfigurationModel): + """ + Manages configuration for connecting to the Credential service and using its + API. + """ + internal_service_url = models.URLField(verbose_name=_("Internal Service URL")) + public_service_url = models.URLField(verbose_name=_("Public Service URL")) + + enable_learner_issuance = models.BooleanField( + verbose_name=_("Enable Learner Issuance"), + default=False, + help_text=_( + "Enable issuance of credentials via Credential Service." + ) + ) + enable_studio_authoring = models.BooleanField( + verbose_name=_("Enable Authoring of Credential in Studio"), + default=False, + help_text=_( + "Enable authoring of Credential Service credentials in Studio." + ) + ) + + def __unicode__(self): + return self.public_api_url + + @property + def internal_api_url(self): + """ + Generate a URL based on internal service URL and API version number. + """ + return urljoin(self.internal_service_url, '/api/v1/') + + @property + def public_api_url(self): + """ + Generate a URL based on public service URL and API version number. + """ + return urljoin(self.public_service_url, '/api/v1/') + + @property + def is_learner_issuance_enabled(self): + """ + Indicates whether the learner credential should be enabled or not. + """ + return self.enabled and self.enable_learner_issuance + + @property + def is_studio_authoring_enabled(self): + """ + Indicates whether Studio functionality related to Credential should + be enabled or not. + """ + return self.enabled and self.enable_studio_authoring diff --git a/openedx/core/djangoapps/credentials/tests/mixins.py b/openedx/core/djangoapps/credentials/tests/mixins.py new file mode 100644 index 0000000000..12270870ed --- /dev/null +++ b/openedx/core/djangoapps/credentials/tests/mixins.py @@ -0,0 +1,24 @@ +"""Mixins for use during testing.""" + +from openedx.core.djangoapps.credentials.models import CredentialsApiConfig + + +class CredentialsApiConfigMixin(object): + """ Utilities for working with Credentials configuration during testing.""" + + DEFAULTS = { + 'enabled': True, + 'internal_service_url': 'http://internal.credentials.org/', + 'public_service_url': 'http://public.credentials.org/', + 'enable_learner_issuance': True, + 'enable_studio_authoring': True, + } + + def create_config(self, **kwargs): + """ Creates a new CredentialsApiConfig with DEFAULTS, updated with any + provided overrides. + """ + fields = dict(self.DEFAULTS, **kwargs) + CredentialsApiConfig(**fields).save() + + return CredentialsApiConfig.current() diff --git a/openedx/core/djangoapps/credentials/tests/test_models.py b/openedx/core/djangoapps/credentials/tests/test_models.py new file mode 100644 index 0000000000..76891c62bb --- /dev/null +++ b/openedx/core/djangoapps/credentials/tests/test_models.py @@ -0,0 +1,47 @@ +"""Tests for models supporting Credentials-related functionality.""" + +from django.test import TestCase +from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin + + +class TestCredentialsApiConfig(CredentialsApiConfigMixin, TestCase): + """Tests covering the CredentialsApiConfig model.""" + def test_url_construction(self): + """Verify that URLs returned by the model are constructed correctly.""" + credentials_config = self.create_config() + + self.assertEqual( + credentials_config.internal_api_url, + credentials_config.internal_service_url.strip('/') + '/api/v1/') + + self.assertEqual( + credentials_config.public_api_url, + credentials_config.public_service_url.strip('/') + '/api/v1/') + + def test_is_learner_issuance_enabled(self): + """ + Verify that the property controlling display on the student dashboard is only True + when configuration is enabled and all required configuration is provided. + """ + credentials_config = self.create_config(enabled=False) + self.assertFalse(credentials_config.is_learner_issuance_enabled) + + credentials_config = self.create_config(enable_learner_issuance=False) + self.assertFalse(credentials_config.is_learner_issuance_enabled) + + credentials_config = self.create_config() + self.assertTrue(credentials_config.is_learner_issuance_enabled) + + def test_is_studio_authoring_enabled(self): + """ + Verify that the property controlling display in the Studio authoring is only True + when configuration is enabled and all required configuration is provided. + """ + credentials_config = self.create_config(enabled=False) + self.assertFalse(credentials_config.is_studio_authoring_enabled) + + credentials_config = self.create_config(enable_studio_authoring=False) + self.assertFalse(credentials_config.is_studio_authoring_enabled) + + credentials_config = self.create_config() + self.assertTrue(credentials_config.is_studio_authoring_enabled)