Commit 82e42cc5 by chrisndodge

Merge pull request #1156 from edx/feature/cdodge/add-preview-configs-via-middleware

add some middleware to determine whether draft modulestore or non-draft ...
parents 5b1b0fe1 8f9d815f
......@@ -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.
......
"""
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'],
......
"""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())
"""
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
# -*- 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
......
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