Merge pull request #12552 from edx/release
Merge Release 2016-05-24 to Master
This commit is contained in:
@@ -8,7 +8,7 @@ from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from ...api import get_course_in_cache
|
||||
from ...api import get_course_in_cache, update_course_in_cache
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -39,6 +39,12 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
help='Force update of the course blocks for the requested courses.',
|
||||
action='store_true',
|
||||
default=False,
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
@@ -57,7 +63,10 @@ class Command(BaseCommand):
|
||||
|
||||
for course_key in course_keys:
|
||||
try:
|
||||
block_structure = get_course_in_cache(course_key)
|
||||
if options.get('force'):
|
||||
block_structure = update_course_in_cache(course_key)
|
||||
else:
|
||||
block_structure = get_course_in_cache(course_key)
|
||||
if options.get('dags'):
|
||||
self._find_and_log_dags(block_structure, course_key)
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
|
||||
@@ -44,6 +44,21 @@ class TestGenerateCourseBlocks(ModuleStoreTestCase):
|
||||
self._assert_courses_not_in_block_cache(self.course_1.id, self.course_2.id)
|
||||
self.command.handle(all=True)
|
||||
self._assert_courses_in_block_cache(self.course_1.id, self.course_2.id)
|
||||
with patch(
|
||||
'openedx.core.lib.block_structure.factory.BlockStructureFactory.create_from_modulestore'
|
||||
) as mock_update_from_store:
|
||||
self.command.handle(all=True)
|
||||
mock_update_from_store.assert_not_called()
|
||||
|
||||
def test_generate_force(self):
|
||||
self._assert_courses_not_in_block_cache(self.course_1.id, self.course_2.id)
|
||||
self.command.handle(all=True)
|
||||
self._assert_courses_in_block_cache(self.course_1.id, self.course_2.id)
|
||||
with patch(
|
||||
'openedx.core.lib.block_structure.factory.BlockStructureFactory.create_from_modulestore'
|
||||
) as mock_update_from_store:
|
||||
self.command.handle(all=True, force=True)
|
||||
mock_update_from_store.assert_called()
|
||||
|
||||
def test_generate_one(self):
|
||||
self._assert_courses_not_in_block_cache(self.course_1.id, self.course_2.id)
|
||||
|
||||
@@ -111,7 +111,7 @@ class DateSummary(object):
|
||||
date_format = _(u"{relative} ago - {absolute}") if date_has_passed else _(u"in {relative} - {absolute}")
|
||||
return date_format.format(
|
||||
relative=relative_date,
|
||||
absolute=self.date.strftime(self.date_format),
|
||||
absolute=self.date.strftime(self.date_format.encode('utf-8')).decode('utf-8'),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -157,7 +157,9 @@ class TodaysDate(DateSummary):
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return _(u'Today is {date}').format(date=datetime.now(pytz.UTC).strftime(self.date_format))
|
||||
return _(u'Today is {date}').format(
|
||||
date=datetime.now(pytz.UTC).strftime(self.date_format.encode('utf-8')).decode('utf-8')
|
||||
)
|
||||
|
||||
|
||||
class CourseStartDate(DateSummary):
|
||||
|
||||
@@ -1,19 +1,53 @@
|
||||
""" Course Discovery API Service. """
|
||||
from django.conf import settings
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
import jwt
|
||||
|
||||
from openedx.core.djangoapps.theming import helpers
|
||||
from openedx.core.lib.token_utils import get_id_token
|
||||
from provider.oauth2.models import Client
|
||||
from student.models import UserProfile, anonymous_id_for_user
|
||||
|
||||
CLIENT_NAME = 'course-discovery'
|
||||
|
||||
|
||||
def get_id_token(user):
|
||||
"""
|
||||
Return a JWT for `user`, suitable for use with the course discovery service.
|
||||
|
||||
Arguments:
|
||||
user (User): User for whom to generate the JWT.
|
||||
|
||||
Returns:
|
||||
str: The JWT.
|
||||
"""
|
||||
try:
|
||||
# Service users may not have user profiles.
|
||||
full_name = UserProfile.objects.get(user=user).name
|
||||
except UserProfile.DoesNotExist:
|
||||
full_name = None
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
expires_in = getattr(settings, 'OAUTH_ID_TOKEN_EXPIRATION', 30)
|
||||
|
||||
payload = {
|
||||
'preferred_username': user.username,
|
||||
'name': full_name,
|
||||
'email': user.email,
|
||||
'administrator': user.is_staff,
|
||||
'iss': helpers.get_value('OAUTH_OIDC_ISSUER', settings.OAUTH_OIDC_ISSUER),
|
||||
'exp': now + datetime.timedelta(seconds=expires_in),
|
||||
'iat': now,
|
||||
'aud': helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_AUDIENCE'],
|
||||
'sub': anonymous_id_for_user(user, None),
|
||||
}
|
||||
secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY']
|
||||
|
||||
return jwt.encode(payload, secret_key)
|
||||
|
||||
|
||||
def course_discovery_api_client(user):
|
||||
""" Returns a Course Discovery API client setup with authentication for the specified user. """
|
||||
course_discovery_client = Client.objects.get(name=CLIENT_NAME)
|
||||
secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY']
|
||||
return EdxRestApiClient(
|
||||
course_discovery_client.url,
|
||||
jwt=get_id_token(user, CLIENT_NAME, secret_key=secret_key)
|
||||
)
|
||||
return EdxRestApiClient(course_discovery_client.url, jwt=get_id_token(user))
|
||||
|
||||
@@ -96,3 +96,13 @@ def update_course_structure(course_key):
|
||||
structure_model.structure_json = structure_json
|
||||
structure_model.discussion_id_map_json = discussion_id_map_json
|
||||
structure_model.save()
|
||||
|
||||
# TODO (TNL-4630) For temporary hotfix to update the block_structure cache.
|
||||
# Should be moved to proper location.
|
||||
from django.core.cache import cache
|
||||
from openedx.core.lib.block_structure.manager import BlockStructureManager
|
||||
|
||||
store = modulestore()
|
||||
course_usage_key = store.make_course_usage_key(course_key)
|
||||
block_structure_manager = BlockStructureManager(course_usage_key, store, cache)
|
||||
block_structure_manager.update_collected()
|
||||
|
||||
@@ -45,13 +45,13 @@ class BlockStructureCache(object):
|
||||
)
|
||||
zp_data_to_cache = zpickle(data_to_cache)
|
||||
|
||||
# Set the timeout value for the cache to None. This caches the
|
||||
# value forever. The expectation is that the caller will delete
|
||||
# the cached value once it is outdated.
|
||||
# Set the timeout value for the cache to 1 day as a fail-safe
|
||||
# in case the signal to invalidate the cache doesn't come through.
|
||||
timeout_in_seconds = 60 * 60 * 24
|
||||
self._cache.set(
|
||||
self._encode_root_cache_key(block_structure.root_block_usage_key),
|
||||
zp_data_to_cache,
|
||||
timeout=None,
|
||||
timeout=timeout_in_seconds,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestBlockStructureCache(ChildrenMapTestMixin, TestCase):
|
||||
|
||||
self.add_transformers()
|
||||
self.block_structure_cache.add(self.block_structure)
|
||||
self.assertEquals(self.mock_cache.timeout_from_last_call, None)
|
||||
self.assertEquals(self.mock_cache.timeout_from_last_call, 60 * 60 * 24)
|
||||
|
||||
cached_value = self.block_structure_cache.get(self.block_structure.root_block_usage_key)
|
||||
self.assertIsNotNone(cached_value)
|
||||
|
||||
Reference in New Issue
Block a user