Merge pull request #1156 from edx/feature/cdodge/add-preview-configs-via-middleware
add some middleware to determine whether draft modulestore or non-draft ...
This commit is contained in:
@@ -75,6 +75,11 @@ Common: Allow instructors to input complicated expressions as answers to
|
||||
`NumericalResponse`s. Prior to the change only numbers were allowed, now any
|
||||
answer from '1/3' to 'sqrt(12)*(1-1/3^2+1/5/3^2)' are valid.
|
||||
|
||||
Studio/LMS: Allow for 'preview' and 'published' in a single LMS instance. Use
|
||||
middlware components to retain the incoming Django request and put in thread
|
||||
local storage. It is recommended that all developers define a 'preview.localhost'
|
||||
which maps to the same IP address as localhost in his/her HOSTS file.
|
||||
|
||||
LMS: Enable beta instructor dashboard. The beta dashboard is a rearchitecture
|
||||
of the existing instructor dashboard and is available by clicking a link at
|
||||
the top right of the existing dashboard.
|
||||
|
||||
12
cms/envs/dev_shared_preview.py
Normal file
12
cms/envs/dev_shared_preview.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
This configuration is have localdev use a preview.localhost hostname for the preview LMS so that we can share
|
||||
the same process between preview and published
|
||||
"""
|
||||
|
||||
# We intentionally define lots of variables that aren't used, and
|
||||
# want to import all variables from base settings files
|
||||
# pylint: disable=W0401, W0614
|
||||
|
||||
from .dev import *
|
||||
|
||||
MITX_FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost:8000"
|
||||
@@ -7,10 +7,13 @@ Passes settings.MODULESTORE as kwargs to MongoModuleStore
|
||||
from __future__ import absolute_import
|
||||
from importlib import import_module
|
||||
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import get_cache, InvalidCacheBackendError
|
||||
from django.dispatch import Signal
|
||||
from xmodule.modulestore.loc_mapper_store import LocMapperStore
|
||||
from xmodule.util.django import get_current_request_hostname
|
||||
|
||||
# We may not always have the request_cache module available
|
||||
try:
|
||||
@@ -68,11 +71,41 @@ def create_modulestore_instance(engine, doc_store_config, options):
|
||||
)
|
||||
|
||||
|
||||
def modulestore(name='default'):
|
||||
def get_default_store_name_for_current_request():
|
||||
"""
|
||||
This method will return the appropriate default store mapping for the current Django request,
|
||||
else 'default' which is the system default
|
||||
"""
|
||||
store_name = 'default'
|
||||
|
||||
# see what request we are currently processing - if any at all - and get hostname for the request
|
||||
hostname = get_current_request_hostname()
|
||||
|
||||
# get mapping information which is defined in configurations
|
||||
mappings = getattr(settings, 'HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS', None)
|
||||
|
||||
# compare hostname against the regex expressions set of mappings
|
||||
# which will tell us which store name to use
|
||||
if hostname and mappings:
|
||||
for key in mappings.keys():
|
||||
if re.match(key, hostname):
|
||||
store_name = mappings[key]
|
||||
return store_name
|
||||
|
||||
return store_name
|
||||
|
||||
|
||||
def modulestore(name=None):
|
||||
"""
|
||||
This returns an instance of a modulestore of given name. This will wither return an existing
|
||||
modulestore or create a new one
|
||||
"""
|
||||
|
||||
if not name:
|
||||
# If caller did not specify name then we should
|
||||
# determine what should be the default
|
||||
name = get_default_store_name_for_current_request()
|
||||
|
||||
if name not in _MODULESTORES:
|
||||
_MODULESTORES[name] = create_modulestore_instance(
|
||||
settings.MODULESTORE[name]['ENGINE'],
|
||||
|
||||
20
common/lib/xmodule/xmodule/tests/test_utils_django.py
Normal file
20
common/lib/xmodule/xmodule/tests/test_utils_django.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Tests for methods defined in util/django.py"""
|
||||
from xmodule.util.django import get_current_request, get_current_request_hostname
|
||||
from nose.tools import assert_is_none
|
||||
from unittest import TestCase
|
||||
|
||||
class UtilDjangoTests(TestCase):
|
||||
"""
|
||||
Tests for methods exposed in util/django
|
||||
"""
|
||||
def test_get_current_request(self):
|
||||
"""
|
||||
Since we are running outside of Django assert that get_current_request returns None
|
||||
"""
|
||||
assert_is_none(get_current_request())
|
||||
|
||||
def test_get_current_request_hostname(self):
|
||||
"""
|
||||
Since we are running outside of Django assert that get_current_request_hostname returns None
|
||||
"""
|
||||
assert_is_none(get_current_request_hostname())
|
||||
20
common/lib/xmodule/xmodule/util/django.py
Normal file
20
common/lib/xmodule/xmodule/util/django.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Exposes Django utilities for consumption in the xmodule library
|
||||
NOTE: This file should only be imported into 'django-safe' code, i.e. known that this code runs int the Django
|
||||
runtime environment with the djangoapps in common configured to load
|
||||
"""
|
||||
|
||||
# NOTE: we are importing this method so that any module that imports us has access to get_current_request
|
||||
from crum import get_current_request
|
||||
|
||||
|
||||
def get_current_request_hostname():
|
||||
"""
|
||||
This method will return the hostname that was used in the current Django request
|
||||
"""
|
||||
hostname = None
|
||||
request = get_current_request()
|
||||
if request:
|
||||
hostname = request.META.get('HTTP_HOST')
|
||||
|
||||
return hostname
|
||||
@@ -1,8 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.http import Http404
|
||||
from django.test.utils import override_settings
|
||||
from courseware.courses import get_course_by_id, get_cms_course_link_by_id
|
||||
from xmodule.modulestore.django import get_default_store_name_for_current_request
|
||||
|
||||
CMS_BASE_TEST = 'testcms'
|
||||
|
||||
@@ -26,3 +29,14 @@ class CoursesTest(TestCase):
|
||||
self.assertEqual("//{}/".format(CMS_BASE_TEST), get_cms_course_link_by_id("blah_bad_course_id"))
|
||||
self.assertEqual("//{}/".format(CMS_BASE_TEST), get_cms_course_link_by_id("too/too/many/slashes"))
|
||||
self.assertEqual("//{}/org/num/course/name".format(CMS_BASE_TEST), get_cms_course_link_by_id('org/num/name'))
|
||||
|
||||
|
||||
@mock.patch('xmodule.modulestore.django.get_current_request_hostname', mock.Mock(return_value='preview.localhost'))
|
||||
@override_settings(HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS={'preview\.': 'draft'})
|
||||
def test_default_modulestore_preview_mapping(self):
|
||||
self.assertEqual(get_default_store_name_for_current_request(), 'draft')
|
||||
|
||||
@mock.patch('xmodule.modulestore.django.get_current_request_hostname', mock.Mock(return_value='localhost'))
|
||||
@override_settings(HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS={'preview\.': 'draft'})
|
||||
def test_default_modulestore_published_mapping(self):
|
||||
self.assertEqual(get_default_store_name_for_current_request(), 'default')
|
||||
|
||||
@@ -12,3 +12,5 @@ with open(ENV_ROOT / "cms.auth.json") as auth_file:
|
||||
CMS_AUTH_TOKENS = json.load(auth_file)
|
||||
|
||||
MODULESTORE = CMS_AUTH_TOKENS['MODULESTORE']
|
||||
|
||||
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS = ENVS_TOKENS.get('HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS',{})
|
||||
|
||||
@@ -40,6 +40,10 @@ MODULESTORE = {
|
||||
'DOC_STORE_CONFIG': DOC_STORE_CONFIG,
|
||||
'OPTIONS': modulestore_options
|
||||
},
|
||||
'draft': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
|
||||
'OPTIONS': modulestore_options
|
||||
},
|
||||
}
|
||||
|
||||
CONTENTSTORE = {
|
||||
@@ -59,3 +63,10 @@ INSTALLED_APPS += (
|
||||
DEBUG_TOOLBAR_PANELS += (
|
||||
'debug_toolbar_mongo.panel.MongoDebugPanel',
|
||||
)
|
||||
|
||||
# HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS defines, as dictionary of regex's, a set of mappings of HTTP request hostnames to
|
||||
# what the 'default' modulestore to use while processing the request
|
||||
# for example 'preview.edx.org' should use the draft modulestore
|
||||
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS = {
|
||||
'preview\.': 'draft'
|
||||
}
|
||||
|
||||
@@ -585,6 +585,7 @@ MIDDLEWARE_CLASSES = (
|
||||
#'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'cache_toolbox.middleware.CacheBackedAuthenticationMiddleware',
|
||||
'contentserver.middleware.StaticContentServer',
|
||||
'crum.CurrentRequestUserMiddleware',
|
||||
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'track.middleware.TrackMiddleware',
|
||||
|
||||
@@ -98,6 +98,7 @@ django_debug_toolbar
|
||||
django-debug-toolbar-mongo
|
||||
nose-ignore-docstring
|
||||
nose-exclude
|
||||
django-crum==0.5
|
||||
|
||||
git+https://github.com/mfogel/django-settings-context-processor.git
|
||||
|
||||
|
||||
Reference in New Issue
Block a user