Commit 8f9d815f by Chris Dodge

add some middleware to determine whether draft modulestore or non-draft modulestore should be used

change to use regex to do the domain mappings. Also add config to AWS to be able to set from configuration file.

handle cases where HTTP_HOST is none, like in unit tests

add linefeed at end

fix up regex matches

switch to use thread local storage to hold the request itself

.

.

convert over to use open source 3rd party library

convert over to use django-cum

add unit test

remove comment

.

add comment to config setting

fix comment

use better regex for localdev

no need to break

no need to wrap an imported function, it's visible to any file that is importing us

add comment

add unit test

clean up test

use a separate env file to set the preview hostname
parent a03a11e0
......@@ -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:
......@@ -67,11 +70,41 @@ def create_modulestore_instance(engine, 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'],
settings.MODULESTORE[name]['OPTIONS'])
......
"""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',{})
......@@ -39,6 +39,10 @@ MODULESTORE = {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
'OPTIONS': modulestore_options
},
'draft': {
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
'OPTIONS': modulestore_options
},
}
CONTENTSTORE = {
......@@ -58,3 +62,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'
}
......@@ -579,6 +579,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