Commit e64d45b7 by Ned Batchelder

Merge pull request #6563 from edx/ned/fix-6013

Fixes for #6013: Implement user service to return currently-logged-in user
parents b2e2048b 0c9d687f
...@@ -24,6 +24,7 @@ from xblock.django.request import webob_to_django_response, django_to_webob_requ ...@@ -24,6 +24,7 @@ from xblock.django.request import webob_to_django_response, django_to_webob_requ
from xblock.exceptions import NoSuchHandlerError from xblock.exceptions import NoSuchHandlerError
from xblock.fragment import Fragment from xblock.fragment import Fragment
from student.auth import has_studio_read_access, has_studio_write_access from student.auth import has_studio_read_access, has_studio_write_access
from xblock_django.user_service import DjangoXBlockUserService
from lms.djangoapps.lms_xblock.field_data import LmsFieldData from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from cms.lib.xblock.field_data import CmsFieldData from cms.lib.xblock.field_data import CmsFieldData
...@@ -112,20 +113,6 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method ...@@ -112,20 +113,6 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
] ]
class StudioUserService(object):
"""
Provides a Studio implementation of the XBlock user service.
"""
def __init__(self, request):
super(StudioUserService, self).__init__()
self._request = request
@property
def user_id(self):
return self._request.user.id
class StudioPermissionsService(object): class StudioPermissionsService(object):
""" """
Service that can provide information about a user's permissions. Service that can provide information about a user's permissions.
...@@ -176,7 +163,6 @@ def _preview_module_system(request, descriptor, field_data): ...@@ -176,7 +163,6 @@ def _preview_module_system(request, descriptor, field_data):
_studio_wrap_xblock, _studio_wrap_xblock,
] ]
descriptor.runtime._services['user'] = StudioUserService(request) # pylint: disable=protected-access
descriptor.runtime._services['studio_user_permissions'] = StudioPermissionsService(request) # pylint: disable=protected-access descriptor.runtime._services['studio_user_permissions'] = StudioPermissionsService(request) # pylint: disable=protected-access
return PreviewModuleSystem( return PreviewModuleSystem(
...@@ -204,6 +190,7 @@ def _preview_module_system(request, descriptor, field_data): ...@@ -204,6 +190,7 @@ def _preview_module_system(request, descriptor, field_data):
"i18n": ModuleI18nService(), "i18n": ModuleI18nService(),
"field-data": field_data, "field-data": field_data,
"library_tools": LibraryToolsService(modulestore()), "library_tools": LibraryToolsService(modulestore()),
"user": DjangoXBlockUserService(request.user),
}, },
) )
......
...@@ -12,7 +12,6 @@ from django.test import TestCase ...@@ -12,7 +12,6 @@ from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from contentstore.utils import reverse_usage_url, reverse_course_url from contentstore.utils import reverse_usage_url, reverse_course_url
from contentstore.views.preview import StudioUserService
from contentstore.views.component import ( from contentstore.views.component import (
component_handler, get_component_templates component_handler, get_component_templates
...@@ -30,6 +29,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase ...@@ -30,6 +29,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import ItemFactory, LibraryFactory, check_mongo_calls from xmodule.modulestore.tests.factories import ItemFactory, LibraryFactory, check_mongo_calls
from xmodule.x_module import STUDIO_VIEW, STUDENT_VIEW from xmodule.x_module import STUDIO_VIEW, STUDENT_VIEW
from xblock.exceptions import NoSuchHandlerError from xblock.exceptions import NoSuchHandlerError
from xblock_django.user_service import DjangoXBlockUserService
from opaque_keys.edx.keys import UsageKey, CourseKey from opaque_keys.edx.keys import UsageKey, CourseKey
from opaque_keys.edx.locations import Location from opaque_keys.edx.locations import Location
from xmodule.partitions.partitions import Group, UserPartition from xmodule.partitions.partitions import Group, UserPartition
...@@ -1170,7 +1170,7 @@ class TestEditSplitModule(ItemTest): ...@@ -1170,7 +1170,7 @@ class TestEditSplitModule(ItemTest):
# (CachingDescriptorSystem is used in tests, PreviewModuleSystem in Studio). # (CachingDescriptorSystem is used in tests, PreviewModuleSystem in Studio).
# CachingDescriptorSystem doesn't have user service, that's needed for # CachingDescriptorSystem doesn't have user service, that's needed for
# SplitTestModule. So, in this line of code we add this service manually. # SplitTestModule. So, in this line of code we add this service manually.
split_test.runtime._services['user'] = StudioUserService(self.request) # pylint: disable=protected-access split_test.runtime._services['user'] = DjangoXBlockUserService(self.user) # pylint: disable=protected-access
# Call add_missing_groups method to add the missing group. # Call add_missing_groups method to add the missing group.
split_test.add_missing_groups(self.request) split_test.add_missing_groups(self.request)
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
Tests for contentstore.views.preview.py Tests for contentstore.views.preview.py
""" """
import re import re
import ddt
from mock import Mock
from xblock.core import XBlock
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
...@@ -10,8 +13,9 @@ from xblock.core import XBlockAside ...@@ -10,8 +13,9 @@ from xblock.core import XBlockAside
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from contentstore.views.preview import get_preview_fragment from contentstore.views.preview import get_preview_fragment, _preview_module_system
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.test_asides import AsideTestType from xmodule.modulestore.tests.test_asides import AsideTestType
from cms.djangoapps.xblock_config.models import StudioConfig from cms.djangoapps.xblock_config.models import StudioConfig
...@@ -101,3 +105,44 @@ class GetPreviewHtmlTestCase(TestCase): ...@@ -101,3 +105,44 @@ class GetPreviewHtmlTestCase(TestCase):
self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']")
self.assertNotRegexpMatches(html, "Aside rendered") self.assertNotRegexpMatches(html, "Aside rendered")
@XBlock.needs("field-data")
@XBlock.needs("i18n")
@XBlock.needs("user")
class PureXBlock(XBlock):
"""
Pure XBlock to use in tests.
"""
pass
@ddt.ddt
class StudioXBlockServiceBindingTest(ModuleStoreTestCase):
"""
Tests that the Studio Module System (XBlock Runtime) provides an expected set of services.
"""
def setUp(self):
"""
Set up the user and request that will be used.
"""
super(StudioXBlockServiceBindingTest, self).setUp()
self.user = UserFactory()
self.course = CourseFactory.create()
self.request = Mock()
self.field_data = Mock()
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
@ddt.data("user", "i18n", "field-data")
def test_expected_services_exist(self, expected_service):
"""
Tests that the 'user' and 'i18n' services are provided by the Studio runtime.
"""
descriptor = ItemFactory(category="pure", parent=self.course)
runtime = _preview_module_system(
self.request,
descriptor,
self.field_data,
)
service = runtime.service(descriptor, expected_service)
self.assertIsNotNone(service)
"""
Tests for the DjangoXBlockUserService.
"""
from django.test import TestCase
from xblock_django.user_service import (
DjangoXBlockUserService,
ATTR_KEY_IS_AUTHENTICATED,
ATTR_KEY_USER_ID,
ATTR_KEY_USERNAME,
)
from student.tests.factories import UserFactory, AnonymousUserFactory
class UserServiceTestCase(TestCase):
"""
Tests for the DjangoXBlockUserService.
"""
def setUp(self):
self.user = UserFactory(username="tester", email="test@tester.com")
self.user.profile.name = "Test Tester"
self.anon_user = AnonymousUserFactory()
def assert_is_anon_xb_user(self, xb_user):
"""
A set of assertions for an anonymous XBlockUser.
"""
self.assertFalse(xb_user.opt_attrs[ATTR_KEY_IS_AUTHENTICATED])
self.assertIsNone(xb_user.full_name)
self.assertListEqual(xb_user.emails, [])
def assert_xblock_user_matches_django(self, xb_user, dj_user):
"""
A set of assertions for comparing a XBlockUser to a django User
"""
self.assertTrue(xb_user.opt_attrs[ATTR_KEY_IS_AUTHENTICATED])
self.assertEqual(xb_user.emails[0], dj_user.email)
self.assertEqual(xb_user.full_name, dj_user.profile.name)
self.assertEqual(xb_user.opt_attrs[ATTR_KEY_USERNAME], dj_user.username)
self.assertEqual(xb_user.opt_attrs[ATTR_KEY_USER_ID], dj_user.id)
def test_convert_anon_user(self):
"""
Tests for convert_django_user_to_xblock_user behavior when django user is AnonymousUser.
"""
django_user_service = DjangoXBlockUserService(self.anon_user)
xb_user = django_user_service.get_current_user()
self.assertTrue(xb_user.is_current_user)
self.assert_is_anon_xb_user(xb_user)
def test_convert_authenticate_user(self):
"""
Tests for convert_django_user_to_xblock_user behavior when django user is User.
"""
django_user_service = DjangoXBlockUserService(self.user)
xb_user = django_user_service.get_current_user()
self.assertTrue(xb_user.is_current_user)
self.assert_xblock_user_matches_django(xb_user, self.user)
"""
Support for converting a django user to an XBlock user
"""
from xblock.reference.user_service import XBlockUser, UserService
ATTR_KEY_IS_AUTHENTICATED = 'edx-platform.is_authenticated'
ATTR_KEY_USER_ID = 'edx-platform.user_id'
ATTR_KEY_USERNAME = 'edx-platform.username'
class DjangoXBlockUserService(UserService):
"""
A user service that converts Django users to XBlockUser
"""
def __init__(self, django_user, **kwargs):
super(DjangoXBlockUserService, self).__init__(**kwargs)
self._django_user = django_user
def get_current_user(self):
"""
Returns the currently-logged in user, as an instance of XBlockUser
"""
return self._convert_django_user_to_xblock_user(self._django_user)
def _convert_django_user_to_xblock_user(self, django_user):
"""
A function that returns an XBlockUser from the current Django request.user
"""
xblock_user = XBlockUser(is_current_user=True)
if django_user is not None and django_user.is_authenticated():
# This full_name is dependent on edx-platform's profile implementation
full_name = getattr(django_user.profile, 'name') if hasattr(django_user, 'profile') else None
xblock_user.full_name = full_name
xblock_user.emails = [django_user.email]
xblock_user.opt_attrs[ATTR_KEY_IS_AUTHENTICATED] = True
xblock_user.opt_attrs[ATTR_KEY_USER_ID] = django_user.id
xblock_user.opt_attrs[ATTR_KEY_USERNAME] = django_user.username
else:
xblock_user.opt_attrs[ATTR_KEY_IS_AUTHENTICATED] = False
return xblock_user
...@@ -381,7 +381,11 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ...@@ -381,7 +381,11 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
lib_tools = self.runtime.service(self, 'library_tools') lib_tools = self.runtime.service(self, 'library_tools')
user_service = self.runtime.service(self, 'user') user_service = self.runtime.service(self, 'user')
user_perms = self.runtime.service(self, 'studio_user_permissions') user_perms = self.runtime.service(self, 'studio_user_permissions')
user_id = user_service.user_id if user_service else None # May be None when creating bok choy test fixtures if user_service:
# May be None when creating bok choy test fixtures
user_id = user_service.get_current_user().opt_attrs.get('edx-platform.user_id', None)
else:
user_id = None
lib_tools.update_children(self, user_id, user_perms) lib_tools.update_children(self, user_id, user_perms)
return Response() return Response()
......
...@@ -14,7 +14,6 @@ from django.core.cache import get_cache, InvalidCacheBackendError ...@@ -14,7 +14,6 @@ from django.core.cache import get_cache, InvalidCacheBackendError
import django.utils import django.utils
import re import re
import threading
from xmodule.util.django import get_current_request_hostname from xmodule.util.django import get_current_request_hostname
import xmodule.modulestore # pylint: disable=unused-import import xmodule.modulestore # pylint: disable=unused-import
...@@ -30,6 +29,14 @@ try: ...@@ -30,6 +29,14 @@ try:
except ImportError: except ImportError:
HAS_REQUEST_CACHE = False HAS_REQUEST_CACHE = False
# We also may not always have the current request user (crum) module available
try:
from xblock_django.user_service import DjangoXBlockUserService
from crum import get_current_user
HAS_USER_SERVICE = True
except ImportError:
HAS_USER_SERVICE = False
ASSET_IGNORE_REGEX = getattr(settings, "ASSET_IGNORE_REGEX", r"(^\._.*$)|(^\.DS_Store$)|(^.*~$)") ASSET_IGNORE_REGEX = getattr(settings, "ASSET_IGNORE_REGEX", r"(^\._.*$)|(^\.DS_Store$)|(^.*~$)")
...@@ -44,7 +51,15 @@ def load_function(path): ...@@ -44,7 +51,15 @@ def load_function(path):
return getattr(import_module(module_path), name) return getattr(import_module(module_path), name)
def create_modulestore_instance(engine, content_store, doc_store_config, options, i18n_service=None, fs_service=None): def create_modulestore_instance(
engine,
content_store,
doc_store_config,
options,
i18n_service=None,
fs_service=None,
user_service=None,
):
""" """
This will return a new instance of a modulestore given an engine and options This will return a new instance of a modulestore given an engine and options
""" """
...@@ -74,6 +89,11 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options ...@@ -74,6 +89,11 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options
if issubclass(class_, BranchSettingMixin): if issubclass(class_, BranchSettingMixin):
_options['branch_setting_func'] = _get_modulestore_branch_setting _options['branch_setting_func'] = _get_modulestore_branch_setting
if HAS_USER_SERVICE and not user_service:
xb_user_service = DjangoXBlockUserService(get_current_user())
else:
xb_user_service = None
return class_( return class_(
contentstore=content_store, contentstore=content_store,
metadata_inheritance_cache_subsystem=metadata_inheritance_cache, metadata_inheritance_cache_subsystem=metadata_inheritance_cache,
...@@ -83,6 +103,7 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options ...@@ -83,6 +103,7 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options
doc_store_config=doc_store_config, doc_store_config=doc_store_config,
i18n_service=i18n_service or ModuleI18nService(), i18n_service=i18n_service or ModuleI18nService(),
fs_service=fs_service or xblock.reference.plugins.FSService(), fs_service=fs_service or xblock.reference.plugins.FSService(),
user_service=user_service or xb_user_service,
**_options **_options
) )
......
...@@ -99,7 +99,17 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -99,7 +99,17 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
""" """
ModuleStore knows how to route requests to the right persistence ms ModuleStore knows how to route requests to the right persistence ms
""" """
def __init__(self, contentstore, mappings, stores, i18n_service=None, fs_service=None, create_modulestore_instance=None, **kwargs): def __init__(
self,
contentstore,
mappings,
stores,
i18n_service=None,
fs_service=None,
user_service=None,
create_modulestore_instance=None,
**kwargs
):
""" """
Initialize a MixedModuleStore. Here we look into our passed in kwargs which should be a Initialize a MixedModuleStore. Here we look into our passed in kwargs which should be a
collection of other modulestore configuration information collection of other modulestore configuration information
...@@ -139,6 +149,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -139,6 +149,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store_settings.get('OPTIONS', {}), store_settings.get('OPTIONS', {}),
i18n_service=i18n_service, i18n_service=i18n_service,
fs_service=fs_service, fs_service=fs_service,
user_service=user_service,
) )
# replace all named pointers to the store into actual pointers # replace all named pointers to the store into actual pointers
for course_key, store_name in self.mappings.iteritems(): for course_key, store_name in self.mappings.iteritems():
...@@ -303,7 +314,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -303,7 +314,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
:param course_key: must be a CourseKey :param course_key: must be a CourseKey
""" """
assert(isinstance(course_key, CourseKey)) assert isinstance(course_key, CourseKey)
store = self._get_modulestore_for_courseid(course_key) store = self._get_modulestore_for_courseid(course_key)
try: try:
return store.get_course(course_key, depth=depth, **kwargs) return store.get_course(course_key, depth=depth, **kwargs)
...@@ -340,7 +351,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -340,7 +351,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
* ignore_case (bool): If True, do a case insensitive search. If * ignore_case (bool): If True, do a case insensitive search. If
False, do a case sensitive search False, do a case sensitive search
""" """
assert(isinstance(course_id, CourseKey)) assert isinstance(course_id, CourseKey)
store = self._get_modulestore_for_courseid(course_id) store = self._get_modulestore_for_courseid(course_id)
return store.has_course(course_id, ignore_case, **kwargs) return store.has_course(course_id, ignore_case, **kwargs)
...@@ -348,7 +359,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -348,7 +359,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
""" """
See xmodule.modulestore.__init__.ModuleStoreWrite.delete_course See xmodule.modulestore.__init__.ModuleStoreWrite.delete_course
""" """
assert(isinstance(course_key, CourseKey)) assert isinstance(course_key, CourseKey)
store = self._get_modulestore_for_courseid(course_key) store = self._get_modulestore_for_courseid(course_key)
return store.delete_course(course_key, user_id) return store.delete_course(course_key, user_id)
......
...@@ -281,7 +281,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): ...@@ -281,7 +281,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
# decache any computed pending field settings # decache any computed pending field settings
module.save() module.save()
return module return module
except: except Exception: # pylint: disable=broad-except
log.warning("Failed to load descriptor from %s", json_data, exc_info=True) log.warning("Failed to load descriptor from %s", json_data, exc_info=True)
return ErrorDescriptor.from_json( return ErrorDescriptor.from_json(
json_data, json_data,
...@@ -498,6 +498,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -498,6 +498,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
error_tracker=null_error_tracker, error_tracker=null_error_tracker,
i18n_service=None, i18n_service=None,
fs_service=None, fs_service=None,
user_service=None,
retry_wait_time=0.1, retry_wait_time=0.1,
**kwargs): **kwargs):
""" """
...@@ -551,6 +552,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -551,6 +552,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
self.render_template = render_template self.render_template = render_template
self.i18n_service = i18n_service self.i18n_service = i18n_service
self.fs_service = fs_service self.fs_service = fs_service
self.user_service = user_service
self._course_run_cache = {} self._course_run_cache = {}
...@@ -850,6 +852,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -850,6 +852,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if self.fs_service: if self.fs_service:
services["fs"] = self.fs_service services["fs"] = self.fs_service
if self.user_service:
services["user"] = self.user_service
system = CachingDescriptorSystem( system = CachingDescriptorSystem(
modulestore=self, modulestore=self,
course_key=course_key, course_key=course_key,
...@@ -932,7 +937,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -932,7 +937,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
""" """
Get the course with the given courseid (org/course/run) Get the course with the given courseid (org/course/run)
""" """
assert(isinstance(course_key, CourseKey)) assert isinstance(course_key, CourseKey)
course_key = self.fill_in_run(course_key) course_key = self.fill_in_run(course_key)
location = course_key.make_usage_key('course', course_key.run) location = course_key.make_usage_key('course', course_key.run)
try: try:
...@@ -949,7 +954,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -949,7 +954,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
If ignore_case is True, do a case insensitive search, If ignore_case is True, do a case insensitive search,
otherwise, do a case sensitive search otherwise, do a case sensitive search
""" """
assert(isinstance(course_key, CourseKey)) assert isinstance(course_key, CourseKey)
if isinstance(course_key, LibraryLocator): if isinstance(course_key, LibraryLocator):
return None # Libraries require split mongo return None # Libraries require split mongo
course_key = self.fill_in_run(course_key) course_key = self.fill_in_run(course_key)
...@@ -1150,6 +1155,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -1150,6 +1155,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if self.fs_service: if self.fs_service:
services["fs"] = self.fs_service services["fs"] = self.fs_service
if self.user_service:
services["user"] = self.user_service
runtime = CachingDescriptorSystem( runtime = CachingDescriptorSystem(
modulestore=self, modulestore=self,
module_data={}, module_data={},
...@@ -1598,7 +1606,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -1598,7 +1606,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
elif isinstance(course_assets['assets'], list): elif isinstance(course_assets['assets'], list):
# This record is in the old course assets format. # This record is in the old course assets format.
# Ensure that no data exists before updating the format. # Ensure that no data exists before updating the format.
assert(len(course_assets['assets']) == 0) assert len(course_assets['assets']) == 0
# Update the format to a dict. # Update the format to a dict.
self.asset_collection.update( self.asset_collection.update(
{'_id': doc_id}, {'_id': doc_id},
......
...@@ -607,7 +607,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -607,7 +607,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
def __init__(self, contentstore, doc_store_config, fs_root, render_template, def __init__(self, contentstore, doc_store_config, fs_root, render_template,
default_class=None, default_class=None,
error_tracker=null_error_tracker, error_tracker=null_error_tracker,
i18n_service=None, fs_service=None, i18n_service=None, fs_service=None, user_service=None,
services=None, **kwargs): services=None, **kwargs):
""" """
:param doc_store_config: must have a host, db, and collection entries. Other common entries: port, tz_aware. :param doc_store_config: must have a host, db, and collection entries. Other common entries: port, tz_aware.
...@@ -638,6 +638,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -638,6 +638,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
if fs_service is not None: if fs_service is not None:
self.services["fs"] = fs_service self.services["fs"] = fs_service
if user_service is not None:
self.services["user"] = user_service
def close_connections(self): def close_connections(self):
""" """
Closes any open connections to the underlying databases Closes any open connections to the underlying databases
......
...@@ -27,7 +27,15 @@ def load_function(path): ...@@ -27,7 +27,15 @@ def load_function(path):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def create_modulestore_instance(engine, contentstore, doc_store_config, options, i18n_service=None, fs_service=None): def create_modulestore_instance(
engine,
contentstore,
doc_store_config,
options,
i18n_service=None,
fs_service=None,
user_service=None
):
""" """
This will return a new instance of a modulestore given an engine and options This will return a new instance of a modulestore given an engine and options
""" """
...@@ -69,7 +77,7 @@ class MixedSplitTestCase(TestCase): ...@@ -69,7 +77,7 @@ class MixedSplitTestCase(TestCase):
Stripped-down version of ModuleStoreTestCase that can be used without Django Stripped-down version of ModuleStoreTestCase that can be used without Django
(i.e. for testing in common/lib/ ). Sets up MixedModuleStore and Split. (i.e. for testing in common/lib/ ). Sets up MixedModuleStore and Split.
""" """
RENDER_TEMPLATE = lambda t_n, d, ctx = None, nsp = 'main': u'{}: {}, {}'.format(t_n, repr(d), repr(ctx)) RENDER_TEMPLATE = lambda t_n, d, ctx=None, nsp='main': u'{}: {}, {}'.format(t_n, repr(d), repr(ctx))
modulestore_options = { modulestore_options = {
'default_class': 'xmodule.raw_module.RawDescriptor', 'default_class': 'xmodule.raw_module.RawDescriptor',
'fs_root': DATA_DIR, 'fs_root': DATA_DIR,
......
...@@ -64,7 +64,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -64,7 +64,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
self.unnamed = defaultdict(int) # category -> num of new url_names for that category self.unnamed = defaultdict(int) # category -> num of new url_names for that category
self.used_names = defaultdict(set) # category -> set of used url_names self.used_names = defaultdict(set) # category -> set of used url_names
# cdodge: adding the course_id as passed in for later reference rather than having to recomine the org/course/url_name # Adding the course_id as passed in for later reference rather than
# having to recombine the org/course/url_name
self.course_id = course_id self.course_id = course_id
self.load_error_modules = load_error_modules self.load_error_modules = load_error_modules
self.modulestore = xmlstore self.modulestore = xmlstore
...@@ -292,8 +293,8 @@ class XMLModuleStore(ModuleStoreReadBase): ...@@ -292,8 +293,8 @@ class XMLModuleStore(ModuleStoreReadBase):
An XML backed ModuleStore An XML backed ModuleStore
""" """
def __init__( def __init__(
self, data_dir, default_class=None, course_dirs=None, course_ids=None, self, data_dir, default_class=None, course_dirs=None, course_ids=None,
load_error_modules=True, i18n_service=None, fs_service=None, **kwargs load_error_modules=True, i18n_service=None, fs_service=None, user_service=None, **kwargs
): ):
""" """
Initialize an XMLModuleStore from data_dir Initialize an XMLModuleStore from data_dir
...@@ -304,8 +305,8 @@ class XMLModuleStore(ModuleStoreReadBase): ...@@ -304,8 +305,8 @@ class XMLModuleStore(ModuleStoreReadBase):
default_class (str): dot-separated string defining the default descriptor default_class (str): dot-separated string defining the default descriptor
class to use if none is specified in entry_points class to use if none is specified in entry_points
course_dirs or course_ids (list of str): If specified, the list of course_dirs or course_ids to load. Otherwise, course_dirs or course_ids (list of str): If specified, the list of course_dirs or course_ids to load.
load all courses. Note, providing both Otherwise, load all courses. Note, providing both
""" """
super(XMLModuleStore, self).__init__(**kwargs) super(XMLModuleStore, self).__init__(**kwargs)
...@@ -331,6 +332,7 @@ class XMLModuleStore(ModuleStoreReadBase): ...@@ -331,6 +332,7 @@ class XMLModuleStore(ModuleStoreReadBase):
self.i18n_service = i18n_service self.i18n_service = i18n_service
self.fs_service = fs_service self.fs_service = fs_service
self.user_service = user_service
# If we are specifically asked for missing courses, that should # If we are specifically asked for missing courses, that should
# be an error. If we are asked for "all" courses, find the ones # be an error. If we are asked for "all" courses, find the ones
...@@ -479,6 +481,9 @@ class XMLModuleStore(ModuleStoreReadBase): ...@@ -479,6 +481,9 @@ class XMLModuleStore(ModuleStoreReadBase):
if self.fs_service: if self.fs_service:
services['fs'] = self.fs_service services['fs'] = self.fs_service
if self.user_service:
services['user'] = self.user_service
system = ImportSystem( system = ImportSystem(
xmlstore=self, xmlstore=self,
course_id=course_id, course_id=course_id,
......
...@@ -641,7 +641,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes ...@@ -641,7 +641,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
for group in user_partition.groups: for group in user_partition.groups:
str_group_id = unicode(group.id) str_group_id = unicode(group.id)
if str_group_id not in self.group_id_to_child: if str_group_id not in self.group_id_to_child:
user_id = self.runtime.service(self, 'user').user_id user_id = self.runtime.service(self, 'user').get_current_user().opt_attrs['edx-platform.user_id']
self._create_vertical_for_group(group, user_id) self._create_vertical_for_group(group, user_id)
changed = True changed = True
......
...@@ -53,7 +53,7 @@ from xmodule_modifiers import ( ...@@ -53,7 +53,7 @@ from xmodule_modifiers import (
) )
from xmodule.lti_module import LTIModule from xmodule.lti_module import LTIModule
from xmodule.x_module import XModuleDescriptor from xmodule.x_module import XModuleDescriptor
from xblock_django.user_service import DjangoXBlockUserService
from util.json_request import JsonResponse from util.json_request import JsonResponse
from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip
if settings.FEATURES.get('MILESTONES_APP', False): if settings.FEATURES.get('MILESTONES_APP', False):
...@@ -644,6 +644,7 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -644,6 +644,7 @@ def get_module_system_for_user(user, field_data_cache,
'i18n': ModuleI18nService(), 'i18n': ModuleI18nService(),
'fs': xblock.reference.plugins.FSService(), 'fs': xblock.reference.plugins.FSService(),
'field-data': field_data, 'field-data': field_data,
'user': DjangoXBlockUserService(user),
}, },
get_user_role=lambda: get_user_role(user, course_id), get_user_role=lambda: get_user_role(user, course_id),
descriptor_runtime=descriptor.runtime, descriptor_runtime=descriptor.runtime,
......
...@@ -46,6 +46,10 @@ from xmodule.x_module import XModuleDescriptor, XModule, STUDENT_VIEW ...@@ -46,6 +46,10 @@ from xmodule.x_module import XModuleDescriptor, XModule, STUDENT_VIEW
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
@XBlock.needs("field-data")
@XBlock.needs("i18n")
@XBlock.needs("fs")
@XBlock.needs("user")
class PureXBlock(XBlock): class PureXBlock(XBlock):
""" """
Pure XBlock to use in tests. Pure XBlock to use in tests.
...@@ -1177,3 +1181,40 @@ class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -1177,3 +1181,40 @@ class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase):
mock_track_function.assert_called_once_with(request) mock_track_function.assert_called_once_with(request)
mock_track_function.return_value.assert_called_once_with(event_type, event) mock_track_function.return_value.assert_called_once_with(event_type, event)
@ddt.ddt
class LMSXBlockServiceBindingTest(ModuleStoreTestCase):
"""
Tests that the LMS Module System (XBlock Runtime) provides an expected set of services.
"""
def setUp(self):
"""
Set up the user and other fields that will be used to instantiate the runtime.
"""
super(LMSXBlockServiceBindingTest, self).setUp()
self.user = UserFactory()
self.field_data_cache = Mock()
self.course = CourseFactory.create()
self.track_function = Mock()
self.xqueue_callback_url_prefix = Mock()
self.request_token = Mock()
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
@ddt.data("user", "i18n", "fs", "field-data")
def test_expected_services_exist(self, expected_service):
"""
Tests that the 'user', 'i18n', and 'fs' services are provided by the LMS runtime.
"""
descriptor = ItemFactory(category="pure", parent=self.course)
runtime, _ = render.get_module_system_for_user(
self.user,
self.field_data_cache,
descriptor,
self.course.id,
self.track_function,
self.xqueue_callback_url_prefix,
self.request_token
)
service = runtime.service(descriptor, expected_service)
self.assertIsNotNone(service)
...@@ -89,8 +89,7 @@ class ViewsTestCase(TestCase): ...@@ -89,8 +89,7 @@ class ViewsTestCase(TestCase):
self.component = ItemFactory.create(category='problem', parent_location=self.vertical.location) self.component = ItemFactory.create(category='problem', parent_location=self.vertical.location)
self.course_key = self.course.id self.course_key = self.course.id
self.user = User.objects.create(username='dummy', password='123456', self.user = UserFactory(username='dummy', password='123456', email='test@mit.edu')
email='test@mit.edu')
self.date = datetime(2013, 1, 22, tzinfo=UTC) self.date = datetime(2013, 1, 22, tzinfo=UTC)
self.enrollment = CourseEnrollment.enroll(self.user, self.course_key) self.enrollment = CourseEnrollment.enroll(self.user, self.course_key)
self.enrollment.created = self.date self.enrollment.created = self.date
...@@ -164,10 +163,12 @@ class ViewsTestCase(TestCase): ...@@ -164,10 +163,12 @@ class ViewsTestCase(TestCase):
request_url = '/'.join([ request_url = '/'.join([
'/courses', '/courses',
self.course.id.to_deprecated_string(), self.course.id.to_deprecated_string(),
'courseware',
self.chapter.location.name, self.chapter.location.name,
self.section.location.name, self.section.location.name,
'f' 'f'
]) ])
self.client.login(username=self.user.username, password="123456")
response = self.client.get(request_url) response = self.client.get(request_url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
...@@ -176,11 +177,12 @@ class ViewsTestCase(TestCase): ...@@ -176,11 +177,12 @@ class ViewsTestCase(TestCase):
url_parts = [ url_parts = [
'/courses', '/courses',
self.course.id.to_deprecated_string(), self.course.id.to_deprecated_string(),
'courseware',
self.chapter.location.name, self.chapter.location.name,
self.section.location.name, self.section.location.name,
'1' '1'
] ]
self.client.login(username=self.user.username, password="123456")
for idx, val in enumerate(url_parts): for idx, val in enumerate(url_parts):
url_parts_copy = url_parts[:] url_parts_copy = url_parts[:]
url_parts_copy[idx] = val + u'χ' url_parts_copy[idx] = val + u'χ'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment