Implement user service to return currently-logged-in user

returns XBlockuser with 2 data layers
This commit is contained in:
Jason Bau
2014-11-20 17:39:34 -05:00
committed by Ned Batchelder
parent dd18a8e618
commit 7f1b60b286
16 changed files with 254 additions and 25 deletions

View File

@@ -0,0 +1,57 @@
"""
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)

View File

@@ -0,0 +1,42 @@
"""
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

View File

@@ -30,6 +30,14 @@ try:
except ImportError:
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$)|(^.*~$)")
@@ -44,7 +52,15 @@ def load_function(path):
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
"""
@@ -74,6 +90,11 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options
if issubclass(class_, BranchSettingMixin):
_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_(
contentstore=content_store,
metadata_inheritance_cache_subsystem=metadata_inheritance_cache,
@@ -83,6 +104,7 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options
doc_store_config=doc_store_config,
i18n_service=i18n_service or ModuleI18nService(),
fs_service=fs_service or xblock.reference.plugins.FSService(),
user_service=user_service or xb_user_service,
**_options
)

View File

@@ -99,7 +99,17 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
"""
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
collection of other modulestore configuration information
@@ -139,6 +149,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store_settings.get('OPTIONS', {}),
i18n_service=i18n_service,
fs_service=fs_service,
user_service=user_service,
)
# replace all named pointers to the store into actual pointers
for course_key, store_name in self.mappings.iteritems():

View File

@@ -498,6 +498,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
error_tracker=null_error_tracker,
i18n_service=None,
fs_service=None,
user_service=None,
retry_wait_time=0.1,
**kwargs):
"""
@@ -551,6 +552,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
self.render_template = render_template
self.i18n_service = i18n_service
self.fs_service = fs_service
self.user_service = user_service
self._course_run_cache = {}
@@ -850,6 +852,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if self.fs_service:
services["fs"] = self.fs_service
if self.user_service:
services["user"] = self.user_service
system = CachingDescriptorSystem(
modulestore=self,
course_key=course_key,
@@ -1150,6 +1155,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if self.fs_service:
services["fs"] = self.fs_service
if self.user_service:
services["user"] = self.user_service
runtime = CachingDescriptorSystem(
modulestore=self,
module_data={},

View File

@@ -607,7 +607,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
def __init__(self, contentstore, doc_store_config, fs_root, render_template,
default_class=None,
error_tracker=null_error_tracker,
i18n_service=None, fs_service=None,
i18n_service=None, fs_service=None, user_service=None,
services=None, **kwargs):
"""
: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):
if fs_service is not None:
self.services["fs"] = fs_service
if user_service is not None:
self.services["user"] = user_service
def close_connections(self):
"""
Closes any open connections to the underlying databases

View File

@@ -27,7 +27,15 @@ def load_function(path):
# 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
"""

View File

@@ -293,7 +293,7 @@ class XMLModuleStore(ModuleStoreReadBase):
"""
def __init__(
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
@@ -331,6 +331,7 @@ class XMLModuleStore(ModuleStoreReadBase):
self.i18n_service = i18n_service
self.fs_service = fs_service
self.user_service = user_service
# If we are specifically asked for missing courses, that should
# be an error. If we are asked for "all" courses, find the ones
@@ -479,6 +480,9 @@ class XMLModuleStore(ModuleStoreReadBase):
if self.fs_service:
services['fs'] = self.fs_service
if self.user_service:
services['user'] = self.user_service
system = ImportSystem(
xmlstore=self,
course_id=course_id,

View File

@@ -641,7 +641,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
for group in user_partition.groups:
str_group_id = unicode(group.id)
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)
changed = True