diff --git a/lms/djangoapps/badges/service.py b/lms/djangoapps/badges/service.py new file mode 100644 index 000000000000..05cc5207951a --- /dev/null +++ b/lms/djangoapps/badges/service.py @@ -0,0 +1,11 @@ +""" +Badging service for XBlocks +""" +from badges.models import BadgeClass + + +class BadgingService(object): + """ + A class that provides functions for managing badges which XBlocks can use. + """ + get_badge_class = BadgeClass.get_badge_class diff --git a/lms/djangoapps/badges/tests/test_models.py b/lms/djangoapps/badges/tests/test_models.py index 0a17c92b53a1..a59ec41e2c27 100644 --- a/lms/djangoapps/badges/tests/test_models.py +++ b/lms/djangoapps/badges/tests/test_models.py @@ -17,19 +17,15 @@ from student.tests.factories import UserFactory -class ImageFetchingMixin(object): +def get_image(name): """ - Provides the ability to grab a badge image from the test data root. + Get one of the test images from the test data directory. """ - def get_image(self, name): - """ - Get one of the test images from the test data directory. - """ - return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png')) + return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png')) @attr('shard_1') -class BadgeImageConfigurationTest(TestCase, ImageFetchingMixin): +class BadgeImageConfigurationTest(TestCase): """ Test the validation features of BadgeImageConfiguration. """ @@ -38,10 +34,10 @@ def test_no_double_default(self): """ Verify that creating two configurations as default is not permitted. """ - CourseCompleteImageConfiguration(mode='test', icon=self.get_image('good'), default=True).save() + CourseCompleteImageConfiguration(mode='test', icon=get_image('good'), default=True).save() self.assertRaises( ValidationError, - CourseCompleteImageConfiguration(mode='test2', icon=self.get_image('good'), default=True).full_clean + CourseCompleteImageConfiguration(mode='test2', icon=get_image('good'), default=True).full_clean ) def test_runs_validator(self): @@ -50,7 +46,7 @@ def test_runs_validator(self): """ self.assertRaises( ValidationError, - CourseCompleteImageConfiguration(mode='test2', icon=self.get_image('unbalanced')).full_clean + CourseCompleteImageConfiguration(mode='test2', icon=get_image('unbalanced')).full_clean ) @@ -60,7 +56,7 @@ class DummyBackend(object): """ -class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin): +class BadgeClassTest(ModuleStoreTestCase): """ Test BadgeClass functionality """ @@ -80,7 +76,7 @@ def test_get_badge_class_preexisting(self): # Ignore additional parameters. This class already exists. badge_class = BadgeClass.get_badge_class( slug='test_slug', issuing_component='test_component', description='Attempted override', - criteria='test', display_name='Testola', image_file_handle=self.get_image('good') + criteria='test', display_name='Testola', image_file_handle=get_image('good') ) # These defaults are set on the factory. self.assertEqual(badge_class.criteria, 'https://example.com/syllabus') @@ -97,11 +93,11 @@ def test_unique_for_course(self): premade_badge_class = BadgeClassFactory.create(course_id=course_key) badge_class = BadgeClass.get_badge_class( slug='test_slug', issuing_component='test_component', description='Attempted override', - criteria='test', display_name='Testola', image_file_handle=self.get_image('good') + criteria='test', display_name='Testola', image_file_handle=get_image('good') ) course_badge_class = BadgeClass.get_badge_class( slug='test_slug', issuing_component='test_component', description='Attempted override', - criteria='test', display_name='Testola', image_file_handle=self.get_image('good'), + criteria='test', display_name='Testola', image_file_handle=get_image('good'), course_id=course_key, ) self.assertNotEqual(badge_class.id, course_badge_class.id) @@ -114,7 +110,7 @@ def test_get_badge_class_create(self): badge_class = BadgeClass.get_badge_class( slug='new_slug', issuing_component='new_component', description='This is a test', criteria='https://example.com/test_criteria', display_name='Super Badge', - image_file_handle=self.get_image('good') + image_file_handle=get_image('good') ) # This should have been saved before being passed back. self.assertTrue(badge_class.id) @@ -152,7 +148,7 @@ def test_get_badge_class_validate(self): BadgeClass.get_badge_class, slug='new_slug', issuing_component='new_component', description='This is a test', criteria='https://example.com/test_criteria', display_name='Super Badge', - image_file_handle=self.get_image('unbalanced') + image_file_handle=get_image('unbalanced') ) def test_get_for_user(self): @@ -185,7 +181,7 @@ def test_runs_validator(self): ValidationError, BadgeClass( slug='test', issuing_component='test2', criteria='test3', - description='test4', image=self.get_image('unbalanced') + description='test4', image=get_image('unbalanced') ).full_clean ) @@ -221,7 +217,7 @@ def test_assertions_for_user(self): self.assertEqual(course_scoped_assertions, course_assertions) -class ValidBadgeImageTest(TestCase, ImageFetchingMixin): +class ValidBadgeImageTest(TestCase): """ Tests the badge image field validator. """ @@ -229,18 +225,18 @@ def test_good_image(self): """ Verify that saving a valid badge image is no problem. """ - validate_badge_image(self.get_image('good')) + validate_badge_image(get_image('good')) def test_unbalanced_image(self): """ Verify that setting an image with an uneven width and height raises an error. """ - unbalanced = ImageFile(self.get_image('unbalanced')) + unbalanced = ImageFile(get_image('unbalanced')) self.assertRaises(ValidationError, validate_badge_image, unbalanced) def test_large_image(self): """ Verify that setting an image that is too big raises an error. """ - large = self.get_image('large') + large = get_image('large') self.assertRaises(ValidationError, validate_badge_image, large) diff --git a/lms/djangoapps/lms_xblock/runtime.py b/lms/djangoapps/lms_xblock/runtime.py index 7fc1ddceddb2..2ad4b1a045e0 100644 --- a/lms/djangoapps/lms_xblock/runtime.py +++ b/lms/djangoapps/lms_xblock/runtime.py @@ -7,6 +7,8 @@ from django.core.urlresolvers import reverse from django.conf import settings + +from badges.service import BadgingService from request_cache.middleware import RequestCache from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig from openedx.core.djangoapps.user_api.course_tag import api as user_course_tag_api @@ -211,6 +213,8 @@ def __init__(self, **kwargs): services['library_tools'] = LibraryToolsService(modulestore()) services['fs'] = xblock.reference.plugins.FSService() services['settings'] = SettingsService() + if settings.FEATURES["ENABLE_OPENBADGES"]: + services['badging'] = BadgingService() self.request_token = kwargs.pop('request_token', None) super(LmsModuleSystem, self).__init__(**kwargs) diff --git a/lms/djangoapps/lms_xblock/test/test_runtime.py b/lms/djangoapps/lms_xblock/test/test_runtime.py index e844c9206929..5bf25f7ee228 100644 --- a/lms/djangoapps/lms_xblock/test/test_runtime.py +++ b/lms/djangoapps/lms_xblock/test/test_runtime.py @@ -5,13 +5,20 @@ from django.contrib.auth.models import User from django.conf import settings from ddt import ddt, data -from mock import Mock -from unittest import TestCase +from django.test import TestCase +from mock import Mock, patch from urlparse import urlparse + +from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locations import BlockUsageLocator, CourseLocator, SlashSeparatedCourseKey + +from badges.tests.factories import BadgeClassFactory +from badges.tests.test_models import get_image from lms.djangoapps.lms_xblock.runtime import quote_slashes, unquote_slashes, LmsModuleSystem from xblock.fields import ScopeIds +from student.tests.factories import UserFactory + TEST_STRINGS = [ '', 'foobar', @@ -138,9 +145,7 @@ class TestUserServiceAPI(TestCase): def setUp(self): super(TestUserServiceAPI, self).setUp() self.course_id = SlashSeparatedCourseKey("org", "course", "run") - - self.user = User(username='runtime_robot', email='runtime_robot@edx.org', password='test', first_name='Robot') - self.user.save() + self.user = UserFactory.create() def mock_get_real_user(_anon_id): """Just returns the test user""" @@ -181,3 +186,64 @@ def test_get_set_tag(self): # Try to get tag in wrong scope with self.assertRaises(ValueError): self.runtime.service(self.mock_block, 'user_tags').get_tag('fake_scope', self.key) + + +class TestBadgingService(TestCase): + """Test the badging service interface""" + + def setUp(self): + super(TestBadgingService, self).setUp() + self.course_id = CourseKey.from_string('course-v1:org+course+run') + + self.user = User(username='test_robot', email='test_robot@edx.org', password='test', first_name='Test') + self.user.save() + + self.mock_block = Mock() + self.mock_block.service_declaration.return_value = 'needs' + + def create_runtime(self): + """ + Create the testing runtime. + """ + def mock_get_real_user(_anon_id): + """Just returns the test user""" + return self.user + + return LmsModuleSystem( + static_url='/static', + track_function=Mock(), + get_module=Mock(), + render_template=Mock(), + replace_urls=str, + course_id=self.course_id, + get_real_user=mock_get_real_user, + descriptor_runtime=Mock(), + ) + + @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True}) + def test_service_rendered(self): + runtime = self.create_runtime() + self.assertTrue(runtime.service(self.mock_block, 'badging')) + + @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': False}) + def test_no_service_rendered(self): + runtime = self.create_runtime() + self.assertFalse(runtime.service(self.mock_block, 'badging')) + + @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True}) + def test_get_badge_class(self): + runtime = self.create_runtime() + badge_service = runtime.service(self.mock_block, 'badging') + premade_badge_class = BadgeClassFactory.create() + # Ignore additional parameters. This class already exists. + # We should get back the first class we created, rather than a new one. + badge_class = badge_service.get_badge_class( + slug='test_slug', issuing_component='test_component', description='Attempted override', + criteria='test', display_name='Testola', image_file_handle=get_image('good') + ) + # These defaults are set on the factory. + self.assertEqual(badge_class.criteria, 'https://example.com/syllabus') + self.assertEqual(badge_class.display_name, 'Test Badge') + self.assertEqual(badge_class.description, "Yay! It's a test badge.") + # File name won't always be the same. + self.assertEqual(badge_class.image.path, premade_badge_class.image.path)